-- Pokemon Overlay Studio - mGBA Party Reader
-- Reads Gen 3 party data from memory and writes JSON
-- Supports: FireRed, LeafGreen, Emerald, Ruby, Sapphire (US)

local GAME_OFFSETS = {
    ["BPRE"] = { -- FireRed US
        party = 0x02024284, party_count = 0x02024029,
        sb1Ptr = 0x03005008, sb2Ptr = 0x0300500C,
        moneyOff = 0x0290, moneyKeyOff = 0x0F20,
        flagsOff = 0x0EE0, badgeFlagStart = 0x820,
        dexCaughtOff = 0x28, dexSeenOff = 0x5C,
    },
    ["BPGE"] = { -- LeafGreen US
        party = 0x02024284, party_count = 0x02024029,
        sb1Ptr = 0x03005008, sb2Ptr = 0x0300500C,
        moneyOff = 0x0290, moneyKeyOff = 0x0F20,
        flagsOff = 0x0EE0, badgeFlagStart = 0x820,
        dexCaughtOff = 0x28, dexSeenOff = 0x5C,
    },
    ["BPEE"] = { -- Emerald US
        party = 0x020244EC, party_count = 0x020244E9,
        sb1Ptr = 0x03005D8C, sb2Ptr = 0x03005D90,
        moneyOff = 0x0490, moneyKeyOff = 0x00AC,
        flagsOff = 0x1270, badgeFlagStart = 0x807,
        dexCaughtOff = 0x28, dexSeenOff = 0x5C,
    },
    ["AXVE"] = { -- Ruby US
        party = 0x03004360, party_count = 0x03004350,
        sb1Ptr = 0x03005D8C, sb2Ptr = 0x03005D90,
        moneyOff = 0x0490, moneyKeyOff = 0x00AC,
        flagsOff = 0x1270, badgeFlagStart = 0x807,
        dexCaughtOff = 0x28, dexSeenOff = 0x5C,
    },
    ["AXPE"] = { -- Sapphire US
        party = 0x03004360, party_count = 0x03004350,
        sb1Ptr = 0x03005D8C, sb2Ptr = 0x03005D90,
        moneyOff = 0x0490, moneyKeyOff = 0x00AC,
        flagsOff = 0x1270, badgeFlagStart = 0x807,
        dexCaughtOff = 0x28, dexSeenOff = 0x5C,
    },
}

-- Substructure order based on personality % 24
local SUBSTRUCTURE_ORDER = {
    [0]  = "GAEM", [1]  = "GAME", [2]  = "GEAM", [3]  = "GEMA",
    [4]  = "GMAE", [5]  = "GMEA", [6]  = "AGEM", [7]  = "AGME",
    [8]  = "AEGM", [9]  = "AEMG", [10] = "AMGE", [11] = "AMEG",
    [12] = "EGAM", [13] = "EGMA", [14] = "EAGM", [15] = "EAMG",
    [16] = "EMGA", [17] = "EMAG", [18] = "MGAE", [19] = "MGEA",
    [20] = "MAGE", [21] = "MAEG", [22] = "MEGA", [23] = "MEAG",
}

-- Gen 3 species index to national dex mapping (only entries that differ)
-- In Gen 3, internal IDs 1-251 match national dex, but 252+ are shuffled
-- We include the full mapping for Pokemon that appear in the games
local SPECIES_TO_NATIONAL = {}
-- Gen 3 internal species indices 252-411 map to national dex numbers
local GEN3_SPECIES_REMAP = {
    [252]=252,[253]=253,[254]=254,[255]=255,[256]=256,[257]=257,[258]=258,[259]=259,
    [260]=260,[261]=261,[262]=262,[263]=263,[264]=264,[265]=265,[266]=266,[267]=267,
    [268]=268,[269]=269,[270]=270,[271]=271,[272]=272,[273]=273,[274]=274,[275]=275,
    [276]=276,[277]=277,[278]=278,[279]=279,[280]=280,[281]=281,[282]=282,[283]=283,
    [284]=284,[285]=285,[286]=286,[287]=287,[288]=288,[289]=289,[290]=290,[291]=291,
    [292]=292,[293]=293,[294]=294,[295]=295,[296]=296,[297]=297,[298]=298,[299]=299,
    [300]=300,[301]=301,[302]=302,[303]=303,[304]=304,[305]=305,[306]=306,[307]=307,
    [308]=308,[309]=309,[310]=310,[311]=311,[312]=312,[313]=313,[314]=314,[315]=315,
    [316]=316,[317]=317,[318]=318,[319]=319,[320]=320,[321]=321,[322]=322,[323]=323,
    [324]=324,[325]=325,[326]=326,[327]=327,[328]=328,[329]=329,[330]=330,[331]=331,
    [332]=332,[333]=333,[334]=334,[335]=335,[336]=336,[337]=337,[338]=338,[339]=339,
    [340]=340,[341]=341,[342]=342,[343]=343,[344]=344,[345]=345,[346]=346,[347]=347,
    [348]=348,[349]=349,[350]=350,[351]=351,[352]=352,[353]=353,[354]=354,[355]=355,
    [356]=356,[357]=357,[358]=358,[359]=359,[360]=360,[361]=361,[362]=362,[363]=363,
    [364]=364,[365]=365,[366]=366,[367]=367,[368]=368,[369]=369,[370]=370,[371]=371,
    [372]=372,[373]=373,[374]=374,[375]=375,[376]=376,[377]=377,[378]=378,[379]=379,
    [380]=380,[381]=381,[382]=382,[383]=383,[384]=384,[385]=385,[386]=386,
}

