This mod's own changelog. The Unreleased block becomes the site changelog
entry for the next version pushed from this folder, titled with the version
in mod.txt. The app's changelog is a separate file at the repo root.

Unreleased
- Nothing yet.

0.3.4
- Erase this save's data, from the bottom of Your Pokemon. For starting a new
  playthrough beside an old one's records, which otherwise haunt it: a ledger
  full of a finished game's Pokemon, caught marks for a dex you no longer
  have, and a rival whose starter belongs to somebody else.
  TWO presses, and the second one is a different button. The first arms a
  prompt that NAMES what would go, counted from the ledger in front of you --
  "Erase 11 Pokemon, 94 caught, route, rival, badges?" -- because "are you
  sure" answered blind is how people lose things they meant to keep. Keep it
  is the wider button. Closing the panel disarms it.
  What goes is the SAVE: the ledger, the caught marks, the route, the rival,
  and the badges (eight facts about a save, and a new one has none). What
  stays is YOURS: where you dragged the panels, and your dex preferences.
  Afterwards it says what it removed, rather than leaving "did that work?"
  hanging -- and with a battle on screen the ledger immediately records the
  Pokemon in front of you again, with no history behind it. That is the erase
  working, not failing.
- The reader's own test suite runs again, and its fixture is in the repo. The
  windows it reads -- real captured pixels, fed to the real reader worker --
  lived in a scratch folder outside version control. That folder went, the
  suite stopped running, and nothing said so: the checks over the one thing
  no other suite covers, turning pixels into names, were dark.
  Rebuilt from screenshots that survived, and committed beside the suite.
  Eight windows: the VIRIDIAN FOREST banner, an HP box (VILEPLUME), the
  message window while the move list owns the strip, a real Safari encounter
  (VENONAT, 18 balls) and the same two windows in an ordinary battle, and a
  party row lying across a double battle.
  Six cases could not be rebuilt and the suite says which and why, rather
  than quietly covering less: the POKeMON TOWER banner, the PC box's two
  panel rows, and three party rows -- every surviving capture of the party
  menu was taken with the overlay open, which dims the picture.
  fixtures/README.txt has the recipe, including the two things that are easy
  to get wrong: a banner cannot calibrate on its own, so a battle frame goes
  first as a primer; and the expected text must be read off the screenshot by
  eye, never taken from the reader's own output.
- An emulator window with no black bars calibrates. A picture filling the
  whole frame was always read as the console's own UI - a lockscreen or the
  home screen - and refused, which is right for a capture card and wrong for
  a window, where the emulator's client area IS the game and only gets bars
  when it is keeping the aspect ratio.
  The engine now says where the picture comes from (camera.sourceKind), and a
  full-frame image is taken as the game when it came from a window. The same
  answer lets the 3:2 guard through for a window that stretches the picture
  across a client area too short for it, which emulators do.
  Nothing changes for a card: a full-frame image there is still refused, with
  the same reason on the debug panel.
  Verified against a real captured window: refused and reading nothing
  before, then "game area 1000x800 at 0,0" and both names read (GENGAR,
  NIDOQUEEN) after, off the identical window.
- Abilities that change damage are applied. The same audit against
  CalculateBaseDamage that found the screens found that none of the ability
  modifiers were being applied either.
  THICK FAT is the one that bites in Kanto: it halves the attacker's Sp. Atk
  against Fire and Ice, DEWGONG's only ability is THICK FAT, and Lorelei has
  one - so an ICE BEAM that read as a clean KO was twice the truth. GUTS is
  half again the Attack while statused AND attacks through a burn (the game's
  burn line reads "&& ability != ABILITY_GUTS"), HUGE POWER and PURE POWER
  double it, HUSTLE is half again, MARVEL SCALE is half again the defence
  while THEY are statused.
  Each step is applied on its own, in the game's order, flooring after each
  one rather than folding into a single multiplier - the difference is a
  point, which is the size that decides a KO.
  Only when the ability is KNOWN: yours from the ledger, a trainer's from the
  first slot (their personality is always even, so the slot is always 0), and
  any species that has only one. A wild two-ability Pokemon rolls for it and
  nothing on screen says which, so nothing changes.
  OVERGROW, BLAZE, TORRENT and SWARM are NOT applied: they need your own
  current HP, which is on no window this mod reads. The move says so instead
  of guessing.
- REFLECT and LIGHT SCREEN are read and subtracted. They halve what the other
  side deals and nothing on screen says so afterwards -- no pill, no HP box,
  only the sentence when they go up -- so a physical number against a REFLECT
  was exactly twice the truth. That is what made the move panel promise a
  certain KO that hit and left the Pokemon standing (reported 2026-08-18,
  against the Champion, whose ALAKAZAM carries REFLECT and whose EXEGGUTOR
  carries LIGHT SCREEN).
  The damage formula had honoured a screen flag since it was written; nothing
  ever set it. The battle text does now: "Foe's REFLECT raised DEFENSE!" up,
  "wore off!" down, both sides tracked separately, cleared when a new battle
  starts. A screen belongs to the SIDE, so it survives a switch here as it
  does in the game.
  Physical is stopped by REFLECT and special by LIGHT SCREEN, a crit goes
  straight through both, and a double battle takes two thirds instead of a
  half -- floored before doubling, which is the game's own order. The damage
  line says "through REFLECT" when it applies, because a halved number with
  no reason given is indistinguishable from a broken calculator.
