- Day-Care and breeding: deposits, withdrawal pricing and growth, compatibility, egg species, DVs and moves, DayCareStep, egg collection and the Odd Egg, compared with the cartridge's routines. - CountStep: walking happiness, DoEggStep, DayCareStep and DoBikeStep in the original's order; HatchEggs with the ROM's hatch animation, compared frame by frame with the original. - MAIL: all ten stationery types and the Portrait MAIL picture, compared with the cartridge's tilemaps, graphics and palettes. - Printing: Bill's PC boxes, Pokedex PRNT, MAIL and the Photo Studio take the original's absent-printer path. - Hall of Fame records (saved apart from the League flag, ready for the League to call) and the Hall of Fame PC, compared with the cartridge's _HallOfFamePC on 255 pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
723 lines
130 KiB
Markdown
723 lines
130 KiB
Markdown
# Full native port plan
|
||
|
||
## Direction
|
||
|
||
Translate Crystal's Game Boy assembly systems into a native TypeScript game. The browser runs the translated engine; the user's original ROM supplies data and assets. The emulator is a separate development reference for checking behavior, never a dependency of the native release.
|
||
|
||
PRET's [disassembly](https://github.com/pret/pokecrystal) is pinned at `7a7881d0d62e0ddbd82dcf10e7116807487ac651`. Its `crystal11` build with RGBDS 1.0.3 was verified byte-for-byte against the supplied English US/Europe 1.1 ROM. Required SHA-1: `f2f52230b536214ef7c9924f483392993e226cfb`. Other revisions require separate verified profiles.
|
||
|
||
The emulator-backed prototype is now at `/reference.html`. The default application is the native port. The previous plan to progressively modify an emulated ROM has been superseded.
|
||
|
||
## Status overview
|
||
|
||
| Area | Status |
|
||
| ---------------------------------------------------------------------------- | --------------------------------------- |
|
||
| Exploration foundation, rendering, saves and shell | Done |
|
||
| Opening story: New Bark Town to Mr. Pokémon, the rival and the return to Elm | Done |
|
||
| Route 29, Cherrygrove City, Route 30 and their buildings | Done |
|
||
| Route 31, Violet City, Sprout Tower and Falkner's gym | Done (Dark Cave: Violet entrance only) |
|
||
| Goldenrod City, Whitney and local shops and gifts | Done |
|
||
| Routes 35–37, the National Park, Ecruteak and Morty | Done (Tin Tower closed) |
|
||
| Routes 38–41, Olivine, Cianwood, Chuck and Jasmine | Done (port, Battle Tower closed) |
|
||
| Route 42 to Mahogany, the Lake of Rage, the Rocket Hideout and Pryce | Done |
|
||
| The Radio Tower takeover, Ice Path, Blackthorn, Clair and Dragon's Den | Done |
|
||
| Tin Tower, Suicune, Ho-Oh, Whirl Islands and Lugia | Done |
|
||
| The Battle Tower | Done |
|
||
| Wild and trainer battles, capture and the catching tutorial | Done for the areas that are open |
|
||
| Music and sound effects (Web Audio) | Done (no move animation sounds) |
|
||
| The phone's trainers, random calls, rematches and the radio | Done |
|
||
| 1. Complete the overworld foundation | Done |
|
||
| 2. Opening story and first battle slice | Mostly done |
|
||
| 3. Complete Crystal | In progress (Johto done) |
|
||
| 4. Modern mobile presentation and delivery | First increment done |
|
||
| 5. World map and Pokédex | Early (atlas, Pokédex list and entries) |
|
||
| 6. Internet Link and friends | Not started |
|
||
| 7. Optional Pokéwalker-style egg assistance | Not started |
|
||
|
||
The numbered sections below keep their full scope. Each one opens with a checklist: `[x]` is done and tested, `[ ]` is remaining. The "Implemented" sections record what each completed increment delivered.
|
||
|
||
## Implemented: first native exploration milestone
|
||
|
||
- Bounds-checked ROM access, banked pointers and native LZ decompression.
|
||
- On-demand extraction of six maps: New Bark Town, both floors of the player's home, Elm's lab, the neighbor's house and Elm's house.
|
||
- Metatiles, collision quadrants, palettes, roofs, sprites and facing tables read directly from the user's ROM.
|
||
- Native canvas rendering, camera scrolling, walking, solid-tile/NPC collision, doors and stairs within the supported area.
|
||
- A bounded interpreter for a subset of the game's own event bytecode, plus text decoding. This does not execute GB instructions.
|
||
- Original initial event flags, readable signs, bedroom radio, bookshelves and supported conversations. Unknown interactions abort without committing partial changes.
|
||
- Native DOM dialogue, name/avatar choice, touch/keyboard controls and pause/resume. (The name and character are now chosen in the introduction; see below.)
|
||
- A minimal Crystal-style shell with Game, Savegames, Map, Pokédex, Link and Friends tabs across the top. The game uses the available native canvas, and leaving Game releases input and pauses play.
|
||
- Existing save, backup export and backup import actions in Savegames. Map shares a live local minimap with the game and shows the current supported area, player position and coordinates. Link and Friends are clearly marked as planned rather than populated with placeholder data. (The Pokédex tab now shows the game's records; see the Pokédex section.)
|
||
- Native JSON state inside the backup envelope, separate IndexedDB storage and a separate tab lock. No emulator snapshot or cartridge save migration is implied.
|
||
- Synthetic tests, local-ROM tests, browser checks, and comparisons against the original decompression routine running in the reference emulator.
|
||
- Native production output excludes the emulator and original game assets.
|
||
|
||
## Implemented: opening scenes
|
||
|
||
- Resumable event interpreter: text, yes/no and weekday choices, scripted movement, `follow`, emotes, pauses, scene variables, phone contacts, string buffers and translated specials. Unknown commands still abort without committing.
|
||
- Movement data decoder for turns, steps, slides, jumps, sleeps, facing locks, visibility and emotes, animated at the original 30 Hz overworld rate. Player walking now takes the original 8 ticks per tile, not half that.
|
||
- Coordinate events, scene scripts with `sdefer`, and NEWMAP/TILES/OBJECTS map callbacks, including the bedroom's own initialization callback. Objects spawn at map load as in the original.
|
||
- Mom's Pokégear, weekday and DST scene; the teacher at the town's west edge; the rival at the lab window; and Elm's introduction with the lab's exit guard, all from the ROM's scripts.
|
||
- Map headers for all 26 groups, ROM location names and connected maps drawn beyond the edges. Entering an unported connection is refused with its location name.
|
||
- Version 2 native saves with scenes, phone, game-clock settings and moved objects; version 1 saves migrate.
|
||
|
||
## Implemented: starter and first items
|
||
|
||
- ROM decoders for species names, base data, learnsets, moves, items, pockets, growth rates and front pictures.
|
||
- Native party generation for script gifts, following `GeneratePartyMonStats`: random DVs, level-up moves, PP, exp, stats, happiness and caught data. The Pokédex records the species as seen and caught.
|
||
- Bag pockets with the original capacities and stacking rules. `verbosegiveitem` runs the ROM's own `GiveItemScript`.
|
||
- Elm's Poké Balls, with a picture box, the nickname prompt with a native naming field, Elm's directions and phone number, and the aide's Potion.
|
||
- Version 3 saves with the player ID, party, bag and Pokédex; restores recompute stats and check item pockets.
|
||
|
||
## Implemented: ledges and warp movement
|
||
|
||
- Walkable ledge tops, directional two-tile jumps over 16 ticks, and jump shadows.
|
||
- Exit mats wait for input in their exit direction. Arriving on a mat preserves the actual position; doors, stairs and caves animate the downward exit step.
|
||
- Mid-step saves replay from the source tile, including jumps and map connections, so reload cannot bypass landing scenes or warps.
|
||
- Synthetic movement tests run without a ROM. Local-ROM tests exercise five Route 29 ledge types; browser checks cover stairs, opening scenes and an exit-mat save/reload.
|
||
|
||
## Done: Route 29 and Cherrygrove City
|
||
|
||
- Route 29, its Route 46 gate, Cherrygrove City and its five buildings are enabled. Route 46 remains closed.
|
||
- Item balls run the ROM's `FindItemInBallScript`. Fruit trees run `FruitTreeScript`, with picked trees saved and regrown by a native `CheckDailyResetTimer` that clears the daily engine flags once per game day.
|
||
- The Pokémon Center nurse runs `PokecenterNurseScript`: `HealParty` (with `ComputeMaxPP`'s PP Up clamp), `CheckPokerus` and the phone-call checks. Entering a Center from a town or route records the whiteout spawn, as `EnterMapWarp` does.
|
||
- Standard Poké Marts run `pokemart` natively from `StandardMart`, `BuyMenu` and `SellMenu`: stock from the ROM's `Marts` table, `ItemAttributes` prices, half-price selling and the original texts, with keyboard, D-pad and pointer menus. Special mart types remain refused.
|
||
- `VAR_BOXSPACE` reads an empty box: nothing can be caught or deposited yet.
|
||
- The battle engine gained `recoil` (Struggle). A local stress run of 360 Route 29 battles across all starters and times of day finishes without refusals.
|
||
- Save format 3 gains `fruitTrees`, `dailyReset` and `lastSpawnMap`; older backups migrate by absence.
|
||
- NPCs roam as `HandleNPCStep` does: random walks (Y, X, XY) and slow/fast spins from `SpriteMovementData`, `CanObjectMoveInDirection` (side walls, land, objects' current and previous tiles, radius stored plus one, screen edge), random sleeps, freezing during text and `applymovement`, and deletion/respawn at home beyond the object window. Wandering positions are not saved; objects start from home on reload.
|
||
- The catching tutorial (`catchtutorial`) runs since the capture section below.
|
||
|
||
## Implemented: first scripted trainer battle slice
|
||
|
||
- ROM decoder for 542 trainer records across 67 classes, all four party formats, class names, DVs, rewards and map trainer headers. SRAM-dependent Mystery Gift parties remain refused.
|
||
- Native move scoring for Basic, Setup, Types, Offensive, Opportunist, Aggressive, Cautious, Status and Risky, with deterministic tie selection and the original pre-variation damage estimate. Smart runs only for effects without a Smart handler (see the Mr. Pokémon section).
|
||
- Scripted single-Pokémon trainer battles support the challenge, sending out, RUN refusal, experience, win/loss text and prize money, including Amulet Coin. Script results, party changes and rewards commit together; interrupted saves replay from committed state.
|
||
- Local-ROM tests and Chromium/WebKit tests exercise Youngster Joey through a test-only script. No new maps or trainer encounters are enabled in normal play.
|
||
- Next: Smart AI handlers, trainer items and player switching.
|
||
|
||
## Implemented: Route 30 trainers
|
||
|
||
- Map trainers run the ROM's `TalkToTrainerScript` and `SeenByTrainerScript`. `_CheckTrainerBattle` runs before tile events after each step and on every idle tick: the first unbeaten trainer facing the player in line within its sight range spots them. The native `TrainerWalkToPlayer` path is played from `wMovementBuffer`, and `writeobjectxy` keeps the trainer where it stopped (saved with the map's moved objects).
|
||
- New script commands: `loadtemptrainer`, `trainertext`, `trainerflagaction`, `scripttalkafter`, `endifjustbattled`, `checkjustbattled`, `writeobjectxy`, `encountermusic` (silent) and `loadmem` for `wRunningTrainerBattleScript`.
|
||
- Trainers with several Pokémon: `HandleEnemyMonFaint` sends out the next one through `EnemySwitch_SetMode`, `FindMonInOTPartyToSwitchIntoBattle` (including its `BattleRandom` fallback) and `NewEnemyMonStatus`. `AI_SwitchOrTryItem` translates `CheckAbleToSwitch` with its original bugs (every matchup uses the enemy's move type; the quarter-HP test rotates bits) and `AI_Switch`. The player's used moves are recorded for it.
|
||
- Hidden items (`BGEVENT_ITEM`) run `HiddenItemScript`.
|
||
- Route 30 and its Berry house are enabled, and Mr. Pokémon's house since the section below. Route 31 stays closed; Youngster Joey (after the Egg) needs phone registration and stays hidden until then, as in the original.
|
||
- A refused trainer script does not restart until the player moves, so a rollback cannot trap the player in a trainer's sight.
|
||
- A local stress run of 1,080 Route 30 battles (three starters, four levels, three times of day, both trainers and wild encounters) found one refusal: Chikorita's Reflect at level 12 (`screen`), now addressed by the screen increment below.
|
||
|
||
## Implemented: Reflect and Light Screen
|
||
|
||
- Both moves execute their ROM effect scripts, set independent five-turn counters and fail without refreshing an active screen. The setup turn counts toward expiration.
|
||
- Screens persist across enemy send-outs and expire in the original non-Link order, with ROM messages. Physical/special defenses and critical-hit stage rules use the existing damage path.
|
||
- Local-ROM regressions cover both sides, repeated use and PP, concurrent durations, send-out persistence, expiration order and damage stats. These checks are not an emulator comparison of the screen routines.
|
||
|
||
## Implemented: Mr. Pokémon's house and the return to Elm
|
||
|
||
- Mr. Pokémon's house is enabled. Its ROM scene gives the Mystery Egg and Pokédex, heals the party (`FadeOutToBlack`, `ReloadSpritesNoPalettes` and `FadeInFromBlack` are presentation-only), sets the Cherrygrove whiteout spawn with `blackoutmod` and queues Elm's call with `specialphonecall`. A refusal late in the scene rolls back the rewards, healing and Oak's exit.
|
||
- Special calls: after trainer, warp and coordinate events, a queued call runs if its `SpecialPhoneCallList` condition allows (Elm's only outdoors). It runs the ROM's `CheckSpecialPhoneCall.script` and `Script_ReceivePhoneCall`, with `memcall` into the entry's caller script and `HangUp`'s timed "Click!". Ringing tones and the caller name box are not presented yet. Queued calls are saved.
|
||
- Cherrygrove's rival scene runs both triggers and all three `RIVAL1_1` parties as a `BATTLETYPE_CANLOSE` battle. `reloadmap` returns to the script, and a loss continues the scene without whiteout or money loss. The name shows as "RIVAL ?", as in the original. Smart AI accepts the rival's Normal Hit, Attack Down and Defense Down moves, because `AI_Smart_EffectHandlers` has no entries for them. Other Smart effects are still refused before any random draw, including for reserve Pokémon.
|
||
- Elm's lab: the officer's `NameRival` opens the naming field with the ROM title "RIVAL'S NAME?". A blank name takes the ROM default, as `InitName` does. The rival name is saved and used by `<RIVAL>` text. Handing over the Egg and the aide's five Poké Balls run from the ROM.
|
||
- Save format 3 gained `rivalName` and `specialPhoneCall` (migrated by absence to "???" and 0).
|
||
- Next: the catching tutorial on Route 29 (`catchtutorial`) with capture; then player switching and items in battle.
|
||
|
||
## Implemented: capture and the catching tutorial
|
||
|
||
- Battle PACK opens the Balls pocket. Throwing a ball runs `PokeBallEffect`: the catch rate with the Great/Ultra multipliers and the original HP arithmetic, the sleep/freeze bonus and `HELD_CATCH_CHANCE`, then one `Random` draw. Master Balls always catch. The original bugs are kept: burn, poison and paralysis add nothing, and a maximum HP above 341 overflows the formula. A maximum HP of 342 would divide by zero and is refused.
|
||
- `GetPokeBallWobble` runs with `WobbleProbabilities` for each shake and picks the break-free message. Every throw uses the ball and, if the Pokémon breaks free, the turn. In trainer battles the ball is blocked, but the ball and turn are still used.
|
||
- A caught Pokémon is reloaded as `LoadEnemyMon` does, which redraws the wild item roll (the item itself is kept). It keeps its current HP, status, item and DVs, and gets back the moves and full PP it appeared with. It joins the party with `SetCaughtData`, is registered as caught (with the new-data message once the Pokédex is owned) and can be nicknamed; a blank name keeps the species name. The Pokédex page itself is not presented yet.
|
||
- Route 29's `Route29Tutorial1/2` and `CatchingTutorialDudeScript` run from the ROM. `catchtutorial` runs the battle as "DUDE": no Pokémon is sent out, the Dude's own Poké Ball always catches, and the Rattata is only seen, not kept. Messages advance on their own, as with `CatchTutorial`'s auto input.
|
||
- A catch with six Pokémon in the party goes to the current box (see the PC section below). Other balls (Kurt's balls, Park and Safari) and non-ball items in battle are not usable yet. (Kurt's balls followed; see below.)
|
||
- A local stress run of 2,160 wild battles (three starters, ten early species, six levels) caught 2,047 Pokémon without refusals.
|
||
- Next: the PC and boxes, player switching and other items in battle, then party and bag menus.
|
||
|
||
## Implemented: the PC and boxes
|
||
|
||
- `TileCollisionStdScripts` is read from the ROM, so facing a `COLL_PC` tile (in any direction, as in the original) runs `PCScript`. Bookshelves, TVs and similar tiles no longer need the player to face up.
|
||
- `PokemonCenterPC` checks for a Pokémon, then lists the PCs from `PokemonCenterPC.WhichPC`: Bill's PC and the player's PC, plus Oak's PC once the Pokédex is owned. The Hall of Fame PC joins them once a team is recorded (see the Hall of Fame PC increment below).
|
||
- Bill's PC: deposit (`BillsPC_CheckMail_PreventBlackout` keeps the last Pokémon and the last one that has not fainted; `RestorePPOfDepositedPokemon`), withdraw (`CalcLevel`, then `CalcMonStats` with stat experience, full HP and no status), release with confirmation ("Bye," with the species name), and change box: switch with the ROM's confirmation, name (a blank name keeps the old one) and print (the empty-box message, or the absent printer's error since the printer increment below). Menus and messages come from the ROM; lists are presented natively with each Pokémon's level or each box's count.
|
||
- The player's PC withdraws, deposits (from every pocket; untossable items move one at a time) and tosses items (up to 50 stacks, `RemoveItemFromPocket` slot rules), and holds MAIL since the remaining field items section. The house PC adds DECORATION, which closes the PC after a change.
|
||
- Oak's PC rates the Pokédex from `OakRatings`.
|
||
- Capture with a full party runs `SendMonIntoBox`: the Pokémon goes first in the current box, with restored PP and `SetBoxMonCaughtData`, can be nicknamed and shows `BallSentToPCText`. If the box is already full the ball is not used (`Ball_BoxIsFullMessage`). A catch that fills the box sets `BATTLERESULT_BOX_FULL`; `reloadmapafterbattle` then queues Bill's call (`Script_SpecialBillCall`) as a memory script that runs at the next `PlayerEvents` and is not saved, as in the original.
|
||
- `VAR_BOXSPACE` reads the current box.
|
||
- Save format 3 gained `storage` (boxes, the current box and box names; a null name is the ROM default) and `pcItems`, migrated by absence to empty storage.
|
||
- Not ported: printing (MOVE PKMN W/O MAIL and the stats screen are ported since), a `givepoke` with a full party (refused; no open map reaches it) and the tutorial with a full party and box (the original repeats the unusable ball). Changing boxes does not show the save messages; the native session saves continuously.
|
||
- Next: the introduction's naming and clock screens, player switching and other items in battle, then party and bag menus.
|
||
|
||
## Implemented: the introduction's naming and clock screens
|
||
|
||
- A new game runs `NewGame`'s `InitGender` and `OakSpeech` natively before the bedroom: "Boy"/"Girl" (B disabled), `InitClock`, Oak's speech with Wooper, then `NamePlayer`. Texts, menu items, the preset names, the naming title and the time strings come from the ROM (`INTRO` table in the profile, verified by `check:reference`).
|
||
- `InitClock`: the hour and minute steppers wrap as `SetHour` and `SetMinutes` do, each confirmed with YES/NO; NO asks again. Labels follow `PrintHour` ("DAY 5 o'clock"), and `OakText_ResponseToSetTime` keeps its `DAY_HOUR + 1` comparison, so 10 AM still "overslept".
|
||
- The chosen time is stored as a minute offset from the device clock (the shortest one, within ±12 hours). Time of day, the weekday, NPC hours and the daily reset all read this game clock. Mom's `SetDayOfWeek` offset is taken from the game clock's day.
|
||
- `NamePlayer`: NEW NAME opens the naming field; a blank name takes CHRIS or KRIS, as `InitName` does. The other items are the gender's preset names.
|
||
- Oak's picture decodes `TrainerPicPointers` through `FixPicBank`; the player's is the uncompressed `ChrisPic`/`KrisPic`. Both use `TrainerPalettes` (Chris class 0, Kris class 1), as `DrawIntroPlayerPic` sets them.
|
||
- The intro covers the world and its minimap until it ends. It runs only when no session is saved. A save made during it keeps `intro: true`, and loading that save starts it again. The Game tab's name and character fields are gone.
|
||
- Save format 3 gained `intro` and `clock.minuteOffset`, migrated by absence to a finished intro on the device clock.
|
||
- Intentional change: the steppers start at the device's current time rather than 10:00, so confirming keeps the device clock.
|
||
- Not presented: the palette fades, Wooper's cry, the picture slide during name selection, `ShrinkPlayer` and the Route 30 music (no audio yet).
|
||
- Next: MOVE PKMN W/O MAIL with the party menu.
|
||
|
||
## Implemented: evolution data, reference vectors and the opening playthrough
|
||
|
||
- `PokemonData.evolutions` decodes the evolution list before each learnset: level, item, trade (with or without a held item), happiness by time of day, and Tyrogue's four-byte stat records. All 251 lists match the pinned PRET source. Evolving itself is not ported yet.
|
||
- The reference suite also runs the original `CalcMonStats` (300 cases, with and without stat experience, including levels above 100 that reach the 999 cap) and `CalcExpAtLevel` (every growth rate at levels 1–100).
|
||
- `tests/playthrough.test.ts` plays a new game from the introduction to the catching tutorial with a seeded RNG: Mom, Elm, the starter and Potion, Route 29, Cherrygrove, Route 30, Mr. Pokémon, Elm's call, the rival battle, the officer, the Egg, the aide's Poké Balls and the Dude's demonstration. Wild battles are fought, or fled below half HP. Each stage ends with a JSON save loaded into a fresh world. It uses no fixtures or hand-set flags, but drives `GameWorld` directly rather than the browser UI.
|
||
|
||
## Implemented: switching, battle items and the stats screen
|
||
|
||
- PKMN opens the party menu; SWITCH sends out the chosen Pokémon and STATS opens the stats screen. The texts, the trapped and fainted checks, and the enemy's turn after the switch follow the original. Both Pokémon share the experience.
|
||
- After a faint, wild battles ask "Use next POKéMON?" (NO tries to run) and the party menu cannot be cancelled. Trainers' next Pokémon come with Shift mode's offer to switch; a double faint sends out both sides.
|
||
- PACK's Items pocket uses HP items (Potion, Berry and the rest of `HealingHPAmounts`) and status healers (Antidote, Parlyz Heal, Awakening, PSNCUREBERRY and the rest of `StatusHealingActions`) on any party Pokémon. Items with no battle use get Oak's warning.
|
||
- Every other battle item effect runs too: Full Restore, revives, the bitter herbs, Bitter Berry, Ethers and Elixers, X items, X Accuracy, Guard Spec, Dire Hit and Poké Doll. Happiness changes come from the ROM's `HappinessChanges`.
|
||
- `hBattleTurn` at the battle menu is modelled, so `CalcPlayerStats` after a status heal penalises the side the original does.
|
||
- The stats screen shows its three pages from BattleMonMenu and Bill's PC.
|
||
- The reference suite compares the send-out and withdraw texts, `RestoreHealth`, `RestorePP`, `ChangeHappiness` and `CalcPlayerStats` with the original routines.
|
||
|
||
## Implemented: the start menu, party menu and pack
|
||
|
||
- Start opens `StartMenu` with the items `SetUpMenuItems` lists for the player's progress (POKéDEX, POKéMON, PACK, POKéGEAR, the player's name, SAVE, OPTION, EXIT) and their ROM descriptions. POKéDEX opens the Pokédex (see the Pokédex section below; POKéGEAR, the trainer card and OPTION run since the sections below); SAVE points to the automatic session saves.
|
||
- POKéMON: the party menu with the ROM's prompts and `GetMonSubmenuItems`: field moves the Pokémon knows (from `MonMenuOptions`), STATS, SWITCH, MOVE, ITEM or MAIL, and CANCEL when there is room. STATS opens the stats screen; SWITCH asks "Move to where?" and swaps the two Pokémon; MOVE (`MoveScreenLoop`) swaps two moves with their PP; ITEM gives an item from the pack (key items and untossable items "can't be held"), swaps it with the held item or takes it back, with the pack-full checks of `TryGiveItemToPartymon` and `TakePartyItem`.
|
||
- PACK: the Items, Balls, Key and TM pockets (left and right change pockets; there are no TMs or HMs yet), and each item's submenu chosen by its permissions and field menu (`USE`, `GIVE`, `TOSS`, `SEL`, `QUIT`). TOSS asks how many and confirms. USE follows `UseItem`: Oak's warning without a field use, "You don't have a POKéMON!" for party items without one, and `_DoItemEffect` by the item's `ItemEffects` address: Potions and the other `HealingHPAmounts` items, status healers, Full Restore, Revive and Max Revive, the bitter Energypowder, Energy Root, Heal Powder and Revival Herb, Ethers, Elixers, the MysteryBerry and PP Up. The Bicycle closes the menu and runs `Script_GetOnBike` or `Script_GetOffBike` outdoors; elsewhere Oak objects, because `wFieldMoveSucceeded` shares `wItemEffectSucceeded`'s byte.
|
||
- PP Up keeps a quirk: `wUsePPUp` shares `wTempPP`'s byte, so `ComputeMaxPP` adds one step (a fifth of the base PP, at most 7) to the current PP as well as the maximum. Sketch and moves with three PP Ups are "maxed out".
|
||
- The menu runs on a script draft, like the PC. Changes commit when it closes; a save made while it is open keeps the state from before it. The start menu cursor, the party menu cursor and the last pocket stay in memory between openings and are cleared after a battle, as `CleanUpBattleRAM` does. The party cursor reaches the next battle's `ChangeHappiness`.
|
||
- The Escape Rope runs since the Fly section below; Repels and the other field items, the rods, item reordering with Select, left and right on the MOVE screen and MAIL since the last section. Rare Candy, evolution stones and vitamins run since the evolution section below; field moves, SEL and Select since the section after it.
|
||
|
||
## Implemented: evolution
|
||
|
||
- `EvolveAfterBattle` runs after every won battle for the party members that levelled up (`wEvolvableFlags`, now recorded by the battle's experience routine), before the script resumes. Level, happiness (220 or more, by time of day) and Tyrogue's Attack/Defense evolutions follow the original checks, including the Everstone. The evolution data is walked byte by byte like the original, so a forced evolution skips a four-byte `EVOLVE_STAT` record the way the original does. Trades never evolve without Link mode.
|
||
- The evolution screen shows "What? … is evolving!", then the old picture flashing into the new one on the original schedule (80 frames, then seven rounds of flashes). B stops it only during the waits between flashes, and never for a stone. A stopped evolution prints "Huh? … stopped evolving!" and skips that Pokémon's other entries.
|
||
- After evolving: the species name replaces the nickname only if the nickname was the old species name, stats are recomputed with stat experience, HP rises by the maximum HP's increase, moves learned at exactly the current level are offered, and the Pokédex records the new species as seen and caught.
|
||
- Learning a move outside battle follows `LearnMove`: an empty slot, or "delete an older move?", the choice of a move (HM moves can't be forgotten), "1, 2 and… Poof!", or "Stop learning …?" and "did not learn".
|
||
- PACK: Rare Candy (`RareCandyEffect`: one level at the level's minimum experience; the HP gains the maximum HP's increase even for a fainted Pokémon; the caught-location happiness bonus; "grew to level", the stat box, new moves and a possible evolution), evolution stones (`EvoStoneEffect`: a forced `EvolvePokemon`; the party menu shows ABLE or NOT ABLE with the original's ten-byte, three-byte-step check; an Everstone or no matching evolution has no effect) and vitamins (`VitaminEffect`: ten more in the stat experience's high byte, up to 100, stats recomputed without changing the current HP, happiness).
|
||
- Not presented: the evolution music, cries and the palette changes. Trade evolutions need Link.
|
||
|
||
## Implemented: field moves from the menu, SEL and moving Pokémon
|
||
|
||
- Field moves in the Pokémon submenu run their menu functions: the world checks the badge (`CheckBadge`: "Sorry! A new BADGE is required."), the facing tile, water or object, then the menu closes and the ROM's `…FromMenu` script runs for the chosen Pokémon (no "Want to …?" question): Cut (`CheckMapForSomethingToCut`), Surf (including "already surfing" and forced cycling), Strength, Flash (a `PALETTE_DARK` map or the Aerodactyl chamber; `BlindingFlash` sets the Flash flag), Whirlpool and Waterfall. Failures print the ROM's messages and return to the party menu.
|
||
- Sweet Scent runs `SweetScentFromMenu`: where wild Pokémon can appear and the map has a rate, `ChooseWildEncounter` picks one without the rate roll and a battle starts; elsewhere "Looks like there's nothing here…".
|
||
- Softboiled and Milk Drink give a fifth of the user's maximum HP to another Pokémon that has not fainted and is not at full HP; the user needs more than that fifth.
|
||
- Headbutt (facing a tree) and Rock Smash (facing a rock) run their checks and messages; their encounters run since a later section, as do Fly, Dig and Teleport.
|
||
- SEL registers an item that `CANT_SELECT` allows (in practice key items) in `wWhichRegisteredItem` and `wRegisteredItem`, which saves keep. Select runs `SelectMenu`: `CheckRegisteredItem` (a slot that no longer holds the item clears the registration; a registered ball is never found, as in the original) and `UseRegisteredItem` by field menu. The Bicycle uses `Script_GetOnBike_Register` and `Script_GetOffBike_Register`, without a message; elsewhere Oak objects. Without a registration Select explains SEL. This replaces the earlier Key Items shortcut.
|
||
- Bill's PC's MOVE PKMN W/O MAIL: refused while a party Pokémon holds MAIL, then "Each time you move a POKéMON, data will be saved. OK?". Choose the party or a box, a Pokémon (MOVE, STATS, CANCEL), then a box and the position to insert before. The party keeps its last Pokémon and its last healthy one, a different full box or party has "no room", a Pokémon moved down its own list lands one place earlier (`.CheckTrivialMove`), a boxed Pokémon joining the party gets its stats with full HP and no status (`CalcBufferMonStats`), and one entering a box gets its PP restored. The original's left/right box switching is presented as a list of boxes; after "no room" the box is chosen again rather than the position.
|
||
- Save format 3 gained `registeredItem`, migrated by absence to none.
|
||
|
||
## Implemented: the Pokégear, phone calls and Mom's savings
|
||
|
||
- POKéGEAR in the start menu runs `PokeGear` and returns to the start menu. The clock card shows the weekday (`GearTodayText`) and `PrintHoursMins`'s 12-hour time with the ROM's AM and PM, follows the game clock while open, and exits on A, B, Start or Select. Left and right change cards in the original order, skipping the cards the player lacks (the map card from the Guide Gent, the phone card from Mom).
|
||
- The map card draws the ROM's Johto or Kanto Town Map with the player's landmark (a special landmark uses the backup map's, as `TownMap_InitCursorAndPlayerIconPositions` does) and a cursor that up and down step through the region's landmarks with wrapping; Kanto's routes need the Hall of Fame. The cursor keeps its place while the Pokégear stays open. The landmark name comes from the ROM.
|
||
- The phone card lists `wPhoneList`'s ten slots four at a time, with `GetCallerClassAndName` (non-trainer names, or a trainer's name over its class) and dashes for empty slots, and the signal bars from the map header's phone service nybble. A on a contact opens CALL/CANCEL, or CALL/DELETE/CANCEL for numbers `CheckCanDeletePhoneNumber` allows (never Mom or Elm). Deleting asks first.
|
||
- Calls follow `PokegearPhone_MakePhoneCall` and `MakePhoneCallFromPokegear`: "You're out of the service area." without service; otherwise two "……" rings, then `PhoneOutOfAreaScript` outside the callee's hours, `PhoneScript_JustTalkToThem` on the callee's map, or the callee script from `PhoneContacts`. The script runs on the start menu's draft, so its changes commit with the menu. The call ends with `HangUp`'s "Click!" and boops. Elm's and Mom's callee scripts run; trainer contacts are not reachable yet.
|
||
- Mom's savings: `wMomsMoney` is saved as `momsMoney` (migrated by absence to 0). The interpreter gained `givemoney`, `takemoney`, `checkmoney` and `getmoney` for both accounts, `getcurlandmarkname`, `getlandmarkname` and `random`. After the catching lesson, calling Mom runs her lecture, which sets `ENGINE_MOM_SAVING_MONEY` on a yes; later calls report where the player is and her balance. While she saves, a won trainer battle sends a quarter of the prize to her (`WinTrainerBattle.give_money`, "Sent some to MOM!"), unless her account is full.
|
||
- `HangUp` now shows "Click!" for its 20 frames and the boops' "……" for the remaining 120, instead of "Click!" for twice the whole duration.
|
||
- Since board #30: the radio card, trainer callee and caller scripts, random incoming calls and the ringing are ported (see below); the original card graphics are not.
|
||
|
||
## Implemented: Mom's bank and her purchases
|
||
|
||
- Talking to Mom after the Mystery Egg runs `BankOfMom`. The first time she offers to save money (`MomLeavingText1`–`3`); until New Bark Town's flypoint callback clears `EVENT_FIRST_TIME_BANKING_WITH_MOM` she only cheers the player on. Later visits ask "Or is this about your money?": GET, SAVE, CHANGE or CANCEL.
|
||
- GET and SAVE open `Mom_ContinueMenuSetup`'s box (SAVED, HELD, then WITHDRAW or DEPOSIT and a six-digit amount with a blinking digit). `Mom_WithdrawDepositMenuJoypad`: left and right move the digit cursor (starting on the last digit, stopping at both ends), up and down add or take the digit's `DigitQuantities` step through `GiveMoney`/`TakeMoney`'s ¥999999 and zero limits, A confirms and B cancels. A zero amount cancels. Refusals ("You don't have that much.", "You can't save that much.", "You haven't saved that much.", "You can't take that much.") return to a fresh box. CHANGE starts or stops her savings.
|
||
- Declining the money question runs `DSTChecks`: switch Daylight Saving Time on or off, moving the clock an hour, unless that would change the day (within an hour of midnight), when Mom has lost the Pokégear's booklet.
|
||
- `wMomSavingMoney` stays two engine flags: nothing in the ROM sets the half and all bits.
|
||
- After a won trainer battle with phone service, `MomTriesToBuySomething` buys MomItems_2's next item once her savings reach its trigger, or a random MomItems_1 item when `wMomItemTriggerBalance`'s ¥2300 steps land exactly on her savings. Items go to the PC (nothing happens when it is full), dolls to the room's decorations, and her call follows ("I bought it with your money. Sorry!").
|
||
- Save format 3 gained `momItem` and `momItemTriggerBalance` (`wWhichMomItem`, `wMomItemTriggerBalance`), migrated by absence to 0 and ¥2300.
|
||
- The amount box has no key repeat: like the other native menus, a held direction counts once.
|
||
|
||
## Implemented: the trainer card and the play time
|
||
|
||
- The player's name in the start menu runs `TrainerCard` and returns to the start menu. Page 1 shows `.Name_Money`'s NAME and MONEY, the ID number with leading zeros, the Pokédex's caught count (`CountSetBits` over the caught species; the line is cleared without the Pokédex), the play time and "BADGES▶". A or right turns to page 2, the eight Johto leaders with the badges owned; there A leaves and left turns back. B leaves from either page. The Kanto page is reachable only through unreferenced code and is not shown.
|
||
- The card picture (`GetCardPic`: Chris or Kris), the leader faces (`TrainerCard_Page2_3_PlaceLeadersFaces`) and the badges come from the ROM, coloured by `_CGB_TrainerCard`'s palettes and attribute map: the frame in the other gender's palette, the picture in the player's, each leader's own and `PREDEFPAL_CGB_BADGE` for the badges. The badges step through `TrainerCard_JohtoBadgesOAM` every eight frames (the Rising Badge mirrors on alternate cycles), and the play time's colon blinks every 32 frames. Both timings start when the page loads; the original's phase also depends on the VBlank counter.
|
||
- `wGameTime` is new: `GameTimer` counts 60 frames to the second, two per overworld tick, from the end of the introduction through scenes, menus and battles, and caps at 999:59:59. A save made during a scene keeps the current play time; everything else in it is still the state before the scene. Save format 3 gained `gameTime`, migrated by absence to zero. The save screen and the Hall of Fame, which pause it in the original, are not ported.
|
||
- The labels drawn as tiles ("ID NO", "STATUS" and "BADGES") are presented as text; the other strings come from the ROM.
|
||
|
||
## Implemented: the Pokédex
|
||
|
||
- POKéDEX in the start menu runs `Pokedex` in New mode and returns to the start menu. `StartMenu_Pokedex` needs a party. The list follows `NewPokedexOrder` up to the last seen Pokémon (`.FindLastSeen`), seven rows at a time. Unseen rows show `NameNotSeen` ("-----") and owned ones the ball. The picture beside the list is the selected Pokémon or `LoadQuestionMarkPic`, in `PokedexQuestionMarkPalette`, with the SEEN and OWN counts.
|
||
- Up and down move a row and left and right a page, as `Pokedex_ListingHandleDPadInput` does. A opens a seen Pokémon's entry and B leaves. The cursor reopens on `wPrevDexEntry` through `Pokedex_InitCursorPosition`. `wPrevDexEntry` lives with the menu cursors in the world's memory, survives battles and is not saved.
|
||
- `DisplayDexEntry` shows the name, category and number of every seen Pokémon. The height, weight and page text appear only once it is caught; otherwise `.Height` and `.Weight` keep their question marks. The picture takes the species' palette, and the footprint comes from `Footprints`. Left and right move the arrow over PAGE, AREA, CRY and PRNT without wrapping. A on PAGE turns the page. Up and down step to the previous or next seen Pokémon, back on page 1 with the arrow on PAGE. B returns to the list where the entries left the cursor.
|
||
- The Pokédex tab reads the loaded game's state. It lists New mode's order up to the last seen Pokémon and shows a selected entry with the same rules: unseen Pokémon stay unnamed, and uncaught ones show no measurements or text. Without a ROM or before the Pokédex is received, it says so.
|
||
- `tests/pokedex.test.ts` compares the order, all 251 entries and every footprint with the pinned PRET source.
|
||
- Not ported: AREA (`Pokedex_GetArea`), CRY (no audio), PRNT (no printer; it now shows the absent printer's error, see below), the option screen (SELECT), the search screen (START), Old and A to Z modes (`wLastDexMode` stays New), Unown mode and the arrow's blink. Screens are native; the strings, entries and graphics come from the ROM.
|
||
|
||
## Implemented: the region atlas
|
||
|
||
- The Map tab shows the region's Town Map from the ROM under the local map, in the player's Pokégear palettes. Johto is always available. Kanto becomes available once the player stands there or has visited Indigo Plateau, as `_FlyMap` requires.
|
||
- The player's landmark follows `GetWorldMapLocation`, with the backup map's landmark in `LANDMARK_SPECIAL` rooms. The Fast Ship's landmark lies outside both regions, so no marker shows there.
|
||
- Visited areas are the visited flypoints (`wVisitedSpawns` through `EngineFlags`). The original records no other visits, so the atlas adds no save data. Visited flypoints are the Fly destinations, listed by their ROM landmark names.
|
||
- "Open in this port" lists the landmarks with at least one map in `NATIVE_MAPS`, so the atlas never suggests travel through unported areas.
|
||
- `tests/atlas.test.ts` compares every landmark position with `data/maps/landmarks.asm` and the flypoints with `data/maps/flypoints.asm`.
|
||
|
||
## Implemented: the installable web app
|
||
|
||
- `public/manifest.webmanifest` makes Crystal installable (standalone, relative start URL and scope, so subdirectory hosting works). `scripts/make-icons.mjs` renders the PNG icons, including a maskable one and the Apple touch icon, from the project's own `icon.svg`.
|
||
- The native build emits `sw.js` (`scripts/service-worker.mjs`, a Vite plugin). It precaches exactly the built files under a cache named by their contents, serves them first and passes every other request to the network. ROMs are read from local files and never fetched, so they never reach the cache. The reference build has no worker, and development registers none.
|
||
- Updates wait. A new version installs in the background, and a banner offers Update; the page saves, then the worker takes over and the page reloads. Nothing reloads during play without that choice. Activation deletes older shells.
|
||
- Savegames gains App and Storage. Install uses the browser's prompt where it exists; iPhone and iPad get Safari's Share, Add to Home Screen steps. Storage shows whether saves are persistent and how much is used, and asks for persistence on request (and once at start when installed). A full disk gets its own save error, which points to backups.
|
||
- `check:release` accepts only the four known icons beside the text assets and checks that the worker's list matches the build. Tests run the generated worker against a fake cache, and a Chromium test opens the production build offline.
|
||
- The ROM is still selected at each launch; keeping it is a separate, explicit preference for later.
|
||
|
||
## Implemented: interface settings
|
||
|
||
- Settings, beside Help, sets the text size (Standard, Large, Largest: 100, 125 and 150 %) and the keyboard key of each button. Every font size in the stylesheet scales with `--text-scale`; the dialogue box scrolls instead of growing past the screen.
|
||
- Change waits for the next key. Escape cancels, and Tab, Escape and Space stay reserved for focus, dialogs and scrolling. A key given to a new button leaves its old one, which then has no key until the player chooses one. Reset controls restores X, Z, Enter, Shift and the arrows.
|
||
- The preferences live in this browser's `localStorage` (`crystal-preferences-v1`), validated on load; without storage the defaults apply. They are not game state, so saves and backups do not carry them. The reference player keeps the default keys.
|
||
- The game screen's label, the Play message and Help's control list name the current keys.
|
||
|
||
## Implemented: the options
|
||
|
||
- OPTION in the start menu runs `_Option` and returns to the start menu. The labels come from `StringOptions` and the settings from each routine's strings. Up and down move the ▶ with `OptionsControl`'s wrapping, left and right change the option under it, A on CANCEL, B or Start leaves. Each routine is ported from its bit handling: text speed cycles FAST, MID and SLOW through `GetTextSpeed` (`.Save` keeps only the high nybble), the battle scene, battle style, sound and menu account toggle with either direction, printing cycles through the five `GBPRINTER_*` levels through `GetPrinterSetting`, and the frame wraps through `TYPE 1` to `8`.
|
||
- The four bytes (`wOptions`, `wTextboxFrame`, `wGBPrinterBrightness`, `wOptions2`) are kept in the script state, so they change on the start menu's draft and commit when it closes. Save format 3 gained `options`, migrated by absence to `DefaultOptions`.
|
||
- In effect: the battle style (`CheckWhetherToAskSwitch`: SET makes no offer when a trainer sends out its next Pokémon) and the menu account (`StartMenu`'s descriptions). Not yet: text speed (native text appears at once), the battle scene (no move animations), sound (no audio), printing and the textbox frame. The catching tutorial's temporary medium text speed has nothing to change.
|
||
|
||
## Implemented: Fly, Dig, Teleport and the Escape Rope
|
||
|
||
- Fly needs the Storm Badge and an outdoor map, then opens `_FlyMap`: the Johto map (Kanto's only once Indigo Plateau's flypoint is visited; before that the Johto map shows no player icon), "Where?" with the flypoint's name, the player's place and the Fly user's party icon on the selected flypoint. New Bark Town (or Indigo Plateau in Kanto) is the default, visited or not; up and down move to the next or previous visited flypoint with wrapping, A flies there, B returns to the party menu. Flypoints are read from `Flypoints` and visits from `wVisitedSpawns`, the `ENGINE_FLYPOINT_*` flags that town callbacks set.
|
||
- `FlyScript`: `FlyFromAnim` hides every object while the party icon waits 64 frames and rises off the screen, swaying (`SpriteAnimFunc_FlyFrom`), the player arrives at the flypoint's `SpawnPoints` entry facing down (`MAPSETUP_FLY`), and `FlyToAnim` brings the icon down onto the player (`SpriteAnimFunc_FlyTo`). The icon flaps its wings with `SPRITE_ANIM_OBJ_RED_WALK`'s frames. The leaves and the sound are not presented.
|
||
- Teleport works outdoors when `wLastSpawnMapGroup/Number` is a spawn point (`IsSpawnPoint`): "Return to the last POKéMON CENTER.", `teleport_from`, the spawn point facing down, then `teleport_to`. Otherwise "Can't use that here.".
|
||
- Dig and the Escape Rope work in caves and dungeons once `wDigWarpNumber` is set: `EnterMapWarp`'s `SaveDigWarp` records the warp of any step from an outdoor map into an indoor one (houses included, as in the original; not from Mount Moon Square or Tin Tower's roof). The ROM's `UsedDigScript` or `UsedEscapeRopeScript` digs out (`step_dig`, hidden), arrives on that warp through `MAPSETUP_DOOR` and returns (`return_dig`); a cave entrance then walks the player out. Dig elsewhere says "Can't use that here."; the Escape Rope fails silently, then Oak warns, and is used up only on success. In the Kabuto chamber the rope opens the wall (`SpecialKabutoChamber`).
|
||
- `newloadmap` now stops its script and resumes it on the new map, which these scripts need for their landing movements. Save format 3 gained `digWarp`, migrated by absence to none.
|
||
- Unreachable in normal play so far: the Storm Badge, caves (Dark Cave is beyond Route 31) and flypoints beyond Cherrygrove. A destination that is not ported gives a notice. `JumpRoamMons` waits for roaming Pokémon.
|
||
|
||
## Implemented: Headbutt and Rock Smash
|
||
|
||
- Facing a Headbutt tree with a Pokémon that knows Headbutt asks "Want to HEADBUTT it?" (`AskHeadbuttScript`); HEADBUTT from the party menu skips the question (`HeadbuttFromMenuScript`). "did a HEADBUTT!", then `ShakeHeadbuttTree`: the tree's four tiles become grass tile $05 while `SPRITE_ANIM_OBJ_HEADBUTT` shakes over them in `PAL_OW_TREE`, two frames alternating every three steps with the second mirrored on every other pass, for 40 frames. The sound is not presented.
|
||
- `TreeMonEncounter`: the map's set in `TreeMonMaps` (`TREEMON_SET_NONE` and unlisted maps have none), `GetTreeScore`'s facing-tile score ((x × y + x + y) / 5 modulo 10, in `wPlayerMapX/Y` coordinates) against the trainer ID's last digit, then 10%, 50% or 80% for a bad, good or rare score, the rare one drawing from the rare table, and `SelectTreeMon`'s percentages. Otherwise "Nope. Nothing…".
|
||
- A tree battle is `BATTLETYPE_TREE`: "fell out of the tree!" instead of "appeared!", and the time of day's `AsleepTreeMons` species start asleep for `TREEMON_SLEEP_TURNS`.
|
||
- Talking to a smashable rock runs `SmashRockScript`: "Maybe a POKéMON can break this." without Rock Smash (`HasRockSmash`), otherwise the question; ROCK SMASH from the party menu skips it and acts on the facing rock as `hLastTalked`. The earthquake, the rock's flicker and its disappearance run from the ROM's script, then `RockMonEncounter`: the map's `RockMonMaps` set and a 40% chance of a wild battle.
|
||
- Unreachable in normal play so far: no open map has rocks, and no Pokémon available yet learns Headbutt (the Ilex Forest tutor) or Rock Smash (a TM on Route 36). Route 29 and Route 30 have tree sets.
|
||
|
||
## Implemented: the remaining field items
|
||
|
||
- Repel, Super Repel and Max Repel set `wRepelEffect` to 100, 200 or 250 steps ("used the REPEL."), unless "The REPEL used earlier is still in effect.". Each step counts one off where `CountStep` does, after trainer, warp and coordinate events and a special call; the last one prints "REPEL's effect wore off." instead of looking for a wild Pokémon. Under a Repel, wild Pokémon below the level of the first party member that has not fainted are not met (`CheckRepelEffect`); Sweet Scent and Headbutt ignore it, as in the original. A save during the message or a battle replays the step with its Repel step.
|
||
- The Coin Case shows `wCoins` and the Blue Card `wBlueCardBalance`; nothing earns coins or points yet (the Game Corner and the radio's Buena show).
|
||
- Sacred Ash: when a Pokémon that is not an Egg has fainted, the menu closes and `SacredAshScript` heals the whole party; otherwise Oak objects.
|
||
- The Normal Box and the Gorgeous Box hold the silver and the gold trophy doll (`SetSpecificDecorationFlag`), sent home for the bedroom PC.
|
||
- The Itemfinder answers "Yes! ITEMFINDER indicates there's an item nearby." when a hidden item not found yet lies on the original's screen around the player (`CheckForHiddenItems`: four squares left and up, five right, four down), and "Nope!" otherwise. Route 30's hidden Potion is the only one on an open map.
|
||
- The Card Key and the Basement Key run their door scripts only facing the Radio Tower 3F slot or the Goldenrod Underground door (Oak objects elsewhere). The Squirtbottle sprinkles water, and on Route 36 starts the Sudowoodo battle when facing it (`WateredWeirdTreeScript`). None of these maps is open yet.
|
||
- The Old, Good and Super Rods fish facing water, never while surfing (Oak objects otherwise; the facing tile may hold an object, as in the original). The map header's fish group decides: none gives "Not even a nibble!" at once; otherwise `Fish` bites below 50% + 1 and picks the rod's `FishGroups` entry, with `TimeFishGroups` for the morning and day or the night. The player casts with the fishing frames (`LoadFishingGFX`'s body and rod tiles), jiggles four times on a bite, shows "!", and after "Oh! A bite!" puts the rod away before a `BATTLETYPE_FISH` battle: "The hooked MAGIKARP attacked!". Qwilfish and Remoraid swarms come from trainer calls, which are not ported. New Bark Town, Route 30 and Cherrygrove City have fish.
|
||
- In the start menu's pack, Select marks an item and asks "Where should this be moved to?"; A or Select on another row of the same pocket moves it there (the rows between shift), merging it into a stack of the same item unless either holds 99 (what exceeds 99 stays behind), and B ends the move without leaving the pack. Left and right keep the pocket meanwhile. The packs that GIVE and the PC open ignore Select, as `DepositSellPack` does.
|
||
- On the MOVE screen, left and right show the previous or next party member's moves, skipping Eggs (`MoveScreenLoop`'s `.cycle_left` and `.cycle_right`, which come back to the same Pokémon past either end), with the cursor back on the first move; not while a move is being moved. The party menu's cursor follows.
|
||
- MAIL: giving MAIL (from the pack, or swapped for a held item) opens the mail entry, two lines of up to 16 characters with the second after a full first one (`_ComposeMailMessage`), and keeps the message with the player's name and ID, the holder's species and the MAIL (`ComposeMailMessage`). A Pokémon holding MAIL shows MAIL in its submenu: READ shows the letter with its ROM stationery and the Portrait MAIL picture, TAKE asks "Send the removed MAIL to your PC?" (`SendMailToPC`: up to ten in the MAILBOX, else "Your PC's MAILBOX is full."), or on NO "The MAIL will lose its message. OK?" and puts the item in the pack. SWITCH carries the MAIL with its Pokémon; another item still needs "Please remove the MAIL first."; Bill's PC refuses to deposit or release a Pokémon holding MAIL ("Remove MAIL."). The player's PC MAIL BOX lists the MAIL by author with READ MAIL, PUT IN PACK (the message is lost) and ATTACH MAIL (not to an Egg or a Pokémon holding an item). The mail entry offers the original's characters that have one of their own; `<PK>`, `<MN>`, `<PO>`, `<KE>` and the 'd-'v ligatures are left out. Save format 3 gained each Pokémon's `mail` and the `mailbox`, migrated by absence to none.
|
||
- Wild Magikarp now battle: `LoadEnemyMon` draws their DVs again as the original does, through `CalcMagikarpLength` (compared with the original routine) and its filters, whose millimetre limits only work as a floor of four feet that about 39% of draws skip (39 percent + 1), nowhere in map group 9 or on maps numbered 6 (the Lake of Rage check). Unown are still refused.
|
||
- Save format 3 gained `repel`, `coins` and `blueCardBalance`, migrated by absence to 0.
|
||
|
||
## Implemented: multi-hit and special-damage moves
|
||
|
||
- Multi-hit moves loop as `BattleCommand_EndLoop` does: back to the script's `critical`, so accuracy is checked once; two to five hits (a second draw above two), two for Double Kick, Bonemerang and Twineedle, one to three kicks for Triple Kick with growing damage (its count lives in `wBattleAnimParam`). "Hit n times!" reads the high byte of the user's `wDamageTaken`, which the loop borrows; effectiveness is said on the first hit only. A target that faints mid-loop ends the move without the count.
|
||
- Beat Up hits once per party member that is neither fainted nor statused, with its base Attack and level against the target's base Defense. The original's bugs are kept: the player's active member is recognised by comparing `wCurBattleMon` with the HP's low byte, a lone Pokémon skips King's Rock, and a wild user says "But it failed!" after hitting. Eggs follow their stored HP and status too: the routine never reads the EGG marker.
|
||
- `ConstantDamage`: Seismic Toss and Night Shade (the level), Sonicboom and Dragon Rage, Psywave (below 1.5 × level; a level-1 user is refused, as the original loops forever), Super Fang and Flail/Reversal (HP bar pixels into `FlailReversalPower`, then the move's power becomes 1). Immune types make them miss (`ResetTypeMatchup`).
|
||
- One-hit KO moves, Counter and Mirror Coat (twice the opponent's last physical or special damage when it moved first, still working after the opponent used an item), False Swipe, Magnitude, Present (a full target refuses the gift, said only with battle animations off), Hidden Power, Return and Frustration (no damage at the extremes of happiness, a kept bug), Selfdestruct and Explosion, Dream Eater, Thief (not MAIL; a trainer's Pokémon keeps the player's item), Pay Day (paid out after a win, doubled by an Amulet Coin, which is now checked at every send-out), freezing, Tri Attack, added confusion, Swagger, Flame Wheel and Sacred Fire thawing their user, fire moves thawing their target, Jump Kick's crash damage, Minimize with Stomp's double damage, Earthquake against a digging target and Thunder's weather accuracy.
|
||
- A Focus Band now hangs on at 1 HP even when the hit would only take that 1 HP, and cancels a one-hit KO's text.
|
||
- `tests/reference-move-effects.spec.ts` runs the original `ConstantDamage`, `OHKO`, `Counter`, `MirrorCoat`, `EndLoop`, `TripleKick`, `HiddenPower` with `DamageCalc`, and `FalseSwipe` in the reference emulator on the same state as the native commands, with the Link RNG; damage, flags, move structs, loop counters, the script position and the random stream must agree. Deliberate mutations of six rules each made a comparison fail.
|
||
|
||
## Implemented: moves that span turns
|
||
|
||
- A locked-in Pokémon (recharging, charged, rampaging or rolling) plays on without the battle menu and keeps last turn's move and move struct, on both sides (`ParsePlayerAction`'s `.locked_in`, `ParseEnemyAction`'s `ResetVarsForSubstatusRage`). While biding, FIGHT goes on without the move list.
|
||
- Razor Wind, Solarbeam (no charging turn in the sun), Skull Bash (its Defense raise on the charging turn), Sky Attack, Fly and Dig charge with the original texts. Flying Pokémon can only be hit by Gust, Whirlwind, Thunder and Twister, digging ones by Earthquake, Fissure and Magnitude (Earthquake and Magnitude hit twice as hard). Hyper Beam recharges.
|
||
- Thrash, Petal Dance and Outrage go on for 1-2 more turns and then confuse the user for 2-3 turns, unless its side has Safeguard. Rollout doubles for each hit, once more after Defense Curl, for up to five; Fury Cutter doubles per consecutive hit up to 16 times. Bide stores the damage taken from its second turn on and unleashes twice that.
|
||
- Bind, Wrap, Fire Spin, Clamp and Whirlpool trap for 2-5 more turns; `HandleWrap` takes 1/16 of the maximum HP each turn between Future Sight/weather and Perish Song, until "was released". A substitute shields the trapped Pokémon and pauses the count.
|
||
- Mind Reader and Lock-On mark the target: the next move hits unless the target flies from an Earthquake, Fissure or Magnitude (`CheckHit`'s `.LockOn`, which lets a locked-on move hit a digging target).
|
||
- Pursuit doubles against a Pokémon being withdrawn, and `PursuitSwitch` runs it before the player's or the trainer's switch; the Pursuer then cannot move. A withdrawn Pokémon fainting this way keeps its status and gets no faint handling (a kept bug).
|
||
- The reference comparisons also cover `RolloutPower`, `FuryCutter`, `CheckRampage`, `Rampage`, `CheckRollout`, `CheckCharge`, `Pursuit` and `CheckHit` with Fly, Dig and Lock-On; six deliberate mutations each failed. The fixture's sentinel moved off `wPlayerScreens` (`$c6ff`), which it had been zeroing.
|
||
|
||
## Implemented: status and field moves
|
||
|
||
- Toxic (one more sixteenth each turn), Leech Seed (the seeder regains what is drained; not on Grass types), Nightmare and Curse (a Ghost type halves its HP to curse; others trade Speed for Attack and Defense, or say "ABILITY won't rise anymore!" at the top) hurt in `ResidualDamage`'s order.
|
||
- Protect, Detect and Endure (`ProtectChance`: only for the side moving first, halving per use in a row), Destiny Bond, Spite (the PP goes in the party struct, or the wild Pokémon's kept PP), Heal Bell (all six party slots), Disable, Encore (the target switches to its last move at once if it has not moved; FIGHT keeps an encored player's move), Attract with the 50% immobility, Mist, Focus Energy, Haze, Recover, Softboiled, Milk Drink and Rest, Mean Look and Spider Web, Pain Split (the party structs keep their HP until the next update), Belly Drum (two stages stay when it fails, a kept bug), Psych Up, Safeguard, Perish Song, Spikes, Rapid Spin, AncientPower and Future Sight.
|
||
- `HandleBetweenTurnEffects` runs Future Sight, trapping and Perish Song with the faint checks between them, then Safeguard, and ends with `HandleEncore`. Spikes hurt Pokémon sent out after a faint or by the player's switch; the trainer AI's own switch skips them in single play, as in the original. A trainer's last Perish count makes the AI switch at the highest chance.
|
||
- The trainer's party struct now follows its Pokémon where the original updates it (`UpdateEnemyMonInParty`: status changes, heals, waking up), and moves used by the enemy spend PP in its party struct or in `wWildMonPP`, so a caught Pokémon keeps the PP it used and a trainer's Pokémon sent back in keeps its spent PP.
|
||
- A move missing from its user's move list (after Metronome, for instance) is refused by Spite, Disable and Encore, which search on past the list in the original.
|
||
|
||
## Implemented: weather, substitutes and moves that copy or switch
|
||
|
||
- Rain Dance and Sunny Day (renewed when used again) and Sandstorm (which fails when already raging) last five turns with the original messages between turns; a sandstorm hurts all but Rock, Ground, Steel and digging Pokémon. `DoWeatherModifiers` scales Water and Fire damage before STAB (else rain halves Solarbeam's), in the AI's estimate too; Thunder, Solarbeam's charge, freezing and the time-based heals follow the weather.
|
||
- Substitute (a quarter of the maximum HP, which needs more HP than that) takes the hits in `DoSubstituteDamage`, and its break clears the attacker's move effect unless it hits several times. The animation commands now keep their `wBattleAnimParam` effects with a substitute up, which Triple Kick counts from.
|
||
- Transform (5 PP per move, the enemy's own DVs kept for Attract and capture), Mimic, Metronome, Mirror Move and Sleep Talk (which run the called move through `ResetTurn`), Snore, Conversion, Conversion2 and Sketch (for good, in the party struct or the wild Pokémon's kept moves). Catching a transformed wild Pokémon gives a Ditto with its own DVs and moves, as in the original.
|
||
- Baton Pass hands the substatuses and stat levels to a Pokémon chosen without a send-out text; Roar and Whirlwind end a wild battle or drag out a random healthy Pokémon of a trainer's; Teleport ends a wild battle, which a wild Pokémon always manages (a kept bug).
|
||
- A trainer's Pokémon sent back in with a status no longer has its paralysis or burn penalty applied, as in the original.
|
||
- Every move's effect script now runs. The battle rollback tests use Psywave at level 1 instead, which loops forever in the original and stays refused. `tests/reference-move-effects.spec.ts` also compares `Stab` with the weather; two deliberate mutations of the weather modifier failed it.
|
||
|
||
## Implemented: held items, Exp. Share and obedience
|
||
|
||
- Smoke Ball escapes wild battles after the trapping checks; Metal Powder uses the original species even after Transform, with the byte-overflow quirk in both damage and AI estimates. MysteryBerry restores the first exhausted stored move (one PP for Sketch, otherwise five), preserves PP Ups and leaves copied moves alone. Berserk Gene activates before the menu and keeps the original confusion-count bug. The unused held stat-raising effects also follow their between-turn order.
|
||
- Exp. Share halves experience and stat-experience bytes before splitting the participant and holder pools. Healthy holders receive the second pool, including a holder that also fought; fainted holders are excluded. Existing traded, trainer, Lucky Egg and Pokérus modifiers still apply to each award.
|
||
- Traded Pokémon obey according to the world's Johto badges. Disobedience preserves the original random draws and can ignore orders, nap, hurt itself or use another move; it spends only the alternate move's PP and breaks Encore where the original does.
|
||
- Consumed healing and confusion berries are removed from a trainer's stored party too, so switching cannot restore them.
|
||
- Local-ROM regressions cover these paths; the reference emulator compares Metal Powder over 320 cases in each browser. The other new routines have source review and unit coverage, without a direct emulator comparison yet.
|
||
|
||
## Implemented: Smart AI
|
||
|
||
- Every `AI_Smart_EffectHandlers` entry is translated, so trainers with the Smart layer are no longer refused. The handlers read battle history, stat levels, both parties' reserves, the switch score and the weather.
|
||
- The AI's type checks keep the original `CheckTypeMatchup` bug: the move type and Foresight flag come from whichever side `hBattleTurn` names, so Encore and the switch score can read the player's move struct. Conversion2's write to the player's move type persists, as in the original.
|
||
- `tests/reference-smart-ai.spec.ts` runs the unmodified `AI_Smart` routine in the reference emulator over 6,024 states (all 251 moves, random battle state, either starting turn), comparing scores, Random consumption, `hBattleTurn` and the player's move type. A one-off run over 18,072 more states also matched in both browsers.
|
||
|
||
## Implemented: Kurt's balls
|
||
|
||
- The Heavy, Level, Lure, Fast, Friend, Moon and Love Balls and the Park Ball (outside the contest) can be thrown. Their documented bugs are kept: the Heavy Ball reads Tauros's weight from the wrong bank, the Fast Ball checks only Magnemite, Grimer and Tangela, the Moon Ball never boosts, and the Love Ball boosts for the same gender. The Level Ball skips the HP, status and held-item terms. A Friend Ball catch gets 200 happiness, in the party or the PC.
|
||
- For Kadabra and Sunflora the original Heavy Ball scan runs off the wrong bank into VRAM, so that throw is refused.
|
||
- `tests/reference-capture.spec.ts` runs the unmodified `PokeBallEffect` over 6,153 more states per browser: every species for the Heavy, Fast, Moon and Love Balls, level pairs for the Level Ball and fishing for the Lure Ball.
|
||
- There is no Safari Ball to port: `SAFARI_BALL` is Moon Stone's item number and never reaches `PokeBallEffect`.
|
||
|
||
## Implemented: special battle types
|
||
|
||
- The battle engine runs every remaining wild battle type. The Red Gyarados (`BATTLETYPE_FORCESHINY`) has shiny DVs; Ho-Oh, Lugia and Snorlax (`FORCEITEM`) hold their first item without the item rolls, even when reloaded after a catch. The player cannot run from those, the trap battles, Celebi or Suicune. Celebi has its own appearance text and sets `BATTLERESULT_CAUGHT_CELEBI` when caught.
|
||
- Roaming battles take and return a `wRoamMon` struct: the DVs are drawn once, Speed/Special first, and only the HP's low byte is kept. A win or a catch removes the Pokémon from the map. Every other battle ends with `CleanUpBattleRAM`'s one-in-sixteen roll to move the roaming Pokémon. The result reports it, but nothing roams yet: Raikou, Entei and Suicune are released in the Burned Tower.
|
||
- In the Bug-Catching Contest the menu shows PARKBALL× with the count, and RUN always works. Each throw takes a Park Ball rather than an item from the pack, and the battle ends as a draw when the last one misses. A catch goes to the contest, not the party; a second catch shows both Pokémon and asks to switch. While a contest runs (`bugContest` in the script state), encounters use `BugCatchingContestBattleScript` and its `ContestMons` table, as `RandomEncounter` does.
|
||
- Scripts can load the forced-shiny, trap, forced-item, Suicune and contest types with `loadvar VAR_BATTLETYPE`.
|
||
- Still to come with their maps: National Park's contest event (registration, timer, judging; the contest state is not saved), Raikou's, Entei's and Suicune's roaming, and the Ilex Forest Celebi event with `CheckCaughtCelebi`.
|
||
|
||
## Implemented: Route 31 to Violet City and the first gym
|
||
|
||
- Route 31 and its Violet gate, Dark Cave's Violet entrance, Violet City with its Mart, gym, Earl's Pokémon Academy, the nickname speech house, the Pokémon Center and Kyle's house, and Sprout Tower's three floors are open, with their scripts, trainers, items, fruit trees and encounters. Route 32, Route 36, Dark Cave's other entrances and the Pokémon Center's second floor stay closed.
|
||
- New script commands: `gettrainername`, `askforphonenumber` (AddPhoneNumber keeps room for Mom and Elm; `addcellnum` now reports failure as the original does), `checkpokemail`, `loadmenu`/`_2dmenu`/`closewindow` (Earl's blackboard, shown as a list), `giveegg`, `trade` (NPCTrade with saved `wTradeFlags`), `loadvar VAR_CALLERID` and the `RandomPhoneMon` special. The trade animation is not presented.
|
||
- Eggs keep their species with an `egg` flag, as `wPartySpecies` holds EGG: the party menu shows no level or HP, the stats screen shows the Egg page with its hatching hint, healing skips them, and `checkpoke` does not count them. Eggs now hatch through the walking routine described below.
|
||
- The pack's TM/HM pocket holds counts by number: TMs and HMs are received, listed with their moves, given (TMs) and taught with ABLE/NOT ABLE, the compatibility and "already knows" checks and LearnMove. A TM is used up and makes the Pokémon happier; an HM stays.
|
||
- The Johto badges raise the battle Pokémon's stats (BadgeStatBoosts, with the PlainBadge/MineralBadge swap and the Glacier Badge register bug), compared against the unmodified routine for all 256 badge bytes in `tests/reference-badges.spec.ts`.
|
||
- `tests/playthrough.test.ts` now continues to Route 31 (Mom's call), Sprout Tower (Elder Li's Flash), Falkner (the ZEPHYRBADGE, TM31 and the aide's call), the Togepi Egg, Kyle's Onix, Flash in Dark Cave, Wade's number and Earl's blackboard. `tests/violet.test.ts` covers the other answers.
|
||
- Kenya's MAIL is checked as the original does; the Goldenrod increment below opens the Route 35 gate and adds storage of its gift text. Wade's calls, rematches and gifts come with the phone (board #30).
|
||
|
||
## Implemented: Route 32 to Route 34, the Ruins of Alph and Azalea Town (board #32)
|
||
|
||
- Route 32 with its Pokémon Center, the Ruins of Alph (outside, the four chambers, their item and word rooms, the inner chamber and the research center) and both gates, Union Cave's three floors, Route 33, Azalea Town with its Pokémon Center, Mart, Kurt's house, the charcoal kiln and the gym, the Slowpoke Well, the Ilex Forest gates and forest, Route 34 and the Day-Care are open. Goldenrod City opens in the next increment below; Route 36 and the Pokémon Centers' second floor stay closed.
|
||
- The story runs: the Miracle Seed, Kurt's run to the Slowpoke Well, the Rocket grunts there, the Lure Ball, Bugsy and the HIVEBADGE, the rival at Azalea's west end, the Farfetch'd herding in Ilex Forest (`wFarfetchdPosition`), HM01 from the charcoal master and the Cut tree.
|
||
- Saved WRAM bytes that scripts read and write directly (`readmem`, `writemem`, `loadmem`) are kept by PRET label in the save's `memory` (`SAVED_WRAM`, verified by `check:reference`): so far `wFarfetchdPosition` and `wKurtApricornQuantity`.
|
||
- Kurt makes balls: `SelectApricornForKurt` lists the apricorns in `ApricornBalls` order with their counts, asks how many with the mart's 1-99 stepper and takes them; `verbosegiveitemvar` hands over the balls the next day (`VAR_KURT_APRICORNS`, `ENGINE_KURT_MAKING_BALLS` cleared by the daily reset).
|
||
- Unown: `GetUnownLetter` from the DVs, the per-letter front and back pictures (`UnownPicPointers`), `CheckUnownLetter`'s redraw against `UnlockedUnownLetterSets`, no wild Unown before a set is unlocked, `UpdateUnownDex` on a catch to the party or a box, `wFirstUnownSeen` (also the Pokédex's Unown picture) and `VAR_UNOWNCOUNT`.
|
||
- The chamber puzzles (`_UnownPuzzle`) run as a tile screen with the ROM's graphics: the random border layout, the cursor's moves around START/CANCEL, picking up and placing pieces, the solved check, then the chamber's floor opens and the player falls into the inner chamber. The wall patterns (`DisplayUnownWords`) draw their word in Unown letters from the chamber's tileset in a menu box over the map. `HoOhChamber` and `OmanyteChamber` open their walls.
|
||
- Tile screens are a general effect for minigames and specials that draw a whole Game Boy screen in tiles (`scripts/screen.ts`, `rendering/tile-screen.ts`): a tilemap with palettes and sprites, answered with buttons. `rom/font.ts` supplies the standard font and the chosen textbox frame for them.
|
||
- `tests/playthrough.test.ts` continues through Route 32, Union Cave, Azalea Town, the Slowpoke Well, Bugsy, the rival and Ilex Forest to the Route 34 gate; its helpers now live in `tests/playthrough-driver.ts`, and `PLAYTHROUGH_SAVES` keeps each stage's save. `tests/ruins.test.ts` solves the Kabuto puzzle through the world and catches Unown; `tests/azalea.test.ts` covers Kurt's apricorns.
|
||
- Still closed or refused here: the radios' `MapRadio` and trainers' rematch counts (board #30), the research center's `UnownPrinter` (it needs all 26 letters), the Pokédex's Unown mode, the GS Ball and Celebi shrine (not reachable without the mobile event), and sound.
|
||
|
||
## Implemented: Goldenrod City (board #32, increment B)
|
||
|
||
- Goldenrod City and its buildings, the department store and elevator, the Underground and its connecting rooms, the Radio Tower and the Route 35 gate are open. The continuous playthrough gets the Bicycle, trains on Route 34, beats Whitney, collects the PLAINBADGE and TM45, and answers the radio card quiz.
|
||
- The herb shop, bargain shop, pharmacy and rooftop sale use their ROM stock, prices and dialogue. Bargain stock sells once per visit and a purchase closes the shop for the day; the rooftop sale changes stock after the Hall of Fame.
|
||
- The Name Rater checks the Pokémon's OT and ID. The haircut brothers use the ROM happiness tables. The Lucky Number show checks the party and all boxes, keeps the first winner in a prize tier, and saves its number and Friday countdown with the RTC's 140-day wrap.
|
||
- Kenya arrives with Randy's name, ID and MAIL. Gift Pokémon can go to the current box when the party is full. Coin vendors and prize menus show the balance; a newly owned prize species shows both Pokédex entry pages before the nickname prompt. An already caught species skips registration.
|
||
- Saves gain `hallOfFame`, `timers` and `luckyNumber`, with defaults for older saves. `wUndergroundSwitchPositions` joins the saved script bytes. Goldenrod's Pokémon Center handles the GS Ball availability check; other Battle Tower actions remain refused.
|
||
- `tests/goldenrod.test.ts` covers shops, naming, Kenya and save reload, prize registration, Lucky Number party/box winners, save validation and boxed gifts. `tests/day-timers.test.ts` covers countdown expiry and rollover without a ROM. The 20-stage playthrough reaches Whitney and the radio card.
|
||
- Local validation (2026-10-06): 676 unit tests passed, 3 skipped (scratch tests excluded); without a ROM, 163 passed and 516 skipped. Both builds, reference/profile checks, vendor integrity and release isolation passed. Browser checks: 157 passed, 21 skipped, 12 known WebKit menu failures (#70). CI and physical-device acceptance were not run.
|
||
- Remaining: SlotMachine and CardFlip, radio broadcasts and rematches (#30), breeding (#29), the later Rocket takeover, routes beyond the Route 35 gate, mobile/link features and audio. This increment does not complete #32 or start #33. Boxed gifts still approximate the original stale enemy held-item byte as zero when no item is specified.
|
||
|
||
## Implemented: Routes 35–37, the National Park and Ecruteak to the Fog Badge (board #32, increment C)
|
||
|
||
- Route 35, Route 36, Route 37, the National Park and both of its gates, Ecruteak City with its Pokémon Center, Mart, gym, Dance Theater and houses, the Tin Tower entrance, the Wise Trio's room and the Burned Tower's two floors are open. Tin Tower itself and the Route 38 and Route 42 gates stay closed.
|
||
- The story runs: Floria and the SquirtBottle, Sudowoodo, the Kimono Girls and HM03 Surf, Eusine and the rival in the Burned Tower, the beasts' release (`ReleaseTheBeasts`, `InitRoamMons`), Ecruteak Gym opening, its trainers and Morty, the FOGBADGE and TM30.
|
||
- Roaming Pokémon (`game/world/roam-mons.ts`): `InitRoamMons`, `UpdateRoamMons` on connections, door and pit warps and one wild battle in sixteen, `JumpRoamMons` on Fly, Teleport and Continue, and `CheckEncounterRoamMon` in the grass. Suicune's slot is kept but not started, as the ROM leaves it. Saves gain `roamMons` and `roamMonsMap`. A rollback restore (a failed import) keeps the beasts in place; only Continue moves them.
|
||
- The Bug-Catching Contest: the gates on contest days, `GiveParkBalls`, `SelectRandomBugContestContestants`, `ContestDropOffMons`/`ContestReturnMons`, the timer on the game clock, running out of balls, QUIT in the start menu, the catch-and-swap prompt, `BugContestJudging` with the ROM's contestant table and prizes, and `CheckPartyFullAfterContest`. The contest state saves. The start menu's balls and catch status shows as a text page above the menu.
|
||
- The Game Corner: `SlotMachine` (reels with their spin rates and scrolling, the normal and lucky bias tables, the seven streak, Golem and Chansey with the egg, payouts, the 9999-coin cap) and `CardFlip` (deck, board, cursor and group navigation, paytable). They run on a realtime tile screen, which advances two frames per world tick with the buffered button. The Card Flip board and slot frame are checked tile by tile against the ROM's own tilemaps.
|
||
- Engine fixes found on the way: `getnum`, a plain `warp` that continues its script on the new map, `PokemonNames`' EGG entry, and a map trainer whose script is `ObjectEvent` (the Burned Tower rival) is no longer sighted once its own flag is set. In the original, its "beaten" check 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; reference checks now confirm that zero, one, four or five key items make the original sight the rival with a garbage trainer header. Its seen text runs an out-of-range text command into home code before any battle, so the native port does not reproduce it (see NATIVE_PORT.md).
|
||
- Tests: `tests/roaming.test.ts`, `tests/bug-contest.test.ts`, `tests/game-corner.test.ts` and `tests/ecruteak.test.ts`. The playthrough continues through the National Park to Floria, Sudowoodo, the Kimono Girls, the Burned Tower and Morty; its driver now chooses moves by type matchup, buys at Marts, and uses Super Potions and Awakenings in trainer battles.
|
||
- Remaining: Tin Tower and the Clear Bell, Suicune's appearances, the contest status box layout, radio and rematches (#30), breeding (#29), routes beyond Ecruteak and the gyms after Morty, mobile and link features, and audio.
|
||
|
||
## Implemented: trainer items and Game Corner reference follow-up (board #32)
|
||
|
||
- Trainer item AI now handles all 13 supported healing, status and X items, the highest-level gate, item consumption, and the original random and state quirks. Mean Look and Wrap stop switching but still allow item use. This removes the item refusal for Chuck, Jasmine and later leaders; Chuck and Jasmine are reachable since increment D.
|
||
- Game Corner sprites now use the original screen coordinates, reel symbol window and Golem flips. Reference tests cover all cursor positions and selected reel states. The Golem, Chansey and egg are compared frame by frame against the original's `PlaySpriteAnimations`: a three-Golem run and a Chansey round with two eggs, 385 frames of shadow OAM, `wSlotsDelay` and `hSCY`. That covers the whole-frame flips, frame timing, the extra frame a removed sprite is still drawn, and the Golem's roll shake, which the slot screen now shows.
|
||
- The Burned Tower sight check is verified for zero through six key items. With 0, 1, 4 or 5 key items the original's garbage header would battle SwimmerF's ELAINE, but its seen text first jumps into home code through text command $7f. The native suppression stays, as a documented boundary.
|
||
- Trainer-item reference coverage compares 5,632 controlled states plus four dispatch cases. The unit suite also runs a battle using Clair's Kingdra and real class items. These comparisons bypass text/audio/HUD routines and do not establish physical-device acceptance.
|
||
|
||
## Implemented: Routes 38–41, Olivine and Cianwood to the Mineral Badge (board #32, increment D)
|
||
|
||
- Route 38 with its Ecruteak gate, Route 39 with the MooMoo farm's barn and farmhouse, Olivine City with its Pokémon Center, Mart, Café, gym and houses, the Lighthouse's six floors, Routes 40 and 41, and Cianwood City with its gym, Pokémon Center, pharmacy, Photo Studio, Mania's house, the Poké Seer and the Lugia speech house are open. Olivine's port, the Battle Tower gate and Whirl Islands stay closed.
|
||
- The story runs on existing primitives: the rival's Olivine scene, Jasmine and Amphy, the SecretPotion (the pharmacist then runs `MARTTYPE_PHARMACY`), Suicune and Eusine at Cianwood, Chuck's boulder-throwing intro and Strength boulders, the STORMBADGE, TM01 and HM02, Amphy's cure, Jasmine's return to her gym, the MINERALBADGE and TM23.
|
||
- New specials (`game/scripts/cianwood.ts`): `GiveShuckle` and `ReturnShuckie`, with Mania's OT, ID, nickname, Berry, gift caught data, the daily flag, every return code and the original's wrong speech when Shuckie is the last Pokémon that can fight; `PokeSeer`, with the action table, the OT check that compares only the ID's high byte, gift and event landmarks, the advice thresholds and the caught level reused from the last reading; `PhotoStudio`, which takes the no-printer path; `PlaySlowCry` (audio only). `wMooMooBerries` joins the saved script bytes.
|
||
- `PhotoStudio` without a Game Boy Printer: `PrintPartymon`'s handshake reads $ff, `CheckPrinterStatus` reports `PRINTER_ERROR_2`, and B cancels with no photo. The port shows that status box's text on one page; the Pokémon's print page behind it and the box alternating with "CHECKING LINK" on each retry are not drawn.
|
||
- The Seer's reused caught level is `wSeerCaughtLevel`, a byte in the "Miscellaneous WRAM 1" union. The port keeps the Seer's own last reading until a load; in the original, other users of that union (such as `wBufferMon`) may have changed it in between.
|
||
- The playthrough driver gained `sail` (Surf across open water, answering `AskSurfScript`), water-aware `cross` and `walk`, breadth-first `travel` through floors whose holes land in cut-off corners, Great Balls and pharmacy purchases.
|
||
- Tests: `tests/olivine-cianwood.test.ts` covers MooMoo's seven Berries and the saved byte, every Shuckie outcome, the Seer's readings, its ID quirk and reused level, and the Photo Studio. The playthrough continues for six more stages: Routes 38 and 39 and the rival, Strength and the Good Rod, a fished Krabby taught Surf, the Lighthouse, Routes 40 and 41, Eusine, the SecretPotion, Chuck (a loss retries from Cianwood's Pokémon Center), Fly, Amphy and Jasmine.
|
||
- Local validation (2026-10-13): 790 unit tests passed, 3 skipped (scratch tests excluded), including the 32-stage playthrough; without a ROM, 168 passed and 625 skipped. Both builds, reference/profile checks, vendor integrity and release isolation passed. Browser checks: 181 passed, 21 skipped, 12 known WebKit menu failures (#70). CI and physical-device acceptance were not run.
|
||
- Remaining: Olivine's port and the S.S. Aqua, the Battle Tower, Whirl Islands, the Game Boy Printer, routes beyond Olivine and Cianwood (Route 42 onward), radio and rematches (#30), breeding (#29), mobile and link features, and audio.
|
||
|
||
## Implemented: Route 42 to Mahogany, the Lake of Rage, the Rocket Hideout and Pryce (board #32, increment E)
|
||
|
||
- Route 42 with its Ecruteak gate, Mt. Mortar's four maps, Mahogany Town with its Pokémon Center, gym, souvenir shop and the red Gyarados speech house, Route 43 with both gates, the Lake of Rage with the Hidden Power and fishing guru houses, the Rocket Hideout's three floors and Route 44 are open. Ice Path and the Radio Tower takeover follow.
|
||
- The story runs on existing primitives: the Rockets' toll at the Route 43 gate (charged on every pass until the hideout is cleared), the red Gyarados (forced shiny) and the Red Scale, Lance, Dragonite's Hyper Beam on the souvenir shop's stairs, the hideout's cameras and exploding traps, Lance's healing, the two door passwords, the rival, both executives, the Murkrow's password, the six Electrodes and HM06, Pryce's ice floor, the GLACIERBADGE and TM16, and `RadioTowerRocketsScript` at seven badges.
|
||
- New specials: `CheckMagikarpLength`, `MagikarpHouseSign` and `FindPartyMonThatSpecies` (`game/scripts/lake-of-rage.ts`). The Magikarp record and holder are saved as `magikarpRecord` (3′6″ by RALPH in a new game, and in older saves).
|
||
- Engine fixes: `takemoney` leaves a short account at zero, as `TakeMoney` does (it used to leave it unchanged). `setmapscene` and `setscene` on a map without a scene variable consume the scene byte instead of refusing; see NATIVE_PORT.md for where the original writes it.
|
||
- The playthrough driver's path search slides on ice. Tests: `tests/mahogany.test.ts` covers the record, its holder and reload, every guru answer, an Egg, the Elixer on standby and the toll. The playthrough continues for five more stages, from Olivine to Pryce.
|
||
- Remaining: the Radio Tower takeover, Ice Path, Blackthorn, Clair and Dragon's Den follow in increment F below; Tin Tower, Whirl Islands, radio and rematches (#30), breeding (#29), mobile and link features, and audio.
|
||
|
||
## Implemented: the Radio Tower takeover, Ice Path, Blackthorn, Clair and Dragon's Den (board #32, increment F)
|
||
|
||
- Ice Path's five floors, Blackthorn City with its gym, Pokémon Center, Mart, Move Deleter's house, Emy's house and the dragon speech house, Routes 45 and 46, Dark Cave's Blackthorn entrance, Dragon's Den and the Dragon Shrine are open. The Radio Tower, the Underground, its switch room and warehouse change for the takeover.
|
||
- The story runs: the Rocket-held Radio Tower to the fake director and the Basement Key, the Underground's locked door, the switch room's doors and the rival, the director and the Card Key, the 3F shutters, the executive and the Clear Bell; Ice Path's falling boulder; Clair's gym, whose 2F boulders fall to bridge 1F; Clair; the Dragon Shrine's five questions, the RISINGBADGE, TM24 and the elder's Dratini.
|
||
- New specials (`game/scripts/blackthorn.ts`): `GiveDratini` (the Extremespeed moves after a test without mistakes, the normal ones otherwise, on the last Dratini in the party) and `MoveDeletion` (one-move and Egg refusals, every way to say no, and moves and PP bytes shifting up).
|
||
- Engine fixes: a save keeps an object that disappeared without an event flag hidden (the Radio Tower director walks off the 5F stairs), and one that appeared with its flag set shown, as Continue keeps the saved object structs. `Script_Whiteout`'s `newloadmap` may run inside a call (a lost battle started from an `scall`); its `endall` drops the caller.
|
||
- The playthrough driver gained `push` and `boulders` (pushes planned breadth first over the boulders and the player's reachable area, each into its own stone-table hole), clears whirlpools in `sail`, can choose the move `teach` forgets, and heals with the strongest potion carried. Tests: `tests/blackthorn.test.ts` (Move Deleter, Dratini) and `tests/radio-tower.test.ts` (switches through a reload and the warehouse reset, a lost rival battle, the director after a save). The playthrough continues for six more stages: the Radio Tower, the Underground, the Clear Bell, Ice Path, training and Clair, and the Dragon Shrine.
|
||
- Remaining: Tin Tower and Suicune, Whirl Islands and Lugia, the Battle Tower, radio and rematches (#30, including Buena's password show), breeding (#29), mobile and link features, and audio.
|
||
|
||
## Implemented: Tin Tower and Whirl Islands (board #32, increment G)
|
||
|
||
- Tin Tower's nine floors and roof and the Whirl Islands' eight maps are open. The Clear Bell lets the player past the entrance's sages and through the Wise Trio's room; in Tin Tower 1F Raikou and Entei flee unless caught, and Suicune battles (`BATTLETYPE_SUICUNE`), followed by Eusine.
|
||
- New specials (`game/scripts/tin-tower.ts`): `MonCheck` and `BeastsCheck` over `CheckOwnMonAnywhere`: the party and every box, the player's ID and the first five letters of the OT, an empty party finding nothing, the Day-Care omitted by the original's documented bug. After the League with all three beasts, the second Wise Trio appears and gives the Rainbow Wing; its stairs lead up to Ho-Oh on the roof. Lugia waits at the bottom of the Whirl Islands for a player with the Silver Wing. Both battles use `BATTLETYPE_FORCEITEM`.
|
||
- The playthrough driver plans ledge jumps (for the later stages only, so the earlier seeded stages keep their recorded paths), waterfalls carrying a surfer down, warp panels landing elsewhere on a map, and Surf and Whirlpool between warps in `travel`. Tests: `tests/tin-tower.test.ts` (MonCheck's rules, the Rainbow Wing and the climb to Ho-Oh, the Whirl Islands to Lugia). The playthrough continues for two more stages: Routes 45 and 46 back to Ecruteak, and Tin Tower's Suicune.
|
||
- Remaining: the Battle Tower (increment H below), Olivine's port and the S.S. Aqua (board #33), radio and rematches (#30), breeding (#29), mobile and link features, and audio. `CheckOwnMonAnywhere` searches the current box and skips its numbered SRAM copy; the port searches each logical box once.
|
||
|
||
## Implemented: the Battle Tower (board #32, increment H)
|
||
|
||
- The Route 40 gate, the Battle Tower outside and its 1F, elevator, hallway and Battle Room are open. Offline challenges work as in the original: the explanation, the Challenge/Explanation/Cancel menu, the party rules (three Pokémon, different species and items, no Egg), the save before entering, the level menu (L:10-L:40, or to L:100 after the Hall of Fame) with its level and uber checks, the walk to the room, seven trainers with healing and "next opponent" questions, save and quit, cancelling, a loss, and the reward of five of a random vitamin (not Lucky Punch).
|
||
- New in `game/scripts/battle-tower.ts`: `BattleTowerAction`'s offline actions on a saved `battleTower` state (the challenge state, streak count and trainers, the last two trainers' species, the level group, the reward and the file flags); `LoadOpponentTrainerAndPokemon`'s trainer and Pokémon sampling (no repeated trainer, species or item, none of the last two trainers' species) from `BattleTowerTrainers` and `BattleTowerMons`; `BattleTowerText` (a random greeting, kept for the end texts); `_CheckForBattleTowerRules`; `Menu_ChallengeExplanationCancel`; the level menu of `_BattleTowerRoomMenu`; `TryQuickSave`; and `Reset`. New script command: `battletowertext`. The opponent's overworld sprite comes from `BTTrainerClassSprites`.
|
||
- Battle Tower battles (`battleTower` in the battle engine): the SET style, no obedience check, badge boosts, experience, items or prize, no AI item use, Falkner's AI attributes, Sleep of one to three turns, no random failure of the AI's status moves, and BattleTowerText instead of the prize or a whiteout. After each battle `LoadPokemonData` returns the party and Pokédex to the quick save's, so held items used in battle come back.
|
||
- `DoBadgeTypeBoosts` was missing from all battles: on the player's turn, the first badge whose type is the move's adds an eighth of the damage. `tests/reference-badges.spec.ts` now compares it with the original routine for every Johto badge byte, Kanto badges, types, damage values, both turns and the Battle Tower.
|
||
- The native game saves itself, so a reload during a challenge replays the room's script from its start instead of reaching the original's "left without saving" state, and `Reset` (save and quit) returns to where the quick save was made. The original also rolls back money gained by Pay Day there; the port keeps it.
|
||
- Tests: `tests/battle-tower.test.ts` (the gate, every rule's text, the level and uber checks, the L:40 menu, a full streak and its reward, save and quit and the resume, a saved-and-left state after a reload, cancelling and losing) and `tests/battle-tower-engine.test.ts` (no experience, prize or items; obedience; a loss without a whiteout).
|
||
|
||
## Implemented: music and sound (board #34)
|
||
|
||
- `game/audio/engine.ts` ports the whole sound engine (`audio/engine.asm` and `home/audio.asm`): `_UpdateSound` and its channel updates, `ParseMusic` and every music command, notes, rests, drums and noise samples, cries and their pitch and length, sound effects and `PlaySFX`'s priority, `PlayStereoSFX`, vibrato, pitch slides, duty patterns, `FadeMusic` (with the Bicycle's fade-in) and the low HP tone. Its state is the original's audio WRAM byte for byte, and it drives a model of the sound registers, including their read masks (`.ch1_vibrato_override` reads NR11 back).
|
||
- `tests/reference-audio.spec.ts` runs the cartridge's own routines in the reference emulator next to the port and compares all of the audio WRAM and the readable sound registers after every `UpdateSound`: every song for 20 seconds, every sound effect through `PlaySFX` and `PlayStereoSFX`, every Pokémon's cry over music and from a battle side, fades in and out, the danger tone and sound effects over music with priority. All match. Deliberate mutations of the port (vibrato rounding, pitch slide steps, an envelope bit) make it fail.
|
||
- `game/audio/apu.ts` synthesizes the four channels from those register writes: both pulse channels (the first with its sweep), the wave channel, the noise channel's LFSR, envelopes and length counters on the 512 Hz frame sequencer, NR50/NR51 mixing and the output capacitor. Each sample averages the channels over its CPU cycles, against aliasing. `tests/reference-audio-synth.spec.ts` records the reference emulator's own APU output for five songs, a sound effect and two cries and compares it with the port's, window by window, by spectrum: the median similarity is 0.89 to 0.998.
|
||
- `game/audio/worklet.ts` runs the synthesizer in an AudioWorklet. The world runs the engine twice per tick, as VBlank does, and sends each frame's writes; the worklet plays one frame per 70224 cycles with a few frames of queue. Browsers start audio only from a user gesture: Play and the first button press unlock it. Pausing (another tab, the app in the background) suspends it; playing again restores the registers. Settings has Off, Quiet, Medium and Full, kept for this browser. OPTION's SOUND sets mono or stereo (`Music_StereoPanning`, the cries' battle side).
|
||
- `game/audio/sound.ts` holds the home routines the game calls: `PlayMapMusic`, `PlayMapMusicBike`, `FadeToMapMusic`, `RestartMapMusic`, `TryRestartMapMusic`, `ForceMapMusic`, `PlayTrainerEncounterMusic`, `PlayBattleMusic`, `PlayMonCry2`, `PlayStereoCry`. The map setup scripts' music follows the entry: warps fade in with `PlayMapMusicBike`, doors skip six frames and fade to the new map's song, connections fade, Continue plays at once. `GetMapMusic_MaybeSpecial`: Surf's music, the contest gates' ranking music, the Mahogany shop and the Radio Tower while the Rockets hold them.
|
||
- Scripts: `playmusic`, `musicfadeout`, `playmapmusic`, `dontrestartmapmusic`, `cry` and `waitsfx` (which wait, frozen, as the original's busy loops do), `playsound`, `warpsound`, `specialsound` and `encountermusic`; the specials `RestartMapMusic`, `FadeOutMusic`, `PlayMapMusic`, `WaitSFX`, `PlaySlowCry`, `PlayCurMonCry` and `HealMachineAnim` (a sound per Poké Ball, MUSIC_HEAL, and its 30-frame and 80-frame waits); the text commands' fanfares (`TextSFX`). Text is shown a page at a time, so a sound started after text plays when its page appears.
|
||
- Battles: `PlayBattleMusic`'s choice (wild by time and region, Rockets, gym leaders, rivals, the Champion, the roaming beasts), the wild Pokémon's and each sent-out Pokémon's cry from its side, fainting (`SFX_KINESIS`, `SFX_FAINT`, the player's cry), `PlayVictoryMusic`, the capture music, `SFX_RUN`, the low HP tone (`CheckDanger` with `ComputeHPBarPixels`) and the map's music again after `reloadmapafterbattle`.
|
||
- Elsewhere: the intro's music and Wooper's cry, the talk and menu clicks (`PlayTalkObject`, `PromptButton`, `MenuClickSound`), `SFX_MENU`, bumping into walls, ledges, warps, Strength's boulders, the Bicycle's and Surf's music, the evolution music, cries and jingles, the Pokédex's cries and CRY, the PCs' boot, choice and shutdown sounds, the marts' transactions and the healing items' sounds.
|
||
- Not presented yet: move animations' sounds (there are no move animations), the experience bar, Poké Ball throws and wobbles, the Game Corner's and Unown puzzle's sounds, the battle transition, and the main menu and title music (there is no title screen). Physical phones are not tested (manual card).
|
||
|
||
## Implemented: the phone's trainers, random calls and the radio (board #30)
|
||
|
||
- Every registered trainer's callee and caller scripts run (`engine/phone/scripts`): their news, bragging and lost-Pokémon stories (`RandomPhoneMon`, `RandomPhoneWildMon`, `RandomUnseenWildMon` with its morning-list bug), rematch requests, gift offers, swarms (`swarm`, `ActivateFishingSwarm`), Kenji's break (`VAR_KENJI_BREAK`, a 3-6 day countdown at the daily reset) and Buena. New commands: `swarm`, `gettrainerclassname`; `readvar` reads the Blue Card balance, Buena's password and Kenji's break, and `writevar` sets the balance. A sweep runs every contact's callee and caller scripts under many flag and clock states without a refusal.
|
||
- Rematches: the trainers' fight counts (`w*FightCount`) are saved, and every registered trainer on an open map battles again, gives gifts or talks when ready (`tests/phone.test.ts` drives all of them through five fight counts). `CheckDailyResetTimer` now also clears the daily rematch, phone gift and phone time-of-day flags.
|
||
- Random calls (`CheckTimeEvents`' `CheckPhoneCall`): not on a doorway, after `CheckReceiveCallTimer`'s 20, 10, 5 then 3 minutes (restarted at every load, as `StartMap` does), half the time, with service, from a registered caller whose calling hours include now and who is not on this map. `Script_ReceivePhoneCall` rings twice (`RingTwice_StartCall`: `SFX_CALL` with the caller's name box shown, cleared and shown again), runs the caller script and hangs up with `SFX_HANG_UP`. Mom's and Bill's calls name their caller too. Outgoing calls ring with `SFX_CALL`, and `SFX_NO_SIGNAL` without service.
|
||
- Swarms: Dunsparce's and Yanma's grass swarms use `SwarmGrassWildMons` on the map a trainer named (`_SwarmWildmonCheck`), and the Qwilfish and Remoraid fishing swarms switch the fish group (`GetFishGroupIndex`).
|
||
- The radio (`game/scripts/radio.ts`): `PlayRadioShow` and every station frame by frame: Oak's Pokémon Talk (routes, Pokémon, adverbs and adjectives, the Pokémon Channel jingle), the Pokédex Show, Pokémon Music and Let's All Sing (march or lullaby by weekday), the Lucky Channel (with `CheckLuckyNumberShowFlag`), Places and People, Buena's Password (from 6 PM, the day's password; midnight and off-air texts), Team Rocket's broadcast during the takeover, and the Unown, Poké Flute and Lake of Rage stations' music. Lines show for 100 frames and scroll as `PrintRadioLine` and `RadioScroll` do. Station songs become the map music and keep playing after the radio is left, as in the original (`ExitPokegearRadio_HandleMusic`).
|
||
- The Pokégear's radio card runs every frame: up and down turn the knob by two, `RadioChannels` picks the station the place can hear (Johto, the Ruins, the Lake of Rage area, Kanto with the EXPN card). House radios (`Radio1Script`, `Radio2Script`) run `MapRadio`: the station's name in quotes for 100 frames, then the station until A or B.
|
||
- Buena's show at the Radio Tower: `AskRememberPassword`, `BuenasPassword` (the category's three words, B disabled) and `BuenaPrize` (prizes and points beside the Blue Card balance, confirm, pay, receive). Tests: `tests/phone.test.ts` (rematches, a random call, no call without a caller, the daily reset, swarms, each station's lines and music, the radio card, a house radio, Buena's password and prizes) and `tests/radio-browser.spec.ts` (the radio card in the browser).
|
||
- Not ported: the radio card's tuning dial graphics (the frequency is shown as text), holding up or down to turn the knob continuously, the Poké Flute channel waking Snorlax (Kanto is closed), and calls that use the mobile adapter.
|
||
|
||
## Implemented: save slots (board #37)
|
||
|
||
- `storage/slots.ts`: the native database (`crystal-native-port`) moves to IndexedDB version 2 with `slots` (durable ID, ROM hash, name, created/updated time, newest generation, and a display summary), `records` (keyed by slot and generation) and `meta` (the slot last played per ROM, the migration record). The version-1 `saves` store is copied into "Save 1" inside the upgrade transaction itself, so it happens exactly once (an aborted upgrade retries; a committed one never runs again); the record is copied byte for byte and the old store is left untouched.
|
||
- Writes are atomic: one transaction adds generation n+1, drops generations below n and updates the slot. Each record carries a CRC-32 of its fields and state. Loading takes the newest intact record that the game accepts, falling back to the previous one (reported to the player); the next write supersedes the damaged one.
|
||
- Versioning: slots carry `version: 1`, records the existing envelope (`version: 1`, `coreId`) and the game state its schema (1-3, migrated by `migrateSave`). A slot whose model or newest record is newer is listed, exportable and deletable, but never opened, copied or written.
|
||
- `app/slot-session.ts` (`SlotSession`, native only; the reference keeps `GameSession`): load (last played slot, otherwise the newest readable one, otherwise a new game in a new slot), save into the slot captured with the snapshot (an autosave queued before a switch lands in its own slot), switch, new game, rename (1-24 characters), duplicate, delete (not the running slot), export, and import. Switching, new games and imports save the running game first and restore it if the target cannot be opened. `NativeRuntime.reset()` rebuilds the world from the loaded ROM so nothing carries over between slots.
|
||
- Import preview (`previewImport`) checks the envelope, cartridge, player, base64 and the game state in a scratch world (`NativeRuntime.inspectState`) before anything changes, and shows the trainer and ID, place, play time, badges, caught count, saved time, the game data version (and its upgrade) and the slots of the same trainer, marking one with identical progress. The player adds the backup as a new save or replaces the running save or a conflicting one; a replaced slot keeps its previous record.
|
||
- Tests: `tests/save-slots.test.ts` (migration once, a newer singleton kept, slot lifecycle, autosave isolation, recovery from a damaged or missing newest record, an interrupted write, rollback when a slot cannot be opened, rejected imports, conflicts and both import targets), `tests/native.test.ts` (`describe` on saves of schema 1, 2 and 3) and `tests/save-slots-browser.spec.ts` (the migration and every Savegames action in Chromium and WebKit).
|
||
|
||
## Implemented: MAIL stationery and printer cancellation
|
||
|
||
- `ReadAnyMail` now draws all ten stationery types from the ROM, including their color planes, palettes, message/author positions and the mirrored Portrait MAIL picture (Unown A). A/B return; Start opens printing, and B cancels back to the unchanged letter. The accessible message and author remain available with the canvas.
|
||
- Nonempty Bill's PC boxes, Pokédex PRNT, MAIL and the Photo Studio share `SendScreenToPrinter`'s absent-printer result (`PRINTER_ERROR_2`). A does not dismiss the error; B cancels. Empty boxes still show their original refusal. The print preview, printer music and alternating link-check status are not presented. Unown stamps and the diploma remain unreachable with the unported Unown mode and Kanto maps.
|
||
- Controlled cartridge comparisons cover all ten stationery tilemaps, used graphics, message/author placement and palettes; the font is loaded separately from ROM. Chromium and mobile WebKit checks cover writing, reading, printing and returning to MAIL. Unit checks cover the four reachable print paths and unchanged stored data.
|
||
|
||
## Implemented: Day-Care and breeding (board #29, egg creation)
|
||
|
||
- `DayCareMan`, `DayCareLady`, `DayCareAskDepositPokemon`, withdrawal pricing/text, deposits and `RetrieveBreedmon` run through the ROM map scripts. The last Pokémon, last usable Pokémon, Eggs and MAIL holders are refused; retrieval restores HP/PP, learns intervening moves and charges ¥100 plus ¥100 per gained level. The cartridge's loss of experience beyond the retrieved level is preserved.
|
||
- `CheckBreedmonCompatibility`, `DayCare_InitBreeding`, `InitEggMoves`, `GetHeritableMoves`, `GetBreedmonMovePointer` and `DayCareStep` preserve Ditto, gender, egg-group, matching-DV and trainer-ID rules, Nidoran species selection, inherited DVs/moves, random-byte order and egg-chance thresholds. The Day-Care gains experience on counted steps. Route 34's callbacks place the man inside/outside and show deposited species using ROM party icons; its compatibility texts and egg collection/full-party/refusal paths run.
|
||
- `GiveOddEgg` selects the ROM probability row and copies its species, DVs, moves, stats, name and OT; the Egg Ticket is consumed when held. The stored parents, prepared egg, active/compatible bits, step countdown and mother index live in version-3 saves, migrate by absence and are validated alongside the three engine flags. A refused egg follows the cartridge script's flag-clearing behavior. Tin Tower ownership deliberately continues to omit deposited Pokémon: `engine/pokemon/search_owned.asm` labels this an original bug.
|
||
- Validation: 47 Day-Care unit cases plus the Tin Tower ownership regression; the full unit suite passes 916 tests (three skipped), with typecheck and pinned reference-address verification passing; six Chromium reference checks compare compatibility, level growth, egg construction, step experience/chance, retrieval and Odd Egg results, including RNG-byte consumption. These execute the cartridge routines with only Random replaced by controlled bytes. They do not establish frame-perfect keeper UI/audio or fence-sprite OAM/palette parity. Hatching and the full `CountStep` sequence follow below.
|
||
|
||
## Implemented: walking counters and Egg hatching
|
||
|
||
- `CountStep` runs after coordinate events and before random encounters. Special calls and an expired Repel consume the step event first. The saved byte counter wraps every 256 steps; `StepHappiness` increases non-Egg happiness on alternate wraps (512 steps), saturating at 255. At byte 128, `DoEggStep` decrements Eggs in order and stops at the first reaching zero; an already-zero byte wraps to 255. `DoBikeStep` counts eligible Bicycle steps and queues the Bike Shop call from 1024 steps. `DayCareStep` follows the Egg check, as in the original, so the step that starts a hatch gives the Day-Care nothing; overworld poison damage/fainting remains unported.
|
||
- `HatchEggs` hatches every zero-cycle Egg in party order on a script draft: happiness 120, caught time/place at level 1, full recalculated HP, cleared status, player OT/ID, seen/caught registration, Togepi's event, and the ROM nickname question. Mid-hatch saves rewind the triggering tile and counters; failed presentation restores the step and party. Save format 3 gained `stepCount`, `poisonStepCount`, `happinessStepCount`, `bikeStep`, defaulting to zero in older saves.
|
||
- The ROM's Egg and hatchling pictures, palettes, 344 wobble frames, three cracks and ten shell fragments run on a realtime tile screen. Horizontal scrolling extends the existing screen renderer. `tests/reference-hatching.spec.ts` checks all wobble OAM/tilemap/attribute/scroll frames, shell OAM states and normal Togepi/shiny Pichu palettes against the original. Object palettes 0/1 remain the overworld colors, as `_CGB_Evolution` only reloads 2–7. Hardware loading waits and the final species-specific `AnimateFrontpic` are not reproduced; the hatchling holds its base frame and cries. A hypothetical Unown Egg uses letter A rather than the original's stale battle-DV letter bug.
|
||
- `tests/egg-hatching.test.ts` covers byte rules, 512-step happiness, walking Togepi cycles, multiple Eggs, caught data, naming, mid-hatch replay, rollback, expired Repel and migration; Beat Up tests include Eggs. The continuous playthrough now waits through realtime screens and hatches Togepi naturally. No physical-device or listening validation.
|
||
|
||
## Implemented: the Hall of Fame PC (board #29)
|
||
|
||
- `wHallOfFameCount` and `sHallOfFame` are saved as `hallOfFameCount` and `hallOfFameTeams` (newest first, at most 30 teams of up to six Pokémon with species, ID, DVs, level and nickname), migrated by absence to none and validated against each other. They are separate from `hallOfFame` (`STATUSFLAGS_HALL_OF_FAME_F`), which the Battle Tower, radio, Pokégear and marts read.
|
||
- `GetHallOfFameParty` (Eggs left out) and `AddHallOfFameEntry` (the oldest of 30 teams drops off, the count stops at 200) are ported as `addHallOfFameEntry`, but nothing calls it until the League's ceremony is ported.
|
||
- `PokemonCenterPC.ChooseWhichPCListToUse` picks `PCPC_POSTGAME` from the count, as the original does, so HALL OF FAME appears once a team is recorded. `_HallOfFamePC`, `LoadHOFTeam` and `DisplayHOFMon` show each Pokémon's page with its cry: A shows the next Pokémon, then the next team; Start skips to the next team; B returns to the PC. The original's unreachable "HOF Master!" title (it needs a count of 201) is kept.
|
||
- `tests/reference-hall-of-fame.spec.ts` writes 255 teams (every species, Unown, level 100, the Nidorans, win 200, all eight frames) into the cartridge's SRAM, runs its `_HallOfFamePC`, and compares the tilemap, attributes, graphics and palette with the port's page. `HOF_AnimateFrontpic` is the one routine stubbed: the port shows the picture at rest, without the `ANIM_MON_HOF` animation. `tests/hall-of-fame.test.ts` covers the records, the PC menu with and without them, browsing, and save, reload and migration.
|
||
|
||
## Native battles: current state
|
||
|
||
The battle core, ROM tables, encounter hooks, battle UI and whiteout hooks are present. Unit tests cover wins, escape, loss, experience, move learning and capture; reference tests compare damage, type effectiveness, stat calculation and experience curves. Browser tests drive a test-only wild encounter through the real UI: victory with level-up and move learning, escape, whiteout, catching with a nickname, keyboard and on-screen D-pad menu navigation, mid-battle backup restore, rollback of unsupported effects, switching through the party menu and a Potion from the Items pocket. Real Route 29 encounters now use the ROM's encounter tables. Every ball can be thrown; only the contest's Park Ball handling remains.
|
||
|
||
This is still not a complete new game. The Pokégear's radio, the Pokédex's area view, search and other modes, the League, Kanto and the postgame remain unfinished. The start menu opens the party menu and the pack, and Select uses the registered item, such as the Bicycle; the PCs store Pokémon, items and decorations. Surfing and terrain animation now have native implementations; Kanto's maps remain gated. Save slots, online Link, friends and real-world steps come later.
|
||
|
||
## 1. Complete the overworld foundation
|
||
|
||
- [x] Map headers for all 26 groups, location names, connection offsets and connected-border drawing
|
||
- [x] Walking, collisions, doors, stairs, ledges and directional warp mats
|
||
- [x] Coordinate events, scene scripts, map callbacks and scripted movement
|
||
- [x] NPC random walks and spins, trainer sight lines
|
||
- [x] Item balls, hidden items and fruit trees with the daily reset
|
||
- [x] Connected transitions for every enabled map (New Bark Town to Route 30)
|
||
- [x] Dynamic warps, elevators, ice, currents and forced movement
|
||
- [x] Native Surf, Cut and Strength interactions, cycling rules and boulder holes
|
||
- [x] Bicycle access from the pack or a registered Select, with keyboard and touch controls
|
||
- [x] Tile changes (`changeblock`, `changemapblocks`) with rollback and minimap refresh
|
||
- [x] Room decorations, ownership, selection, removal, save validation and migration
|
||
- [x] Additional NPC movement types, variable sprites, Pokémon icons and movement-command decoding
|
||
- [x] Expanded original-engine coverage for scripted NPC poses and movement
|
||
- [x] Tileset animation, grass sprite priority and special map palettes
|
||
- [x] Restless forest animation and broader palette/animation reference comparisons
|
||
- [x] Reference comparison of player turn timing
|
||
- [x] Acceptance: outdoor, interior, cave and connected-map traces match the reference engine
|
||
|
||
Movement now covers turn-in-place delays, bumps, ice, currents, forced tiles,
|
||
dynamic return warps, elevator menus, pits, Surf, Cut, Strength and cycling
|
||
rules. Map block changes use a working copy and roll back with a refused script.
|
||
Local-ROM tests exercise elevator travel, Burned Tower pits, Ice Path boulders,
|
||
field moves and cave/water encounters without enabling those later areas.
|
||
|
||
Fourteen original-engine traces cover outdoor turns and bumps, connections in
|
||
both directions, ledges, house stairs and doors, the teacher's follow scene,
|
||
Ice Path, and eight controlled movement sequences: spin, teleport out/in,
|
||
skyfall, skyfall departure, tree shake, fishing, flicker and spin-flicker. They compare positions, facing,
|
||
selected actors and event flags. Map-entry ticks compare the destination
|
||
against the original's completed transition; other ticks compare directly.
|
||
Pose checks compare direction, visibility and vertical offset during scripted
|
||
movement, excluding blocking-text boundaries and subsequent random NPC behavior.
|
||
|
||
Tileset animation runs from ROM command tables on a private graphics copy.
|
||
Johto, Ice Path, restless Ilex Forest and Tin Tower animation each match 96
|
||
original VRAM frames. All 36 tilesets run for 2,048 frames without modifying
|
||
cached graphics. An executed original palette routine matches 192 native
|
||
cases. These checks establish sampled engine behavior, not exhaustive
|
||
pixel-perfect screenshots or full Celebi story support.
|
||
|
||
The bedroom PC selects and puts away owned decorations, including left/right
|
||
doll placement. Room blocks, sprites and descriptions reflect saved choices.
|
||
The Town Map poster opens the native ROM-backed map. Start opens the start
|
||
menu and Select the registered item; using the Bicycle follows the native
|
||
outdoor/indoor rules. Browser
|
||
checks cover both controls and decoration flows in Chromium and WebKit.
|
||
Full item/Pokémon PC storage and the other bag pockets belong to later work.
|
||
|
||
The foundation milestone is complete within these acceptance boundaries.
|
||
Next: finish the opening-story milestone below. Later maps remain gated until
|
||
their scripts and story progression are supported.
|
||
|
||
## 2. Opening story and first battle slice
|
||
|
||
- [x] Native player, party, Pokémon, bag, money and quest models with versioned saves
|
||
- [x] Typed ROM decoders for species, moves, growth rates, items, encounters and trainers
|
||
- [x] Decoder for evolutions, and evolution after battles and from items (see section 3)
|
||
- [x] Mom, Elm's introduction, the starter, naming and the aide's Potion
|
||
- [x] Route 29, Cherrygrove City, the Poké Mart and the Pokémon Center
|
||
- [x] Route 30's trainers, Mr. Pokémon's house, the Mystery Egg and the Pokédex
|
||
- [x] The Cherrygrove rival battle, Elm's call and the return to Elm
|
||
- [x] Capture and the catching tutorial
|
||
- [x] Battles: stats, damage, types, accuracy, criticals, status, turn order, fainting, experience, move learning, trainer AI, trainer send-outs and prize money
|
||
- [x] Player switching and every non-ball battle item, with the stats screen
|
||
- [x] The PC and boxes (catching with a full party)
|
||
- [x] The introduction's naming and clock screens
|
||
- [x] Controlled-seed comparisons with the original for accuracy, criticals and damage variation, including random-stream consumption. Damage, types, stats and experience curves are also compared.
|
||
- [x] Controlled-input comparisons of capture odds, success and ball shakes with the original, including random-draw counts. Hardware RNG timing and the full capture sequence remain outside this comparison.
|
||
- [x] Acceptance: one continuous playthrough of the opening quest with save/reload (`tests/playthrough.test.ts`, driven through the world's inputs rather than the browser UI)
|
||
|
||
Introduce native player, party, Pokémon, inventory, money and quest models. Read species, moves, growth curves, evolutions, encounters, trainers and items through typed ROM decoders. Define serialization/migrations before persisting new systems.
|
||
|
||
Expand script calls, choices, scene state, inventory commands and native special handlers. Translate routines reached through assembly calls into explicit native services; never fall back to emulator execution in the shipping game.
|
||
|
||
Meeting Mom, Elm's introduction, the starter, the aide's Potion, Route 29, Cherrygrove, Route 30's trainers, Mr. Pokémon's house, the Cherrygrove rival battle, the return to Elm, capture, the catching tutorial, the PC, the introduction's naming and clock screens, and switching and healing items in battle run now. The opening milestone is complete within the recorded acceptance boundaries. The start menu, party menu and pack now run outside battle, and Pokémon evolve. Field moves from the menu, SEL/Select and MOVE PKMN W/O MAIL run too, and the Pokégear's clock, map and phone cards, with calls to Mom and Elm, Mom's bank at home and her purchases, the trainer card with the play time, the options, Fly, Dig, Teleport and the Escape Rope, and Headbutt and Rock Smash with their encounters, every remaining field item, the fishing rods, MAIL, moving items with Select and the MOVE screen's other Pokémon. Kurt's balls are thrown too, and the battle engine handles every special battle type (board #28 is complete). Route 31, Violet City, Sprout Tower and Falkner's gym are open (board #31), and Route 32 to Route 34 with the Ruins of Alph, Azalea Town, Bugsy's gym and Ilex Forest (board #32, in progress). The synthetic CI suite runs on the shared server runner (board #45): 143 unit tests and 10 Chromium/WebKit browser tests passed in [run 119](https://git.azuze.fr/kawa/Crystal/actions/runs/119), with ROM-dependent tests skipped. Both builds, vendor integrity and native release isolation passed too. Next roadmap milestone: the remaining Johto cities (board #32); check current board priorities before selecting work.
|
||
|
||
Complete and validate the native battle engine: deterministic randomness, stats, move selection, accuracy/critical checks, damage, status, turn order, switching, fainting, experience, capture and rewards. Compare controlled seeds and states with the original, including integer rounding and overflow behavior.
|
||
|
||
Acceptance: finish the opening quest, including encounters and the rival battle, party/inventory changes and save/reload, without emulation or manually setting story flags.
|
||
|
||
## 3. Complete Crystal
|
||
|
||
- [x] Pokémon Center healing, standard Poké Marts and Reflect/Light Screen
|
||
- [x] Remaining battle commands, move and item effects, AI layers and special battles (board #28)
|
||
- [x] Evolution by level, happiness, Tyrogue's stats and stones, with Rare Candy and vitamins (trade evolutions need Link)
|
||
- [x] Breeding, eggs and the rest of PC storage (board #29)
|
||
- [ ] Field moves, gyms, Rocket events, the phone and Pokégear, puzzles, minigames, daily events, roaming Pokémon and the postgame
|
||
- [x] Johto beyond Route 30: every city, gym, Rocket event, puzzle and minigame to the Rising Badge, Tin Tower, Whirl Islands and the Battle Tower
|
||
- [ ] The League, Kanto and the final battle
|
||
- [x] Music and sound through Web Audio (board #34; move animations and minigame sounds remain)
|
||
- [ ] Acceptance: main-game and postgame playthroughs, battle regression vectors, long sessions and save recovery
|
||
|
||
Port remaining battle commands, move/item effects, AI, special battles, evolutions, breeding, happiness, eggs and PC storage. Gen II behavior is the baseline; list intentional changes separately.
|
||
|
||
Complete field moves, gyms, Rocket events, phone/Pokégear, shops, healing, puzzles, minigames, daily events, roaming Pokémon and postgame. Cover Johto, the League, Kanto and the final battle. Track unsupported script/special handlers and add fixtures before translating each one.
|
||
|
||
Port the music/sound sequencer and synthesize its channels through Web Audio, reading music, effects and wave data from the local ROM. No recordings or CPU emulator are needed in the release. Verify timing and audio unlocking on phones.
|
||
|
||
Acceptance: main-game/postgame playthroughs, representative optional content, battle regression vectors, long sessions, clock rollover, interruption and save recovery. No unknown command is silently skipped. The full port is not complete until these checks pass.
|
||
|
||
## 4. Modern mobile presentation and delivery
|
||
|
||
- [x] Crystal-style shell with Game, Savegames, Map, Pokédex, Link and Friends tabs
|
||
- [x] Adaptive canvas, live local minimap, keyboard and on-screen controls
|
||
- [x] Save, backup export and import for the single native save
|
||
- [x] Touch battle commands, move list, PKMN party menu, PACK (Items and Balls pockets) and naming fields
|
||
- [x] Start menu, party menu and pack outside battle (switching, moves, held items, healing and PP items, tossing, the Bicycle)
|
||
- [x] Bill's PC: MOVE PKMN W/O MAIL between the party and every box
|
||
- [x] Pokégear clock, map, phone and radio cards
|
||
- [x] Mom's bank: the amount box with its digit cursor, on keyboard, D-pad and on-screen buttons
|
||
- [x] Trainer card: both pages, with the ROM's card picture, leader faces and animated badges
|
||
- [x] Options screen, with the battle style and menu account in effect
|
||
- [x] Pokédex screen: New mode list and entries
|
||
- [x] Scalable text and configurable keyboard controls
|
||
- [ ] Fuller accessibility: announcements for every native screen and touch control remapping
|
||
- [x] Save slots with migration, atomic writes and import previews
|
||
- [x] Installable web app with offline app shell, safe updates and quota handling
|
||
- [ ] Acceptance on physical iPhone and Android devices
|
||
|
||
The first presentation increment replaces the landing page and handheld frame with a restrained Crystal-style interface. Game, Savegames, Map, Pokédex, Link and Friends stay above a viewport-filling native canvas. The renderer draws more world tiles as space grows instead of stretching a 160 × 144 image. The game and Map tab share a live local minimap; the atlas and unsupported tabs remain honest planned states. The linked UI reference could not be inspected through automated access because the site returned a Cloudflare challenge, so exact screenshot matching is not claimed.
|
||
|
||
Build touch interfaces directly over native models: party, pack, battle commands, PC and Pokégear. Keep UI separate from gameplay calculations. Add scalable text, configurable controls and accessible focus/announcements.
|
||
|
||
Replace the single logical native save with real slots only after party, inventory and story state have stable schemas. Each slot needs a durable ID, display metadata, independent autosave and explicit create, load, rename, duplicate, export, import and delete flows. Version every persisted model. Migrate the current singleton save into a default slot exactly once, preserve unknown or newer backups without rewriting them, and use atomic writes with recovery from the last valid record. Import must preview identity, version and conflicts before replacement.
|
||
|
||
Dependencies: stable serialization for every system stored in a slot, migration fixtures for every released schema, quota/error handling and a transaction boundary that also covers future Link and walking credits.
|
||
|
||
Acceptance: migrate existing browser data without loss, keep several independent playthroughs, recover from interrupted writes and reject incompatible or corrupt imports without changing a valid slot. Test create/load/delete/rename/duplicate, autosave isolation, export/import conflicts and upgrades from every released schema.
|
||
|
||
Add PWA installation/offline caching of the app shell only. ROM/data retention remains a separate explicit local preference; otherwise reselect the ROM after reopening. Add safe application updates, schema migrations, storage persistence/quota handling and portable backups. Investigate original `.sav` import only with verified structure, checksums and RTC conversion.
|
||
|
||
Acceptance: installation, offline startup, portrait/landscape, touch/audio, background/resume and updates on physical iPhone/Safari and Android/Chrome. Browser automation does not establish physical-device compatibility.
|
||
|
||
## 5. World map and Pokédex
|
||
|
||
- [x] Current-area minimap in the game and Map tab
|
||
- [x] Seen and caught sets saved from real game state
|
||
- [x] Johto/Kanto atlas with visited areas and Fly destinations
|
||
- [x] Start menu Pokédex and Pokédex tab with ROM-derived records
|
||
- [ ] Habitat (AREA) view from the encounter tables
|
||
- [ ] Old and A to Z modes, type search and Unown mode
|
||
|
||
Promote the current-area minimap into a Johto/Kanto atlas after all map groups, connections and location names can be decoded from the selected ROM. Derive region geometry, visited areas, player location and Fly destinations from native map and story state. Do not ship copied map artwork or claim navigation through maps whose collisions and events are still unsupported.
|
||
|
||
Build the Pokédex from typed ROM decoders for species names, base data, evolutions, moves and graphics. Seen, caught, forms and habitat or area views must come from native game state and encounter tables. Keep ROM-derived records unavailable until a verified ROM is selected.
|
||
|
||
Dependencies: complete map indexing and connections, stable player/story flags, party and capture flow, encounter decoding, species graphics and save migrations.
|
||
|
||
Acceptance: atlas positions and Pokédex records match pinned PRET fixtures and local-ROM comparisons; visited, seen and caught state survives slot switching, backup round trips and schema upgrades. The UI must distinguish decoded data from progress the player has actually unlocked.
|
||
|
||
## 6. Internet Link and friends
|
||
|
||
- [ ] Not started. Link and Friends tabs show their planned scope only.
|
||
|
||
Implement a game-level protocol over native state. This architecture does not need to tunnel serial timing or synchronize GB CPU cycles.
|
||
|
||
Separate `LinkTransport`, `TradeSession` and `BattleSession`. Start with local peers, then private invite rooms using signaling and WebRTC data channels or a relay. Negotiate compatible schema/game versions and exchange only required game data. Never transmit ROMs or extracted assets.
|
||
|
||
Trades need durable preparation, a transaction ID, agreed completion, idempotent recovery and pre-trade backups for both players. Do not retry ambiguous completion automatically. Battles need deterministic state, agreed randomness, turn commands, state checks and a disconnect policy. Define validation, room expiry and persistence before public service access.
|
||
|
||
Add a separate account and relationship layer before Friends becomes active. Define a stable public identity that does not expose save-slot or ROM data. Support private invite creation, acceptance and expiry; friend removal; user blocking; and abuse reporting if user-generated text is ever added. Presence is opt-in, coarse and independently disableable. Privacy controls must cover discoverability, invite permissions and online status. Blocking must prevent new invites, presence disclosure and Link sessions in both directions.
|
||
|
||
Dependencies: native party, PC, battle and save-slot transactions; a versioned wire protocol; authenticated identity; signaling/relay operations; account recovery and deletion; rate limits, moderation operations and a published privacy policy. Link and Friends remain unavailable until these dependencies have production ownership and failure handling.
|
||
|
||
Acceptance: cross-network trades/battles and induced duplicate messages, latency, disconnects and reconnects at every phase, without duplication or lost Pokémon. Verify invite expiry, simultaneous relationship changes, remove/re-add, block/unblock, hidden presence, account deletion and attempts to join with incompatible game or schema versions.
|
||
|
||
## 7. Optional Pokéwalker-style egg assistance
|
||
|
||
- [ ] Not started. Depends on eggs, hatching and a native companion app.
|
||
|
||
This optional mobile feature uses real-world walking to assist egg hatching after native party, breeding, eggs and faithful in-game hatching are complete. The pinned Crystal engine decrements party egg counters from overworld walking in [`CountStep`](https://github.com/pret/pokecrystal/blob/7a7881d0d62e0ddbd82dcf10e7116807487ac651/engine/overworld/events.asm#L866-L900) and [`DoEggStep`](https://github.com/pret/pokecrystal/blob/7a7881d0d62e0ddbd82dcf10e7116807487ac651/engine/pokemon/breeding.asm#L174-L196); battles do not advance them. A battle-based bonus would therefore be an explicit, separately enabled extension rather than Crystal behavior.
|
||
|
||
Separate a platform `StepProvider`, local activity ledger and reward rules. Request read access to steps only, after an explicit explanation and opt-in. Keep real-world steps distinct from the in-game tile counter, show what interval will be credited and provide a disconnect/delete control. Do not request distance, heart rate, workouts or permission to write health records. Keep raw activity data on device by default.
|
||
|
||
An ordinary PWA cannot directly query Apple HealthKit, Core Motion pedometer history, Android Health Connect or Android step sensors. Reliable pocket and locked-screen behavior therefore needs a native companion or wrapper and a narrow bridge into the web game. On Apple platforms, evaluate read-only HealthKit step counts and Core Motion `CMPedometer`; HealthKit requires the capability, a purpose string and per-type authorization, while Core Motion exposes historical pedometer queries. On Android, prefer Health Connect `READ_STEPS` where available and evaluate `TYPE_STEP_COUNTER` with `ACTIVITY_RECOGNITION` as a device-local fallback. Handle absent hardware, unavailable services, denied or revoked access and partial history without blocking the game.
|
||
|
||
Normalize provider records into immutable local intervals with provider/source IDs, start/end times and step totals. Deduplicate overlapping samples before reward calculation. Commit each credit as one transaction containing a unique credit ID, the source interval set, the target slot and egg identity, the applied hatch-counter delta and resulting state version. Persist the ledger and game mutation atomically. Restoring an older backup must not make an already consumed interval eligible again; trading, moving or hatching an egg must close or retarget pending credits explicitly; wall-clock or timezone changes must not create new credit. Sensor callbacks only append provider observations, never mutate game state.
|
||
|
||
The first reward is bounded egg-hatch assistance for an egg currently in the party. Define and test the conversion rate and daily cap before release, never reduce an egg below the normal hatch trigger outside a safe game checkpoint, and show the pending effect before applying it. No party or no eligible egg means steps remain unspent or expire according to a documented rule; do not silently redirect them to another reward.
|
||
|
||
Store distribution is a release dependency, not an assumption. Apple says HealthKit must be used for health and fitness purposes and its review rules restrict health and Motion data use, so a game-only reward may not qualify for HealthKit/App Store approval. Google Play lists health-integrated games as an approved health-permission use case, but still requires a Health apps declaration and review of the requested permissions. Recheck both policies before implementation and submission. Core Motion or a device step sensor may reduce health-store scope, but still requires native permissions and review.
|
||
|
||
Dependencies: native party, breeding, eggs and hatching; stable egg identity; save-slot transactions and migrations; a native iOS and Android host; permission/privacy UI; and confirmed store eligibility for the chosen provider.
|
||
|
||
Acceptance: on supported physical iPhone and Android devices, a real walk with the phone locked is read with steps-only consent, credited to an eligible egg exactly once after reopening, and safely reaches the normal hatch flow. Exercise duplicate and overlapping samples, provider corrections, permission revocation, device reboot, offline use, timezone and clock changes, backup restore, slot switching, trade, egg movement and hatch between observation and credit. Denial, unsupported hardware and failed review eligibility leave the base game fully usable.
|
||
|
||
Official platform references: Apple [HealthKit authorization and capability setup](https://developer.apple.com/documentation/xcode/configuring-healthkit-access), [Core Motion pedometer history](<https://developer.apple.com/documentation/coremotion/cmpedometer/querypedometerdata(from:to:withhandler:)>) and [App Review Guidelines 2.5.1 and 5.1.3](https://developer.apple.com/app-store/review/guidelines/); Android [Health Connect step access](https://developer.android.com/health-and-fitness/health-connect/features/steps), [step counter sensor guidance](https://developer.android.com/develop/sensors-and-location/sensors/sensors_motion) and Google Play [Android health permissions guidance](https://support.google.com/googleplay/android-developer/answer/12991134?hl=en).
|
||
|
||
## Working method
|
||
|
||
For each subsystem: identify PRET routines/data, implement native code behind a narrow interface, compare controlled inputs against the reference, then integrate a playable scenario. Retain synthetic fixtures for public CI. Keep real-ROM state, graphics, audio, screenshots and reference builds in ignored local locations.
|
||
|
||
See [NATIVE_PORT.md](NATIVE_PORT.md) for the current routine map and verification commands.
|