-- Species names (national dex ID -> name), Gen 1-3
local SPECIES_NAMES = {
    [1]="Bulbasaur",[2]="Ivysaur",[3]="Venusaur",[4]="Charmander",[5]="Charmeleon",
    [6]="Charizard",[7]="Squirtle",[8]="Wartortle",[9]="Blastoise",[10]="Caterpie",
    [11]="Metapod",[12]="Butterfree",[13]="Weedle",[14]="Kakuna",[15]="Beedrill",
    [16]="Pidgey",[17]="Pidgeotto",[18]="Pidgeot",[19]="Rattata",[20]="Raticate",
    [21]="Spearow",[22]="Fearow",[23]="Ekans",[24]="Arbok",[25]="Pikachu",
    [26]="Raichu",[27]="Sandshrew",[28]="Sandslash",[29]="Nidoran-f",[30]="Nidorina",
    [31]="Nidoqueen",[32]="Nidoran-m",[33]="Nidorino",[34]="Nidoking",[35]="Clefairy",
    [36]="Clefable",[37]="Vulpix",[38]="Ninetales",[39]="Jigglypuff",[40]="Wigglytuff",
    [41]="Zubat",[42]="Golbat",[43]="Oddish",[44]="Gloom",[45]="Vileplume",
    [46]="Paras",[47]="Parasect",[48]="Venonat",[49]="Venomoth",[50]="Diglett",
    [51]="Dugtrio",[52]="Meowth",[53]="Persian",[54]="Psyduck",[55]="Golduck",
    [56]="Mankey",[57]="Primeape",[58]="Growlithe",[59]="Arcanine",[60]="Poliwag",
    [61]="Poliwhirl",[62]="Poliwrath",[63]="Abra",[64]="Kadabra",[65]="Alakazam",
    [66]="Machop",[67]="Machoke",[68]="Machamp",[69]="Bellsprout",[70]="Weepinbell",
    [71]="Victreebel",[72]="Tentacool",[73]="Tentacruel",[74]="Geodude",[75]="Graveler",
    [76]="Golem",[77]="Ponyta",[78]="Rapidash",[79]="Slowpoke",[80]="Slowbro",
    [81]="Magnemite",[82]="Magneton",[83]="Farfetch'd",[84]="Doduo",[85]="Dodrio",
    [86]="Seel",[87]="Dewgong",[88]="Grimer",[89]="Muk",[90]="Shellder",
    [91]="Cloyster",[92]="Gastly",[93]="Haunter",[94]="Gengar",[95]="Onix",
    [96]="Drowzee",[97]="Hypno",[98]="Krabby",[99]="Kingler",[100]="Voltorb",
    [101]="Electrode",[102]="Exeggcute",[103]="Exeggutor",[104]="Cubone",[105]="Marowak",
    [106]="Hitmonlee",[107]="Hitmonchan",[108]="Lickitung",[109]="Koffing",[110]="Weezing",
    [111]="Rhyhorn",[112]="Rhydon",[113]="Chansey",[114]="Tangela",[115]="Kangaskhan",
    [116]="Horsea",[117]="Seadra",[118]="Goldeen",[119]="Seaking",[120]="Staryu",
    [121]="Starmie",[122]="Mr. Mime",[123]="Scyther",[124]="Jynx",[125]="Electabuzz",
    [126]="Magmar",[127]="Pinsir",[128]="Tauros",[129]="Magikarp",[130]="Gyarados",
    [131]="Lapras",[132]="Ditto",[133]="Eevee",[134]="Vaporeon",[135]="Jolteon",
    [136]="Flareon",[137]="Porygon",[138]="Omanyte",[139]="Omastar",[140]="Kabuto",
    [141]="Kabutops",[142]="Aerodactyl",[143]="Snorlax",[144]="Articuno",[145]="Zapdos",
    [146]="Moltres",[147]="Dratini",[148]="Dragonair",[149]="Dragonite",[150]="Mewtwo",
    [151]="Mew",
    [152]="Chikorita",[153]="Bayleef",[154]="Meganium",[155]="Cyndaquil",[156]="Quilava",
    [157]="Typhlosion",[158]="Totodile",[159]="Croconaw",[160]="Feraligatr",
    [161]="Sentret",[162]="Furret",[163]="Hoothoot",[164]="Noctowl",[165]="Ledyba",
    [166]="Ledian",[167]="Spinarak",[168]="Ariados",[169]="Crobat",[170]="Chinchou",
    [171]="Lanturn",[172]="Pichu",[173]="Cleffa",[174]="Igglybuff",[175]="Togepi",
    [176]="Togetic",[177]="Natu",[178]="Xatu",[179]="Mareep",[180]="Flaaffy",
    [181]="Ampharos",[182]="Bellossom",[183]="Marill",[184]="Azumarill",[185]="Sudowoodo",
    [186]="Politoed",[187]="Hoppip",[188]="Skiploom",[189]="Jumpluff",[190]="Aipom",
    [191]="Sunkern",[192]="Sunflora",[193]="Yanma",[194]="Wooper",[195]="Quagsire",
    [196]="Espeon",[197]="Umbreon",[198]="Murkrow",[199]="Slowking",[200]="Misdreavus",
    [201]="Unown",[202]="Wobbuffet",[203]="Girafarig",[204]="Pineco",[205]="Forretress",
    [206]="Dunsparce",[207]="Gligar",[208]="Steelix",[209]="Snubbull",[210]="Granbull",
    [211]="Qwilfish",[212]="Scizor",[213]="Shuckle",[214]="Heracross",[215]="Sneasel",
    [216]="Teddiursa",[217]="Ursaring",[218]="Slugma",[219]="Magcargo",[220]="Swinub",
    [221]="Piloswine",[222]="Corsola",[223]="Remoraid",[224]="Octillery",[225]="Delibird",
    [226]="Mantine",[227]="Skarmory",[228]="Houndour",[229]="Houndoom",[230]="Kingdra",
    [231]="Phanpy",[232]="Donphan",[233]="Porygon2",[234]="Stantler",[235]="Smeargle",
    [236]="Tyrogue",[237]="Hitmontop",[238]="Smoochum",[239]="Elekid",[240]="Magby",
    [241]="Miltank",[242]="Blissey",[243]="Raikou",[244]="Entei",[245]="Suicune",
    [246]="Larvitar",[247]="Pupitar",[248]="Tyranitar",[249]="Lugia",[250]="Ho-Oh",
    [251]="Celebi",
    [252]="Treecko",[253]="Grovyle",[254]="Sceptile",[255]="Torchic",[256]="Combusken",
    [257]="Blaziken",[258]="Mudkip",[259]="Marshtomp",[260]="Swampert",[261]="Poochyena",
    [262]="Mightyena",[263]="Zigzagoon",[264]="Linoone",[265]="Wurmple",[266]="Silcoon",
    [267]="Beautifly",[268]="Cascoon",[269]="Dustox",[270]="Lotad",[271]="Lombre",
    [272]="Ludicolo",[273]="Seedot",[274]="Nuzleaf",[275]="Shiftry",[276]="Taillow",
    [277]="Swellow",[278]="Wingull",[279]="Pelipper",[280]="Ralts",[281]="Kirlia",
    [282]="Gardevoir",[283]="Surskit",[284]="Masquerain",[285]="Shroomish",[286]="Breloom",
    [287]="Slakoth",[288]="Vigoroth",[289]="Slaking",[290]="Nincada",[291]="Ninjask",
    [292]="Shedinja",[293]="Whismur",[294]="Loudred",[295]="Exploud",[296]="Makuhita",
    [297]="Hariyama",[298]="Azurill",[299]="Nosepass",[300]="Skitty",[301]="Delcatty",
    [302]="Sableye",[303]="Mawile",[304]="Aron",[305]="Lairon",[306]="Aggron",
    [307]="Meditite",[308]="Medicham",[309]="Electrike",[310]="Manectric",[311]="Plusle",
    [312]="Minun",[313]="Volbeat",[314]="Illumise",[315]="Roselia",[316]="Gulpin",
    [317]="Swalot",[318]="Carvanha",[319]="Sharpedo",[320]="Wailmer",[321]="Wailord",
    [322]="Numel",[323]="Camerupt",[324]="Torkoal",[325]="Spoink",[326]="Grumpig",
    [327]="Spinda",[328]="Trapinch",[329]="Vibrava",[330]="Flygon",[331]="Cacnea",
    [332]="Cacturne",[333]="Swablu",[334]="Altaria",[335]="Zangoose",[336]="Seviper",
    [337]="Lunatone",[338]="Solrock",[339]="Barboach",[340]="Whiscash",[341]="Corphish",
    [342]="Crawdaunt",[343]="Baltoy",[344]="Claydol",[345]="Lileep",[346]="Cradily",
    [347]="Anorith",[348]="Armaldo",[349]="Feebas",[350]="Milotic",[351]="Castform",
    [352]="Kecleon",[353]="Shuppet",[354]="Banette",[355]="Duskull",[356]="Dusclops",
    [357]="Tropius",[358]="Chimecho",[359]="Absol",[360]="Wynaut",[361]="Snorunt",
    [362]="Glalie",[363]="Spheal",[364]="Sealeo",[365]="Walrein",[366]="Clamperl",
    [367]="Huntail",[368]="Gorebyss",[369]="Relicanth",[370]="Luvdisc",[371]="Bagon",
    [372]="Shelgon",[373]="Salamence",[374]="Beldum",[375]="Metang",[376]="Metagross",
    [377]="Regirock",[378]="Regice",[379]="Registeel",[380]="Latias",[381]="Latios",
    [382]="Kyogre",[383]="Groudon",[384]="Rayquaza",[385]="Jirachi",[386]="Deoxys",
}

-- Pokemon types
local TYPES = {
    [0]="normal",[1]="fighting",[2]="flying",[3]="poison",[4]="ground",
    [5]="rock",[6]="bug",[7]="ghost",[8]="steel",[9]="fire",
    [10]="water",[11]="grass",[12]="electric",[13]="psychic",[14]="ice",
    [15]="dragon",[16]="dark",
}

-- Species -> types mapping (Gen 3, national dex ID)
-- Format: {type1, type2} (type2 can be nil for single-type)
local SPECIES_TYPES = {
    [1]={"grass","poison"},[2]={"grass","poison"},[3]={"grass","poison"},
    [4]={"fire"},[5]={"fire"},[6]={"fire","flying"},
    [7]={"water"},[8]={"water"},[9]={"water"},
    [10]={"bug"},[11]={"bug"},[12]={"bug","flying"},
    [13]={"bug","poison"},[14]={"bug","poison"},[15]={"bug","poison"},
    [16]={"normal","flying"},[17]={"normal","flying"},[18]={"normal","flying"},
    [19]={"normal"},[20]={"normal"},[21]={"normal","flying"},[22]={"normal","flying"},
    [23]={"poison"},[24]={"poison"},[25]={"electric"},[26]={"electric"},
    [27]={"ground"},[28]={"ground"},[29]={"poison"},[30]={"poison"},
    [31]={"poison","ground"},[32]={"poison"},[33]={"poison"},[34]={"poison","ground"},
    [35]={"normal"},[36]={"normal"},[37]={"fire"},[38]={"fire"},
    [39]={"normal"},[40]={"normal"},[41]={"poison","flying"},[42]={"poison","flying"},
    [43]={"grass","poison"},[44]={"grass","poison"},[45]={"grass","poison"},
    [46]={"bug","grass"},[47]={"bug","grass"},[48]={"bug","poison"},[49]={"bug","poison"},
    [50]={"ground"},[51]={"ground"},[52]={"normal"},[53]={"normal"},
    [54]={"water"},[55]={"water"},[56]={"fighting"},[57]={"fighting"},
    [58]={"fire"},[59]={"fire"},[60]={"water"},[61]={"water"},
    [62]={"water","fighting"},[63]={"psychic"},[64]={"psychic"},[65]={"psychic"},
    [66]={"fighting"},[67]={"fighting"},[68]={"fighting"},
    [69]={"grass","poison"},[70]={"grass","poison"},[71]={"grass","poison"},
    [72]={"water","poison"},[73]={"water","poison"},[74]={"rock","ground"},
    [75]={"rock","ground"},[76]={"rock","ground"},[77]={"fire"},[78]={"fire"},
    [79]={"water","psychic"},[80]={"water","psychic"},[81]={"electric","steel"},
    [82]={"electric","steel"},[83]={"normal","flying"},[84]={"normal","flying"},
    [85]={"normal","flying"},[86]={"water"},[87]={"water","ice"},
    [88]={"poison"},[89]={"poison"},[90]={"water"},[91]={"water","ice"},
    [92]={"ghost","poison"},[93]={"ghost","poison"},[94]={"ghost","poison"},
    [95]={"rock","ground"},[96]={"psychic"},[97]={"psychic"},
    [98]={"water"},[99]={"water"},[100]={"electric"},[101]={"electric"},
    [102]={"grass","psychic"},[103]={"grass","psychic"},[104]={"ground"},[105]={"ground"},
    [106]={"fighting"},[107]={"fighting"},[108]={"normal"},[109]={"poison"},[110]={"poison"},
    [111]={"ground","rock"},[112]={"ground","rock"},[113]={"normal"},[114]={"grass"},
    [115]={"normal"},[116]={"water"},[117]={"water"},[118]={"water"},[119]={"water"},
    [120]={"water"},[121]={"water","psychic"},[122]={"psychic"},[123]={"bug","flying"},
    [124]={"ice","psychic"},[125]={"electric"},[126]={"fire"},[127]={"bug"},[128]={"normal"},
    [129]={"water"},[130]={"water","flying"},[131]={"water","ice"},[132]={"normal"},
    [133]={"normal"},[134]={"water"},[135]={"electric"},[136]={"fire"},
    [137]={"normal"},[138]={"rock","water"},[139]={"rock","water"},
    [140]={"rock","water"},[141]={"rock","water"},[142]={"rock","flying"},
    [143]={"normal"},[144]={"ice","flying"},[145]={"electric","flying"},
    [146]={"fire","flying"},[147]={"dragon"},[148]={"dragon"},[149]={"dragon","flying"},
    [150]={"psychic"},[151]={"psychic"},
    -- Gen 2
    [152]={"grass"},[153]={"grass"},[154]={"grass"},
    [155]={"fire"},[156]={"fire"},[157]={"fire"},
    [158]={"water"},[159]={"water"},[160]={"water"},
    [169]={"poison","flying"},[175]={"normal"},[176]={"normal","flying"},
    [181]={"electric"},[196]={"psychic"},[197]={"dark"},
    [212]={"bug","steel"},[214]={"bug","fighting"},[229]={"dark","fire"},
    [245]={"water"},[248]={"rock","dark"},[249]={"psychic","flying"},
    [250]={"fire","flying"},[251]={"psychic","grass"},
    -- Gen 3
    [252]={"grass"},[253]={"grass"},[254]={"grass"},
    [255]={"fire"},[256]={"fire","fighting"},[257]={"fire","fighting"},
    [258]={"water"},[259]={"water","ground"},[260]={"water","ground"},
    [282]={"psychic"},[330]={"ground","dragon"},[334]={"dragon","flying"},
    [350]={"water"},[359]={"dark"},[373]={"dragon","flying"},
    [376]={"steel","psychic"},[384]={"dragon","flying"},
}