- Your badges reach the stat panel. The Thunder badge is +10% Speed for your
  side and it was missing from the YOURS rows, so the panel put a Lv54
  NIDOQUEEN's 120 beside Agatha's GENGAR at 126 and called you slower -- while
  the game raced 132 against 126 and you moved first (reported 2026-08-18).
  Both numbers were right; the comparison was not, because the game does not
  race the number on the summary screen.
  All four boosts are on those rows now -- Boulder Attack, Soul Defence,
  Volcano Sp. Atk and Sp. Def, Thunder Speed -- in the game's own order, which
  differs per stat: Speed is staged first and boosted after
  (GetWhoStrikesFirst), the rest are boosted on the raw stat before stages
  (CalculateBaseDamage). Paralysis still quarters what the badge leaves.
  A boosted number is marked with a trailing "+", because it deliberately no
  longer matches the summary screen and an unexplained mismatch reads as a
  broken reader.
  The enemy never wears them: the boost is the player's side alone, in the
  game and here.
- A revived fossil is noticed. "Your fossil is back to life! It was OMANYTE
  like I think!" is the only announcement there is: the Pokemon is handed over
  the counter, never battled, so no HP box ever carries its name and no Poke
  Ball is ever drawn beside it -- and it went unrecorded (reported after being
  handed an OMANYTE in the Cinnabar lab). The overworld message box was
  already being read; the sentence just meant nothing. OMANYTE, KABUTO and
  AERODACTYL all mark themselves now.
  Nothing is taken until the sentence finishes typing: "It was OMANYT" is a
  complete-looking read one frame earlier, and banking that would have stored
  most of a name.
  It joins Your Pokemon too, with only the species filled in -- there is no
  level or stat to read anywhere yet. That is a real entry and not a
  placeholder: an unknown field cannot disagree with anything, so the first
  battle or summary visit lands on it and fills it in rather than adding a
  second OMANYTE beside it.
- The movable windows stay where you put them. The Pokedex and Your Pokemon
  are dragged to wherever suits, and every restart put them back at the edge
  the mod chose -- because a panel that paints itself parks a home position
  every frame and the engine adds your drag on top of it, so the arrangement
  lived in the engine and died with the process. The panel now remembers the
  answer instead: where it actually ended up, written when the drag settles
  and parked from then on. A position saved on a bigger screen is pulled back
  fully on screen rather than left off the edge with nothing to grab.
- Gender no longer waits on a read it cannot trust. For the 58 species the
  game settles by itself -- NIDOQUEEN is always female, TAUROS always male,
  MAGNEMITE neither -- knowing the species IS knowing the gender, from the
  game's own genderRatio (05_data_gender.lua, generated). Genderless
  records as "none", an answer rather than a gap. Species that can be
  either still wait for the mark, which the battle box reads.
- Fixed: a species name whose letters TOUCH was unreadable, so the whole
  summary page recorded nothing (reported live: NIDOQUEEN, whose Q and U
  share every column and come back as one blob). A blurred read now
  resolves against the dex with the blob standing for one letter or
  several -- and only when exactly one species fits, so an ambiguous
  shape is still refused.
- Fixed: a nicknamed Pokemon never got its species prompt. It waits for
  three identical reads, but the name was keyed WITH its gender mark, and
  that mark only inks at some contrast cutoffs -- so the same CH'DING
  alternated between two spellings and three in a row never happened.
- Ability is a ledger field now. Most species can only have one, so
  knowing the species is knowing the ability -- those fill in with no
  screen at all; the two-ability ones are read off the SKILLS page, which
  prints the individual's own. The YOURS panel then shows THE ability
  instead of the coin-flip pair, and the hover card says why it is
  certain. Editable on the card, and refused if the species cannot have
  it.
- Fixed: gender never landed. The mark only inks at some contrast
  cutoffs, and the first reading the dex recognises is usually the one
  WITHOUT it -- so the winning read was genderless and the rest were
  never consulted. The mark is now harvested from every reading, in
  battle and on the summary page.
- The Your Pokemon and Pokedex windows can be dragged anywhere.
- When a summary page is open but nothing records, the log now names the
  fields it is still waiting for instead of going silent.
