Files
Crystal/docs/SESSION_HANDOFF.md
T
Sébastien QUEROLandClaude Opus 5.5 802397b668
Checks / web (push) Canceled after 0s
Record the field items, fishing and MAIL commits in the session handoff
The handoff lists the four commits for the remaining field items, the
gaps found on the way and the manual test cards, whose backup the
manual-test save writer now produces.

[no-card]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 01:12:26 +02:00

64 KiB
Raw Blame History

Recovered Claude session

Latest continuation: see Field items, fishing, Select and MAIL: 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 <PLAYER>, 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 <POKE> ($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 <PK>, <MN>, <PO>, <KE> 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).