-- Ability names (Gen 3 ability indices)
local ABILITIES = {
    [0]="",[1]="Stench",[2]="Drizzle",[3]="Speed Boost",[4]="Battle Armor",
    [5]="Sturdy",[6]="Damp",[7]="Limber",[8]="Sand Veil",[9]="Static",
    [10]="Volt Absorb",[11]="Water Absorb",[12]="Oblivious",[13]="Cloud Nine",
    [14]="Compound Eyes",[15]="Insomnia",[16]="Color Change",[17]="Immunity",
    [18]="Flash Fire",[19]="Shield Dust",[20]="Own Tempo",[21]="Suction Cups",
    [22]="Intimidate",[23]="Shadow Tag",[24]="Rough Skin",[25]="Wonder Guard",
    [26]="Levitate",[27]="Effect Spore",[28]="Synchronize",[29]="Clear Body",
    [30]="Natural Cure",[31]="Lightning Rod",[32]="Serene Grace",[33]="Swift Swim",
    [34]="Chlorophyll",[35]="Illuminate",[36]="Trace",[37]="Huge Power",
    [38]="Poison Point",[39]="Inner Focus",[40]="Magma Armor",[41]="Water Veil",
    [42]="Magnet Pull",[43]="Soundproof",[44]="Rain Dish",[45]="Sand Stream",
    [46]="Pressure",[47]="Thick Fat",[48]="Early Bird",[49]="Flame Body",
    [50]="Run Away",[51]="Keen Eye",[52]="Hyper Cutter",[53]="Pickup",
    [54]="Truant",[55]="Hustle",[56]="Cute Charm",[57]="Plus",[58]="Minus",
    [59]="Forecast",[60]="Sticky Hold",[61]="Shed Skin",[62]="Guts",
    [63]="Marvel Scale",[64]="Liquid Ooze",[65]="Overgrow",[66]="Blaze",
    [67]="Torrent",[68]="Swarm",[69]="Rock Head",[70]="Drought",
    [71]="Arena Trap",[72]="Vital Spirit",[73]="White Smoke",[74]="Pure Power",
    [75]="Shell Armor",[76]="Cacophony",
}

