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