Play the music and sound effects from the ROM's sound engine (Closes #34)

The sound engine (audio/engine.asm, home/audio.asm) is ported on a byte
copy of its audio WRAM and a sound register model. A reference test runs
the cartridge's routines beside it and matches the WRAM and registers
after every UpdateSound for all songs, effects and cries, fades, the
danger tone and effect priority. apu.ts synthesizes the four channels in
an AudioWorklet; its output matches the reference emulator's APU by
spectrum. The world runs the engine twice per tick; Play or the first
press unlocks sound, pausing suspends it, Settings sets the volume.

Wired in: map music by map setup and special map music, script audio
commands, text fanfares, WaitSFX and cries that hold the script frozen
(the last page stays shown), battle music, cries, fainting, victory,
capture and the low HP tone, Pokémon Center healing, evolution, the
intro, the Pokédex's cries, PCs, marts, healing items, talk and menu
clicks, bumps, ledges, warps, boulders, the Bicycle and Surf.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kawaandClaude Opus 5.5 committed 2026-10-15 15:54:02 +02:00
1 parent aff8391e55
commit 7f30c3f450
34 files changed
+3961 -74

No files matched your search

+2 -1
View File
@@ -25,6 +25,7 @@ flowchart LR
| `src/game/scripts` | The game's event, movement and text formats and translated specials, no GB instructions |
| `src/game/rendering` | Planar pixels, metatiles, connection strips, adaptive camera, sprites, emotes and minimap |
| `src/game/party` | Party Pokémon generation, stat formulas, bag pockets and their validation |
| `src/game/audio` | The sound engine on its original WRAM, the home sound routines, and the Web Audio synthesizer |
| `src/game/runtime.ts` | 30 Hz tick scheduling from real time, input routing and presentation updates |
| `src/input` | Abstract buttons, multiple sources and browser bindings |
| `src/app` | Runtime-independent load/play/pause/backup lifecycle |
@@ -48,7 +49,7 @@ Asset caches and the ROM reader remain in memory for lazy extraction. Neither RO
The native viewport follows its available CSS dimensions and draws additional world tiles at a bounded pixel resolution. Beyond a map's edge it draws the connected map with the current tileset, as the original connection strips do, and the border block elsewhere. Resizing also redraws paused sessions. The local minimap caches terrain from the loaded map and updates its marker from the same world position used by the player; the Map tab shares that view. Below it, the Map tab draws the region atlas (`world/atlas.ts`): the ROM's Town Map with the player's landmark, the visited flypoints and the areas open in this port. Leaving Game pauses the session and releases held input; returning requires explicit play. The reference player retains its own fixed-screen styling.
`game/battle` now holds the native battle core and ROM-backed battle tables. Browser tests cover the overworld-to-battle flow using a test-only encounter fixture; Route 29 uses ROM encounter tables. Trainer battles share that core, with ROM trainer decoders, the trainer's send-out and switch AI, and explicit AI support checks. Map trainers (sight and talk) are started by `game/world` and run the ROM's own trainer scripts. Battles receive the script's draft party, bag and Pokédex, so a catch, a thrown ball or a nickname commits only with the script that started the battle. Introduce future modules with working features: `game/audio`, then `features/link` and `features/walking`. Party and bag models live in `game/party`; scripts change them only on their draft state. Rules must not import UI/browser modules. Transports and step providers must not write directly into world state.
`game/battle` now holds the native battle core and ROM-backed battle tables. Browser tests cover the overworld-to-battle flow using a test-only encounter fixture; Route 29 uses ROM encounter tables. Trainer battles share that core, with ROM trainer decoders, the trainer's send-out and switch AI, and explicit AI support checks. Map trainers (sight and talk) are started by `game/world` and run the ROM's own trainer scripts. Battles receive the script's draft party, bag and Pokédex, so a catch, a thrown ball or a nickname commits only with the script that started the battle. The world runs the sound engine twice per tick, as VBlank does; the runtime sends each frame's sound register writes to an AudioWorklet that synthesizes them (`game/audio/worklet.ts`), unlocked by the first user gesture and suspended while paused. Sound never changes game state; `waitsfx` and cries hold the script, frozen, while sound effects play, as the original's busy loops do. Introduce future modules with working features: `features/link` and `features/walking`. Party and bag models live in `game/party`; scripts change them only on their draft state. Rules must not import UI/browser modules. Transports and step providers must not write directly into world state.
The overworld extension keeps player travel mode, backup-warp data and Flash
+6 -3
View File
@@ -30,14 +30,17 @@ These paths refer to the pinned [PRET source](https://github.com/pret/pokecrysta
| `game/world/world.ts` | `engine/overworld/events.asm`, `player_movement.asm`, `warp_connection.asm`, `data/collision/collision_permissions.asm` | 30 Hz overworld ticks, 8-tick walks, 16-tick ledge jumps, directional mats, animated door/stair exits, connection coordinates, events, scenes and callbacks. NPC random walks and spins follow `HandleNPCStep`. Forced movement, Surf, Cut, Strength, cycling rules, pits and elevators implemented. Fourteen reference traces compare turn timing, actors and event flags; map-entry boundaries verify the completed destination. The Bicycle is used from the pack or with Select. Field moves chosen from the party menu are checked here (`menuFieldMove`). |
| Event interpreter | `macros/scripts/events.asm`, `engine/overworld/scripting.asm`, `engine/events/std_scripts.asm` | Resumable; yields text, choices, movement, emotes and pauses. Scenes, phone contacts, string buffers and object commands. Unknown commands and specials abort without committing. |
| Movement interpreter | `macros/scripts/movement.asm`, `engine/overworld/movement.asm`, `engine/overworld/map_objects.asm` | Turns, steps, slides, jumps (two tiles with the original arc), sleeps, facing locks, visibility and emotes. `follow` takes one tile toward the leader's previous square. All movement opcodes decode, including teleport, fishing, skyfall, shake and flicker poses; Controlled reference scenes check spin, teleport, skyfall, fishing and flicker direction, visibility and lift. |
| Specials | `data/events/special_pointers.asm`, `engine/rtc/timeset.asm`, `engine/overworld/decorations.asm` | Selected by routine address from the ROM table: `SetDayOfWeek`, initial DST flags, `RestartMapMusic` (no audio yet), room decoration toggles, `PokemonCenterPC` and `PlayersHousePC` (Bill's, the player's and Oak's PCs), the Town Map, `HealParty`, `HealMachineAnim` (presentation only), `CheckPokerus` and the `StubbedTrainerRankings_Healings` stub. |
| Specials | `data/events/special_pointers.asm`, `engine/rtc/timeset.asm`, `engine/overworld/decorations.asm` | Selected by routine address from the ROM table: `SetDayOfWeek`, initial DST flags, `RestartMapMusic`, room decoration toggles, `PokemonCenterPC` and `PlayersHousePC` (Bill's, the player's and Oak's PCs), the Town Map, `HealParty`, `HealMachineAnim` (its sounds and waits; the machine's sprites are not drawn), `CheckPokerus` and the `StubbedTrainerRankings_Healings` stub. |
| `game/scripts/pc.ts` | `engine/events/pokecenter_pc.asm`, `engine/pokemon/bills_pc_top.asm`, `engine/pokemon/bills_pc.asm`, `engine/events/prof_oaks_pc.asm`, `engine/events/std_collision.asm` | The Pokémon Center and house PCs: Bill's PC (deposit, withdraw, release, change and name boxes, MOVE PKMN W/O MAIL, stats), item storage, the MAIL BOX (`MailboxPC`) and Oak's rating. Menu strings and texts come from the ROM; lists are native. Printing is not ported. |
| `game/scripts/start-menu.ts` | `engine/menus/start_menu.asm`, `engine/pokemon/mon_menu.asm`, `mon_submenu.asm`, `party_menu.asm`, `switchpartymons.asm`, `engine/items/pack.asm`, `engine/items/item_effects.asm` | The start menu, the party menu with the Pokémon submenu (STATS, SWITCH, MOVE, ITEM) and the pack (USE, GIVE, TOSS) with the field item effects listed below, including Rare Candy, stones and vitamins. Menu strings, prompts and texts come from the ROM; screens are native. Field moves from the menu (checked by the world), Softboiled and Milk Drink, SEL and the Select button (`SelectMenu`), MAIL (`MonMailAction`, `ComposeMailMessage`) and `SwitchItemsInBag`. `party/mail.ts` holds the `mailmsg` struct. |
| `game/scripts/pokegear.ts` | `engine/pokegear/pokegear.asm`, `engine/phone/phone.asm`, `engine/phone/scripts/mom.asm`, `elm.asm`, `engine/rtc/print_hours_mins.asm` | `PokeGear` with each card's joypad handling: the clock (`Pokegear_UpdateClock`, `PrintHoursMins`), the map card's landmark cursor (`TownMap_InitCursorAndPlayerIconPositions`, `TownMap_GetKantoLandmarkLimits`), the phone list (`PokegearPhone_UpdateDisplayList`, `GetCallerClassAndName`, `PokegearPhoneContactSubmenu`, `CheckCanDeletePhoneNumber`) and calls (`MakePhoneCallFromPokegear`, `CheckPhoneContactTimeOfDay`, `GetMapPhoneService`, `HangUp`). Callee scripts run in the event interpreter on the start menu's draft. Screens are native; the radio's stations are not ported. |
| `game/scripts/trainer-card.ts` | `engine/menus/trainer_card.asm`, `engine/gfx/player_gfx.asm` (`GetCardPic`), `engine/gfx/cgb_layouts.asm` (`_CGB_TrainerCard`), `home/game_time.asm` | `TrainerCard`'s two reachable pages with their joypad rules, strings from `.Name_Money`, `.Dex_PlayTime` and `.Badges`, the caught count, and the badges from `TrainerCard_JohtoBadgesOAM` with their eight-frame steps. `rendering/trainer-card.ts` draws the card picture, leader faces and badges from the ROM with the card's palettes and attribute map. `world/game-time.ts` is `GameTimer` (capped at 999:59:59). Text is native; the blinking colon's and badges' phase start at the page load. |
| `game/scripts/pokedex.ts`, `game/rom/pokedex.ts` | `engine/pokedex/pokedex.asm`, `pokedex_2.asm` (`DisplayDexEntry`, `GetDexEntryPointer`), `pokedex_3.asm` (`LoadQuestionMarkPic`), `data/pokemon/dex_order_new.asm`, `dex_entry_pointers.asm`, `dex_entries/`, `gfx/footprints.asm`, `engine/gfx/cgb_layouts.asm` (`_CGB_Pokedex`) | New mode's main screen and entry screen with their joypad rules: `Pokedex_ListingHandleDPadInput`, `Pokedex_InitCursorPosition`, `Pokedex_NextOrPreviousDexEntry`, `Pokedex_MoveArrowCursor` and `Pokedex_Page`. Strings come from the ROM; screens are native. Tests compare the order, all 251 entries and every footprint with the PRET source. AREA, CRY, PRNT, the option and search screens, Old and A to Z modes and Unown mode are not ported. `ui/pokedex.ts` draws the screen and the Pokédex tab. |
| `game/scripts/pokedex.ts`, `game/rom/pokedex.ts` | `engine/pokedex/pokedex.asm`, `pokedex_2.asm` (`DisplayDexEntry`, `GetDexEntryPointer`), `pokedex_3.asm` (`LoadQuestionMarkPic`), `data/pokemon/dex_order_new.asm`, `dex_entry_pointers.asm`, `dex_entries/`, `gfx/footprints.asm`, `engine/gfx/cgb_layouts.asm` (`_CGB_Pokedex`) | New mode's main screen and entry screen with their joypad rules: `Pokedex_ListingHandleDPadInput`, `Pokedex_InitCursorPosition`, `Pokedex_NextOrPreviousDexEntry`, `Pokedex_MoveArrowCursor` and `Pokedex_Page`. Strings come from the ROM; screens are native. Tests compare the order, all 251 entries and every footprint with the PRET source. CRY and the entries' cries play; AREA, PRNT, the option and search screens, Old and A to Z modes and Unown mode are not ported. `ui/pokedex.ts` draws the screen and the Pokédex tab. |
| `game/world/atlas.ts` | `data/maps/landmarks.asm`, `data/maps/flypoints.asm`, `engine/pokegear/pokegear.asm` (`_FlyMap`, `CheckIfVisitedFlypoint`), `home/map.asm` (`GetWorldMapLocation`) | The Map tab's atlas: each region's landmarks with their Town Map positions and ROM names, the player's landmark, the visited flypoints and the landmarks with ported maps. Kanto follows the Fly map's Indigo Plateau rule. `rendering/town-map.ts` draws it. Tests compare positions and flypoints with the PRET source. |
| `game/scripts/options.ts` | `engine/menus/options_menu.asm`, `data/default_options.asm`, `engine/battle/core.asm` (`CheckWhetherToAskSwitch`), `engine/menus/start_menu.asm` | `_Option` with `OptionsControl` and each option's routine on the four option bytes, `DefaultOptions`, and the two settings consumed so far: `BATTLE_SHIFT` and `MENU_ACCOUNT`. Strings come from the ROM; the screen is native. |
| `game/scripts/options.ts` | `engine/menus/options_menu.asm`, `data/default_options.asm`, `engine/battle/core.asm` (`CheckWhetherToAskSwitch`), `engine/menus/start_menu.asm` | `_Option` with `OptionsControl` and each option's routine on the four option bytes, `DefaultOptions`, and the three settings consumed so far: `BATTLE_SHIFT`, `MENU_ACCOUNT` and `STEREO` (the sound engine's panning). Strings come from the ROM; the screen is native. |
| `game/audio/engine.ts` | `audio/engine.asm`, `home/audio.asm`, `audio/notes.asm`, `wave_samples.asm`, `drumkits.asm`, `music_pointers.asm`, `sfx_pointers.asm`, `cry_pointers.asm`, `data/pokemon/cries.asm` | The sound engine routine by routine, on a byte-for-byte copy of the audio WRAM ($c100-$c2c1) and a sound register model with read masks. `tests/reference-audio.spec.ts` compares the WRAM and readable registers after every `UpdateSound` with the cartridge's own routines: all 102 songs for 1200 frames, all 207 sound effects through `PlaySFX` and `PlayStereoSFX`, all 251 cries over music, fades, the danger tone and SFX priority. Mutation checks fail it. One branch is unreached by the data: a vibrato step that would carry exactly past $ff. |
| `game/audio/apu.ts`, `worklet.ts`, `output.ts` | Game Boy Color sound hardware (pandocs) | Pulse, sweep, wave, noise, envelopes, length counters and NR50/NR51, averaged per output sample, in an AudioWorklet fed one video frame of register writes per 70224 cycles. `tests/reference-audio-synth.spec.ts` compares the output with binjgb's APU by spectrum (median similarity 0.89-0.998 over five songs, a sound effect and two cries). Absolute loudness and the console's analog filtering are not matched. Unlocked by Play or the first press; suspended while paused. |
| `game/audio/sound.ts`, `battle-music.ts` | `home/audio.asm`, `engine/overworld/map_setup.asm`, `data/maps/setup_scripts.asm`, `home/map.asm` (`GetMapMusic`), `engine/battle/start_battle.asm` (`PlayBattleMusic`), `engine/battle/core.asm` (`PlayVictoryMusic`, `CheckDanger`, cries, fainting), `engine/pokemon/health.asm` (`ComputeHPBarPixels`), `data/trainers/leaders.asm`, `engine/overworld/landmarks.asm` (`RegionCheck`) | Map music by map setup, special map music, script and text sounds, battle music and sounds, the evolution's, the Pokédex's, the PCs', the marts' and healing items'. `tests/audio-world.test.ts` checks a wild battle's music, cries, victory and return to the map's music, the low HP tone, the Pokémon Center's jingle and waits, and a door's sound and fade. |
| `game/scripts/fly-map.ts` | `engine/pokegear/pokegear.asm` (`_FlyMap`, `FlyMap`, `TownMapBubble`, `CheckIfVisitedFlypoint`), `data/maps/flypoints.asm`, `engine/events/field_moves.asm` (`FlyFromAnim`, `FlyToAnim`), `engine/sprite_anims/functions.asm` | The Fly map's region choice (Kanto only after Indigo Plateau, without the player's icon otherwise), the default flypoint and up/down scrolling over visited flypoints (`wVisitedSpawns` through the ROM's `EngineFlags`). The flights follow `SpriteAnimFunc_FlyFrom` and `SpriteAnimFunc_FlyTo` frame by frame with the party icon. The Town Map, bubble and icons are drawn natively; the leaves are not presented. |
| `game/scripts/evolution.ts` | `engine/pokemon/evolve.asm`, `engine/pokemon/learn.asm`, `engine/movie/evolution_animation.asm`, `engine/pokemon/party_menu.asm` | `EvolveAfterBattle`/`EvolvePokemon` over the raw evolution bytes, the evolution texts and a timed, B-stoppable picture sequence, `UpdateSpeciesNameIfNotNicknamed`, `CalcMonStats` and the HP rise, `LearnLevelMoves`, `LearnMove`/`ForgetMove` outside battle and `PlacePartyMonEvoStoneCompatibility`. |
| `game/rom/pokemon.ts` | `data/pokemon/base_stats`, `evos_attacks.asm`, `data/moves/moves.asm`, `data/items/attributes.asm`, `engine/gfx/load_pics.asm` | Species, move and item names, base data, evolution lists, learnsets, move PP, item pockets, growth-rate exp and front pictures (`FixPicBank`, `PadFrontpic`). Starter pictures match rebuilt bytes; all 251 evolution lists match the pinned source. |
+15 -2
View File
@@ -24,6 +24,7 @@ The emulator-backed prototype is now at `/reference.html`. The default applicati
| 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) |
| 1. Complete the overworld foundation | Done |
| 2. Opening story and first battle slice | Mostly done |
| 3. Complete Crystal | In progress (Johto done) |
@@ -458,11 +459,23 @@ The numbered sections below keep their full scope. Each one opens with a checkli
- 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, the radio and phone ringing (board #30), and the main menu and title music (there is no title screen). Physical phones are not tested (manual card).
## 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, and audio 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.
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
@@ -555,7 +568,7 @@ Acceptance: finish the opening quest, including encounters and the rival battle,
- [ ] 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
- [ ] Music and sound through Web Audio
- [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.