- Fixed: the Sevii Islands' outdoor areas claimed "no wild Pokemon here"
  (reported live: KINDLE ROAD). The encounter tables were keyed by the
  decomp's MAP names ("ONE ISLAND KINDLE ROAD") while the banner prints
  the region-map-section name ("KINDLE ROAD") -- twenty-two places whose
  tables sat under keys no banner read could produce. Regenerated with
  the banner spellings; Mt Ember's three maps fold into one entry with
  per-area odds, the way Mt Moon's floors always did.
- The Your Pokemon card: Escape now closes it (or lets go of the field),
  a field takes its typing EXCLUSIVELY -- keys it consumes no longer fire
  other mods' keybinds or the app's own Escape -- and a two-click Delete
  removes an entry, because the ledger is the only place a wrong one can
  leave from. Needs the engine that understands consumed keys.
- Fixed: pressing Escape while typing in the species prompt or the card
  QUIT THE PROGRAM (the app's own Escape handling saw it first).
- Fixed: a misread summary level could mint a phantom entry (reported
  live: a "level 5" RAICHU carrying the level-35 one's stats -- a 3 that
  failed to read off "Lv35"). The page refutes itself: every stat must
  now fit inside what that species at that level can produce, or the
  whole observation is refused, for the skills page and the level-up
  window both.
- Fixed: the battle verdict colour decayed to neutral during the
  end-of-battle freeze (the EXP tally), because it sat on a freshness
  clock while the panel it belongs to deliberately stopped updating. It
  freezes with the panel now -- and a level-up window landing during the
  tally turns the frozen panel green in place.
- The mod now keeps a ledger of YOUR Pokemon: one entry per individual, with
  its name as the reader sees it (nickname included), species, gender,
  level, nature, and stats recorded per level. Entries fill in over time
  from the screens that show each fact -- the battle box (name, level, max
  HP in singles), the POKeMON SKILLS summary page (all six stats), the
  POKeMON INFO page (species and nature), and the level-up stat window,
  which records the fresh totals every time something grows. The per-level
  stats table has no consumer yet; it is the raw material for an EV/IV
  calculator later.
- The YOURS battle panel wears the ledger's verdict as its whole palette
  -- background, title bar and title, not a hairline border: green for a
  matched entry with everything known, yellow when facts are still missing
  (the label names the first gap), red when more than one entry fits what
  the box shows -- with the candidates listed beside the panel and a
  "This is a different X" button that deliberately creates the second entry
  when you swapped identical twins in. Matching trusts agreement on every
  value both sides know; six identical RATTATAs are genuinely ambiguous and
  the panel says so instead of guessing.
- Once the ledger knows a stat, the panel shows THE number: recorded stats
  collapse their rows' ranges exactly the way an identified trainer's do,
  row by row -- a battle box that only taught max HP collapses HP alone.
- The FIGHT menu's damage numbers use the ledger too: with your attacking
  stat recorded, the "Dmg" band no longer carries YOUR unknown -- what
  spread remains is the foe's (a wild one's IVs are genuinely unknowable)
  and the game's own 85-100% roll.
- "Your Pokemon", a new window in this mod's settings beside the Pokedex
  checklist: the ledger as a list -- verdict dot, name, species, level --
  and hovering a row opens its card with every recorded fact as an
  editable field (name, species, gender, level, nature, the six stats at
  its current level). Enter saves, Escape cancels, and a value the rules
  refuse stays in the box painted red. For the day a screen taught the
  ledger something wrong -- or for writing down what you already know
  without walking to a summary screen.
- A nicknamed Pokemon finally gets a panel. An unknown clean name in your
  box asks "Which species is your CH'DING?" with a type-to-filter species
  list where the stats panel would be; one click links it, and from then on
  the nickname carries its species' numbers everywhere. (While typing in
  that box, the engine's own P/O screenshot keys still fire -- a known wart
  until engine keys become consumable by mods.)
- Fixed: when a battle's ending fade darkened the coloured gender mark into
  legibility, it read as part of the name ("RATTATAM!"). A trailing mark is
  now always the individual's GENDER, never part of the name -- except the
  two NIDORANs, where the symbol is the species. Nicknames ending in a real
  M or F are safe by construction (the mark's own '!' never reads from
  letters).
- The location banner's reader also notices the summary screens now: the
  page headers land in the same top-left rectangle it already watches, from
  anywhere -- battle included -- so no context gating applies to it.
- The reader now knows WHICH SITUATION the game is in -- overworld, wild
  battle, safari battle, trainer singles or doubles, with honest ambiguous
  states between -- and only points windows at screens that can contain
  them. Battle boxes, move slots, status pills and the caught-ball probe
  sleep on the overworld (a lazy enemy-box sniffer stays up so a battle
  joined mid-way is still noticed within a sixth of a second); safari
  windows wake only around safari battles; the doubles windows only when
  the trainer table says doubles or the battle hasn't said which yet.
  Situations are decided by the game's own sentences and boxes, battles
  end when their boxes actually leave (so panels still hold through the
  EXP tally), and an entire live session ran with the machine observing
  before anything was gated on it: 51+ transitions -- every wild ending,
  every safari ending, trainers caught by intro and by send-out, a SIS AND
  BRO double called from the intro sentence alone, and a detour into the
  eShop that moved nothing -- zero wrong calls.
