Verify random battle and capture routines against the original engine
Checks / web (push) Canceled after 0s

This commit is contained in:
Sébastien QUEROL committed 2026-09-30 15:48:00 +02:00
1 parent be7575a4d0
commit 00972bad10
9 files changed
+978 -74

No files matched your search

+42 -2
View File
@@ -48,6 +48,26 @@ The opening scenes currently run end to end from the ROM: Mom's Pokégear, weekd
The same suite compares 400 damage cases and 400 type-effectiveness cases per browser against the original battle routines, 300 `CalcMonStats` cases (with and without stat experience, including levels above 100 that reach the 999 cap) and every `CalcExpAtLevel` value for the six growth rates and levels 1–100. Battle unit tests cover win, escape, loss, experience, level-up and move-learning flows, and `tests/playthrough.test.ts` plays the whole opening quest from a new game through `GameWorld`'s inputs, with save/reload after each stage. This establishes parity only for the compared routines and inputs. It does not prove complete gameplay, rendering, battles, audio, timing or physical-device parity. Extend controlled comparisons as each subsystem is translated.
`tests/reference-random.spec.ts` also executes the unpatched
`BattleCommand_Critical`, `BattleCommand_CheckHit` and
`BattleCommand_DamageVariation` routines. The original Link RNG and the native
random callback start from the same ten-byte stream. Each case compares the
result, modified accuracy, miss damage and final RNG stream/cursor, covering
skipped draws and rejection sampling across stream regeneration. Each browser
runs 2,560 critical cases, 2,914 accuracy cases and 2,149 damage-variation cases.
Critical coverage includes every random byte at all five reachable stages,
Focus Energy, Scope Lens and the species-specific items. Accuracy coverage
includes all stage pairs, Foresight, BrightPowder, perfect accuracy, X Accuracy,
rain/Thunder, Dream Eater, drain/substitute failure and Jump Kick miss damage.
Damage coverage includes 0/1's no-draw path, rounding and 16-bit boundaries.
Mutating each command's threshold or divisor makes its comparison fail.
These are isolated command comparisons using controlled Link RNG inputs, not
a claim of Link battle support or matching the hardware-timed non-Link RNG.
Protect's text path and the refused Lock-On/Fly/Dig paths are outside these
fixtures. Capture decision comparisons are described below; whole-battle
reference comparisons remain open.
## Validation
```sh
@@ -153,8 +173,28 @@ division by zero. Pure vectors cover the catch formula; local-ROM tests
cover capture, every break-free message, Master Ball draws, blank
nicknames, the blocked trainer ball, full-party rollback, both tutorial
triggers, declining and the repeat offer, and mid-battle reloads. Browser
tests cover PACK, catching and naming. No emulator comparison of the
capture routines has been run.
tests cover PACK, catching and naming.
`tests/reference-capture.spec.ts` compares the production capture decision
and shake loop with `PokeBallEffect`'s calculation and the original
`GetPokeBallWobble`. Each browser runs 3,220 capture cases and 7,424 shake
cases: every catch-rate byte and supported multiplier, HP scaling and
wrapping, status bonuses, success-draw equality, every random byte at each
wobble-table entry, early/later escapes, and the Master Ball/tutorial
bypasses. Final rates, outcomes, shake counts and random-draw counts match.
The native decision and shake loop are private helpers called by the actual
ball-use path; existing capture integration tests still exercise that path.
Unlike the battle commands, capture calls hardware `Random`. This fixture
patches its entry in the private in-page ROM copy to consume supplied bytes,
enters after the capture UI setup, and inserts a return after the decision.
The catch arithmetic, ball multipliers and wobble routine are unmodified.
It does not compare hardware RNG timing, animation, caught-mon reloading,
party/PC writes, or a whole battle. No real held item has `HELD_CATCH_CHANCE`,
so that synthetic effect remains covered by pure vectors only. Maximum HP
342 and 683 are omitted from emulator execution because their divisor is
zero; synthetic tests verify the native refusal. Deliberate mutations of
success equality, formula rounding and shake equality fail the comparisons.
## Switching, battle items and the stats screen
+3 -2
View File
@@ -235,14 +235,15 @@ their scripts and story progression are supported.
- [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
- [ ] Controlled-seed comparisons with the original for random-driven routines (accuracy, criticals, damage variation, capture). Damage, types, stats and experience curves are compared.
- [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. Next: controlled-seed comparisons of the random-driven battle routines.
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. Next: party and bag menus outside battle, MOVE PKMN W/O MAIL and evolution.
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.
+81 -1
View File
@@ -1,6 +1,6 @@
# Recovered Claude session
Latest continuation: see **Battle items, hBattleTurn and the stats screen: 2026-09-30**
Latest continuation: see **Controlled capture comparisons: 2026-09-30**
at the end of this file. Earlier sections are historical snapshots.
Recovered on 2026-09-23 from the local Claude Code transcript
@@ -607,3 +607,83 @@ without it); 97 Chromium/WebKit browser tests passed, 19 skipped
intentionally; native and reference builds; reference, release and
vendor checks; `git diff --check`. Committed and pushed to `main` as
`b13e09e`.
## Controlled random battle comparisons: 2026-09-30
Resumed from `be7575a` with a clean working tree. The user requested a bounded
increment suitable for a limited session. Continued PLAN section 2 with
accuracy, criticals and damage variation; capture stays as the next item.
- `tests/reference-random.spec.ts` runs the original, unpatched
`BattleCommand_Critical`, `BattleCommand_CheckHit` and
`BattleCommand_DamageVariation` in the reference emulator. The native
commands receive the same ten-byte Link RNG stream. Results include the
final RNG stream/cursor, so extra or missing draws are detected too.
- Per browser: 2,560 critical cases (all random bytes at all five reachable
stages and held-item exceptions), 2,914 accuracy cases (all stage pairs,
Foresight, BrightPowder, bypasses, Dream Eater/substitute and Jump Kick
miss damage), and 2,149 variation cases (0/1, rounding, 16-bit boundaries,
rejected draws and RNG stream regeneration).
- Shared ROM input and symbol helpers moved from `reference-parity.spec.ts`
into `tests/reference-fixture.ts`. No ROM bytes or extracted assets added.
- Deliberately changing critical `<` to `<=`, accuracy `>=` to `>`, and the
damage divisor from 255 to 256 each made its comparison fail. All three
mutations were reverted; no production gameplay change was needed.
Boundary: these compare isolated commands with controlled random inputs.
They do not compare a whole battle, the hardware-timed non-Link RNG, or the
Protect text path. Lock-On/Fly/Dig remain refused. This does not enable Link
battles or later maps.
Checks: 337 unit tests passed with the local ROM; 131 passed and 206
skipped with an absent ROM. All 103 Chromium/WebKit browser tests passed,
with the existing 19 intentional WebKit skips. Native and reference builds,
reference, vendor and release checks, and `git diff --check` passed. No CI
or physical-device run. This increment was initially left uncommitted and
is included in the subsequent capture-comparison delivery below.
Next: controlled-input capture comparisons (`PokeBallEffect` and
`GetPokeBallWobble`), then the party/bag menus outside battle, MOVE PKMN W/O
MAIL and evolution. Keep section 2's capture checklist open until verified.
## Controlled capture comparisons: 2026-09-30
Continued PLAN section 2 from the existing uncommitted accuracy, critical and
damage-variation comparisons. Preserved that work and completed its next
capture-comparison item.
- `tests/reference-capture.spec.ts` compares the native capture decision with
the calculation inside `PokeBallEffect`, then its shake loop with
`GetPokeBallWobble`. Per browser: 3,220 capture cases and 7,424 shake cases,
including supported ball multipliers, all catch-rate bytes, HP scaling and
overflow, status bonuses, equality boundaries, every random byte at each
wobble-table entry, every escape count and guaranteed catches. Final rate,
caught/escaped outcome, shake count and random-draw count agree.
- Capture uses hardware `Random`, not `BattleRandom`. The private in-page ROM
copy replaces `Random` with a supplied-byte reader; the fixture enters
after the UI setup and inserts a return after `wWildMon` is written.
Catch arithmetic, multipliers and `GetPokeBallWobble` remain unmodified.
- Extracted `rollCapture` and `ballWobbles` as private production helpers
called by `pokeBallEffect`, without changing gameplay behavior. Added
maximum HP 683 beside 342 in the synthetic divide-by-zero regression.
- Deliberately changing success `<=` to `<`, formula floor to ceiling, and
shake `>=` to `>` each failed the comparisons. Mutations were reverted.
Boundary: isolated capture decision/shakes with supplied random bytes, not
hardware RNG timing, animation, caught-mon reloading, party/PC writes or a
whole battle. No actual item has `HELD_CATCH_CHANCE`; that effect remains
covered by pure vectors. Native division-by-zero refusal remains in place;
the emulator fixture omits the hanging HP inputs. No additional balls or
maps are enabled.
Checks: 337 unit tests passed with the local ROM; 131 passed and 206 skipped
with an absent ROM. All 107 Chromium/WebKit browser tests passed, with the
existing 19 intentional WebKit skips. The four new capture tests skip cleanly
without a ROM. Native and reference builds, reference, vendor and release
checks, and `git diff --check` passed. No CI or physical-device run.
The accuracy, critical, damage-variation and capture comparisons are delivered
together, preserving the previously uncommitted work.
Next: party and bag menus outside battle, MOVE PKMN W/O MAIL and evolution.
PLAN section 2 is complete within its recorded acceptance boundaries.