Open Routes 38 to 41, Olivine and Cianwood to the Mineral Badge (#32)
Enable Route 38 and its Ecruteak gate, Route 39 with the MooMoo farm, Olivine City and its buildings, the Lighthouse's six floors, Routes 40 and 41, and Cianwood City with its gym, pharmacy, Photo Studio, Mania's house, the Poke Seer and the Lugia speech house. Olivine's port, the Battle Tower gate and Whirl Islands stay closed. The story runs from the rival in Olivine through Jasmine and Amphy, Suicune and Eusine, the SecretPotion, Chuck's boulders, the STORMBADGE, TM01 and HM02, back to Amphy's cure, Jasmine, the MINERALBADGE and TM23. Port GiveShuckle and ReturnShuckie with every return code and Mania's wrong last-Pokemon speech, PokeSeer with its high-byte-only OT check and reused caught level, PhotoStudio's no-printer path (Printer Error 2), and PlaySlowCry. wMooMooBerries joins the saved script bytes. Port trainer item AI: the 13 healing, status and X items, the highest-level gate, item use while Mean Look or Wrap prevents switching, and the original's random and state quirks, checked against the ROM over 5,632 states. Leaders with items are no longer refused. Game Corner sprites match the original frame by frame: cursor and reel positions, Golem's whole-frame flips, frame timing, the frame a removed sprite is still drawn, and the Golem roll's screen shake. The Burned Tower rival's garbage trainer header is documented as a boundary: its seen text dispatches text command $7f into home code before any battle. The playthrough continues for six stages to Jasmine; its driver can Surf, fish and travel through floors joined by holes. New tests cover trainer items, Olivine and Cianwood side content, and reference checks for trainer items, trainer sight and Game Corner animations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7c4e8521d4
commit
b5c2b19094
29 files changed
+2708
-139
No files matched your search
+11
-4
@@ -46,11 +46,12 @@ These paths refer to the pinned [PRET source](https://github.com/pret/pokecrysta
|
||||
| `game/scripts/unown-puzzle.ts` | `engine/games/unown_puzzle.asm`, `engine/events/specials.asm` (`UnownPuzzle`), `engine/gfx/cgb_layouts.asm` (`_CGB_UnownPuzzle`) | `_UnownPuzzle`: `InitUnownPuzzlePiecePositions` with Random, the d-pad rules, picking up and placing pieces, `CheckSolvedUnownPuzzle`, and the graphics (`ConvertLoadedPuzzlePieces`' doubling, the piece borders, the cursor and START/CANCEL, the OAM layouts) and palettes from the ROM. Sounds are not played. |
|
||||
| `game/scripts/ruins.ts` | `engine/events/unown_walls.asm`, `data/events/unown_walls.asm` | `DisplayUnownWords` as an overlay screen with the chamber tileset's Unown letters, `HoOhChamber` and `OmanyteChamber`. |
|
||||
| `game/scripts/goldenrod.ts`, `game/world/day-timers.ts` | `engine/events/name_rater.asm`, `haircut.asm`, `lucky_number.asm`, `engine/overworld/time.asm`, `engine/menus/intro_menu.asm` | Name Rater ownership checks, ROM haircut happiness tables, Lucky Number party and box matching with first-winner ties, saved number and Friday countdown with 140-day rollover. |
|
||||
| Goldenrod shops, gifts and prizes | `engine/items/mart.asm`, `engine/pokemon/move_mon.asm` (`GivePoke`), `engine/events/specials.asm` (`GameCornerPrizeMonCheckDex`), `engine/pokedex/new_pokedex_entry.asm` | All five mart types, gift OT/nickname and MAIL, full-party gifts to the box, coin commands and balance boxes, and first-owned prize registration with two A/B pages. Boxed gifts approximate an unspecified stale enemy held item as zero. SlotMachine and CardFlip remain refused. |
|
||||
| Route 35, Route 36, Route 37, Ecruteak and Burned Tower | `home/trainers.asm` (`_CheckTrainerBattle`), `mobile/mobile_41.asm` (`CheckMobileAdapterStatusSpecial`), `engine/overworld/overworld.asm` (`LoadUsedSpritesGFX`, `RefreshSprites`) | These maps' scripts (Floria and the Sudowoodo Squirtbottle battle, Arthur, Sunny, the Itemfinder house, the Kimono Girls and HM03 Surf, Eusine and the rival's Burned Tower cutscene) run on already-ported primitives. `checkTrainers` skips a map trainer whose script is `ObjectEvent` (the Burned Tower rival) once its own flag is set: the original reads `ObjectEvent`'s bytes as flag $f251, bit 1 of `wNumKeyItems` through echo RAM, which is set with the usual two or three key items; the other case is unverified. `CheckMobileAdapterStatusSpecial` is unused outside the Japanese release and always clears its result; `LoadUsedSpritesGFX`/`RefreshSprites` are VRAM sprite tile loading, presentation only. |
|
||||
| Goldenrod shops, gifts and prizes | `engine/items/mart.asm`, `engine/pokemon/move_mon.asm` (`GivePoke`), `engine/events/specials.asm` (`GameCornerPrizeMonCheckDex`), `engine/pokedex/new_pokedex_entry.asm` | All five mart types, gift OT/nickname and MAIL, full-party gifts to the box, coin commands and balance boxes, and first-owned prize registration with two A/B pages. Boxed gifts approximate an unspecified stale enemy held item as zero. SlotMachine and CardFlip run as described below. |
|
||||
| Route 35, Route 36, Route 37, Ecruteak and Burned Tower | `home/trainers.asm` (`_CheckTrainerBattle`), `mobile/mobile_41.asm` (`CheckMobileAdapterStatusSpecial`), `engine/overworld/overworld.asm` (`LoadUsedSpritesGFX`, `RefreshSprites`) | These maps' scripts (Floria and the Sudowoodo Squirtbottle battle, Arthur, Sunny, the Itemfinder house, the Kimono Girls and HM03 Surf, Eusine and the rival's Burned Tower cutscene) run on already-ported primitives. `checkTrainers` skips a map trainer whose script is `ObjectEvent` (the Burned Tower rival) once its own flag is set: the original reads `ObjectEvent`'s bytes as flag $f251, bit 1 of `wNumKeyItems` through echo RAM. `tests/reference-trainer-sight.spec.ts` runs `_CheckTrainerBattle` for every `wNumKeyItems` 0-6: the original matches the port only with two, three or six key items; otherwise it reports a sighting and `LoadTrainer_continue` copies `ObjectEvent`'s own bytes into `wTempTrainer` as a trainer header (class $26 SWIMMERM, ID $16), one past SWIMMERM's last trainer. `ReadTrainerParty`'s name-skipping loop then runs off the end of its table into the next one with no padding between them, landing on a real, undamaged record: SwimmerF's ELAINE (a single level 21 Staryu), shown with SWIMMERM's sprite, cry and class text — confirmed by running `ReadTrainerParty` and `Battle_GetTrainerName` themselves. That battle is never reached cleanly: `StartBattleWithMapTrainerScript` first prints the decoded seen text ($46ee in the map scripts' bank, the middle of `SproutTower1FStatueText`). Its first byte, the space $7f, is dispatched as a text command; `TextCommands` has 23 entries, so the pointer comes from home code beyond the table and execution jumps to $0428, inside the VBlank handlers. What follows depends on the whole CPU and stack state, so the port keeps skipping the sighting rather than inventing an outcome. The pursuit stops here: the trainer check runs before `CheckTileEvent`'s warp, so the sighting is reachable after the cutscene's `warpcheck`, but no end-to-end emulator replay of the scene was made (the repository has no cartridge-save export). `CheckMobileAdapterStatusSpecial` is unused outside the Japanese release and always clears its result; `LoadUsedSpritesGFX`/`RefreshSprites` are VRAM sprite tile loading, presentation only. |
|
||||
| `game/world/roam-mons.ts` | `engine/overworld/wildmons.asm` (`InitRoamMons`, `CheckEncounterRoamMon`, `UpdateRoamMons`, `JumpRoamMons`), `data/wild/roammon_maps.asm` | Raikou and Entei roam from `InitRoamMons`; Suicune's slot is kept but not started. `UpdateRoamMons` runs on connections, door and pit warps and after one wild battle in sixteen; `JumpRoamMons` on Fly, Teleport and Continue. A roaming beast can take a grass encounter, and the battle writes its HP back or clears the slot. |
|
||||
| Bug-Catching Contest | `engine/events/bug_contest/*.asm`, `maps/Route35NationalParkGate.asm`, `maps/Route36NationalParkGate.asm`, `engine/menus/start_menu.asm` | Registration, Park Balls, contestant selection, the timer on the game clock, out of balls, QUIT, judging from `BugContestantPointers` with ties to the earlier entry, prizes and `CheckPartyFullAfterContest`. The contest state saves. The status box is a text page above the start menu. No sound. |
|
||||
| `game/scripts/slot-machine.ts`, `game/scripts/card-flip.ts` | `engine/games/slot_machine.asm`, `engine/games/card_flip.asm`, `engine/gfx/cgb_layouts.asm` | Both on a realtime tile screen (two frames per tick, buffered buttons). Slots: reel strips, spin rates and scrolling, bias tables with the lucky machine and seven streak, reel 3's Golem and Chansey replay with their sprites, payouts. Card Flip: deck, 12×11 board, cursor sprites and `ChooseCard_HandleJoypad`, paytable. Tilemaps are checked against the ROM. Golem's walk uses two ROM frames with flips; no sound. |
|
||||
| `game/scripts/cianwood.ts`, Routes 38–41, Olivine and Cianwood | `engine/events/shuckle.asm` (`GiveShuckle`, `ReturnShuckie`), `engine/pokemon/bills_pc_top.asm` (`CheckCurPartyMonFainted`), `engine/pokemon/caught_data.asm` (`SetGiftPartyMonCaughtData`), `engine/events/poke_seer.asm`, `engine/events/print_photo.asm`, `engine/printer/printer.asm` (`PrintPartymon`, `CheckPrinterStatus`), `engine/printer/printer_serial.asm`, `engine/events/specials.asm` (`PlaySlowCry`) | Shuckie is a level 15 party-only gift with MANIA's OT, ID 518, nickname, a Berry and `LANDMARK_GIFT` caught data; it sets `ENGINE_GOT_SHUCKIE_TODAY`. `ReturnShuckie` returns all five codes; `CheckCurPartyMonFainted` looks at the rest of the party, and Mania answers `SHUCKIE_FAINTED` with his happiness speech, as the original does. `PokeSeer` follows `ReadCaughtData`, including the missing `cp [hl]` that compares only the OT ID's high byte, `GetCaughtLocation`'s event and gift landmarks, `GetCaughtLevel`'s ??? that leaves `wSeerCaughtLevel` unchanged, and `SeerAdvice`'s inclusive thresholds. `wSeerCaughtLevel` lives in the "Miscellaneous WRAM 1" union: the port keeps the Seer's own last value until a load, while in the original other users of those bytes may change it. `PhotoStudio` has no printer to talk to: the handshake reads $ff, so `CheckPrinterStatus` reports `PRINTER_ERROR_2`, and `SendScreenToPrinter` retries until B sets `hPrinter`. The port shows "Printer Error 2" with "Press B to Cancel" on one page, then the no-photo text; the print page behind the box and its alternation with "CHECKING LINK" are not drawn. `PlaySlowCry` is audio. The story maps run on ported script commands; `tests/olivine-cianwood.test.ts` and six playthrough stages cover them. |
|
||||
| `game/scripts/slot-machine.ts`, `game/scripts/card-flip.ts` | `engine/games/slot_machine.asm`, `engine/games/card_flip.asm`, `engine/gfx/cgb_layouts.asm` | Both on a realtime tile screen (two frames per tick, buffered buttons). Slots: reel strips, spin rates and scrolling, bias tables with the lucky machine and seven streak, reel 3's Golem and Chansey replay with their sprites, payouts. Card Flip: deck, 12×11 board, cursor sprites and `ChooseCard_HandleJoypad`, paytable. Tilemaps, all 48 cursor positions, selected reel states and whole Golem/Chansey/Egg sequences are checked against the ROM (`tests/reference-game-corner.spec.ts`, which runs `CardFlip_UpdateCursorOAM`, `Slots_UpdateReelPositionAndOAM`/`.SpinReel` and the sprite-animation engine's own Golem/Chansey/Egg routines): the cursor, the reel symbols and the three creatures each anchor an OAM-relative pixel pair 8 (x) and 16 (y) beyond the screen's, as `cursorSprites` reads; `depixel`'s own two arguments end up swapped by `_InitSpriteAnimStruct` (confirmed against the reference emulator), so Golem/Chansey/Egg's X and Y bases were swapped to match, and Golem's own OAM table X-flips its right half. `PlaySpriteAnimations` is compared frame by frame over a three-Golem run and a Chansey round with two eggs (shadow OAM, `wSlotsDelay` and `hSCY`): frames last 8 ticks (the first draw plus 7 decrements); Golem's frames 3 and 4 flip the whole OAM set (offsets become −8−offset); a sprite removed by `DeinitializeSprite` is still drawn that tick, because `UpdateAnimFrame` runs after the animation function; the Golem's roll toggles `hSCY` by ±2, which the tile screen shows with the space tiles `_SlotMachine` filled the BG map with. The rows scrolled in use palette 0, since the attribute map beyond row 18 is not reproduced. No sound. |
|
||||
| Text interpreter | `macros/scripts/text.asm`, `constants/charmap.asm`, `home/text.asm` | Literal/far text, RAM names and string buffers from native state, numbers of dots, and sound or pause cues (consumed). `text_asm` and other RAM reads are refused. |
|
||||
|
||||
Supported event opcodes are explicit in the interpreter switch. Audio commands consume their operands, but sound sequencing is pending; those opcodes do not imply complete audiovisual behavior. `pause` holds the current text or waits the original number of overworld ticks.
|
||||
@@ -119,7 +120,13 @@ Later fields keep, by absence-migrated defaults: `memory`, the saved WRAM bytes
|
||||
|
||||
`home/trainers.asm` and `engine/events/trainer_scripts.asm`: talking to an `OBJECTTYPE_TRAINER` object runs `TalkToTrainerScript` with the object's header copied as `wTempTrainer`; `_CheckTrainerBattle` runs `SeenByTrainerScript` with `wSeenTrainerDistance`. The world checks sight in `PlayerEvents` order: before warps, coordinate events and encounters at the end of a step (a seen player's tile events are then skipped, as `DisableEvents` does), and on idle ticks before scene scripts and input. Walls and objects do not block sight. `TrainerWalkToPlayer`/`ComputePathToWalkToPlayer` build the raw `wMovementBuffer` bytes, which `applymovementlasttalked` decodes with the normal movement reader. `wRunningTrainerBattleScript` lives in the script run: zero at the start, set by `loadmem` after the battle so `endifjustbattled` ends the after-script. Trainer commands without a map trainer are refused. `MomTriesToBuySomething` after scripted battles does nothing while Mom's savings (not ported) are zero.
|
||||
|
||||
`engine/battle/core.asm` and `engine/battle/ai/items.asm`, `switch.asm`: when a trainer's Pokémon faints and others remain, `UpdateBattleStateAndExperienceAfterEnemyFaint` zeroes its party HP and runs `NewEnemyMonStatus`; `EnemyPartyMonEntrance` then sends out the Pokémon chosen by `FindMonInOTPartyToSwitchIntoBattle`. When every candidate is threatened it draws `BattleRandom & 7`, which tests pin. `AI_SwitchOrTryItem` runs before either side moves: `CheckAbleToSwitch`, the Often/Rarely/Sometimes thresholds, `AI_TrySwitch` and `AI_Switch` (withdraw text, HP and status back to the party). `CheckTypeMatchup` ignores the type its switch-AI callers load and uses `BATTLE_VARS_MOVE_TYPE`, so every such matchup uses the enemy's chosen move type; `FindEnemyMonsWithASuperEffectiveMove` never resets its result between Pokémon; `FindEnemyMonsWithAtLeastQuarterMaxHP` rotates HP bits instead of multiplying. All three bugs are kept. `UsedMoveText` records `wPlayerUsedMoves`. Refused: Pursuit on a switching Pokémon, Perish Song, Spikes, and trainer items. Side effects of the type scans on the move structs (overwritten types and animation) are not modelled; the next `ParseEnemyAction`/`ParsePlayerAction` reloads them.
|
||||
`engine/battle/core.asm` and `engine/battle/ai/items.asm`, `switch.asm`: when a trainer's Pokémon faints and others remain, `UpdateBattleStateAndExperienceAfterEnemyFaint` zeroes its party HP and runs `NewEnemyMonStatus`; `EnemyPartyMonEntrance` then sends out the Pokémon chosen by `FindMonInOTPartyToSwitchIntoBattle`. When every candidate is threatened it draws `BattleRandom & 7`, which tests pin. `AI_SwitchOrTryItem` runs before either side moves: `CheckAbleToSwitch`, the Often/Rarely/Sometimes thresholds, `AI_TrySwitch` and `AI_Switch` (withdraw text, HP and status back to the party). `CheckTypeMatchup` ignores the type its switch-AI callers load and uses `BATTLE_VARS_MOVE_TYPE`, so every such matchup uses the enemy's chosen move type; `FindEnemyMonsWithASuperEffectiveMove` never resets its result between Pokémon; `FindEnemyMonsWithAtLeastQuarterMaxHP` rotates HP bits instead of multiplying. All three bugs are kept. `UsedMoveText` records `wPlayerUsedMoves`. Refused: Pursuit on a switching Pokémon, Perish Song and Spikes. Side effects of the type scans on the move structs (overwritten types and animation) are not modelled; the next `ParseEnemyAction`/`ParsePlayerAction` reloads them.
|
||||
|
||||
`AI_SwitchOrTryItem`'s DontSwitch path runs `AI_TryItem`, including when Mean Look or Wrap blocks switching. Only the trainer's highest-level Pokémon (ties included) may use the two class items. All 13 `AI_Items` entries are ported: Full Restore, Max/Hyper/Super Potion, Potion, Full Heal, X Accuracy, Guard Spec, Dire Hit and the four X stats. The HP, status, first-turn and random gates preserve CONTEXT_USE, ALWAYS_USE and UNKNOWN_USE. A used item consumes the enemy's turn, updates the party, and clears the original move counters and substatuses.
|
||||
|
||||
The original bugs remain: Full Heal leaves confusion; status healing leaves Nightmare and burn/paralysis stat penalties; X stats see stale miss/failure flags; a declined second-slot item strands the first slot for the rest of the scan. That pointer can also read the base reward as a third item. The port models this extra slot, but not further drift into AI flag bytes; no real two-item trainer's base reward matches an item in this ROM. Battle Tower is still closed. `wEnemyItemState`'s redraw counter is omitted.
|
||||
|
||||
`tests/trainer-items.test.ts` covers every item and the switch-blocked paths. `tests/reference-trainer-items.spec.ts` compares `AI_TryItem` over 5,632 controlled states, including HP boundaries, stale miss/failure flags, item/party/HP/status/substatus/stat-level state and RNG draw counts. Four more reference cases cover the base-reward collision directly and through no-switch-style, Mean Look and Wrap dispatch. These isolated calls redirect Random and bypass `PrintText`, `PlaySFX`, `AnimateHPBar` and `UpdateEnemyHUD`; they do not verify presentation. `tests/trainer-battle.test.ts` exercises Clair's real Kingdra and class items through `Battle.run()`.
|
||||
|
||||
`BGEVENT_ITEM` runs `HiddenItemScript` with `wHiddenItemData`; `SetMemEvent` sets the item's flag.
|
||||
|
||||
|
||||
Reference in new issue
Block a user