- Fixed: male and female SWIMMERs (and every gendered trainer class) were
  never identified, so their battles showed stat ranges instead of exact
  numbers (reported live: SWIMMER RICHARD, read correctly as
  "SWIMMERMRICHARD", matched nothing). The trainer table dropped the gender
  mark from its keys while the reader keeps it as M/F -- the same spelling
  rule the species names have always used. The table now keys gendered
  classes the way the reader reads them, keeps the old spelling as an
  alias, and also carries the game's own single/double flag per trainer.
- Hovering a Pokedex row now answers the checklist's next question: WHERE.
  A card beside the row lists the places that species appears, best odds
  first, with the method that gets it there (Grass, Surf, the rods, Rock
  Smash) -- straight from the game's own encounter tables for the cartridge
  being played. A species only the other version carries says "Version
  exclusive to FireRed/LeafGreen" instead of pretending Kanto is empty of
  it, and one no wild table contains says "Not found in the wild (evolve,
  trade or gift)" -- so nobody walks grass for a MACHAMP.
- Fixed: evolutions stopped marking the dex (reported live: a RATTATA ->
  RATICATE evolution went unread). The guard added against half-typed names
  waited for the sentence's closing "!" -- but the game's font table has no
  "!" glyph, so the reader can never produce one: the mark arrives as the
  unreadable-glyph "?" instead, and the wait was forever. Either mark now
  ends the sentence (a half-typed name has no trailing ink at all, so the
  prefix trap stays shut), and the species token comes back with the mark
  already stripped. The same dead gate had silently disarmed Transform
  tracking, and hid a crash behind it (a Lua `and` truncating lookup's two
  returns); both fixed with it.
  ...and fixed AGAIN the same evening, measured this time: a live ZUBAT
  evolution (thank you for the screenshots) run through the actual worker
  showed the mark arrives as its own space-separated token -- "evolved into
  GOLBAT ?" -- not attached to the name. Both shapes are accepted now, and
  the measured line is pinned in the tests verbatim.
