diff --git a/docs/SESSION_HANDOFF.md b/docs/SESSION_HANDOFF.md deleted file mode 100644 index 36e6ee2..0000000 --- a/docs/SESSION_HANDOFF.md +++ /dev/null @@ -1,1344 +0,0 @@ -# Recovered Claude session - -Latest continuation: see **Battle effects, held items and obedience: 2026-10-03** -at the end of this file. Earlier sections are historical snapshots. - -Recovered on 2026-09-23 from the local Claude Code transcript -`~/.claude/projects/-Users-kawa-Crystal/4ac88f30-29e6-4fd5-a4b2-b6a318dd83b3.jsonl`. -The session stopped at its usage limit at 09:54 UTC, during research, without a final handoff. - -## Resumed increment: completed 2026-09-23 - -The movement increment below is complete. The remaining sections preserve the -original recovery context. - -- Added directional two-tile ledge jumps with the existing arc/shadow animation. - Unsupported terrain, invalid destinations and occupied landings remain refused. -- Corrected directional mats to wait for exit input. Warp arrivals keep their - actual coordinates and appropriate facing; doors, stairs and caves animate a - downward exit. Removing the old inward teleport also corrected Elm's scripted - introduction position. -- Mid-walk, mid-jump and mid-connection saves retain the source position. Landing - scenes rewind to the actual source of a jump, rather than the intervening wall. -- Restored compatibility with early version-3 saves lacking the money field. -- Replaced the scratch diagnostic with synthetic movement regressions and - optional-ROM tests covering five Route 29 ledge types. Route 29 is enabled only - inside that test fixture, not in production. -- Fixed the battle test suite's eager ROM read so missing-ROM runs really skip - private tests. Updated browser stair inputs and added an exit-mat save/reload - flow. Updated the roadmap and architecture to distinguish the existing battle - code from its unfinished integration. - -Final checks: 75 unit tests passed with the local ROM; without it, 55 passed and -20 private tests skipped. All 24 Chromium/WebKit browser tests passed. Native and -reference builds, release isolation, pinned-reference checks, vendor checks and -`git diff --check` passed. No commit or push was made. - -Next: validate the overworld-to-battle browser flow and complete Route 29's -remaining script, capture/tutorial and item interactions before enabling the -route. Player turn timing also remains a documented comparison gap. - -## Scope and current state - -The original request was to read ARCHITECTURE.md, PLAN.md and PRESENTATION_PLAN.md -and continue the native port. The working tree contains substantial uncommitted -work from multiple increments; preserve it when continuing. - -- Opening scenes, scripted movement, starter selection, naming, the aide's Potion - and version 3 saves are implemented and documented. -- Native battle modules, ROM tables, damage/type parity tests, encounter handling, - battle UI and whiteout handling have been added. These later changes are not - fully reflected in the architecture and roadmap documents. -- Only the original six maps are enabled in `NATIVE_MAPS`. Route 29 (`24:3`) - remains disabled. Passing battle unit tests does not establish a playable - Route 29 encounter or the full opening quest. - -## Exact stopping point - -Claude was preparing to revise player movement in `src/game/world/world.ts`. -Its last investigation concerned spawn facing after warps. It had not implemented -the planned ledge/warp revision. - -The pinned local PRET checkout contains the relevant routines: - -- `engine/overworld/player_movement.asm`: `.TryStep`, `.TryJump`, - `.CheckLandPerms`, `.CheckWarp` and the direction tables. -- `engine/overworld/tile_events.asm`: `CheckWarpCollision` and - `CheckDirectionalWarp`. -- `engine/overworld/player_object.asm`: `SpawnPlayer`. -- `engine/overworld/map_objects.asm`: movement/turn timing. - -Research found that ledge tops have land permission; the jump check uses the -current tile and facing after an ordinary step fails. Doors use collision `$71`, -downward warp mats `$70`, and stairs `$7a`. Directional warp mats require their -designated input rather than an immediate warp simply upon entry. Verify these -rules against the local routines before changing the existing movement flow. - -`tests/zz-scratch.test.ts` is Claude's unfinished diagnostic: it logs local-ROM -warp and ledge collisions, contains no assertions and unconditionally requires -the private ROM. Replace it with meaningful optional-ROM or synthetic tests -before treating the suite as ready for public CI. - -## Validation after recovery - -Run against the current working tree with the local ROM and reference checkout: - -- `npm test`: 45 passed, including the assertion-free scratch diagnostic. -- `npm run build`: passed. -- `npm run test:e2e`: 22 passed across Chromium and WebKit, including native - damage/type comparisons against original routines in the reference emulator. -- `npm run check:reference`: ROM, pinned rebuild and profile addresses match. -- `npm run check:release`: native output excludes emulator and game assets. -- `npm run check:vendor`: passed. - -These checks do not establish complete battle behavior, browser battle UI flows, -Route 29 progression or physical-device compatibility. No gameplay source was -changed during session recovery. - -## Original next-increment notes (movement now completed) - -Finish ledge and directional-warp movement with focused regression tests, -including interrupted movement/save behavior and preservation of the existing -opening scenes. Audit Route 29 callbacks, scripts and unsupported destinations -before enabling it. Then test wild encounters and battle controls through the -browser, including win, escape, loss and save/reload, and reconcile the roadmap -with the verified behavior. - -## Battle integration increment: 2026-09-23 (resumed from Codex) - -Codex committed and pushed `c08d250`, then stopped mid-increment with the -keyboard/D-pad battle test failing. Claude Code completed it: - -- Browser battle flows run through the real UI via `tests/battle-fixture.ts` - (encounter selection only; production maps stay gated): win with level-up and - Smokescreen, escape, whiteout, keyboard/D-pad menus, mid-battle backup restore - and unsupported-effect rollback. `tests/battle-world.test.ts` covers the same - at world level, plus pending stat experience and whiteout callback rollback. -- Saves accept stored stats between the no-stat-exp and full-stat-exp values, - since `GiveExperiencePoints` awards stat experience without `CalcMonStats`. -- A script warp (e.g. whiteout) validates its destination and commits healing, - money loss and destination callbacks together; aborts restore state and map. -- Keyboard and on-screen D-pad navigate battle menus (`battle-selected`); A - continues messages, B leaves the move list. -- Fixed a phone-width layout bug: a long notice sat in the Play button's auto - grid column and crushed the status text, pushing the game panel off-screen. - Below 720px the notice now has its own row. -- Test fixes: the navigation test now presses A through the intro message, and - uses a real pointer click on the D-pad (synthetic pointer events cannot be - captured). - -Final checks: 82 unit tests with the local ROM (55 passed, 27 skipped without -it); 36 Chromium/WebKit browser tests; native and reference builds; release, -vendor and reference checks; `git diff --check`. Not committed. - -Next: Route 29 (`24:3`) scripts, catching tutorial and items before enabling it; -then capture, switching and PACK in battle. - -## Route 29 and Cherrygrove increment: 2026-09-23 - -Enabled Route 29, its Route 46 gate, Cherrygrove City and its five buildings. -Item balls, the Pokémon Center nurse and the Poké Mart were each ported by a -Sonnet sub-agent in an isolated worktree, reviewed against the disassembly and -merged; the rest was done directly. - -- Fruit trees run `FruitTreeScript`; a native `CheckDailyResetTimer` clears - the daily engine flags once per game day. `VAR_BOXSPACE` reads an empty box. -- Item balls run `FindItemInBallScript` with a native `TryReceiveItem`. -- The nurse runs `PokecenterNurseScript`; `HealParty` now applies - `ComputeMaxPP`'s PP Up clamp. `EnterMapWarp`'s spawn recording makes - whiteouts return to the last Pokémon Center entered. -- Standard marts run natively (`StandardMart`/`BuyMenu`/`SellMenu`). -- Battle command `recoil` (Struggle) was the only refusal found by a local - stress run of 360 Route 29 battles across starters and times of day. -- Save format 3 gained `fruitTrees`, `dailyReset` and `lastSpawnMap` - (migrated by absence). -- The phone-width status bar layout and parity test map counts were fixed. - -Still refused: `catchtutorial` and Cherrygrove's rival scene (both after the -Mystery Egg), Route 30/46. NPCs do not roam yet. - -Next: NPC roaming movement (`map_objects.asm` random walk/spin, radius and -visibility rules, deterministic RNG in tests), trainer sight lines and trainer -battles, Route 30 and Mr. Pokémon, capture and the catching tutorial. - -## Trainer AI continuation: 2026-09-24 - -The trainer foundation was subsequently committed through `3e92936`. The next -AI increment translates Opportunist, Aggressive and Risky scoring, including -the ROM's stall/reckless/risky tables and Aggressive/Risky's deterministic -pre-variation damage estimate. Smart is now the only refused move-scoring layer, -so the rival remains gated. Continue with Smart before the rival, or with -trainer sight lines and normal trainer-object scripts for the already-supported -Youngster and Bug Catcher classes on Route 30. - -## AI layer review: 2026-09-24 (resumed from Codex) - -Claude Code checked Codex's uncommitted Opportunist, Aggressive and Risky layers -against `engine/battle/ai/scoring.asm`. The HP thresholds, `AI_50_50` (≥128), -`79 percent - 1` (200), last-wins ties, the power < 2 and reckless exclusions, -the strict-borrow KO test and the constant-damage paths all match. - -One fix: `AIDamageCalc` goes through `EnemyAttackDamage` and -`BattleCommand_DamageCalc`, and both read `wCriticalHit`. A flag left over from -an earlier move therefore selects unboosted stats (ignoring screens) and -doubles the estimate. `enemyAIDamage` now reads the native `criticalHit` -instead of hard-coding `false`. It also reuses `truncate`. A local-ROM test -fixes the estimate at 4, 3 (Reflect) and 6 (leftover critical). - -Known side effects of `AIDamageCalc` that are not modelled: it overwrites -`wCurDamage`, `wTypeModifier`, `wAttackMissed` and `hBattleTurn`. `CheckTurn` -resets the modifier and miss flag before each move. Whether the `wCurDamage` -overwrite can reach Counter/Mirror Coat still needs checking before those -effects are ported. - -Final checks: 147 unit tests with the local ROM; 42 Chromium/WebKit browser -tests; native and reference builds; reference, release and vendor checks; -`git diff --check`. Not committed. - -Next (unchanged): Smart AI before the rival, or trainer sight lines and -normal trainer-object scripts for Route 30's Youngster and Bug Catcher. - -## Route 30 trainers: 2026-09-24 - -Chose option 2 from the review above: trainer sight lines and trainer-object -scripts. Both Route 30 trainers have two Pokémon, so the increment also ports -the trainer's send-out and switch AI. Route 30 and its Berry house are now -enabled in production. - -- World: `_CheckTrainerBattle` at step end (before warps, coordinate events and - encounters) and on idle ticks; `TalkToTrainer` for `OBJECTTYPE_TRAINER`; - `BGEVENT_ITEM` hidden items. A refused sighting is not restarted until the - player moves (native safeguard against a rollback loop). -- Interpreter: `loadtemptrainer`, `trainertext`, `trainerflagaction`, - `scripttalkafter`, `endifjustbattled`, `checkjustbattled`, `writeobjectxy`, - `encountermusic`, `loadmem wRunningTrainerBattleScript`, `readmem - wHiddenItemID`, and callasm `TrainerWalkToPlayer`/`SetMemEvent`. - `applymovementlasttalked wMovementBuffer` decodes the native buffer. -- Battle: multi-Pokémon trainer parties (`HandleEnemyMonFaint`, - `EnemyPartyMonEntrance`, `FindMonInOTPartyToSwitchIntoBattle` with its - `BattleRandom` fallback), `AI_SwitchOrTryItem`/`CheckAbleToSwitch`/`AI_Switch` - with the original type-matchup, `e`-carry and quarter-HP bugs, and - `wPlayerUsedMoves`. `BattleTrainer` now requires `switchFlags`. -- Stress (local, not committed): 1,080 Route 30 battles; the only refusal was - Chikorita's Reflect at level 12 (`screen`), a pre-existing gap. - -Final checks: 160 unit tests with the local ROM; 44 Chromium/WebKit browser -tests including the Route 30 sighting through the UI; native and reference -builds; reference, release and vendor checks; `git diff --check`. Not committed. - -Next: Mr. Pokémon's house (Oak, Pokédex, Mystery Egg) and the return to Elm; -Reflect/Light Screen; then Smart AI before the rival. Joey (after the Egg) -needs `askforphonenumber` and the phone standard scripts. - -## Reflect and Light Screen: 2026-09-24 - -Completed the battle gap found in the Route 30 stress run. `screen` now runs -both moves from ROM effect scripts. Independent five-turn counters include -the setup turn, do not refresh on repeated use, survive enemy send-outs and -expire with ROM text in `HandleScreens` order. The existing damage path handles -physical/special defense and critical stage rules. - -Seven new local-ROM regressions cover player and enemy use, PP on failure, -concurrent durations, expiration order, send-out persistence and damage stats. -Also moved the NPC test's eager ROM read into `beforeAll`, restoring its -intended skip behavior when no ROM is available. - -Checks: 167 unit tests with the local ROM; 76 passed and 91 skipped without it; -44 Chromium/WebKit browser tests; native and reference builds; reference, -release and vendor checks; `git diff --check`. Screen routines were reviewed -against pinned PRET source, not compared by executing the original routines. -Changes are uncommitted. - -Next: Mr. Pokémon's house and the Mystery Egg; Smart AI before enabling the -rival; capture and the catching tutorial. Joey's post-Egg phone scripts remain -unsupported. - -## Mr. Pokémon and the return to Elm: 2026-09-24 (resumed from Codex) - -Codex had enabled Mr. Pokémon's house and added `NameRival`, `blackoutmod`, -`reloadmap`, the queued Elm call and the no-handler Smart AI subset. It -stopped with all four quest browser tests failing and without docs. Claude -Code finished the increment: - -- The rival scene needed `special FadeOutMusic` (audio only, now accepted). -- The quest fixture's constant `() => 0` RNG hung `BattleCommand_DamageVariation`, - which redraws until the byte is at least 217. The fixture now uses a seeded LCG. - Relocating to Cherrygrove had also carried the house's moved Mr. Pokémon and - Oak onto the Guide Gent and rival (ids 2 and 3), producing an unrestorable - save. The fixture now clears objects and starts off the Guide Gent's tile. -- The browser test expected `specialPhoneCall` 1; `SPECIALCALL_ROBBED` is 2. -- `NameRival` yielded a naming choice with zero pages, because `promptbutton` - had already flushed them. The dialogue never reached its last page, so the - officer scene soft-locked. It now shows the ROM's "RIVAL'S NAME?" and reads - the "SILVER" default from ROM. As in `InitName`, blank names of any number of - spaces take the default. The naming input's accessible label no longer says - "Nickname". -- Elm's call now runs the ROM's `CheckSpecialPhoneCall.script` → - `Script_ReceivePhoneCall` wrapper. It reads the condition and caller script - from `SpecialPhoneCallList` instead of hard-coding id 2, adds `memcall`, and - shows `HangUp`'s timed "Click!". Ringing and the caller name box are still - not presented. -- Added tests: the rival scene for all three starters and both triggers, a - `CANLOSE` loss, reload after each scene, the Elm call (indoors, outdoors, - mid-call reload), the officer naming (named, blank, all-space, reload at - the naming screen), the Egg handover and the aide's Poké Balls, plus a - browser naming-field test. - -Checks: 191 unit tests with the local ROM (80 passed, 111 skipped without it); -50 Chromium/WebKit browser tests; native and reference builds; reference, -release and vendor checks; `git diff --check`. Not committed. - -Next: capture and the Route 29 catching tutorial (`catchtutorial`), the -first remaining refusal in the opening quest. Then the ringing presentation, -player switching and items in battle. Joey's post-Egg phone scripts remain -unsupported. - -## Capture and the catching tutorial: 2026-09-24 - -Continued the roadmap with the first refusal remaining in the opening quest. - -- Battle PACK opens the Balls pocket and runs `PokeBallEffect`: the Poké, - Great, Ultra and Master Ball multipliers, the original HP arithmetic - (including the overflow above 341 maximum HP), the sleep/freeze bonus - (BRN/PSN/PAR add nothing), `HELD_CATCH_CHANCE` and the catch draw. - `GetPokeBallWobble` draws per shake from `WobbleProbabilities` and picks - the break-free text. Trainer battles block the ball but still use it and - the turn. `catchRate` is exported for pure vectors. -- A catch reloads the Pokémon as `LoadEnemyMon` does (the wild item roll is - redrawn, then the original item restored), keeps HP/status/item/DVs, - restores the moves and PP it appeared with, adds it to the party with - `SetCaughtData`, registers it and offers a nickname (`InitName` default - for blank names). `NewDexDataText` shows when the Pokédex is owned. - `caughtData` is now shared with `givepoke`. -- `catchtutorial` runs `BATTLETYPE_TUTORIAL`: "DUDE" from the ROM as - ``, no send-out, an automatic catch that keeps nothing, and - prompts that continue on their own. Both Route 29 triggers, declining and - `CatchingTutorialDudeScript`'s repeat offer run from the ROM. -- Text: `TX_LOW` ($05) is read as a line break (`ItemUsedText` needs it). -- UI: PACK lists the Balls pocket (B closes it); the in-battle nickname - prompt reuses the naming field. The help dialog and README describe the - current milestone. -- Refused: a catch with six Pokémon (the PC is not ported), other balls, - non-ball items in battle and a transformed target. -- Stress (local, not committed): 2,160 wild battles, 2,047 catches, no - refusals. - -Checks: 207 unit tests with the local ROM (82 passed, 125 skipped without -it); 54 Chromium/WebKit browser tests; native and reference builds; -reference, release and vendor checks; `git diff --check`. The existing -keyboard/D-pad test now steps through the enabled PACK button. No -emulator comparison of the capture routines was run. Not committed. - -Next: the PC and boxes (catching with a full party, `SendMonIntoBox`, Bill's -call), then player switching and other items in battle, and party and bag -menus. The Dude's back picture, ball animation and Pokédex page are not -presented. - - -## Overworld foundation recovery: 2026-09-25 - -Resumed Claude Code session `5d15e2b5-2020-4f49-83e7-eddee08c2315`, whose user -request was “Complete the overworld foundation.” Its last successful check -compared Johto and Ice Path animation with the original VRAM; the session then -hit its usage limit. The previous handoff ended at capture and was stale. - -Preserved and validated Claude's uncommitted implementation: - -- Turn delays, bumps, ice and currents, forced movement, dynamic return warps, - elevator menus, pits and arrival timing. Six original-engine traces cover - outdoor, connected, ledge, interior, scripted-follow and cave movement. -- Surf, Cut, Strength, Whirlpool/Waterfall interaction paths and cycling rules; - Strength boulders can fall into script-controlled holes. Later maps are - enabled only inside tests, not in the production map list. -- Working map blocks, `changeblock`, `changemapblocks`, rollback and minimap - invalidation; cave/water/Kanto encounter tables and `wildoff`. -- Additional NPC movement, variable sprites and Pokémon icons; all movement - command decoding and teleport/fishing/skyfall/shake/flicker presentation. -- Tileset animations on private graphics, grass sprite priority and special - map palettes. Johto and Ice Path match 96 original VRAM frames after matching - the reference animation state, including the scrolling scratch buffer. - -Recovery changes: - -- Removed `zz-grid.test.ts` and `zz-explore.spec.ts`, temporary diagnostics with - no assertions. The grid diagnostic also read a private ROM unconditionally. -- Added grass-priority/transparency and special-palette regressions, and ran - all 36 tilesets for 2,048 animation frames each while checking that cached - ROM graphics stay unchanged. `NUM_TILESETS` is verified by `check:reference`. -- Reconciled PLAN, ARCHITECTURE and NATIVE_PORT with the actual implementation - and its remaining gaps. - -Validation: 271 unit tests passed with the local ROM. With an absent -`CRYSTAL_ROM_PATH`, 127 passed and 144 skipped. The full Chromium/WebKit run -passed 64 tests with 8 intentional WebKit skips; two of those passes were the -subsequently removed exploratory test, leaving 62 substantive browser checks. -The eight new original-engine comparisons run in Chromium only. Native and -reference builds, reference addresses/constants, release isolation and vendor -checks passed. No physical-device or CI run was performed. Changes remain -uncommitted; no push was made. - -The whole foundation milestone is not complete. Continue with: - -1. Room decorations: the two decoration specials still implement empty slots; - stored choices, ownership and the selection flow are absent. -2. Player bag-menu access to `GameWorld.useBicycle()`. Its rules are tested, - but it is not reachable through the current game controls. -3. Remaining reference acceptance: movement traces exclude the original's - map-entry tick and compare position/facing/selected actor positions, not - event flags. Most special poses and palettes lack executed-reference or - screenshot comparisons. The Celebi forest animation stays on frame one. - -Do not clear those checklist items or enable later maps based on the existing -movement and rendering tests alone. - - -## Overworld foundation completed: 2026-09-25 (resumed from Codex) - -After the recovery above, Codex continued with "Complete the foundation" and -stopped during its final validation, before updating this file. It had -already marked the milestone done in PLAN.md. Its work: - -- Room decorations (`world/decorations.ts`): the initial bed and Town Map - poster, ownership through `DecorationAttributes` event flags, the bedroom - PC's selection and put-away flows (including left/right doll placement), - room blocks and variable sprites from saved choices, save validation and - migration of saves without decorations. The poster opens the native map. -- Start/Select opens Key Items; the Bicycle follows the native indoor and - outdoor rules, with keyboard and touch controls (`foundation-browser.spec.ts`). -- Movement traces now compare event flags as well as positions. Pose checks - compare direction, visibility and vertical offset during scripted - movement. An executed `LoadMapPals` matches 192 native palette cases. - Restless Ilex Forest and Tin Tower animation match original VRAM. -- Fixed fishing and flicker pose timing against the original routines. - -Claude Code finished the increment. Codex had just added two pose traces. -Tree shake passed. Skyfall departure (`skyfall_top`) failed: -`StepFunction_SkyfallTop` leaves `OBJECT_SPRITE_Y_OFFSET` at $60 on the tick -that ends the command, and the offset persists through later steps. Actors -now keep a `yOffset`. Skyfall, teleports, the fishing bite and jumps clear it, -because those routines write the offset again. In the game, only the player -uses `skyfall_top` (Ruins of Alph chambers, Burned Tower), just before a warp. -The traces now number fourteen. PLAN and NATIVE_PORT were updated to match. - -Checks: 276 unit tests with the local ROM (127 passed, 149 skipped without -it); 81 Chromium/WebKit browser tests passed and 19 were skipped intentionally -(the engine parity tests run in Chromium only); native and -reference builds; reference, vendor and release checks; `git diff --check`. -Not committed. - -Next (PLAN section 2): the PC and boxes (a catch with a full party, -`SendMonIntoBox`, Bill's call), then the introduction's naming and clock -screens. Also open there: player switching and non-ball items in battle, -controlled-seed comparisons beyond damage and type effectiveness, and a -continuous opening-quest playthrough with save/reload. Later maps stay gated -until their scripts and story are supported. - -## The PC and boxes: 2026-09-25 - -The foundation above was committed and pushed as `451464f`. This increment -continues PLAN section 2 with its first open item. - -- `party/storage.ts`: 14 boxes of `BoxMon` (a party struct without status, - HP and stats), the current box, box names (null is the ROM's "BOX" and - number) and 50 PC item stacks. `SendGetMonIntoFromBox` deposit and - withdraw, `RestorePPOfDepositedPokemon` and `CalcLevel` are shared helpers. - `maxPP` (ComputeMaxPP) now also serves `HealParty`. -- `scripts/pc.ts`: `PokemonCenterPC` and the house PC. The Pokémon Center PC - opens Bill's PC (deposit, withdraw, release, change and name boxes) and the - player's PC (item withdraw, deposit and toss, the empty mail box), plus - Oak's rating once the Pokédex is owned. The house PC adds DECORATION. Menu - strings, lists and texts are read from the ROM (`PC` table in the profile, - verified by `check:reference`). The generic menu effect gained per-item - `details` (levels, box counts). A `notice` effect covers unported - options without aborting. -- Facing tiles now use the ROM's `TileCollisionStdScripts` in any direction, - as in the original (the old table only fired when facing up). -- Capture with a full party: `SendMonIntoBox` puts the catch first in the - current box, with nickname and `BallSentToPCText`. A full box keeps the ball - (`Ball_BoxIsFullMessage`). Filling the box sets `BATTLERESULT_BOX_FULL`. - `reloadmapafterbattle` then queues Bill's call through a new `memscript` - effect (`LoadMemScript`). It is kept in memory only and runs before scene - scripts. -- Save format 3 gained `storage` and `pcItems`, migrated by absence. - -Not ported: MOVE PKMN W/O MAIL and the stats screen (notices), printing, -`givepoke` with a full party (still refused; unreachable in the open maps), -the save messages after a box switch, and the tutorial with a full party and -box (the original loops forever; refused). - -Checks: 290 unit tests with the local ROM (130 passed, 160 skipped without -it); 83 Chromium/WebKit browser tests passed, 19 skipped intentionally -(including a new Pokémon Center PC flow in both browsers); native and -reference builds; reference, vendor and release checks; `git diff --check`. -Not committed. - -Next (PLAN section 2): the introduction's naming and clock screens; player -switching and non-ball items in battle; MOVE PKMN W/O MAIL and the stats -screen with the party menu; a continuous opening playthrough. - -## The introduction's naming and clock: 2026-09-25 - -The PC increment above was committed as `3bdca00`. This increment finishes -the next open item of PLAN section 2. - -- `scripts/intro.ts` (`IntroRun`): `InitGender`, `OakSpeech` with - `InitClock` and `NamePlayer`, as a native generator the world runs like a - script (`GameWorld.beginIntro`). Texts, menus, preset names and time - strings come from the ROM through a new `INTRO` profile table verified by - `check:reference`. -- New effects: `portrait` (Oak's trainer picture or the player's, decoded - by `PokemonData.trainerPic`, `playerPic` and `trainerPalette`) and a - `clock` stepper choice. -- Game clock: `clock.minuteOffset` is added to the device time for time of - day, weekday, NPC hours and the daily reset. The host's `deviceWeekday` - became `clockDay`. -- `GameSession.load` calls the runtime's optional `newGame()` when nothing - is saved. Saves gained `intro` (true while pending; a save made then - restarts the introduction). The shell's name/character fields are gone. -- Browser fixture specs strip `this.world?.beginIntro();` from the served - runtime, since their worlds stand in for saved sessions. Fresh-load tests - play the intro with `tests/intro-driver.ts`. - -Not presented: palette fades, Wooper's cry, the picture slide while -naming, `ShrinkPlayer` and music. Intentional change: the steppers start at -the device time instead of 10:00. - -Checks: 298 unit tests with the local ROM; native and reference builds; -reference, vendor and release checks; `git diff --check`; 87 -Chromium/WebKit browser tests passed, 19 skipped intentionally (including -the new intro flow and a reload during it, in both browsers). Committed and -pushed to `main` as `b13fd20`. - -Next (PLAN section 2): player switching and non-ball items in battle; MOVE -PKMN W/O MAIL and the stats screen with the party menu; a continuous opening -playthrough. - -## Remaining opening-milestone items: 2026-09-30 - -The introduction was committed and pushed as `b13fd20` (handoff note -`eb03a20`). Asked to go over what was left and do the quickest items -first, this session took PLAN section 2's open items in that order: - -1. Evolution decoder: `PokemonData.evolutions` returns typed level, item, - trade, happiness and stat records (Tyrogue's are four bytes). A - local-ROM test compares all 251 lists with the pinned - `evos_attacks.asm`. Evolving is not ported. -2. Reference vectors: `tests/reference-parity.spec.ts` now also calls the - original `CalcMonStats` (300 cases, including levels above 100 for the - 999 cap; the trampoline sets BC, DE and HL) and `CalcExpAtLevel` (all - 600 growth/level pairs). A mutation (removing the cap) was confirmed to - fail. Symbol loading and the ROM hand-off are shared helpers now. The - checklist item stays open for random-driven routines (accuracy, - criticals, damage variation, capture). -3. Continuous playthrough: `tests/playthrough.test.ts` plays a new game - from the intro to the catching tutorial through `GameWorld` inputs with - a seeded RNG, reloading a JSON save after every stage. Wild battles are - fought, or fled below half HP (fighting everything whited out on Route - 29). The rival battle is lost with this seed (`CANLOSE`), and the story - continues. The stages share one world, so run the whole file. -4. Switching and healing items in battle (details in NATIVE_PORT): - - PKMN/`TryPlayerSwitch`/`PlayerSwitch`/`BattleMonEntrance` with - `SendOutMonText`/`WithdrawMonText`; `AskUseNextPokemon`, - `ForcePlayerMonChoice`, `DoubleSwitch`; Shift mode's - `OfferSwitch`. New `party` prompt (`menu`, `cancel`) answered through - `battleChoose` (a slot or -1), and `{ action: "pkmn" }`. - - Items pocket: `CheckItemContext`, `ItemEffects` dispatch by ROM - address, `RestoreHPEffect` and `StatusHealingEffect` with the ROM's - tables and party menu texts. `PARTYMENUTEXT_*` values are PRET's - ($F0-based; `check:reference` caught a wrong value). - - UI: PKMN party list with the SWITCH/STATS/CANCEL submenu (STATS - disabled), and ITEM/BALL POCKET tabs in PACK (opens on Items). The - keyboard test now passes through the enabled PKMN button. - -Known limits: `hBattleTurn` at the battle menu is not modelled everywhere, -so a status heal that would make `CalcPlayerStats` reapply a penalty to a -paralysed or burned enemy is refused. `WithdrawMonText` treats the -multiplicand's leftover high byte as zero. X items, Poké Doll, Full -Restore, revives and the stats screen are not ported. - -Checks: 324 unit tests with the local ROM (130 passed, 194 skipped -without it); 93 Chromium/WebKit browser tests passed, 19 skipped -intentionally; native and reference builds; reference, release and -vendor checks; `git diff --check`. Committed and pushed to `main` as -`f8e0f9c`. - -Next (PLAN section 2): controlled-seed comparisons of random-driven -battle routines. Then PLAN section 3/4 items: the party and bag menus -outside battle (with the stats screen and MOVE PKMN W/O MAIL), evolution -itself, and the remaining battle items. - -## Battle items, hBattleTurn and the stats screen: 2026-09-30 - -After `f8e0f9c`/`cfd083c`, the user asked for the items the previous -summary listed as refused or not ported: - -- Every non-ball item effect usable in battle, found by reading each - item's battle context and `ItemEffects` entry from the ROM: Full - Restore, Revive/Max Revive, Energypowder, Energy Root, Heal Powder, - Revival Herb, Bitter Berry, Ether/Max Ether/Elixer/Max Elixer/ - MysteryBerry (new `move` prompt), X items, X Accuracy, Guard Spec, Dire - Hit and Poké Doll. `ChangeHappiness` now reads `HappinessChanges` - (including the `wPartyMenuCursor` quirk), which also fixed level-ups: - `LevelUpHappinessMod`'s larger gain where the Pokémon was caught was - missing (new `landmark` battle option). A second participant set - (`wBattleParticipantsIncludingFainted`) serves revives. -- `hBattleTurn` at the battle menu: `DoBattle` ends on the player's turn, - full turns end on the enemy's, and `chooseTrainerMove` now returns - `enemyTurn` when `AI_Types`, `AI_Status` or `AIDamageCalc` ran. The - status-heal refusal is gone; the enemy's penalty is reapplied as in the - original. -- Stats screen: three pages from ROM strings (`STATS` profile table, - verified by `check:reference`), `TypeNames` and `GetGender`. It opens from - BattleMonMenu and from Bill's PC (a `stats` script effect; - `world.statsScreen`). -- Emulator comparisons: a new reference test runs `SendOutMonText` (with - its final `jp BattleTextbox` patched to `ret` in the in-page ROM copy), - `WithdrawMonText`'s text routine, `RestoreHealth` (with `wHPBuffer1` set - as `ItemRestoreHP` leaves it), `RestorePP`, `ChangeHappiness` and - `CalcPlayerStats` with both turns. A mutation of `calcPlayerStats` was - confirmed to fail. The pure helpers are exported from `battle.ts`. - -Remaining: Kurt's and the Park Ball; the leftover multiplicand byte in -`WithdrawMonText` is assumed zero; no whole battle has been compared with -the original. - -Checks: 337 unit tests with the local ROM (131 passed, 206 skipped -without it); 97 Chromium/WebKit browser tests passed, 19 skipped -intentionally; native and reference builds; reference, release and -vendor checks; `git diff --check`. Committed and pushed to `main` as -`b13e09e`. - -## Controlled random battle comparisons: 2026-09-30 - -Resumed from `be7575a` with a clean working tree. The user requested a bounded -increment suitable for a limited session. Continued PLAN section 2 with -accuracy, criticals and damage variation; capture stays as the next item. - -- `tests/reference-random.spec.ts` runs the original, unpatched - `BattleCommand_Critical`, `BattleCommand_CheckHit` and - `BattleCommand_DamageVariation` in the reference emulator. The native - commands receive the same ten-byte Link RNG stream. Results include the - final RNG stream/cursor, so extra or missing draws are detected too. -- Per browser: 2,560 critical cases (all random bytes at all five reachable - stages and held-item exceptions), 2,914 accuracy cases (all stage pairs, - Foresight, BrightPowder, bypasses, Dream Eater/substitute and Jump Kick - miss damage), and 2,149 variation cases (0/1, rounding, 16-bit boundaries, - rejected draws and RNG stream regeneration). -- Shared ROM input and symbol helpers moved from `reference-parity.spec.ts` - into `tests/reference-fixture.ts`. No ROM bytes or extracted assets added. -- Deliberately changing critical `<` to `<=`, accuracy `>=` to `>`, and the - damage divisor from 255 to 256 each made its comparison fail. All three - mutations were reverted; no production gameplay change was needed. - -Boundary: these compare isolated commands with controlled random inputs. -They do not compare a whole battle, the hardware-timed non-Link RNG, or the -Protect text path. Lock-On/Fly/Dig remain refused. This does not enable Link -battles or later maps. - -Checks: 337 unit tests passed with the local ROM; 131 passed and 206 -skipped with an absent ROM. All 103 Chromium/WebKit browser tests passed, -with the existing 19 intentional WebKit skips. Native and reference builds, -reference, vendor and release checks, and `git diff --check` passed. No CI -or physical-device run. This increment was initially left uncommitted and -is included in the subsequent capture-comparison delivery below. - -Next: controlled-input capture comparisons (`PokeBallEffect` and -`GetPokeBallWobble`), then the party/bag menus outside battle, MOVE PKMN W/O -MAIL and evolution. Keep section 2's capture checklist open until verified. - - -## Controlled capture comparisons: 2026-09-30 - -Continued PLAN section 2 from the existing uncommitted accuracy, critical and -damage-variation comparisons. Preserved that work and completed its next -capture-comparison item. - -- `tests/reference-capture.spec.ts` compares the native capture decision with - the calculation inside `PokeBallEffect`, then its shake loop with - `GetPokeBallWobble`. Per browser: 3,220 capture cases and 7,424 shake cases, - including supported ball multipliers, all catch-rate bytes, HP scaling and - overflow, status bonuses, equality boundaries, every random byte at each - wobble-table entry, every escape count and guaranteed catches. Final rate, - caught/escaped outcome, shake count and random-draw count agree. -- Capture uses hardware `Random`, not `BattleRandom`. The private in-page ROM - copy replaces `Random` with a supplied-byte reader; the fixture enters - after the UI setup and inserts a return after `wWildMon` is written. - Catch arithmetic, multipliers and `GetPokeBallWobble` remain unmodified. -- Extracted `rollCapture` and `ballWobbles` as private production helpers - called by `pokeBallEffect`, without changing gameplay behavior. Added - maximum HP 683 beside 342 in the synthetic divide-by-zero regression. -- Deliberately changing success `<=` to `<`, formula floor to ceiling, and - shake `>=` to `>` each failed the comparisons. Mutations were reverted. - -Boundary: isolated capture decision/shakes with supplied random bytes, not -hardware RNG timing, animation, caught-mon reloading, party/PC writes or a -whole battle. No actual item has `HELD_CATCH_CHANCE`; that effect remains -covered by pure vectors. Native division-by-zero refusal remains in place; -the emulator fixture omits the hanging HP inputs. No additional balls or -maps are enabled. - -Checks: 337 unit tests passed with the local ROM; 131 passed and 206 skipped -with an absent ROM. All 107 Chromium/WebKit browser tests passed, with the -existing 19 intentional WebKit skips. The four new capture tests skip cleanly -without a ROM. Native and reference builds, reference, vendor and release -checks, and `git diff --check` passed. No CI or physical-device run. -The accuracy, critical, damage-variation and capture comparisons were committed -together as `00972ba` and pushed to `origin/main`; the remote commit was -verified. This handoff-only follow-up records that delivery. - -Next: party and bag menus outside battle, MOVE PKMN W/O MAIL and evolution. -PLAN section 2 is complete within its recorded acceptance boundaries. - - -## Start menu, party menu and pack: 2026-09-30 - -Resumed from `60e06ae` (clean tree) with "continue" from the handoff: the -next plan items were the party and bag menus outside battle, then -evolution. - -- `src/game/scripts/start-menu.ts` (`StartMenuRun`): Start opens the - original start menu (items and descriptions from `StartMenu.Items`, list - from `SetUpMenuItems`). POKéMON: party menu (`PartyMenuStrings`), the - submenu from `MonMenuOptions` (field moves first), STATS, SWITCH, - MOVE (swap moves with PP), ITEM (GIVE from the pack with swap and - pack-full refund, TAKE). PACK: four pockets with left/right, the item - submenus from `MenuHeader_*.MenuData` by permissions and field menu, - TOSS with quantity and confirmation, GIVE, and USE through `ItemEffects` - addresses: HP items, status healers, Full Restore, revives, bitter - herbs, Ethers/Elixers/MysteryBerry and PP Up (one step, because - `wUsePPUp` aliases `wTempPP`), and the Bicycle (queued script after the - menu closes; Oak's warning where it cannot be used). -- It runs on a script draft; saves while open keep the prior state. Menu - cursors persist in memory and reset after battle (`CleanUpBattleRAM`); - the party cursor now reaches the battle's `ChangeHappiness` quirk. -- Select keeps the Key Items shortcut (browser tests moved from Enter to - Shift). New `pack` script effect/choice; `MENU` profile table checked by - `check:reference`; menu, pocket and PP constants moved into the verified - `C` block; `ENGINE_POKEGEAR` added and verified. The text decoder gained - `` ($24), needed for "POKéGEAR". -- Notices without changes: POKéDEX, POKéGEAR, trainer card, OPTION, field - moves from the menu, MAIL (giving or reading), SEL, evolution stones, - Rare Candy, vitamins and other field items. - -Checks: 353 unit tests with the local ROM (16 new in -`tests/start-menu.test.ts`); 109 Chromium/WebKit browser tests passed, 19 -skipped intentionally (one new start-menu flow in both browsers); native -and reference builds, reference, vendor and release checks; `git diff ---check`. No emulator comparison of the menus; no CI or physical-device run. - -Next: evolution (`EvolveAfterBattle` after battles and from stones/Rare -Candy, with the level-up flags the battle must record), then vitamins, -MOVE PKMN W/O MAIL, SEL/registered items and field moves from the menu. - -The start menu increment was committed and pushed as `1af2bea`. - -## Evolution: 2026-09-30 - -Continued in the same session after `1af2bea`. - -- `src/game/scripts/evolution.ts` (`PartyEvolution`): `EvolveAfterBattle` - walking the raw evolution bytes with the original's skip counts - (forced evolutions misread `EVOLVE_STAT` like the original), the texts, - a timed `evolve` effect (40 ticks, then seven wait/flash rounds; B only in - the waits and never for stones), nickname update only for unnicknamed - Pokémon, stats with stat experience, HP rise, `LearnLevelMoves`, - `LearnMove`/`ForgetMove` outside battle, and the Pokédex. -- The battle records `wEvolvableFlags` on level-up and returns them; after a - win the world runs the evolution as a script interlude before resuming - the battle's script (same draft, commits together). -- PACK: Rare Candy (level, exp, HP rise that revives, happiness, level-up - text, stat page, moves, evolution), evolution stones (ABLE/NOT ABLE party - labels with the original 10-byte/3-step check; Everstone and no-match - have no effect) and vitamins (stat experience high byte +10 up to 100). -- UI: the picture box shows the flashing evolution; the pack's pocket tabs - moved into their own compact row (`#mart-tabs`). - -Checks: 360 unit tests with the local ROM (5 new start-menu evolution -tests, 2 new battle-world tests); 111 Chromium/WebKit browser tests passed, -19 skipped intentionally (a new Rare Candy evolution flow in both -browsers); native and reference builds, reference, vendor and release -checks; `git diff --check`. No emulator comparison of evolution or the -menus; no CI or physical-device run. - -Next: MOVE PKMN W/O MAIL, SEL/registered items with Select, field moves -from the menu (Cut, Surf, Strength already have their ROM scripts), then -the Pokégear. Trade evolutions wait for Link. - -The evolution increment was committed and pushed as `45275ba`. - -## Field moves, SEL and moving Pokémon: 2026-09-30 - -Resumed with "Continue" after `45275ba`, taking the recorded next items. - -- Field moves from the party menu: the world's `menuFieldMove` runs each - menu function's checks (badges, facing tile/water/object, dark map) and - either queues the ROM's `…FromMenu` script with the chosen Pokémon or - returns the ROM's failure texts. Cut, Surf, Strength, Flash, Whirlpool, - Waterfall and Sweet Scent work; Softboiled/Milk Drink run in the menu; - Fly, Dig, Teleport, Headbutt and Rock Smash give a notice after their - checks. Encounter selection was split out of `randomEncounter` so Sweet - Scent reuses it without the rate roll. -- SEL and Select: `registeredItem` saved (migrated by absence), Select runs - `SelectMenu` (bike via the `_Register` scripts, Oak's warning, "may be - registered", registration cleared when the slot changed). The invented - Key Items shortcut (UI, runtime flag and CSS) is removed. -- Bill's PC MOVE PKMN W/O MAIL, with lists per box. -- `check:reference` caught `SPRITEMOVEDATA_SMASHABLE_ROCK` written as 18 - instead of $18; fixed and covered by a Rock Smash test. - -Checks: 371 unit tests with the local ROM; 111 Chromium/WebKit browser -tests passed, 19 skipped intentionally (the two Key Items browser tests now -cover SEL/Select); native and reference builds, reference, vendor and -release checks; `git diff --check`. No emulator comparison; no CI or -physical-device run. - -Next: the Pokégear (clock, map, phone and radio cards), then the trainer -card and options; later the Fly map and Teleport/Dig warps (they need -script commands after a warp). - -## The Pokégear, phone calls and Mom's savings: 2026-10-01 - -Resumed with "Continue the plan" after `3456aa4`, taking the recorded next -item (the Pokégear). - -- `src/game/scripts/pokegear.ts` (`Pokegear`): POKéGEAR in the start menu - runs the clock, map and phone cards with the original joypad rules, - yielding a `pokegear` effect per card answered with a button name. The - world routes A/B/directions/Start/Select to it, keeps `world.pokegear` on - screen during call texts and refreshes the clock every tick. The radio - card (Goldenrod only) gives a notice. -- Calls: no-service text, two "……" rings, out-of-hours or same-map scripts, - otherwise the callee script from `PhoneContacts` in `ScriptRun` on the - start menu's draft (adopted with `Object.assign` when it ends), then - `HangUp`. Elm's and Mom's callee scripts run; Mom's lecture after the Dude - sets `ENGINE_MOM_SAVING_MONEY`. -- Interpreter: `random`, `givemoney`/`takemoney`/`checkmoney`/`getmoney` - for both accounts, `getcurlandmarkname`, `getlandmarkname` (operand order - checked against the macros), `ScriptHost.phoneService`, `landmarkName`. - `HangUp` is now "Click!" 10 ticks, then "……" 60 ticks (it was "Click!" for - 140 ticks, double the original's 140 frames); two `mr-pokemon` assertions - were updated. -- Mom's savings: `momsMoney` in `ScriptState` and saves (migrated by absence - to 0). Battles take `mom: { saving, money }` and return `momMoney`; - `WinTrainerBattle.give_money` sends a quarter while she saves some. - `MomTriesToBuySomething` refuses once her balance reaches ¥900 (not - reachable in the open area). -- Map headers decode `phoneService`. New `POKEGEAR` profile table and OW - constants (landmark limits, card/Mom engine flags, money results), all - verified by `check:reference`. -- UI: `#pokegear` panel (card labels, clock, Town Map canvas with the - cursor frame, phone list with ▶/▷ and the submenu); the textbox is the - native dialogue, hidden on the map card; the minimap hides while it is - open. `drawTownMap` takes an optional card (region, player, cursor). - -Checks: 381 unit tests with the local ROM (131 passed, 250 skipped without -it; new `tests/pokegear.test.ts`, plus Mom tests in `trainer-battle` and -`trainer-world`); 113 Chromium/WebKit browser tests passed, 19 skipped -intentionally (a new Pokégear flow in both browsers); native and reference -builds; reference, vendor and release checks; `git diff --check`. No -emulator comparison of the Pokégear; no CI or physical-device run. Not -committed. - -Next: `BankOfMom` (talking to Mom after the Egg still refuses with special -34), then Mom's purchases (`MomTriesToBuySomething`, `wWhichMomItem`, -`wMomItemTriggerBalance`), the trainer card and options; later the Fly map -and Teleport/Dig warps. - -## Mom's bank and her purchases: 2026-10-01 - -Resumed with "Continue the plan" on top of the uncommitted Pokégear -increment above (still not committed), taking the recorded next items. - -- `src/game/scripts/mom.ts` (`bankOfMom`): `BankOfMom` as a special. First - visit: `MomLeavingText1`–`3` and the saving question; later: "Or is this - about your money?", then GET/SAVE/CHANGE/CANCEL from - `BankOfMom_MenuHeader`. The amount box is a `mombank` effect answered - with button names (the world routes A, B and directions to it like the - Pokégear, via `buttonChoice`). `DigitQuantities` is read from the ROM; - `GiveMoney`/`TakeMoney` limits, cursor 0–5 starting on 5, zero or B - cancels, refusals return to a fresh box. Declining runs `DSTChecks` - (near-midnight booklet refusal; `shiftClockHour` wraps `minuteOffset` - into ±720 and moves `weekdayOffset` the other way). -- `MomTriesToBuySomething` is ported (it had to be: deposits make the ¥900 - trigger reachable at once, and it refused after every trainer win): - MomItems_2 triggers, the ¥2300 `wMomItemTriggerBalance` steps with the - exact-match `RandomRange(5)` MomItems_1 purchase, PC items (full PC - cancels), doll decoration events, and the call through the existing - `memscript` path (`Script_ReceivePhoneCall` with - `Mom_GetScriptPointer`'s item or doll script). `.ASMFunction`'s cost and - `wWhichMomItem` are applied when the call is queued. `randomRange` is - shared with `Script_random`. -- Saves gained `momItem` (0–10) and `momItemTriggerBalance`, migrated by - absence to 0 and ¥2300. -- New `MOM` profile table and extra `POKEGEAR` entries (MomItems lists, - item/doll scripts), verified by `check:reference`. -- UI: the amount box in the dialogue choice (`.mom-bank`, a blinking digit - that respects reduced motion) with ◀ ▼ ▲ ▶, OK and CANCEL buttons. - Checked visually at desktop and 390px widths. - -Checks: 398 unit tests with the local ROM (132 passed, 266 skipped without -it; new `tests/mom.test.ts`, purchase tests replace the refusal test in -`tests/trainer-world.test.ts`); 115 Chromium/WebKit browser tests passed, -19 skipped intentionally (new deposit flow in both browsers); native and -reference builds; reference, vendor and release checks; `git diff --check`. -No emulator comparison of the bank or purchases; no CI or physical-device -run. Not committed. - -Next: the trainer card and options; later the Fly map and Teleport/Dig -warps. - -## Trainer card and play time: 2026-10-01 - -Resumed with "continue the plan" after `38b9562` (clean tree), taking the -recorded next item (the trainer card, board card #23). The Pokégear and -Mom's bank above were committed as `209778c`. - -- `src/game/scripts/trainer-card.ts` (`TrainerCard`): the player's name in - the start menu runs the card and returns to the start menu. Each page is - a `trainercard` effect answered with a button (a `buttonChoice` like the - Pokégear): page 1 A/right → page 2; page 2 A leaves, left → page 1; B - leaves from both. The Kanto page (unreferenced code) is not shown. Labels - from `.Name_Money`, `.Dex_PlayTime` and `.Badges`; the Pokédex line only - with `ENGINE_POKEDEX`; badges from the Johto badge engine flags. -- `src/game/world/game-time.ts`: `wGameTime` and `GameTimer` (60 frames a - second, capped at 999:59:59.00). `GameWorld.gameTime` advances two - frames per tick unless the introduction is pending; `beginIntro` resets - it. `save()` during a scene returns the committed state with the current - play time. Saves gained `gameTime`, migrated by absence to zero. -- The world refreshes the card's time, blinking colon (every 32 frames on - page 1) and badge step (every 8 frames on page 2, only with badges) each - tick, counting from the page load. -- `src/game/rendering/trainer-card.ts`: the Chris/Kris card picture - (tile $1c left blank, as the corner covers it) and page 2's leader faces - with the owned badges' sprites, coloured by `_CGB_TrainerCard`'s - attribute map. UI: `#trainer-card` panel, frame colour from the other - gender's palette; checked visually at desktop and 390px widths. -- New `TRAINER_CARD` profile table, OW constants (`ENGINE_MINERALBADGE`, - `NUM_JOHTO_BADGES`, `PREDEFPAL_CGB_BADGE`, the leaders' trainer classes). - `check:reference` now understands the `trainerclass` macro. -- Tests that compare a save before and after a refused or rolled-back - action now ignore the play time (`withoutPlayTime` in - `tests/world-driver.ts`), which keeps counting. - -Checks: 407 unit tests with the local ROM (134 passed, 273 skipped without -it; new `tests/trainer-card.test.ts`); 117 Chromium/WebKit browser tests -passed, 19 skipped intentionally (a new trainer card flow in both -browsers); native and reference builds; reference, vendor and release -checks; `git diff --check`. No emulator comparison of the card; no CI or -physical-device run. - -Next: the options screen (board #24), then the Fly map and Teleport/Dig -warps. - -The trainer card increment was committed as `4769856` (`Closes #23`; the -hook moved the card to Fait). Not pushed. - -## Options: 2026-10-01 - -Continued in the same session with board card #24. - -- `src/game/scripts/options.ts` (`OptionsMenu`): OPTION in the start menu - runs `_Option` on `state.options` (the start menu's draft) and returns to - the start menu. `options` effect answered with buttons; Start/B leave, - A on CANCEL leaves; `OptionsControl` wrapping; each option routine ported - from its bits (text speed's `.Save` keeps only the high nybble, so bit 3 - is lost and bit 4 kept). Labels from `StringOptions` (read with a longer - `readString` limit), settings from the routines' strings. -- `GameOptions` = `wOptions`, `wTextboxFrame`, `wGBPrinterBrightness`, - `wOptions2`; `defaultOptions()` is `DefaultOptions`. In `ScriptState` and - saves (migrated by absence; frame above 7 refused). -- Consumers: `Battle` takes `shift` (from `BATTLE_SHIFT`) and - `CheckWhetherToAskSwitch` honours it; `StartMenu` drops descriptions - without `MENU_ACCOUNT`. Text speed, battle scene, sound, print and frame - are stored only. -- New `OPTIONS` profile table and OW option constants, verified by - `check:reference` (decimal literals: the checker skips `0b` values, which - a deliberately wrong `TEXT_DELAY_MED` confirmed). -- UI: `#options` panel, labels in a fixed column; checked at desktop and - 390px widths. - -Checks: 413 unit tests with the local ROM (134 passed, 279 skipped without -it; new `tests/options.test.ts` and a Set-mode test in -`tests/battle-switch.test.ts`); 119 Chromium/WebKit browser tests passed, -19 skipped intentionally (a new options flow in both browsers); native and -reference builds; reference, vendor and release checks; `git diff --check`. -No emulator comparison; no CI or physical-device run. - -Next: the Fly map and Teleport/Dig/Escape Rope warps (board #25), which -need script commands after a warp; then Headbutt and Rock Smash (#26). - -The options increment was committed as `c4cc0e7` (`Closes #24`). Not pushed. - -## Fly, Dig, Teleport and the Escape Rope: 2026-10-02 - -Resumed with "continue" on a clean tree after `c4cc0e7`, taking board card -#25. - -- `src/game/scripts/fly-map.ts` (`flyMap`): `_FlyMap`/`FlyMap` inside the - start menu run, before the world queues `FlyFunction.FlyScript`. Region - by the current landmark (Kanto only with Indigo Plateau's flypoint, else - Johto without the player icon), default New Bark/Indigo, up/down over - visited flypoints with wrapping (refused if none is visited, where the - original hangs), A returns the spawn, B goes back to the party menu. - `flymap` effect answered with buttons (`FLY_MAP_BUTTONS`). -- Visits: `assets.spawnEngineFlag` inverts the ROM's `EngineFlags` over - `wVisitedSpawns` (Union Cave and the Fast Ship have no flag). - `assets.spawnPoint`/`spawnIndex` read `SpawnPoints` (`IsSpawnPoint`). -- Interpreter: `newloadmap` handles `MAPSETUP_WARP`/`TELEPORT`/`FLY` - (spawn from `fieldMove.spawn` or `GetWhiteoutSpawn`) and `MAPSETUP_DOOR` - (`fieldMove.warp`, resolved by the world), then stops; the `warp` effect - carries `resume` unless only `end`/`endall` follows, and refuses inside a - call. The world's `finishScript` starts the rest of the script right after - entering the map, with the same field-move context (`ActiveScript.fieldMove`). - `door` warps keep the facing unless `FACE_DOWN_ON_WARP`. - `FieldMoveContext` is now one exported type. -- `fly` effect (`FlyFromAnim` 65 ticks, `FlyToAnim` 33): `poses()` is empty, - `world.flyingIcon` replays `SpriteAnimFunc_FlyFrom`/`FlyTo` with the - ROM sine table, and the renderer draws `assets.monIcon` with - `RED_WALK`'s nine-frame flaps in object palette 0. Leaves not drawn. -- World: `SaveDigWarp` in `prepareWarp` (outdoor to `CheckIndoorMap`, not - from 15:10/15:12); `digWarpMove` for Dig and the Escape Rope (with - `SpecialKabutoChamber`); Teleport through `IsSpawnPoint`; an unported - destination gives a notice. Saves gained `digWarp` (absent → null). -- Start menu: `{ flyMap: true }` verdict, `host.flyTo`, `host.escapeRope` - (`MENU.EscapeRopeEffect`: `UseDisposableItem` only on success; failure - falls to Oak's warning), `host.visitedSpawn`. -- UI: `#fly-map` with the bubble and the Town Map (`drawTownMap` takes an - optional `player` and a `species` icon). Checked visually at desktop and - 390px widths, including the flight and landing. - -Checks: 423 unit tests with the local ROM (134 passed, 289 skipped without -it; new `tests/fly-dig-teleport.test.ts`); 121 Chromium/WebKit browser tests -passed, 19 skipped intentionally (a new Fly flow in both browsers, with a -"fly" fixture room); native and reference builds; reference, vendor and -release checks; `git diff --check`. No emulator comparison of the flight -paths or warps; no CI or physical-device run. None of it is reachable in -normal play yet (no Storm Badge, caves or further flypoints); Teleport -works after a Pokémon Center visit for a Pokémon that knows it. - -Next: Headbutt and Rock Smash encounters (board #26). - -The increment was committed as `a7aceda` (`Closes #25`). Not pushed. - -## Manual test cards: 2026-10-02 - -Asked for the checks only a person can do. The board gained an "A tester" -column (between En cours and Fait) with #46 (Fly map, flight and landing), -#47 (Teleport and the Escape Rope outdoors), #48 (touch screens, part of -#40), #49 (screen reader, related to #38) and #50 (Dig and the Escape Rope -in Dark Cave, blocked by #31). `scripts/project-board.mjs` now also reads -that column, so a commit mentioning one of those cards moves it instead of -adding it to a second column. - -`tests/human-test-saves.test.ts` writes the backups the cards use, only with -`MAKE_TEST_SAVES=1` and the local ROM: `test-saves/fly-teleport.crystal-save.json` -(ignored by git, as it holds names read from the ROM). It reloads each save -in a fresh world before writing it; importing it through Savegames and using -Teleport then Fly was checked once in Chromium and WebKit. - -Not made into cards, because they can be automated: emulator comparisons -of the flight paths and of the Teleport/Dig warps through the reference -harness. - -## Headbutt and Rock Smash: 2026-10-02 - -Resumed with "continue the plan" on a clean tree after `ee55d34`, taking -board card #26. - -- `src/game/scripts/treemons.ts`: `treeMonEncounter` (`TreeMonEncounter` - with `GetTreeMonSet`, `GetTreeMons`, `GetTreeScore` and `GetTreeMon`, - including the rare table after the common one), `rockMonEncounter` - (`RockMonEncounter`'s 40%) and `treeScore`, all read from the ROM's - `TreeMonMaps`, `RockMonMaps` and `TreeMons`. -- Interpreter: `callasm` runs `TreeMonEncounter` (facing tile in - `wPlayerMapX/Y` coordinates, +4), `RockMonEncounter`, `HasRockSmash` - (sets `wCurPartyMon`) and `ShakeHeadbuttTree` (a `headbutt` effect after - the text). A `tempWild` local stands for `wTempWildMonSpecies`/ - `wCurPartyLevel`: `randomwildmon` reads it, `readmem wTempWildMonSpecies` - answers from it, and the battle clears it with the tree battle type, as - `CleanUpBattleRAM` does. The `battle` effect gained `tree`. -- World: facing a tree runs `AskHeadbuttScript`; HEADBUTT and ROCK SMASH - from the party menu queue `HeadbuttFromMenuScript` and - `RockSmashFromMenuScript`, the latter with the rock as `hLastTalked` - (`MenuFieldMove.lastTalked`). Talking to a rock already ran - `SmashRockScript`. The `headbutt` effect lasts 20 ticks - (`HEADBUTT_TICKS`) and `world.headbuttTree` gives the renderer the square - and frames; objects do not move meanwhile. -- Battle: `BATTLETYPE_TREE` (`tree` option) prints - `PokemonFellFromTreeText` and puts the time of day's `AsleepTreeMons` - species to sleep for `TREEMON_SLEEP_TURNS` (`CheckSleepingTreeMon`). The - skipped cry is not presented anyway. -- Renderer: tile $05 over the tree's four tiles for 36 frames, and the - `HeadbuttTreeGFX` sprite in `PAL_OW_TREE` with the frameset's timing - (three video frames a step, frame 2 mirrored on every other pass). Checked - in Chromium with close-up screenshots across the shake. -- New profile entries (OVERWORLD, BATTLE, `RAM.tempWildMonSpecies`) and - constants (`BATTLETYPE_TREE`, `TREEMON_SLEEP_TURNS`, `NUM_TREEMON_SETS`), - all verified by `check:reference`. - -Checks: 435 unit tests with the local ROM (434 passed, 1 skipped; 135 -passed, 300 skipped without it; new `tests/headbutt-rock-smash.test.ts`, -and the Rock Smash test in `tests/overworld-rom.test.ts` now smashes the -rock and meets a Krabby); 123 Chromium/WebKit browser tests passed, 19 -skipped intentionally (a new Headbutt flow with a "headbutt" fixture room -below a Route 29 tree); native and reference builds; reference, vendor and -release checks; `git diff --check`. No emulator comparison of the tree -scores or the shake; no CI or physical-device run. Unreachable in normal -play so far: no open map has rocks, and Headbutt (the Ilex Forest tutor) -and Rock Smash (a TM on Route 36) are not obtainable yet. - -Next: the remaining field items, MAIL and pack reordering (board #27). - -The increment was committed as `f259163` (`Closes #26`; the hook moved the -card to Fait). Not pushed. Manual check: card #51 in "A tester" (the shake -and tree battles against the original), with -`test-saves/headbutt.crystal-save.json` from `tests/human-test-saves.test.ts`; -importing it and headbutting was checked once in Chromium and WebKit. - -## Field items, fishing, Select and MAIL: 2026-10-03 - -Resumed with "continue the plan" on a clean tree after `10e1560`, taking -board card #27 in four commits (none pushed): - -- `4cb4676`: Repel, Super Repel and Max Repel (`wRepelEffect`, the step - countdown where `CountStep` calls `DoRepelStep`, `RepelWoreOffScript`, - `CheckRepelEffect` after `ChooseWildEncounter`), the Coin Case and Blue - Card balances, Sacred Ash (`SacredAshScript`, queued), the Normal and - Gorgeous Box trophies (`DecorationIDs`), the Itemfinder - (`CheckForHiddenItems` in the world), the Card Key, Basement Key and - Squirtbottle (their maps are not open; tested with a start-menu run and - the interpreter). `text_ram` of the player's, rival's and Mom's names now - reads the text context. Saves gained `repel`, `coins` and - `blueCardBalance`. New `tests/field-items.test.ts` and the shared - `tests/menu-driver.ts`. -- `397dcef`: the three rods (`FishFunction` in the world, `scripts/fish.ts` - for `Fish`/`TimeFishGroups`, the map header's `fishGroup`), `loademote`, - `loadmem hBGMapMode`, `Fishing_CheckFacingUp`, `PutTheRodAway` (after the - pending text), the actor's fishing action and the renderer's fishing - frames (`LoadFishingGFX` tiles in VRAM bank 1; facing offsets are now - signed, which the up and left rods need), `BATTLETYPE_FISH`'s "The hooked - … attacked!". Wild Magikarp battle now: `CalcMagikarpLength` (compared - with the original over 1,509 cases in `tests/reference-parity.spec.ts`) - and `LoadEnemyMon`'s rerolls, Lake of Rage bug included. Unown are still - refused. Swarms need trainer calls. The four directions were checked in - close-up screenshots. -- `e2dc5ba`: Select in the start menu's pack (`SwitchItemsInBag`: move, - merge stacks under 99, B ends the move; GIVE's and the PC's packs ignore - Select) with the prompt and a marked row in the UI; left and right on the - MOVE screen (`.cycle_left`/`.cycle_right`, skipping Eggs) through a `menu` - effect with `sides`. The pack's cursor now stays on the last row chosen. -- `5c1ac9d` (`Closes #27`; the hook moved it to Fait): MAIL. `party/mail.ts` - holds the `mailmsg` struct; it lives on the holding `PartyMon` and in - the state's `mailbox`. Giving MAIL opens a two-line entry (the naming - effect's `mail` mode), the submenu's MAIL reads (a native letter panel, - `#mail-view`) or takes it (to the PC or into the pack), Bill's PC says - "Remove MAIL." for deposits and releases, and the player's PC MAIL BOX - reads, packs and attaches. A scripted `givepoke` with MAIL is refused. - Checked visually at desktop and 390px widths. - -Checks: 460 unit tests with the local ROM (3 skipped; 136 passed, 327 -skipped without it; new `tests/mail.test.ts`); 131 Chromium/WebKit -browser tests passed, 19 skipped intentionally (new Old Rod, Select and -MAIL flows); native and reference builds; reference, vendor and release -checks; `git diff --check`. "Tin Tower pillars" in -`tests/overworld-parity.spec.ts` failed once in Chromium during a full run -and passed alone and in the later full runs: timing under load. No CI or -physical-device run. - -Gaps found on the way, not on #27: -- The TM/HM pocket does not exist yet (`receiveItem` throws for TMs and - the pack shows an empty TM pocket). Falkner gives TM31, so it is needed - with or before #31; no card names it. -- The MAIL stationery (`MailGFXPointers`) and the Portrait MAIL's picture - are not drawn, and the mail entry leaves out ``, ``, ``, - `` and the 'd-'v ligatures. -- `CountStep`'s poison, happiness, egg, Day-Care and bike steps are not - ported; only the Repel step is. -- #29's description still lists "MAIL storage"; the PC's MAIL BOX was done - here, so only its printing remains there. - -Manual checks: cards #52 (fishing against the original), #53 (touch -screens, part of #40) and #54 (screen reader, related to #38) in -"A tester", with `test-saves/field-items.crystal-save.json` from -`tests/human-test-saves.test.ts`; importing it and fishing with the Super -Rod was checked once in Chromium and WebKit. - -Next: the remaining battle commands, move effects and Smart AI handlers -(board #28). - -## Battle effects, held items and obedience: 2026-10-03 - -Recovered the latest Claude Code session from -`~/.claude/projects/-Users-kawa-Crystal/d316d957-8ee4-4b55-aa7b-12be1bcd8726.jsonl`. -Its request was "continue the plan", taking card #28. It hit the usage limit -at 09:25 UTC while researching held items, Exp. Share and CheckObedience. - -Claude had committed three groups: -- `9e0a2cd`: multi-hit and special-damage effects. -- `c53edb7`: moves spanning several turns. -- `8f5b464`: status and field move effects. - -Its next group was uncommitted: weather, Substitute, Transform, Mimic, -Metronome, Mirror Move, Sleep Talk, Snore, Conversion/Conversion2, Sketch, -Baton Pass, Roar/Whirlwind and Teleport. Every move's effect script now has -a handler. Rollback fixtures use level-1 Psywave, which would loop in the -original and remains explicitly refused. These changes were preserved; -the recovered tree passed 546 unit tests before further edits. Claude's -last recorded browser run had 147 passed and 19 skipped. - -The resumed increment adds: -- Smoke Ball, Metal Powder (also in the AI damage estimate), MysteryBerry, - Berserk Gene and the original unused held stat-raising effects. Item - order and stored/battle PP follow `engine/battle/core.asm`. Metal Powder - checks original species after Transform and retains byte overflow; - Berserk Gene retains the 256-turn/stale confusion-count bug. -- Exp. Share's separate participant/holder awards, halving bytes before - dividing each pool, including stat experience and existing modifiers. -- Traded Pokémon obedience, including badge thresholds, alternate moves, - nap, self-damage and idle outcomes. The world supplies its Johto badge - byte. RNG rejection loops fail closed for an invalid random source. -- Correct removal of consumed trainer healing/confusion berries from - the stored party, preventing them from returning on a switch. -- `tests/battle-held-items.test.ts` (30 local-ROM tests) and - `tests/reference-held-items.spec.ts` (320 Metal Powder cases per browser, - running the unmodified original routine). The new tests skip without - the private ROM; the emulator test also needs the pinned symbol file. - -The reference harness must keep sentinels out of `$c6ff` (player screens). -The move-effect and held-item specs use audio RAM `$c0f0`, with interrupts -disabled, instead. - -Validation on the final code: `npm test` passed 576 tests, with 3 skipped; -without the private ROM, 136 passed and 443 skipped. The full Chromium/WebKit -suite passed 149 tests, with 19 intentional skips, including the original -Tin Tower pillars case on this run. Native/reference builds, profile and -pinned-reference verification, vendor integrity, release isolation and -`git diff --check` passed. Metal Powder has direct emulator evidence; -the other new held-item, experience and obedience paths have local unit -coverage and source review. No CI or physical-device validation was run. - -Next: complete the Smart AI effect dispatch in `src/game/battle/ai.ts`; -card #28 remains open. Other explicit boundaries include the special balls, -Unown generation, Beat Up with Eggs, and original routines that run off a -move list or loop indefinitely. Johto badge stat boosts are still absent; -the new badge option implements obedience only. Do not claim the battle -engine or the full card is complete. Delivery is tracked by the commits -referencing card #28; its commit list includes the recovered groups above. - -## Smart AI handlers: 2026-10-03 - -Codex resumed card #28 after `32d736e` and stopped at its usage limit at -13:21 UTC with an uncommitted Smart AI translation (`smart-ai.ts`), partial -battle wiring and a failing emulator spec. Claude Code took it over. - -- The spec showed 35 of 6,024 states differing, in five handlers. Encore, - Roar/Whirlwind, Baton Pass and Perish Song came from `CheckTypeMatchup` - ignoring the type in `a`: it reads the move type and Foresight flag through - `hBattleTurn`. The AI now tracks the turn and both move structs' types; - `checkPlayerMoveTypeMatchups` takes the type and flag. Conversion2's write - to the player's move type persists in the battle. -- Psych Up sums `NUM_LEVEL_STATS` (8) bytes. The eighth is only ever reset to - 7 or copied, so the 7-entry sum is equivalent; the spec now writes it as 7. -- The spec randomizes the starting turn and the player's move type and also - compares them afterwards. It passes 6,024 states per browser; a one-off - run with another seed passed 18,072 per browser. - -Validation: 579 unit tests passed (3 skipped); without the ROM 138 passed, -444 skipped. Browser suite: 151 passed, 19 intentional skips. Both builds, -reference/release/vendor checks and `git diff --check` passed. No CI or -physical-device validation. - -Next on #28: Kurt's balls, Park and Safari balls, and special battle types. -Other boundaries listed in the previous section are unchanged. - -## Kurt's balls: 2026-10-03 - -Same session, after the Smart AI commit. - -- `Battle.kurtBallRate` translates the Heavy, Level, Lure, Fast, Moon and - Love multipliers; `catchRate` adds the Park Ball's 1.5× and the Level - Ball's skip of the HP, status and held-item terms. Friend Ball catches - get 200 happiness in the party or the PC box. -- The Heavy Ball's buggy bank makes Kadabra's (64) and Sunflora's (192) - scans run past $7fff into VRAM, which the port does not model; those - throws raise `UnsupportedBattle`. Tauros (128) reads a wrong in-ROM - weight, which is reproduced. -- `tests/reference-capture.spec.ts` gained a 6,153-state comparison - against the unmodified `PokeBallEffect`. Mutating the Love Ball gender - test or the Fast Ball's byte count fails it. A Heavy Ball light-mon - off-by-one mutation is undetectable: rates 0 and 1 both end at 1. - -Validation: 582 unit tests passed (3 skipped); without the ROM 139 passed, -446 skipped. Browser suite: 153 passed, 19 skipped. Both builds, -reference/release/vendor checks and `git diff --check` passed. - -Next on #28: the Bug-Catching Contest battle type (Park Ball count, -`BugContest_SetCaughtContestMon`) and the other special battle types. -Neither is reachable yet: National Park and the legendary encounters -are not enabled. - -## Special battle types: 2026-10-03 - -Same session; finishes card #28. - -- `BattleOptions.battleType` takes FORCESHINY, TRAP, FORCEITEM, CELEBI, - SUICUNE, CONTEST (with `contest`) and ROAMING (with `roamer`); invalid - combinations raise `UnsupportedBattle` before any draw. `loadvar - VAR_BATTLETYPE` passes the script-set ones; CleanUpBattleRAM's clear - follows `startbattle`. -- `run()` ends with `BattleEnd_HandleRoamMons`: one extra BattleRandom - after every non-roaming battle (`updateRoamMons` in the outcome). The - world ignores the flag until roaming Pokémon exist. -- Contest: `{ kind: "action", parkBalls }` drives the PARKBALL× button; - `{ kind: "contestswitch" }` is answered like Yes/No (world, keyboard and - DOM). `ScriptState.bugContest` is runtime-only (not saved); - `readmem wParkBallsRemaining` reads it. The fixture scenario `contest` - covers it in `tests/battle-world.test.ts` and - `tests/battle-browser.spec.ts`. The browser test found that the switch - text was missing from the text box; it is fixed. -- `tests/battle-types.test.ts` covers each type with the local ROM, plus - a synthetic `loadvar` script. - -Validation: 596 unit tests passed (3 skipped); without the ROM 141 passed, -458 skipped. Browser suite: 155 passed, 19 skipped. Both builds, -reference/release/vendor checks and `git diff --check` passed. - -Left for the map cards: National Park's contest event (GiveParkBalls, -timer, out-of-balls/time-up scripts, judging, saving the contest state), -Burned Tower's roaming release with UpdateRoamMons/JumpRoamMons and -roaming encounters, and Ilex Forest's CelebiShrineEvent/CheckCaughtCelebi. - -## Route 31 to Violet City: 2026-10-03 - -Card #31 in the same session. - -- Maps: 26:2, 26:11, 3:78, 10:5–10:11, 3:1–3:3 in `NATIVE_MAPS`. The - closed-connection test now uses Violet City's south edge (Route 32). -- Script commands added: 0x43 gettrainername, 0x97 askforphonenumber, - 0x30 checkpokemail, 0x4f/0x58/0x50 loadmenu/_2dmenu/closewindow, - 0x2e giveegg, 0x96 trade; `loadvar VAR_CALLERID` (0x17, wCurCaller) - and the RandomPhoneMon special. `addcellnum` returned success where - the original returns TRUE on failure; fixed. -- Eggs: `PartyMon.egg` with the real species (was species 0xfd). Every - former `species === EGG` check uses the flag. HealParty skips Eggs. - The stats screen has the Egg page (EggPic, EggPalette). Hatching is - not ported (#29). -- TM/HM: `Bag.tmsHms` (57 counts, migrated as zeros for old saves), - item names TM01–HM07, `tmhmMove`, `canLearnTMHM`; the pack's pocket, - USE/GIVE/QUIT submenu and AskTeachTMHM/ChooseMonToLearnTMHM/TeachTMHM. -- `ScriptState.tradeFlags` (saved) for NPCTrade. -- BadgeStatBoosts at InitBattleMon, the in-battle level-up and - CalcPlayerStats; `tests/reference-badges.spec.ts` compares it. -- Event flag numbers for tests: read them from ROM scripts. Counting - `constants/event_flags.asm` by hand drifted by 0x200 for later flags. -- `MAKE_TEST_SAVES=1 npx vitest run tests/playthrough.test.ts` writes - `test-saves/violet-city.crystal-save.json` (Cyndaquil L11 at Violet's - east gate) for the manual check cards. - -Validation: 610 unit tests passed (3 skipped); without the ROM 143 -passed, 470 skipped. Browser suite: 157 passed, 19 skipped. Both builds, -reference/release/vendor checks and `git diff --check` passed. - -Open boundaries: Egg hatching (#29), trainer calls/rematches (#30), the -trade animation, Earl's blackboard as a list rather than a 2×3 grid, -Kenya's MAIL text in the MAIL model, Route 32/36 and Dark Cave's far side.