-- Move data: {name, type, power, accuracy, pp, damageClass}
local MOVES = {
    [0]={"--","normal",0,0,0,"status"},
    [1]={"Pound","normal",40,100,35,"physical"},
    [2]={"Karate Chop","fighting",50,100,25,"physical"},
    [3]={"Double Slap","normal",15,85,10,"physical"},
    [4]={"Comet Punch","normal",18,85,15,"physical"},
    [5]={"Mega Punch","normal",80,85,20,"physical"},
    [6]={"Pay Day","normal",40,100,20,"physical"},
    [7]={"Fire Punch","fire",75,100,15,"physical"},
    [8]={"Ice Punch","ice",75,100,15,"physical"},
    [9]={"Thunder Punch","electric",75,100,15,"physical"},
    [10]={"Scratch","normal",40,100,35,"physical"},
    [11]={"Vise Grip","normal",55,100,30,"physical"},
    [12]={"Guillotine","normal",0,30,5,"physical"},
    [13]={"Razor Wind","normal",80,100,10,"special"},
    [14]={"Swords Dance","normal",0,0,30,"status"},
    [15]={"Cut","normal",50,95,30,"physical"},
    [16]={"Gust","flying",40,100,35,"special"},
    [17]={"Wing Attack","flying",60,100,35,"physical"},
    [18]={"Whirlwind","normal",0,100,20,"status"},
    [19]={"Fly","flying",70,95,15,"physical"},
    [20]={"Bind","normal",15,75,20,"physical"},
    [21]={"Slam","normal",80,75,20,"physical"},
    [22]={"Vine Whip","grass",35,100,10,"physical"},
    [23]={"Stomp","normal",65,100,20,"physical"},
    [24]={"Double Kick","fighting",30,100,30,"physical"},
    [25]={"Mega Kick","normal",120,75,5,"physical"},
    [26]={"Jump Kick","fighting",70,95,25,"physical"},
    [27]={"Rolling Kick","fighting",60,85,15,"physical"},
    [28]={"Sand Attack","ground",0,100,15,"status"},
    [29]={"Headbutt","normal",70,100,15,"physical"},
    [30]={"Horn Attack","normal",65,100,25,"physical"},
    [31]={"Fury Attack","normal",15,85,20,"physical"},
    [32]={"Horn Drill","normal",0,30,5,"physical"},
    [33]={"Tackle","normal",35,95,35,"physical"},
    [34]={"Body Slam","normal",85,100,15,"physical"},
    [35]={"Wrap","normal",15,85,20,"physical"},
    [36]={"Take Down","normal",90,85,20,"physical"},
    [37]={"Thrash","normal",90,100,20,"physical"},
    [38]={"Double-Edge","normal",120,100,15,"physical"},
    [39]={"Tail Whip","normal",0,100,30,"status"},
    [40]={"Poison Sting","poison",15,100,35,"physical"},
    [41]={"Twineedle","bug",25,100,20,"physical"},
    [42]={"Pin Missile","bug",14,85,20,"physical"},
    [43]={"Leer","normal",0,100,30,"status"},
    [44]={"Bite","dark",60,100,25,"physical"},
    [45]={"Growl","normal",0,100,40,"status"},
    [46]={"Roar","normal",0,100,20,"status"},
    [47]={"Sing","normal",0,55,15,"status"},
    [48]={"Supersonic","normal",0,55,20,"status"},
    [49]={"Sonic Boom","normal",0,90,20,"special"},
    [50]={"Disable","normal",0,55,20,"status"},
    [51]={"Acid","poison",40,100,30,"special"},
    [52]={"Ember","fire",40,100,25,"special"},
    [53]={"Flamethrower","fire",95,100,15,"special"},
    [54]={"Mist","ice",0,0,30,"status"},
    [55]={"Water Gun","water",40,100,25,"special"},
    [56]={"Hydro Pump","water",120,80,5,"special"},
    [57]={"Surf","water",95,100,15,"special"},
    [58]={"Ice Beam","ice",95,100,10,"special"},
    [59]={"Blizzard","ice",120,70,5,"special"},
    [60]={"Psybeam","psychic",65,100,20,"special"},
    [61]={"Bubble Beam","water",65,100,20,"special"},
    [62]={"Aurora Beam","ice",65,100,20,"special"},
    [63]={"Hyper Beam","normal",150,90,5,"special"},
    [64]={"Peck","flying",35,100,35,"physical"},
    [65]={"Drill Peck","flying",80,100,20,"physical"},
    [66]={"Submission","fighting",80,80,25,"physical"},
    [67]={"Low Kick","fighting",0,100,20,"physical"},
    [68]={"Counter","fighting",0,100,20,"physical"},
    [69]={"Seismic Toss","fighting",0,100,20,"physical"},
    [70]={"Strength","normal",80,100,15,"physical"},
    [71]={"Absorb","grass",20,100,20,"special"},
    [72]={"Mega Drain","grass",40,100,10,"special"},
    [73]={"Leech Seed","grass",0,90,10,"status"},
    [74]={"Growth","normal",0,0,40,"status"},
    [75]={"Razor Leaf","grass",55,95,25,"physical"},
    [76]={"Solar Beam","grass",120,100,10,"special"},
    [77]={"Poison Powder","poison",0,75,35,"status"},
    [78]={"Stun Spore","grass",0,75,30,"status"},
    [79]={"Sleep Powder","grass",0,75,15,"status"},
    [80]={"Petal Dance","grass",70,100,20,"special"},
    [81]={"String Shot","bug",0,95,40,"status"},
    [82]={"Dragon Rage","dragon",0,100,10,"special"},
    [83]={"Fire Spin","fire",15,70,15,"special"},
    [84]={"Thunder Shock","electric",40,100,30,"special"},
    [85]={"Thunderbolt","electric",95,100,15,"special"},
    [86]={"Thunder Wave","electric",0,100,20,"status"},
    [87]={"Thunder","electric",120,70,10,"special"},
    [88]={"Rock Throw","rock",50,90,15,"physical"},
    [89]={"Earthquake","ground",100,100,10,"physical"},
    [90]={"Fissure","ground",0,30,5,"physical"},
    [91]={"Dig","ground",60,100,10,"physical"},
    [92]={"Toxic","poison",0,85,10,"status"},
    [93]={"Confusion","psychic",50,100,25,"special"},
    [94]={"Psychic","psychic",90,100,10,"special"},
    [95]={"Hypnosis","psychic",0,60,20,"status"},
    [96]={"Meditate","psychic",0,0,40,"status"},
    [97]={"Agility","psychic",0,0,30,"status"},
    [98]={"Quick Attack","normal",40,100,30,"physical"},
    [99]={"Rage","normal",20,100,20,"physical"},
    [100]={"Teleport","psychic",0,0,20,"status"},
    [101]={"Night Shade","ghost",0,100,15,"special"},
    [102]={"Mimic","normal",0,100,10,"status"},
    [103]={"Screech","normal",0,85,40,"status"},
    [104]={"Double Team","normal",0,0,15,"status"},
    [105]={"Recover","normal",0,0,10,"status"},
    [106]={"Harden","normal",0,0,30,"status"},
    [107]={"Minimize","normal",0,0,20,"status"},
    [108]={"Smokescreen","normal",0,100,20,"status"},
    [109]={"Confuse Ray","ghost",0,100,10,"status"},
    [110]={"Withdraw","water",0,0,40,"status"},
    [111]={"Defense Curl","normal",0,0,40,"status"},
    [112]={"Barrier","psychic",0,0,30,"status"},
    [113]={"Light Screen","psychic",0,0,30,"status"},
    [114]={"Haze","ice",0,0,30,"status"},
    [115]={"Reflect","psychic",0,0,20,"status"},
    [116]={"Focus Energy","normal",0,0,30,"status"},
    [117]={"Bide","normal",0,100,10,"physical"},
    [118]={"Metronome","normal",0,0,10,"status"},
    [119]={"Mirror Move","flying",0,0,20,"status"},
    [120]={"Self-Destruct","normal",200,100,5,"physical"},
    [121]={"Egg Bomb","normal",100,75,10,"physical"},
    [122]={"Lick","ghost",20,100,30,"physical"},
    [123]={"Smog","poison",20,70,20,"special"},
    [124]={"Sludge","poison",65,100,20,"special"},
    [125]={"Bone Club","ground",65,85,20,"physical"},
    [126]={"Fire Blast","fire",120,85,5,"special"},
    [127]={"Waterfall","water",80,100,15,"physical"},
    [128]={"Clamp","water",35,75,10,"physical"},
    [129]={"Swift","normal",60,0,20,"special"},
    [130]={"Skull Bash","normal",100,100,15,"physical"},
    [131]={"Spike Cannon","normal",20,100,15,"physical"},
    [132]={"Constrict","normal",10,100,35,"physical"},
    [133]={"Amnesia","psychic",0,0,20,"status"},
    [134]={"Kinesis","psychic",0,80,15,"status"},
    [135]={"Soft-Boiled","normal",0,0,10,"status"},
    [136]={"High Jump Kick","fighting",85,90,20,"physical"},
    [137]={"Glare","normal",0,75,30,"status"},
    [138]={"Dream Eater","psychic",100,100,15,"special"},
    [139]={"Poison Gas","poison",0,55,40,"status"},
    [140]={"Barrage","normal",15,85,20,"physical"},
    [141]={"Leech Life","bug",20,100,15,"physical"},
    [142]={"Lovely Kiss","normal",0,75,10,"status"},
    [143]={"Sky Attack","flying",140,90,5,"physical"},
    [144]={"Transform","normal",0,0,10,"status"},
    [145]={"Bubble","water",20,100,30,"special"},
    [146]={"Dizzy Punch","normal",70,100,10,"physical"},
    [147]={"Spore","grass",0,100,15,"status"},
    [148]={"Flash","normal",0,70,20,"status"},
    [149]={"Psywave","psychic",0,80,15,"special"},
    [150]={"Splash","normal",0,0,40,"status"},
    [151]={"Acid Armor","poison",0,0,40,"status"},
    [152]={"Crabhammer","water",90,85,10,"physical"},
    [153]={"Explosion","normal",250,100,5,"physical"},
    [154]={"Fury Swipes","normal",18,80,15,"physical"},
    [155]={"Bonemerang","ground",50,90,10,"physical"},
    [156]={"Rest","psychic",0,0,10,"status"},
    [157]={"Rock Slide","rock",75,90,10,"physical"},
    [158]={"Hyper Fang","normal",80,90,15,"physical"},
    [159]={"Sharpen","normal",0,0,30,"status"},
    [160]={"Conversion","normal",0,0,30,"status"},
    [161]={"Tri Attack","normal",80,100,10,"special"},
    [162]={"Super Fang","normal",0,90,10,"physical"},
    [163]={"Slash","normal",70,100,20,"physical"},
    [164]={"Substitute","normal",0,0,10,"status"},
    [165]={"Struggle","normal",50,100,1,"physical"},
    -- Gen 2+ moves commonly used in Gen 3
    [168]={"Thief","dark",40,100,10,"physical"},
    [172]={"Flame Wheel","fire",60,100,25,"physical"},
    [173]={"Snore","normal",40,100,15,"special"},
    [174]={"Curse","ghost",0,0,10,"status"},
    [175]={"Flail","normal",0,100,15,"physical"},
    [177]={"Aeroblast","flying",100,95,5,"special"},
    [179]={"Reversal","fighting",0,100,15,"physical"},
    [180]={"Spite","ghost",0,100,10,"status"},
    [182]={"Protect","normal",0,0,10,"status"},
    [183]={"Mach Punch","fighting",40,100,30,"physical"},
    [185]={"Feint Attack","dark",60,0,20,"physical"},
    [188]={"Sludge Bomb","poison",90,100,10,"special"},
    [189]={"Mud-Slap","ground",20,100,10,"special"},
    [192]={"Zap Cannon","electric",120,50,5,"special"},
    [195]={"Perish Song","normal",0,0,5,"status"},
    [196]={"Icy Wind","ice",55,95,15,"special"},
    [197]={"Detect","fighting",0,0,5,"status"},
    [200]={"Outrage","dragon",90,100,15,"physical"},
    [202]={"Giga Drain","grass",60,100,5,"special"},
    [203]={"Endure","normal",0,0,10,"status"},
    [204]={"Charm","normal",0,100,20,"status"},
    [205]={"Rollout","rock",30,90,20,"physical"},
    [206]={"False Swipe","normal",40,100,40,"physical"},
    [207]={"Swagger","normal",0,90,15,"status"},
    [210]={"Fury Cutter","bug",10,95,20,"physical"},
    [211]={"Steel Wing","steel",70,90,25,"physical"},
    [213]={"Attract","normal",0,100,15,"status"},
    [214]={"Sleep Talk","normal",0,0,10,"status"},
    [215]={"Heal Bell","normal",0,0,5,"status"},
    [216]={"Return","normal",0,100,20,"physical"},
    [217]={"Present","normal",0,90,15,"physical"},
    [218]={"Frustration","normal",0,100,20,"physical"},
    [219]={"Safeguard","normal",0,0,25,"status"},
    [221]={"Sacred Fire","fire",100,95,5,"physical"},
    [223]={"Dynamic Punch","fighting",100,50,5,"physical"},
    [225]={"Dragon Breath","dragon",60,100,20,"special"},
    [226]={"Baton Pass","normal",0,0,40,"status"},
    [227]={"Encore","normal",0,100,5,"status"},
    [228]={"Pursuit","dark",40,100,20,"physical"},
    [229]={"Rapid Spin","normal",20,100,40,"physical"},
    [231]={"Iron Tail","steel",100,75,15,"physical"},
    [233]={"Vital Throw","fighting",70,0,10,"physical"},
    [234]={"Morning Sun","normal",0,0,5,"status"},
    [235]={"Synthesis","grass",0,0,5,"status"},
    [236]={"Moonlight","normal",0,0,5,"status"},
    [237]={"Hidden Power","normal",0,100,15,"special"},
    [238]={"Cross Chop","fighting",100,80,5,"physical"},
    [239]={"Twister","dragon",40,100,20,"special"},
    [241]={"Sunny Day","fire",0,0,5,"status"},
    [242]={"Crunch","dark",80,100,15,"physical"},
    [243]={"Mirror Coat","psychic",0,100,20,"special"},
    [245]={"Extreme Speed","normal",80,100,5,"physical"},
    [246]={"Ancient Power","rock",60,100,5,"special"},
    [247]={"Shadow Ball","ghost",80,100,15,"special"},
    [248]={"Future Sight","psychic",80,90,15,"special"},
    [249]={"Rock Smash","fighting",20,100,15,"physical"},
    [250]={"Whirlpool","water",15,70,15,"special"},
    [252]={"Fake Out","normal",40,100,10,"physical"},
    [253]={"Uproar","normal",50,100,10,"special"},
    [254]={"Stockpile","normal",0,0,10,"status"},
    [255]={"Spit Up","normal",0,100,10,"special"},
    [256]={"Swallow","normal",0,0,10,"status"},
    [257]={"Heat Wave","fire",100,90,10,"special"},
    [258]={"Hail","ice",0,0,10,"status"},
    [259]={"Torment","dark",0,100,15,"status"},
    [260]={"Flatter","dark",0,100,15,"status"},
    [261]={"Will-O-Wisp","fire",0,75,15,"status"},
    [262]={"Memento","dark",0,100,10,"status"},
    [263]={"Facade","normal",70,100,20,"physical"},
    [264]={"Focus Punch","fighting",150,100,20,"physical"},
    [265]={"Smelling Salts","normal",60,100,10,"physical"},
    [266]={"Follow Me","normal",0,0,20,"status"},
    [267]={"Nature Power","normal",0,0,20,"status"},
    [269]={"Taunt","dark",0,100,20,"status"},
    [270]={"Helping Hand","normal",0,0,20,"status"},
    [271]={"Trick","psychic",0,100,10,"status"},
    [272]={"Role Play","psychic",0,0,10,"status"},
    [273]={"Wish","normal",0,0,10,"status"},
    [274]={"Assist","normal",0,0,20,"status"},
    [275]={"Ingrain","grass",0,0,20,"status"},
    [276]={"Superpower","fighting",120,100,5,"physical"},
    [277]={"Magic Coat","psychic",0,0,15,"status"},
    [278]={"Recycle","normal",0,0,10,"status"},
    [279]={"Revenge","fighting",60,100,10,"physical"},
    [280]={"Brick Break","fighting",75,100,15,"physical"},
    [281]={"Yawn","normal",0,0,10,"status"},
    [282]={"Knock Off","dark",20,100,20,"physical"},
    [283]={"Endeavor","normal",0,100,5,"physical"},
    [284]={"Eruption","fire",150,100,5,"special"},
    [285]={"Skill Swap","psychic",0,0,10,"status"},
    [286]={"Imprison","psychic",0,0,10,"status"},
    [287]={"Refresh","normal",0,0,20,"status"},
    [288]={"Grudge","ghost",0,0,5,"status"},
    [289]={"Snatch","dark",0,0,10,"status"},
    [290]={"Secret Power","normal",70,100,20,"physical"},
    [291]={"Dive","water",60,100,10,"physical"},
    [292]={"Arm Thrust","fighting",15,100,20,"physical"},
    [293]={"Camouflage","normal",0,0,20,"status"},
    [294]={"Tail Glow","bug",0,0,20,"status"},
    [295]={"Luster Purge","psychic",70,100,5,"special"},
    [296]={"Mist Ball","psychic",70,100,5,"special"},
    [297]={"Feather Dance","flying",0,100,15,"status"},
    [298]={"Teeter Dance","normal",0,100,20,"status"},
    [299]={"Blaze Kick","fire",85,90,10,"physical"},
    [300]={"Mud Sport","ground",0,0,15,"status"},
    [301]={"Ice Ball","ice",30,90,20,"physical"},
    [302]={"Needle Arm","grass",60,100,15,"physical"},
    [303]={"Slack Off","normal",0,0,10,"status"},
    [304]={"Hyper Voice","normal",90,100,10,"special"},
    [305]={"Poison Fang","poison",50,100,15,"physical"},
    [306]={"Crush Claw","normal",75,95,10,"physical"},
    [307]={"Blast Burn","fire",150,90,5,"special"},
    [308]={"Hydro Cannon","water",150,90,5,"special"},
    [309]={"Meteor Mash","steel",100,85,10,"physical"},
    [310]={"Astonish","ghost",30,100,15,"physical"},
    [311]={"Weather Ball","normal",50,100,10,"special"},
    [312]={"Aromatherapy","grass",0,0,5,"status"},
    [313]={"Fake Tears","dark",0,100,20,"status"},
    [314]={"Air Cutter","flying",55,95,25,"special"},
    [315]={"Overheat","fire",140,90,5,"special"},
    [316]={"Odor Sleuth","normal",0,0,40,"status"},
    [317]={"Rock Tomb","rock",50,80,10,"physical"},
    [318]={"Silver Wind","bug",60,100,5,"special"},
    [319]={"Metal Sound","steel",0,85,40,"status"},
    [320]={"Grass Whistle","grass",0,55,15,"status"},
    [321]={"Tickle","normal",0,100,20,"status"},
    [322]={"Cosmic Power","psychic",0,0,20,"status"},
    [323]={"Water Spout","water",150,100,5,"special"},
    [324]={"Signal Beam","bug",75,100,15,"special"},
    [325]={"Shadow Punch","ghost",60,0,20,"physical"},
    [326]={"Extrasensory","psychic",80,100,30,"special"},
    [327]={"Sky Uppercut","fighting",85,90,15,"physical"},
    [328]={"Sand Tomb","ground",15,70,15,"physical"},
    [329]={"Sheer Cold","ice",0,30,5,"special"},
    [330]={"Muddy Water","water",95,85,10,"special"},
    [331]={"Bullet Seed","grass",10,100,30,"physical"},
    [332]={"Aerial Ace","flying",60,0,20,"physical"},
    [333]={"Icicle Spear","ice",10,100,30,"physical"},
    [334]={"Iron Defense","steel",0,0,15,"status"},
    [335]={"Block","normal",0,0,5,"status"},
    [336]={"Howl","normal",0,0,40,"status"},
    [337]={"Dragon Claw","dragon",80,100,15,"physical"},
    [338]={"Frenzy Plant","grass",150,90,5,"special"},
    [339]={"Bulk Up","fighting",0,0,20,"status"},
    [340]={"Bounce","flying",85,85,5,"physical"},
    [341]={"Mud Shot","ground",55,95,15,"special"},
    [342]={"Poison Tail","poison",50,100,25,"physical"},
    [343]={"Covet","normal",40,100,40,"physical"},
    [344]={"Volt Tackle","electric",120,100,15,"physical"},
    [345]={"Magical Leaf","grass",60,0,20,"special"},
    [346]={"Water Sport","water",0,0,15,"status"},
    [347]={"Calm Mind","psychic",0,0,20,"status"},
    [348]={"Leaf Blade","grass",70,100,15,"physical"},
    [349]={"Dragon Dance","dragon",0,0,20,"status"},
    [350]={"Rock Blast","rock",25,80,10,"physical"},
    [351]={"Shock Wave","electric",60,0,20,"special"},
    [352]={"Water Pulse","water",60,100,20,"special"},
    [353]={"Doom Desire","steel",120,85,5,"special"},
    [354]={"Psycho Boost","psychic",140,90,5,"special"},
}