- The picture tracker was REBUILT around what the canvas can actually be,
  measured off real frames at every resolution the console offers. A steady,
  horizontally centred box at the game's exact aspect (3:2 in a 16:9 frame;
  in a stretched non-16:9 capture, 27/32 of the frame's own aspect) replaces
  the calibration outright -- bigger or smaller, no memory, no growing, no
  relocking. Everything else changes nothing, and if a refused box holds
  still, the refusal is a NAMED reason in the log and the debug panel
  ("squished into 800x600 -- pick a 16:9 capture size", "off-centre -- card
  may be cropping", "full-frame -- console UI") instead of silence.
  The old tracker only ever grew and needed a big collapse to let go, so one
  bright toast in the bottom bar poisoned an edge for a whole session and
  every window below mid-screen quietly sheared. Under the rules that box is
  not a canvas shape and never gets in; a console resolution change now takes
  effect in about a second, in both directions, with no restart.
- Fixed: MR. MIME had no panel, no catch rate and no caught mark -- ever. The
  game draws its name with a space and every table in this mod spells it
  "MR.MIME", the way the decomp's own SPECIES_ constant does, so the reading
  was correct and unmatchable. Spaces are folded away when a name is looked
  up; no dex name contains one, so it can only ever match the one that is
  drawn with it.
- Fixed: a name with one unreadable letter in it was always rejected, though
  the code to tolerate one has been there from the start. "?" is the reader's
  mark for a blob it could not classify AND a Lua quantifier, and it was never
  escaped -- so "PIKACH?" asked the dex for "PIKAC" with an optional H and
  matched nothing. It now resolves when exactly one species fits, and still
  refuses to guess between two (five pairs in the dex differ by one letter,
  NIDORINA/NIDORINO among them).
  ...and then SCOPED to the callers a sentence vouches for, within the hour:
  turned on for everybody, the always-on HP-box windows started resolving
  OVERWORLD junk, and a Pokemon Center floor read as a wild HO-OH, ENEMY
  panel and all. Those windows' whole trust model is "junk matches no dex
  name" -- they demand an exact match again, and the tolerance now applies
  only where the text already promised a species ("evolved into",
  "transformed into").
- GYM BADGES are now WHICH ones, not how many. Four of the eight boost
  nothing at all -- Cascade, Rainbow, Marsh and Earth are obedience and field
  moves only -- so a count is right only when the badges you hold happen to
  be the first N, and wrong about every stat the moment they are not.
  Boulder is Attack, Thunder is Speed, Soul is Defense, Volcano is both
  special stats. The setting is a multi-select.
  It also FILLS ITSELF IN: the mod watches for a gym leader handing one over
  and ticks it. Every leader says it differently ("Fine, then, take the
  THUNDERBADGE!", "I must confer on you the RAINBOWBADGE."), so all eight
  sentences are matched verbatim from the game's own scripts -- and it is the
  awarding line that is matched, never the badge's name, because every leader
  also explains what their badge does and NPCs mention badges you have never
  won. All eight phrases were checked to appear exactly once in the entire
  game's text.
  The old setting was a count, and a count names no badges: an existing one
  is dropped rather than guessed at. The log says which badges are held at
  startup, and says so plainly when none are.
- Fixed: an evolution could register the WRONG SPECIES. The box types itself
  in one character at a time, and five species in the dex are another
  species' prefix -- so "PIDGEY evolved into PIDGEOT" is a complete, true
  sentence for as long as it takes "TO" to arrive, and a PIDGEOTTO's
  evolution marked PIDGEOT owned (reported from live play). Four of the five
  pairs are evolutions the game prints: PIDGEOT/PIDGEOTTO, KABUTO/KABUTOPS,
  PARAS/PARASECT, PORYGON/PORYGON2. A species at the end of a sentence is now
  only read once the "!" that ends the sentence has arrived.
  Transform had the same hole and it was worse there: it is latched, so the
  half-typed name was not merely early, it was the only answer the panel ever
  got -- a Ditto copying a PIDGEOTTO showed PIDGEOT's stats for the fight.
- The POKEDEX picks its dex: KANTO or NATIONAL, on the title line. The count
  follows it, so switching to Kanto reads "48/151" rather than going on
  reporting a National total under a Kanto list.
- The POKEDEX can be UNPINNED, so it steps aside with the overlay instead of
  sitting over the game, and pinned again from the same place. Which dex and
  whether it is pinned are both remembered.
- The area list and the Pokedex are measured in WINDOW space again. They are
  pinned to the window's own edges, and following the letterboxed picture
  shrank them away from the corner they are pinned to as soon as bars
  appeared.
- The ABILITY is on the battle panel, in the space the level was leaving
  empty. Hovering it opens the game's own one-line description; clicking it
  opens that ability's Bulbapedia page.
  A TRAINER'S Pokemon shows one ability, and it is certain. The slot is
  personality & 1, and a trainer's Pokemon is not given a random personality:
  the game builds it as 0x80, 0x78 or 0x88 plus (nameHash << 8), all of which
  are even, and the shift cannot reach bit 0 -- so every Pokemon any trainer
  has ever sent out uses its FIRST ability. A wild one is a genuine coin flip
  (its personality is Random32), so both are shown with a slash, each half
  hoverable and clickable, and the hover says which case this is. Most of
  Kanto has one ability anyway and reads as a plain label. Names and
  descriptions are the game's own, generated from pokefirered.
  It has its OWN row, under the types. The type row's right-hand side looked
  like free space and was not -- the status pill lives there, so a paralysed
  SNORLAX drew "IMMUNITY/THICK FAT" straight through "PAR". The panel is one
  row taller for it, which also lets the long pairs fit at full size.
- Transform is followed. A Ditto that has copied your Raichu showed Normal
  and its own flat 48 spread while a Raichu stood on the screen; the panel now
  takes the copy's types and stats and keeps only the caster's own HP, which
  is exactly what the move copies, and the title bar says "-> RAICHU" so it
  cannot be mistaken for a misread species. A transformed side is also dropped
  from trainer-table pinning, which knows what it IS rather than what it
  turned into.
- Fixed: a trainer's send-out was counted more than once, so the party slot
  everything downstream is indexed by ran ahead of the real one and the wrong
  Pokemon's row was read out of the trainer table. The battle message box
  types itself in one character at a time, and the guard against acting on a
  sentence twice keys on the whole box -- so "{TRAINER} sent\nout {MON}!"
  matched the moment line 2's "out" appeared and went on matching as the
  species arrived after it, each extra character counting as a fresh send-out.
  A two-Pokemon trainer counted four. Sentences whose phrase finishes before
  the sentence does are now reported once and re-armed by the next different
  one; the fix is pinned by feeding the growth sequence in verbatim.
  Send-outs are also logged now, with the slot number, because this was wrong
  invisibly for as long as it existed.
- The Safari Zone knows what you should throw. Walk into a Safari battle and
  the panel changes: the three-ball comparison above the enemy box becomes a
  single Safari figure and the chance the Pokemon runs away this turn, and
  where your own Pokemon's panel would be -- there is none, the game puts a
  ball counter there instead -- the whole optimal sequence of baits, balls
  and rocks appears, with a green pulsing ring on the one to throw next.
  The plan and the maths are Professor Rex's ("Kanto Safari Zone Research",
  professorrex.github.io/FRLG_Safari), which is credited on the panel itself
  and is the reason any of this is worth showing. FRLG do not use the
  ordinary catch formula here: the catch rate is divided into a "catch
  factor" and multiplied back up when a ball is thrown, and because the
  division floors, the round trip loses -- CHANSEY's 30 comes back as 25.
  Bait halves the factor but never below 3, which is ABOVE Chansey's own 2,
  so ONE bait raises its per-ball odds from 4.9% to 8.09% for the rest of the
  encounter and nearly doubles the odds of the whole encounter, 10.28% to
  19.08%. Balls-only, which is what everyone has said for years, is wrong.
  Working out the best ORDER is a search over hundreds of thousands of
  patterns, so those tables are shipped rather than computed; the formulas
  around them are ported from pokefirered and the generator re-derives all
  156 numbers in the site's own stats files before it will write the table.
  The plan is looked up by BALLS REMAINING, read from the game's own counter,
  and thrown away and looked up again the moment the game says the Pokemon is
  watching carefully -- which is what the paper says to do, and is why one
  table indexed by ball count is enough. Balls are hard evidence; bait and
  rock messages only move the cursor as far as the next ball, so a missed one
  costs at most the rest of a turn.
- A wiki link on the names the game itself prints in its menus: the SPECIES
  row of the PC box's PKMN DATA panel and every name in the party menu get a
  small "opens in a new tab" mark after them, and clicking it opens that
  Pokemon's Bulbapedia page -- the same page the route list and the battle
  panels link to. It is the species row and not the nickname above it,
  because that is the row the link is about.
  Two things make it trustworthy rather than a guess at where the text is.
  Every coordinate comes from the game's own source (the storage system
  prints its three lines 14 rows apart from tile (0,11); the party menu
  prints each nickname at the rect its column's PartyMenuBoxInfoRects gives),
  so nothing here was measured off a screenshot except the four-row drop the
  text printer adds, which both columns agree on. And the box's species row
  is identified by the '/' the game prints in front of it: we carry no slash
  glyph, so it reads as the '?' any unmatched shape does, and demanding that
  '?' is what separates the species row from the nickname row -- the same
  word, the same font, one line up, and the MOVE ITEMS variant of the screen
  slides it down into exactly that window.
  The party menu shows nicknames, not species, so a renamed Pokemon gets no
  mark; a link to a page that does not exist would be worse than none. The
  windows are only read while no battle is on screen, because one of them
  lies across the player's own HP box in a double battle.
- The per-foe damage rows keep the gender symbol, so a NIDORAN pair stops
  reading as two identical "NIDORAN" lines. Stripping it was wrong twice
  over: for NIDORAN the symbol IS the species (two different Pokemon with
  different stats), and even where it is only an individual's gender it is
  still the one thing telling two of the same kind apart. Drawn with the
  game's own glyphs, and if two foes really are identical the panels' own
  1/2 numbering goes in front (in front, because the symbol is matched at
  the end of the name).
- Fixed: the FIGHT menu's borders and hover panel blinked at the read rate,
  which made them unusable. The bottom strip is shared by the message box
  and the move list, and the mod cleared the move annotations whenever the
  message window produced any line at all. That was safe only by accident:
  the menu's own frame put ink in every row, so the band finder gave up and
  the window read nothing there. Ignoring clipped borders (the fix that made
  map-preview banners readable) took the accident away -- the window started
  returning two lines of junk through the menu, and the annotations were
  cleared on every pass. The colour probe that authorises the move windows
  now dis-authorises the message window, so only one of the two is ever read
  from that strip. tests/reader_windows.lua pins the hazard.
- Locations read on the screens that announce them. Two separate faults hid
  whole areas: the map-preview screens (Viridian Forest, the caves, Rocket
  Hideout) draw the name in a box NARROWER than the reader's window, and the
  dark background beyond it put ink in every row, welding them into one
  18-row block that blew past the band ceiling - no band, no text, no
  location. Columns that are ink through 90% of a window are now treated as
  structure (a clipped border) rather than glyphs, which is also why a
  window no longer has to be threaded inside a box's border by hand. And
  POKeMON TOWER failed for its own reason: the accented e had no glyph, so
  it read "POK?MON" and matched nothing. The glyph is now in both fonts,
  taken from the game's own tilesheet, and folds to a plain E where names
  are matched (every table spells it POKEMON).
- Fixed a calibration regression of my own making: the picture "re-lock"
  added earlier today fired 109 times in one session. Every battle wipe
  measures a stable-looking box (1528x1040, 1560x1021 - 98% of the real
  area) and each one threw the calibration away for a second or two, which
  is exactly when a location banner is on screen. Re-locking is now gated on
  the accumulated box NOT being 3:2 - the lockscreen symptom it was written
  for - so it cannot trigger in normal play at all.
- Evolving a Pokemon marks it caught. No ball is thrown and no "was caught!"
  is printed, so the checklist never learned about it; the evolution message
  is now read, and both species are marked (you plainly owned the one that
  evolved).
- A trainer whose name contains a character the reader has no glyph for is
  identified anyway: an unreadable glyph now matches zero OR one character,
  because the flattened keys DROP such characters. "YOUNG COUPLE LEA & JED"
  read as "YOUNGCOUPLELEA?JED" against a key of "YOUNGCOUPLELEAJED" and so
  could never match.
- A POKEDEX checklist, in this mod's settings. Every species in dex order
  with the ball you can tick, filterable to just what you are still missing,
  scrollable with the wheel; the ball toggles caught and the row opens the
  species' page, the same two gestures the area list already uses.
- The panels are individually switchable now: catch chances and the FIGHT
  menu annotations each have their own setting.
- Multi-hit moves get KO odds. DOUBLE KICK had a damage band and no KO line
  while every other move had one, which reads as a bug: one hit's odds are
  not the turn's, so this walks the real distribution of count x per-hit
  roll x per-hit crit (the count itself is 3/8, 3/8, 1/8, 1/8, not a flat
  quarter each). Checked against a brute force over every outcome.
- A move that cannot reach the target's HP but still shows a KO chance now
  says why: the crit's own damage band is printed beside the ordinary one.
  "Dmg 20-39, KO 6%" against 40 HP is not a contradiction - it is 1-in-16 -
  but it looked like one.
- A trainer's fallen Pokemon narrow their twins: of two identical party
  slots the one that dies first is the earlier one, so each faint pins the
  survivor. BEAUTY BRIDGET's two ODDISH stop being a two-nature band as
  soon as one of them goes down.
- Type names draw in their type's colour - the Gen 3 palette, lifted a
  touch for the dark panels - in the battle panels' type line (each half
  of a dual type its own colour) and in the move hover's header.
