Open Kanto: the S.S. Aqua, the Kanto gyms, the Magnet Train, Mt. Silver and Red (Closes #33)
Checks / web (push) Failing after 18s
Private ROM checks / private-rom (push) Failing after 25m40s

All 146 Kanto maps, Route 22, Route 28 and Silver Cave are open. The
Magnet Train, Snorlax and the Poké Flute channel, the Diploma, the Trainer
House, Bill's grandpa, Oak's Pokédex rating in Pallet and Goldenrod's
postgame move tutor are ported; big objects cover 2x2 tiles. The Magnet
Train and the Diploma are compared with the cartridge frame by frame.

The playthrough continues from the Hall of Fame through the S.S. Aqua,
the eight Kanto badges, Oak opening Mt. Silver and Red's defeat and
credits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kawaandClaude Opus 5.5 committed 2026-10-17 15:53:57 +02:00
1 parent f05fd8ebee
commit d3be732118
22 files changed
+1877 -31

No files matched your search

+4 -2
View File
@@ -51,7 +51,7 @@ These paths refer to the pinned [PRET source](https://github.com/pret/pokecrysta
| `game/scripts/screen.ts`, `game/rendering/tile-screen.ts`, `game/rom/font.ts` | `home/tilemap.asm`, `engine/gfx/load_font.asm` (`_LoadStandardFont`, `LoadFrame`) | Tile screens: a 20x18 tilemap with background palettes and sprites over it, built by a minigame or special from ROM graphics and answered with buttons. Blinking sprites follow bit 4 of the frame counter. Overlay screens (a menu box) leave the map visible around them. The standard font and the option's textbox frame load as 2bpp tiles. |
| `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. |
| `game/scripts/goldenrod.ts`, `game/world/day-timers.ts` | `engine/events/name_rater.asm`, `haircut.asm`, `lucky_number.asm`, `move_tutor.asm` (`MoveTutor`, `CheckCanLearnMoveTutorMove`), `engine/items/tmhm.asm` (`ChooseMonToLearnTMHM`), `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. The move tutor's three moves come from TMHMMoves after the HMs; its party menu marks ABLE or NOT ABLE and returns to itself until a Pokémon learns the move or B. |
| 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. |
@@ -62,6 +62,8 @@ These paths refer to the pinned [PRET source](https://github.com/pret/pokecrysta
| `game/party/day-care.ts`, `game/scripts/day-care.ts`, Day-Care and Route 34 | `engine/events/daycare.asm`, `engine/pokemon/breeding.asm`, `engine/pokemon/move_mon.asm`, `engine/pokemon/breedmon_level_growth.asm`, `DayCareStep`, `_GiveOddEgg` | Deposit refusals, level growth/price, move learning on retrieval, compatibility and egg moves/DVs/species, the egg countdown/chance, collection and Odd Egg tables run from the user's ROM. Retrieval keeps the original experience truncation. The save holds both parents and the prepared egg, active/compatible bits, countdown and mother index, with flag consistency validation and absence migration. Day-Care map objects resolve their deposited species' ROM icons. Six controlled-RNG cartridge comparisons cover routine state and byte consumption; native tests cover map scripts and save/reload. Keeper UI/audio timing and fence OAM/palettes are not reference-validated. Hatching is the row above. |
| `game/party/hall-of-fame.ts`, `game/scripts/hall-of-fame.ts`, `game/scripts/pc.ts` | `engine/events/halloffame.asm` (`GetHallOfFameParty`, `_HallOfFamePC`, `LoadHOFTeam`, `DisplayHOFMon`), `engine/menus/save.asm` (`AddHallOfFameEntry`), `engine/events/pokecenter_pc.asm` (`.ChooseWhichPCListToUse`, `HallOfFamePC`) | Up to 30 recorded teams, newest first, with the capped win count; saved and migrated by absence, separate from the League flag. Nothing records a team until the League is ported. The PC lists HALL OF FAME from the count; pages browse with A, Start and B as the original does, and keep its unreachable "HOF Master!" title. A cartridge comparison of 255 teams checks the tilemap, attributes, graphics and palette; the frontpic animation (`HOF_AnimateFrontpic`) is not presented. |
| `game/scripts/league.ts`, the League, the Hall of Fame and the credits | `engine/events/halloffame.asm` (`HallOfFame`, `RedCredits`, `AnimateHallOfFame`, `AnimateHOFMonEntrance`, `HOF_SlideBackpic`, `HOF_SlideFrontpic`, `HOF_AnimatePlayerPic`), `engine/movie/credits.asm` (`Credits`, `ParseCredits`, `Credits_LoadBorderGFX`, `Credits_LYOverride`, `ConstructCreditsTilemap`, `GetCreditsPalette`), `engine/movie/init_hof_credits.asm`, `engine/events/prof_oaks_pc.asm` (`ProfOaksPCBoot`, `ProfOaksPCRating`, `Rate`, `FindOakRating`), `engine/gfx/player_gfx.asm` (`GetPlayerBackpic`, `HOF_LoadTrainerFrontpic`), `home/vblank.asm` (`VBlank_Credits`), `engine/overworld/scripting.asm` (`Script_halloffame`, `Script_credits`, `Script_wait`), `home/text.asm` (`PlaceEnemysName`) | The ceremony records the team and win, pauses the play time and shows each Pokémon's slides and page, the player's card and Oak's rating; the credits run frame by frame (text by thirds, banner tiles by halves, palette VBlanks, border scroll), with each command group's measured spill frames; after the Hall of Fame the world continues at New Bark as Continue would, after Red at Mt. Silver. Checked against the cartridge by `tests/reference-credits.spec.ts` and `tests/reference-hall-of-fame-ceremony.spec.ts`. |
| `game/scripts/kanto.ts` | `engine/events/specials.asm` (`SnorlaxAwake`, `TrainerHouse`), `engine/events/haircut.asm` (`BillsGrandfather`), `engine/events/diploma.asm` (`PlaceDiplomaOnScreen`, `Diploma`, `PrintDiploma`), `engine/gfx/cgb_layouts.asm` (`_CGB_Diploma`) | Snorlax wakes to the Poké Flute channel (wMapMusic) from `.ProximityCoords`; the Trainer House always fights CAL, as without a Mystery Gift partner; Bill's grandpa reads the party Pokémon shown. The Diploma is drawn from DiplomaGFX and its tilemap with the player's name in `PalPacket_Diploma`'s palette; printing it finds no printer. Checked by `tests/reference-kanto.spec.ts`. |
| `game/scripts/magnet-train.ts` | `engine/events/magnet_train.asm` (`MagnetTrain`, `MagnetTrain_Jumptable`, `MagnetTrain_UpdateLYOverrides`, `MagnetTrain_LoadGFX_PlayMusic`, `DrawMagnetTrain`, `MagnetTrainBGTiles`, `MagnetTrainTilemap`), `engine/overworld/scripting.asm` (`Script_newloadmap`'s `MAPSETUP_TRAIN`) | The ride frame by frame both ways: the player's sprite (Chris or Kris) running its animation with the last frame flipped, the bushes' and train's row scrolls, the station's palettes with an extra palette frame and eight frames of teardown. Checked against the cartridge by `tests/reference-magnet-train.spec.ts`. |
| `game/scripts/tin-tower.ts`, Tin Tower and Whirl Islands | `engine/pokemon/search_owned.asm` (`BeastsCheck`, `MonCheck`, `CheckOwnMonAnywhere`, `CheckOwnMon`) | `CheckOwnMonAnywhere` returns nothing with an empty party, then reads the party structs' species (an Egg counts as its species), `sBox` and the thirteen other `sBoxN` copies, matching the player's ID and the OT's first five letters (`NAME_LENGTH_JAPANESE - 2` compares, stopping early at a shared terminator); the Day-Care is not searched, preserving the original's explicitly documented bug even now that deposits run. The original searches the current `sBox` and skips its numbered `sBoxN` copy; the port searches each logical box once. The sages, Suicune, Ho-Oh and Lugia run on ported primitives (`BATTLETYPE_SUICUNE`, `BATTLETYPE_FORCEITEM`). `tests/tin-tower.test.ts` and two playthrough stages cover them. |
| `game/scripts/battle-tower.ts`, the Battle Tower | `engine/events/battle_tower/battle_tower.asm` (`BattleTowerAction`, `RunBattleTowerTrainer`, `ReadBTTrainerParty`, `CopyBTTrainer_FromBT_OT_TowBT_OTTemp`, `BattleTower_GiveReward`, `BattleTower_RandomlyChooseReward`, `LoadOpponentTrainerAndPokemonWithOTSprite`, `CheckForBattleTowerRules`), `engine/events/battle_tower/load_trainer.asm`, `engine/events/battle_tower/rules.asm`, `engine/events/battle_tower/trainer_text.asm`, `mobile/mobile_46.asm` (`_BattleTowerRoomMenu`, `BattleTower_LevelCheck`, `BattleTower_UbersCheck`), `mobile/mobile_5f.asm` (`Menu_ChallengeExplanationCancel`), `engine/link/link.asm` (`TryQuickSave`), `engine/battle/core.asm`, `engine/battle/effect_commands.asm`, `engine/battle/misc.asm` (`DoBadgeTypeBoosts`) | The Battle Tower's SRAM and the work RAM its scripts share are saved as `battleTower`. The trainer sampler keeps its index as the running byte after a repeated trainer; the Pokémon sampler starts again from `hRandomAdd`; both fold `Random`'s `hRandomAdd` into a byte below a power-of-two mask, which the port draws from its random source. `BattleTowerText` reads the class's gender (1.1 behaviour) and picks the greeting from `hRandomAdd` (0-24 male, 0-14 female), keeping it for the end texts. Each battle start counts the battle and marks the challenge in progress, as `CopyBTTrainer_FromBT_OT_TowBT_OTTemp` does; `LoadPokemonData` then returns the party, Pokédex and Unown Dex to the quick save's. The level menu shows the levels as a list; its CANCEL/B question and the mobile code's YES/NO flow are modelled from their outcomes (YES leaves with $a, NO picks a level again). `TryQuickSave` asks the overwrite question for the player's own save and shows the saving texts; the port keeps the place and Pokémon data `LoadPokemonData` and `Reset` return to. The native game saves itself, so the original's "left without saving" state (a reset during a challenge) cannot arise: a reload replays the room's script. `Reset` returns the player to the quick save's place with the 1F scene set to check the challenge; the original's rollback of money gained by Pay Day is not reproduced. In battle, `wInBattleTowerBattle` turns off obedience, `DoBadgeTypeBoosts`, `BadgeStatBoosts`, experience, the pack (BattleMenu_Pack says items can't be used), AI items and prize money, uses Falkner's AI attributes, keeps Sleep to `%011` and skips the AI's random failure for Sleep, Poison, stat-lowering and Paralyze moves; a loss shows BattleTowerText and does not white out. `DoBadgeTypeBoosts` was added to every battle (it was missing). BATTLETOWERACTION_LEVEL_CHECK and _UBERS_CHECK and the mobile actions are reached only from unreferenced scripts and stay refused. `tests/battle-tower.test.ts`, `tests/battle-tower-engine.test.ts` and `tests/reference-badges.spec.ts` 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. |
@@ -415,7 +417,7 @@ message placement and palettes against the cartridge. Start prints; A/B
close the letter. `scripts/printer.ts` shares the absent-printer error with
Bill's PC, Pokédex PRNT and Photo Studio; B cancels and A leaves it open.
Print previews, printer music and alternating link-check status are omitted;
Unown stamps and the Kanto diploma remain unreachable. A scripted
Unown stamps remain unreachable; the Diploma prints to the same absent printer. A scripted
`givepoke` with MAIL (`GivePokeMail`) is refused.
`Pack_InterpretJoypad`'s Select runs `SwitchItemsInBag` on the bag