-- Held items (Gen 3 item indices for common items)
local ITEMS = {
    [0]="",[1]="Master Ball",[2]="Ultra Ball",[3]="Great Ball",[4]="Poke Ball",
    [13]="Potion",[14]="Antidote",[15]="Burn Heal",[16]="Ice Heal",
    [17]="Awakening",[18]="Parlyz Heal",[19]="Full Restore",[20]="Max Potion",
    [21]="Hyper Potion",[22]="Super Potion",
    [179]="Bright Powder",[180]="White Herb",[181]="Macho Brace",
    [182]="Exp. Share",[183]="Quick Claw",[184]="Soothe Bell",
    [185]="Mental Herb",[186]="Choice Band",[187]="King's Rock",
    [188]="Silver Powder",[189]="Amulet Coin",[190]="Cleanse Tag",
    [191]="Soul Dew",[192]="Deep Sea Tooth",[193]="Deep Sea Scale",
    [194]="Smoke Ball",[195]="Everstone",[196]="Focus Band",
    [197]="Lucky Egg",[198]="Scope Lens",[199]="Metal Coat",
    [200]="Leftovers",[201]="Dragon Scale",[202]="Light Ball",
    [203]="Soft Sand",[204]="Hard Stone",[205]="Miracle Seed",
    [206]="Black Glasses",[207]="Black Belt",[208]="Magnet",
    [209]="Mystic Water",[210]="Sharp Beak",[211]="Poison Barb",
    [212]="Never-Melt Ice",[213]="Spell Tag",[214]="Twisted Spoon",
    [215]="Charcoal",[216]="Dragon Fang",[217]="Silk Scarf",
    [218]="Up-Grade",[219]="Shell Bell",[220]="Sea Incense",
    [221]="Lax Incense",[222]="Lucky Punch",[223]="Metal Powder",
    [224]="Thick Club",[225]="Stick",
}

-- Natures (indexed by personality % 25)
local NATURES = {
    [0]="Hardy",[1]="Lonely",[2]="Brave",[3]="Adamant",[4]="Naughty",
    [5]="Bold",[6]="Docile",[7]="Relaxed",[8]="Impish",[9]="Lax",
    [10]="Timid",[11]="Hasty",[12]="Serious",[13]="Jolly",[14]="Naive",
    [15]="Modest",[16]="Mild",[17]="Quiet",[18]="Bashful",[19]="Rash",
    [20]="Calm",[21]="Gentle",[22]="Sassy",[23]="Careful",[24]="Quirky",
}

-- Gender ratio thresholds: personality % 256 < threshold = female
-- Default 127 (50/50). 0=always male, 254=always female, 255=genderless
local GENDER_RATIO = {}
local function _gr(threshold, ids)
    for _, id in ipairs(ids) do GENDER_RATIO[id] = threshold end