- Doubles got their KO lines back: both enemy HP bars are read at the
  doubles boxes' own positions, so each foe's KO odds and "(they have
  X-Y)" come from its OWN bar. The hover's per-foe rows are a proper
  left-bounded table now - the name is a real column sized from the
  widest name, not spaces pretending to align a proportional font.
- The picture re-locks after the lockscreen. Calibration's edges only
  ever grew, which one startup order defeats completely: engine open,
  console woken, game entered OVER the lockscreen - the first bright
  thing measured is the full-frame lockscreen, the edges lock to 16:9,
  and the real game area can never out-grow them, so every read misses
  for the rest of the session with no error anywhere. Now a measurement
  that holds still for three samples, sits inside the accumulated edges
  and is exactly 3:2 - the GBA's shape, which nothing else the console
  shows has - resets the edges to itself. Growth still wins instantly
  in the other direction, so a wrong re-lock costs a second and heals.
- Double battles work: all four Pokemon are read and get panels. The
  doubles layout redraws every HP box at its own position, so nothing
  used to be read at all - the mod thought there was no battle. Four new
  name/level windows cover the doubles boxes (slot 1 of each side feeds
  the same state the singles windows do; the layouts can't coexist), the
  four panels arrange themselves so no HP box is ever covered, and the
  move hover computes damage PER ENEMY - one line each, named, because a
  single number for "the enemy" means nothing when there are two. What
  doubles can't know is honestly absent: no KO odds (the bar reader is
  singles-positioned), no colour-probe status (battle text still tracks
  it), and stat changes are pinned to whichever of the two the sentence
  names - never guessed when both foes are the same species.
