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

+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.