end
-- Genderless
_gr(255, {81,82,100,101,120,121,132,137,150,151,201,233,243,244,245,249,250,251,
    292,337,338,343,344,351,374,375,376,377,378,379,382,383,384,385,386})
-- Always female
_gr(254, {29,30,31,113,115,124,238,241,242,314,380})
-- Always male
_gr(0, {32,33,34,106,107,128,236,237,313,381})
-- 87.5% male (starters, eevee, fossils, etc.)
_gr(31, {1,2,3,4,5,6,7,8,9,133,134,135,136,138,139,140,141,142,143,
    152,153,154,155,156,157,158,159,160,175,176,196,197,
    252,253,254,255,256,257,258,259,260,345,346,347,348,369,371,372,373})
-- 75% male
_gr(63, {58,59,63,64,65,66,67,68,111,112,125,126,239,240,246,247,248})
-- 75% female
_gr(191, {35,36,39,40,173,174,183,184,298,300,301,315})

-- Species abilities: {ability1, ability2} (Gen 3 ability IDs)
-- ability2 = 0 means only one ability
local SPECIES_ABILITIES = {
    [1]={65,0},[2]={65,0},[3]={65,0},[4]={66,0},[5]={66,0},[6]={66,0},
    [7]={67,0},[8]={67,0},[9]={67,0},[10]={19,0},[11]={61,0},[12]={14,0},
    [13]={19,0},[14]={61,0},[15]={68,0},[16]={51,0},[17]={51,0},[18]={51,0},
    [19]={50,62},[20]={50,62},[21]={51,0},[22]={51,0},[23]={22,61},[24]={22,61},
    [25]={9,0},[26]={9,0},[27]={8,0},[28]={8,0},[29]={38,0},[30]={38,0},
    [31]={38,0},[32]={38,0},[33]={38,0},[34]={38,0},[35]={56,0},[36]={56,0},
    [37]={18,0},[38]={18,0},[39]={56,0},[40]={56,0},[41]={39,0},[42]={39,0},
    [43]={34,0},[44]={34,0},[45]={27,0},[46]={27,0},[47]={27,0},[48]={14,0},
    [49]={19,0},[50]={8,71},[51]={8,71},[52]={53,0},[53]={7,0},
    [54]={6,13},[55]={6,13},[56]={72,0},[57]={72,0},[58]={22,18},[59]={22,18},
    [60]={6,11},[61]={6,11},[62]={6,11},[63]={28,39},[64]={28,39},[65]={28,39},
    [66]={62,0},[67]={62,0},[68]={62,0},[69]={34,0},[70]={34,0},[71]={34,0},
    [72]={29,64},[73]={29,64},[74]={69,5},[75]={69,5},[76]={69,5},
    [77]={50,18},[78]={50,18},[79]={12,20},[80]={12,20},[81]={42,5},
    [82]={42,5},[83]={51,39},[84]={50,48},[85]={50,48},[86]={47,0},[87]={47,0},
    [88]={1,60},[89]={1,60},[90]={75,0},[91]={75,0},[92]={26,0},[93]={26,0},
    [94]={26,0},[95]={69,5},[96]={15,0},[97]={15,0},[98]={52,75},
    [99]={52,75},[100]={43,9},[101]={43,9},[102]={34,0},[103]={34,0},
    [104]={69,31},[105]={69,31},[106]={7,0},[107]={51,0},[108]={12,20},
    [109]={26,0},[110]={26,0},[111]={69,31},[112]={69,31},[113]={30,32},
    [114]={34,0},[115]={48,0},[116]={33,0},[117]={38,0},[118]={33,41},
    [119]={33,41},[120]={35,30},[121]={35,30},[122]={43,0},[123]={68,0},
    [124]={12,0},[125]={9,0},[126]={49,0},[127]={52,0},
    [128]={22,0},[129]={33,0},[130]={22,0},[131]={11,75},
    [132]={7,0},[133]={50,0},[134]={11,0},[135]={10,0},[136]={18,0},
    [137]={36,0},[138]={33,75},[139]={33,75},[140]={33,4},[141]={33,4},
    [142]={69,46},[143]={17,47},[144]={46,0},[145]={46,0},[146]={46,0},
    [147]={61,0},[148]={61,0},[149]={39,0},
    [150]={46,0},[151]={28,0},
    [152]={65,0},[153]={65,0},[154]={65,0},[155]={66,0},[156]={66,0},
    [157]={66,0},[158]={67,0},[159]={67,0},[160]={67,0},
    [161]={50,51},[162]={50,51},[163]={15,51},[164]={15,51},
    [165]={68,48},[166]={68,48},[167]={68,15},[168]={68,15},
    [169]={39,0},[170]={10,35},[171]={10,35},[172]={9,0},
    [173]={56,0},[174]={56,0},[175]={55,32},[176]={55,32},
    [177]={28,48},[178]={28,48},[179]={9,0},[180]={9,0},[181]={9,0},
    [182]={34,0},[183]={47,37},[184]={47,37},[185]={5,69},
    [186]={6,11},[187]={34,48},[188]={34,48},[189]={34,48},
    [190]={50,53},[191]={34,0},[192]={34,48},[193]={3,14},
    [194]={6,11},[195]={6,11},[196]={28,0},[197]={28,0},
    [198]={15,0},[199]={12,20},[200]={26,0},[201]={26,0},
    [202]={23,0},[203]={39,48},[204]={5,0},[205]={5,0},
    [206]={32,50},[207]={52,22},[208]={5,69},[209]={22,50},
    [210]={22,50},[211]={33,38},[212]={68,0},[213]={5,0},
    [214]={68,62},[215]={39,51},[216]={53,0},[217]={62,0},
    [218]={40,49},[219]={69,0},[220]={12,0},[221]={12,0},
    [222]={55,30},[223]={55,0},[224]={21,0},[225]={72,15},
    [226]={33,11},[227]={51,5},[228]={48,18},[229]={48,18},
    [230]={33,0},[231]={53,0},[232]={5,0},[233]={36,0},
    [234]={22,0},[235]={20,0},[236]={62,0},[237]={22,0},
    [238]={12,0},[239]={9,0},[240]={49,0},[241]={47,0},
    [242]={30,32},[243]={46,0},[244]={46,0},[245]={46,0},
    [246]={62,0},[247]={61,0},[248]={45,0},[249]={46,0},[250]={46,0},
    [251]={30,0},
    [252]={65,0},[253]={65,0},[254]={65,0},[255]={66,0},[256]={66,0},
    [257]={66,3},[258]={67,0},[259]={67,0},[260]={67,0},
    [261]={50,51},[262]={22,51},[263]={53,0},[264]={53,0},
    [265]={19,0},[266]={61,0},[267]={68,0},[268]={61,0},[269]={19,0},
    [270]={33,44},[271]={33,44},[272]={33,44},[273]={34,48},
    [274]={34,48},[275]={34,48},[276]={62,0},[277]={62,0},
    [278]={51,0},[279]={51,0},[280]={28,36},[281]={28,36},[282]={28,36},
    [283]={33,0},[284]={22,0},[285]={27,0},[286]={27,0},
    [287]={54,0},[288]={72,0},[289]={54,0},
    [290]={14,0},[291]={3,0},[292]={25,0},
    [293]={43,0},[294]={43,0},[295]={43,0},
    [296]={47,62},[297]={47,62},[298]={47,37},
    [299]={5,42},[300]={56,0},[301]={56,0},
    [302]={51,0},[303]={52,22},[304]={5,69},[305]={5,69},[306]={5,69},
    [307]={74,0},[308]={74,0},[309]={9,31},[310]={9,31},
    [311]={57,0},[312]={58,0},[313]={35,68},[314]={12,0},
    [315]={30,38},[316]={64,60},[317]={64,60},
    [318]={24,0},[319]={24,0},[320]={11,12},[321]={11,12},
    [322]={12,20},[323]={40,0},[324]={73,0},
    [325]={47,20},[326]={47,20},[327]={20,0},
    [328]={52,71},[329]={26,0},[330]={26,0},
    [331]={8,0},[332]={8,0},[333]={30,0},[334]={30,0},
    [335]={17,0},[336]={61,0},[337]={26,0},[338]={26,0},
    [339]={12,0},[340]={12,0},[341]={52,75},[342]={52,75},
    [343]={26,0},[344]={26,0},[345]={21,0},[346]={21,0},
    [347]={4,0},[348]={4,0},[349]={33,0},[350]={63,0},
    [351]={59,0},[352]={16,0},[353]={15,0},[354]={15,0},
    [355]={26,0},[356]={46,0},[357]={34,0},[358]={26,0},
    [359]={46,0},[360]={23,0},[361]={39,0},[362]={39,0},
    [363]={47,0},[364]={47,0},[365]={47,0},
    [366]={75,0},[367]={33,0},[368]={33,0},[369]={33,69},
    [370]={33,0},[371]={69,0},[372]={69,0},[373]={22,0},
    [374]={29,0},[375]={29,0},[376]={29,0},
    [377]={29,0},[378]={29,0},[379]={29,0},
    [380]={26,0},[381]={26,0},[382]={2,0},[383]={70,0},[384]={76,0},
    [385]={32,0},[386]={46,0},
}