- Trainer battles show the enemy's EXACT stats. The game fixes every
  trainer mon's IVs (one value across all six stats) and its nature (a
  hash of the trainer's and species' names), so both ship in a table
  generated from the decompilation: 639 fightable trainers, 1650 mons,
  externally checked against the speedrun community's trainer sheet (37
  of 39 boss stat lines digit-identical; the other two are the sheet's
  own typos). The trainer is read off the battle text - the intro line,
  and again at every send-out, so a battle joined midway corrects itself
  - and with trainer and Pokemon known the stat rows, damage bands, KO
  odds and HP all collapse to single numbers. The nature's name sits in
  the enemy panel's title bar as the reason why.
- The exceptions are honest ones. Four TEAM ROCKET GRUNT keys are
  genuinely ambiguous between two natures and show a two-nature band
  (their HP is still exact - HP has no nature term). The rival's fights
  hinge on which starter he took: the first time his team gives it away
  it is remembered for the save, and every later rival battle is exact.
  Wild battles keep the honest range they had - nothing on screen can
  reveal a wild Pokemon's rolls - and the catch calculator hides in
  trainer battles, where the game blocks the ball anyway.
- Gym badges now count. Badge 1 boosts Attack, 5 Defense, 7 both special
  stats, 10% each, applied exactly where the game applies them (inside
  the damage formula, before stages); a new setting says how many you
  hold. Badge 3's Speed boost is recorded for whenever turn order is
  computed.
- Low Kick's damage band is computed from its real, weight-derived power.
  The panel printed "Power 120" while the band underneath was still
  quietly worked out from the move table's placeholder power of 1 -
  caught by the first fixture test and the reason the fixture harness
  exists.