-- Gen 3 character encoding table
local GEN3_CHARS = {
    [0xBB]="A",[0xBC]="B",[0xBD]="C",[0xBE]="D",[0xBF]="E",
    [0xC0]="F",[0xC1]="G",[0xC2]="H",[0xC3]="I",[0xC4]="J",
    [0xC5]="K",[0xC6]="L",[0xC7]="M",[0xC8]="N",[0xC9]="O",
    [0xCA]="P",[0xCB]="Q",[0xCC]="R",[0xCD]="S",[0xCE]="T",
    [0xCF]="U",[0xD0]="V",[0xD1]="W",[0xD2]="X",[0xD3]="Y",
    [0xD4]="Z",[0xD5]="a",[0xD6]="b",[0xD7]="c",[0xD8]="d",
    [0xD9]="e",[0xDA]="f",[0xDB]="g",[0xDC]="h",[0xDD]="i",
    [0xDE]="j",[0xDF]="k",[0xE0]="l",[0xE1]="m",[0xE2]="n",
    [0xE3]="o",[0xE4]="p",[0xE5]="q",[0xE6]="r",[0xE7]="s",
    [0xE8]="t",[0xE9]="u",[0xEA]="v",[0xEB]="w",[0xEC]="x",
    [0xED]="y",[0xEE]="z",[0xA1]="0",[0xA2]="1",[0xA3]="2",
    [0xA4]="3",[0xA5]="4",[0xA6]="5",[0xA7]="6",[0xA8]="7",
    [0xA9]="8",[0xAA]="9",[0xAB]="!",[0xAC]="?",[0xAD]=".",
    [0xAE]="-",[0xB0]="..",[0xB1]="'",[0xB2]="'",[0xB4]="M",
    [0xB5]="F",[0x00]=" ",[0xFF]=""
}

-- Bitwise helpers
-- Try native Lua 5.3+ operators first (mGBA uses Lua 5.4),
-- then bit32 (Lua 5.2-5.3), then bit (LuaJIT), then manual fallback
local bxor32, band, rshift

-- Detect Lua 5.3+ native bitwise operators
local _native_ok, _native_xor = pcall(load, "return function(a,b) return (a ~ b) & 0xFFFFFFFF end")
if _native_ok and _native_xor then
    bxor32 = _native_xor()
    band = load("return function(a,b) return a & b end")()
    rshift = load("return function(a,n) return (a >> n) & 0xFFFFFFFF end")()
elseif bit32 then
    bxor32 = bit32.bxor
    band = bit32.band
    rshift = bit32.rshift
elseif bit then
    bxor32 = bit.bxor
    band = bit.band
    rshift = bit.rshift
else
    -- Manual fallback for older Lua without bitwise support
    bxor32 = function(a, b)
        local result = 0
        local p = 1
        for i = 0, 31 do
            local a_bit = a % 2
            local b_bit = b % 2
            if a_bit ~= b_bit then result = result + p end
            a = math.floor(a / 2)
            b = math.floor(b / 2)
            p = p * 2
        end
        return result
    end
    band = function(a, b)
        local result = 0
        local p = 1
        for i = 0, 31 do
            if a % 2 == 1 and b % 2 == 1 then result = result + p end
            a = math.floor(a / 2)
            b = math.floor(b / 2)
            p = p * 2
        end
        return result
    end
    rshift = function(a, n)
        return math.floor(a / (2 ^ n))
    end
end

-- ============================================================
-- Memory access wrappers
-- mGBA uses emu:read8/16/32 for absolute GBA addresses
-- ============================================================

local function read8(addr)
    return emu:read8(addr)
end

local function read16(addr)
    return emu:read16(addr)
end

local function read32(addr)
    return emu:read32(addr)
end

-- Decode Gen 3 encoded string
local function decodeString(addr, maxLen)
    local str = ""
    for i = 0, maxLen - 1 do
        local byte = read8(addr + i)
        if byte == 0xFF then break end -- terminator
        str = str .. (GEN3_CHARS[byte] or "?")
    end
    return str
end

-- Decrypt the 48-byte data block of a party Pokemon
local function decryptData(baseAddr, personality, otId)
    local key = bxor32(personality, otId)
    local order = SUBSTRUCTURE_ORDER[personality % 24]
    if not order then return nil end

    -- Read and decrypt all 48 bytes (12 bytes per substructure)
    local decrypted = {}
    for i = 0, 11 do
        local offset = baseAddr + 0x20 + (i * 4)
        local word = read32(offset)
        decrypted[i] = bxor32(word, key)
    end

    -- Map substructures based on order string (G=Growth, A=Attacks, E=EVs, M=Misc)
    local sub = {}
    for i = 1, 4 do
        local ch = order:sub(i, i)
        local idx = (i - 1) * 3 -- each substruct is 3 words (12 bytes)
        sub[ch] = { decrypted[idx], decrypted[idx + 1], decrypted[idx + 2] }
    end

    return sub
end

-- Extract Pokemon data from decrypted substructures
local function extractPokemonData(baseAddr, sub)
    local data = {}

    -- Growth substructure
    if sub["G"] then
        data.species = band(sub["G"][1], 0xFFFF)
        data.heldItem = rshift(sub["G"][1], 16)
    end

    -- Attacks substructure
    if sub["A"] then
        data.moves = {}
        data.moves[1] = band(sub["A"][1], 0xFFFF)
        data.moves[2] = rshift(sub["A"][1], 16)
        data.moves[3] = band(sub["A"][2], 0xFFFF)
        data.moves[4] = rshift(sub["A"][2], 16)
        data.pp = {}
        data.pp[1] = band(sub["A"][3], 0xFF)
        data.pp[2] = band(rshift(sub["A"][3], 8), 0xFF)
        data.pp[3] = band(rshift(sub["A"][3], 16), 0xFF)
        data.pp[4] = band(rshift(sub["A"][3], 24), 0xFF)
    end

    -- Misc substructure (contains ability bit in IVs/ability word)
    if sub["M"] then
        -- IVs and ability are packed in the third word of Misc
        local ivAbility = sub["M"][3]
        data.abilityBit = band(rshift(ivAbility, 31), 1) -- bit 31 determines which ability slot
    end

    -- Party data (unencrypted, after the 80-byte Pokemon data structure)
    data.statusCondition = read32(baseAddr + 0x50)
    data.level = read8(baseAddr + 0x54)
    data.currentHp = read16(baseAddr + 0x56)
    data.maxHp = read16(baseAddr + 0x58)
    data.attack = read16(baseAddr + 0x5A)
    data.defense = read16(baseAddr + 0x5C)
    data.speed = read16(baseAddr + 0x5E)
    data.spAttack = read16(baseAddr + 0x60)
    data.spDefense = read16(baseAddr + 0x62)

    -- Nickname
    data.nickname = decodeString(baseAddr + 0x08, 10)

    return data
end

-- Parse status condition flags
local function parseStatus(statusVal)
    if statusVal == 0 then return nil end
    if band(statusVal, 7) > 0 then return "slp" end
    if band(statusVal, 8) > 0 then return "psn" end
    if band(statusVal, 16) > 0 then return "brn" end
    if band(statusVal, 32) > 0 then return "frz" end
    if band(statusVal, 64) > 0 then return "par" end
    if band(statusVal, 128) > 0 then return "tox" end
    return nil
end

-- JSON encoding helpers
local function jsonString(s)
    if not s then return "null" end
    s = s:gsub('\\', '\\\\'):gsub('"', '\\"'):gsub('\n', '\\n')
    return '"' .. s .. '"'
end

local function jsonNumber(n)
    if not n then return "null" end
    return tostring(n)
end

-- Count set bits in a range of bytes (for Pokedex bitfields)
local function countSetBits(addr, numBytes)
    local count = 0
    for i = 0, numBytes - 1 do
        local byte = read8(addr + i)
        while byte > 0 do
            count = count + band(byte, 1)
            byte = rshift(byte, 1)
        end
    end
    return count
end

-- Read a single flag bit from the flags array
local function readFlag(flagsBase, flagNum)
    local byteOffset = math.floor(flagNum / 8)
    local bitOffset = flagNum % 8
    local byte = read8(flagsBase + byteOffset)
    return band(rshift(byte, bitOffset), 1) == 1
end

-- Read trainer data from SaveBlock1/2
local function readTrainerData(offsets)
    -- Get SaveBlock pointers (these are pointers in WRAM to the actual save blocks)
    local sb1 = read32(offsets.sb1Ptr)
    local sb2 = read32(offsets.sb2Ptr)

    -- Validate pointers — they should be in EWRAM (0x02xxxxxx) or IWRAM (0x03xxxxxx)
    local sb1Top = rshift(sb1, 24)
    local sb2Top = rshift(sb2, 24)
    if (sb1Top ~= 0x02 and sb1Top ~= 0x03) or (sb2Top ~= 0x02 and sb2Top ~= 0x03) then
        return nil
    end

    -- Trainer name: SaveBlock2 + 0x00, 8 bytes Gen 3 encoded
    local trainerName = decodeString(sb2, 8)
    if not trainerName or #trainerName == 0 then return nil end

    -- Gender: SaveBlock2 + 0x08 (0=male, 1=female)
    local genderByte = read8(sb2 + 0x08)
    local trainerGender = genderByte == 1 and "female" or "male"

    -- Trainer ID: SaveBlock2 + 0x0A (16-bit)
    local trainerId = read16(sb2 + 0x0A)

    -- Playtime: SaveBlock2 + 0x0E
    local playHours = read16(sb2 + 0x0E)
    local playMinutes = read8(sb2 + 0x10)

    -- Validate playtime sanity
    if playHours > 999 then playHours = 0 end
    if playMinutes > 59 then playMinutes = 0 end

    -- Money: encrypted in SaveBlock1, key in SaveBlock2
    local moneyEnc = read32(sb1 + offsets.moneyOff)
    local moneyKey = read32(sb2 + offsets.moneyKeyOff)
    local money = bxor32(moneyEnc, moneyKey)
    if money < 0 or money > 999999 then money = 0 end

    -- Badges: 8 flag bits starting at badgeFlagStart in SaveBlock1's flags area
    local flagsBase = sb1 + offsets.flagsOff
    local badges = {}
    local badgeCount = 0
    for b = 0, 7 do
        local earned = readFlag(flagsBase, offsets.badgeFlagStart + b)
        badges[b + 1] = earned
        if earned then badgeCount = badgeCount + 1 end
    end

    -- Pokedex: count set bits in caught/seen bitfields in SaveBlock2
    -- Gen 3 national dex has 386 Pokemon = 49 bytes (ceil(386/8))
    local dexCaught = countSetBits(sb2 + offsets.dexCaughtOff, 49)
    local dexSeen = countSetBits(sb2 + offsets.dexSeenOff, 49)

    return {
        name = trainerName,
        gender = trainerGender,
        id = trainerId,
        playHours = playHours,
        playMinutes = playMinutes,
        money = money,
        badges = badges,
        badgeCount = badgeCount,
        pokedexCaught = dexCaught,
        pokedexSeen = dexSeen,
    }
end

-- Build party JSON
local function buildPartyJson(offsets)
    local partyCount = read8(offsets.party_count)
    if partyCount > 6 then partyCount = 6 end
    if partyCount < 0 then partyCount = 0 end

    local parts = {}
    for i = 0, partyCount - 1 do
        local addr = offsets.party + (i * 100)
        local personality = read32(addr)
        local otId = read32(addr + 0x04)

        if personality == 0 and otId == 0 then
            goto continue
        end

        local sub = decryptData(addr, personality, otId)
        if not sub then goto continue end

        local poke = extractPokemonData(addr, sub)

        -- Validate species — skip corrupt/transitional reads
        if not poke.species or poke.species == 0 or poke.species > 500 then
            goto continue
        end

        -- Get national dex ID
        local nationalId = poke.species
        if GEN3_SPECIES_REMAP[poke.species] then
            nationalId = GEN3_SPECIES_REMAP[poke.species]
        end

        -- Skip if species is unknown (corrupt decryption)
        local speciesName = SPECIES_NAMES[nationalId]
        if not speciesName then goto continue end

        -- Validate level — skip clearly corrupt data
        if not poke.level or poke.level < 1 or poke.level > 100 then
            goto continue
        end
        local types = SPECIES_TYPES[nationalId] or {"normal"}
        local itemName = ITEMS[poke.heldItem] or ""
        local status = parseStatus(poke.statusCondition)
        local nature = NATURES[personality % 25] or ""

        -- Shiny check: (p_upper ^ p_lower ^ tid ^ sid) < 8
        local p_upper = rshift(personality, 16)
        local p_lower = band(personality, 0xFFFF)
        local tid = band(otId, 0xFFFF)
        local sid = rshift(otId, 16)
        local shinyValue = bxor32(bxor32(p_upper, p_lower), bxor32(tid, sid))
        local isShiny = shinyValue < 8

        -- Gender determination
        local gender = nil
        local genderThreshold = GENDER_RATIO[nationalId] or 127
        if genderThreshold == 255 then
            gender = nil -- genderless
        elseif genderThreshold == 0 then
            gender = "male"
        elseif genderThreshold == 254 then
            gender = "female"
        else
            if (personality % 256) < genderThreshold then
                gender = "female"
            else
                gender = "male"
            end
        end

        -- Resolve ability from species table + ability bit
        local abilityName = ""
        local speciesAbilities = SPECIES_ABILITIES[nationalId]
        if speciesAbilities then
            local abilityId = speciesAbilities[1]
            if poke.abilityBit == 1 and speciesAbilities[2] > 0 then
                abilityId = speciesAbilities[2]
            end
            abilityName = ABILITIES[abilityId] or ""
        end

        -- Build moves array
        local movesJson = {}
        for m = 1, 4 do
            local moveId = poke.moves[m] or 0
            if moveId > 0 then
                local moveData = MOVES[moveId]
                if moveData then
                    local mj = string.format(
                        '{"name":%s,"type":%s,"power":%s,"accuracy":%s,"currentPp":%s,"maxPp":%s,"damageClass":%s}',
                        jsonString(moveData[1]), jsonString(moveData[2]),
                        jsonNumber(moveData[3] > 0 and moveData[3] or nil),
                        jsonNumber(moveData[4] > 0 and moveData[4] or nil),
                        jsonNumber(poke.pp[m]),
                        jsonNumber(moveData[5]),
                        jsonString(moveData[6])
                    )
                    table.insert(movesJson, mj)
                end
            end
        end

        -- Build types array
        local typesJson = {}
        for _, t in ipairs(types) do
            table.insert(typesJson, jsonString(t))
        end

        local pokeJson = string.format(
            '{"species":%d,"speciesName":%s,"nickname":%s,"level":%d,"currentHp":%d,"maxHp":%d,' ..
            '"stats":{"attack":%d,"defense":%d,"spAttack":%d,"spDefense":%d,"speed":%d},' ..
            '"moves":[%s],"types":[%s],"ability":%s,"nature":%s,"statusCondition":%s,"heldItem":%s,"isShiny":%s,"gender":%s}',
            nationalId, jsonString(speciesName), jsonString(poke.nickname),
            poke.level, poke.currentHp, poke.maxHp,
            poke.attack, poke.defense, poke.spAttack, poke.spDefense, poke.speed,
            table.concat(movesJson, ","), table.concat(typesJson, ","),
            (#abilityName > 0) and jsonString(abilityName) or "null",
            (#nature > 0) and jsonString(nature) or "null",
            status and jsonString(status) or "null",
            (#itemName > 0) and jsonString(itemName) or "null",
            isShiny and "true" or "false",
            gender and jsonString(gender) or "null"
        )

        table.insert(parts, pokeJson)
        ::continue::
    end

    local partyJson = '{"party":[' .. table.concat(parts, ",") .. ']'

    -- Read trainer data and append if available
    local ok2, trainer = pcall(readTrainerData, offsets)
    if ok2 and trainer then
        local badgesArr = {}
        for b = 1, 8 do
            badgesArr[b] = trainer.badges[b] and "true" or "false"
        end
        partyJson = partyJson .. string.format(
            ',"trainer":{"name":%s,"gender":%s,"id":%d,"playHours":%d,"playMinutes":%d,' ..
            '"money":%d,"badges":[%s],"badgeCount":%d,"pokedexCaught":%d,"pokedexSeen":%d}',
            jsonString(trainer.name), jsonString(trainer.gender), trainer.id,
            trainer.playHours, trainer.playMinutes, trainer.money,
            table.concat(badgesArr, ","), trainer.badgeCount,
            trainer.pokedexCaught, trainer.pokedexSeen
        )
    end

    return partyJson .. '}'
end

-- Detect game from ROM header
local function detectGame()
    local ok, byte0 = pcall(read8, 0x080000AC)
    if not ok or not byte0 then return nil end

    local code = ""
    for i = 0, 3 do
        local b = read8(0x080000AC + i)
        if b == 0 then return nil end
        code = code .. string.char(b)
    end
    console:log("Detected ROM code: " .. code)
    return GAME_OFFSETS[code]
end

-- Main script
local offsets = nil
local detected = false
local frameCounter = 0
local OUTPUT_INTERVAL = 120 -- write every ~2 seconds at 60fps
local lastJson = "" -- change detection: only write when data changes

-- Output path — write next to the script or in mGBA's working directory
local outputPath = "party_data.json"

console:log("Pokemon Overlay Studio: Script loaded. Waiting for a supported game...")

callbacks:add("frame", function()
    -- Try to detect game until we find one
    if not detected then
        frameCounter = frameCounter + 1
        if frameCounter < 60 then return end -- check once per second
        frameCounter = 0

        offsets = detectGame()
        if offsets then
            detected = true
            console:log("Pokemon Overlay Studio: Game detected! Reading party data...")
            console:log("Pokemon Overlay Studio: Writing to " .. outputPath)
        end
        return
    end

    -- Game detected — read party data on interval
    frameCounter = frameCounter + 1
    if frameCounter < OUTPUT_INTERVAL then return end
    frameCounter = 0

    -- Check if game is still loaded (handles reset/ROM swap)
    local stillValid = detectGame()
    if not stillValid then
        detected = false
        local emptyJson = '{"party":[],"trainer":null}'
        if lastJson ~= emptyJson then
            lastJson = emptyJson
            local f = io.open(outputPath, "w")
            if f then f:write(emptyJson); f:close() end
            console:log("Pokemon Overlay Studio: Game reset detected, clearing party data.")
        end
        return
    end

    local ok, json = pcall(buildPartyJson, offsets)
    if not ok then
        console:log("Pokemon Overlay Studio: Error reading party: " .. tostring(json))
        return
    end

    -- Only write when data actually changes
    if json == lastJson then return end

    lastJson = json

    local f = io.open(outputPath, "w")
    if f then
        f:write(json)
        f:close()
    else
        console:log("Pokemon Overlay Studio: Could not write to " .. outputPath)
    end
end)