- The FIGHT menu is annotated. Every move the reader recognises gets a
  hairline border; clicking one opens its page on Bulbapedia, and hovering
  one says what the game does not: what the move does, its accuracy and
  what that comes to after accuracy and evasion stages, physical or
  special, whether it gets STAB, the damage it would do, and the odds it
  ends the fight. PP is left out on purpose - the game already prints it,
  and a number shown twice can disagree with itself.
- The damage is the game's own arithmetic, not an approximation:
  CalculateBaseDamage with the crit doubling, STAB, the type multipliers
  applied one defending type at a time, and the 85-100 roll, every
  division integer and in the order pokefirered does them. Two details
  that are usually got wrong are covered by tests: the formula ends by
  adding 2 BEFORE the doubling and STAB, so on a STAB crit that tail is
  worth 6; and a crit does not ignore stat stages, it ignores the ones
  that would hurt the attacker - your lowered Attack is skipped, your
  Swords Dance is kept, their Harden is skipped, their Screech is kept.
- The numbers are ranges because the inputs are. Neither Pokemon's real
  stats are on screen, only what its level allows, so the low end is your
  weakest possible attacker against their sturdiest possible defender on
  the worst roll and the high end is the reverse. A wide range is the
  honest answer, not a missing one.
- The species name in the battle panels underlines under the pointer. It
  has always opened its wiki page on a click; nothing said so.
- Fixed: the status pill could invent a paralysis. The HP box bobs on its
  idle animation, and at some window sizes that lifted the box far enough
  for the pill probe to land on the EXP bar underneath, where a dark blue
  sample lands inside the rule that recognises yellow. It read as
  paralysed on the way up and healthy on the way back down. A pill is a
  bright colour on a cream box, so samples too dark to be one no longer
  vote, and a probe that is entirely dark now means "the box is not there"
  rather than "no status".
- The catch strip stops saying 100% when it means 99.9%. FireRed catches
  outright only when its odds beat 254; below that it rolls four shake
  checks, and those cannot be certain - the odds they roll against top out
  at 65535 of 65536, one short - so every throw from 201 to 254 is the
  same 99.9939%, and about one in sixteen thousand of them fails. The
  strip rounded that to "100", which is exactly the throw you remember:
  a paralysed MACHOP under half HP, a Poke Ball, and a promise the game
  never made. 100 now means only the branch that catches without rolling
  anything; the near-thing reads 99.9. tests/catch_odds.lua pins the
  boundary at 254 and 255 so it cannot drift back. Reading the ending
  now LOCKS it instead of clearing it: the HP boxes stay up for the EXP
  tally and the parting line, so wiping at the text pulled the panel off
  screen mid-sentence, and letting reads through refilled it a frame later.
  It freezes exactly as it was, stops taking reads, and the values reset the
  moment the boxes actually leave - which is also when the panel closes on
  its own. The next battle opens a clean one.
- Stat changes reset when a battle ends - ANY battle. The reset used to be a
  25-second timer, which 0.2.2 replaced with "clear when the battle text says
  the fight is over" and a ten-minute backstop. The text side only ever
  covered running away and whiting out, so the two commonest endings - a wild
  Pokemon fainting and winning a trainer battle - ended nothing, and the
  backstop did the work. Every ending is now taken from the string table in
  pokefirered's battle_message.c: you ran, it fled (an Abra teleporting is
  one of these), you won the prize money, you beat the trainer, you whited
  out, you lost, or the SAFARI GAME ended.
- The ten-minute timer is gone rather than shortened. No length is right: a
  long fight, an AFK, or a while spent in the bag would wipe stages that are
  still true, and any timer quick enough to tidy up after a battle is quick
  enough to do that. A wild battle STARTING now clears both sides instead, so
  even a missed ending cannot leak into the next fight.
- Mid-battle lines that merely mention the same words are excluded on
  purpose - "Aww! It appeared to be caught!" is a failed catch, not a new
  battle, and matching "appeared" loosely would have wiped your stages every
  time a ball broke open. tests/battle_text.lua covers all of it: 29 cases,
  endings, starts, and the mid-battle lines that must do nothing.
- Stat changes reset when a wild battle ends. "Wild X fainted!" is the end
  of the fight, not just the end of one Pokemon - there is nothing else to
  send out - so both sides' stages clear, the way they do in-game. A
  trainer's "Foe X fainted!" still does NOT clear yours, because the next
  Pokemon is announced separately and your stages really do carry into it.
  Before this, a Growl from something you beat was still sitting on your
  Attack several fights later.
- The route panel says "None obtainable here" instead of "No wild Pokemon
  here", and only says it when there is genuinely nothing to get there.
  The line was tied to the WILD list alone, so a route whose only catch is
  a trade or a gift announced that it had nothing - directly above the
  list of the things it has.
