# Shot balance audit and strong-crit overflow fix — 2026-10-26-r32

jdc requested 100 full critical slapshots by Edmonton #2 from the long-shot
bonus zone, then more teams, all difficulties and different period lengths.
The final requested run is **32 goals / 100 shots**, Evan Bouchard #2 versus
NYR's Dylan Garand, Easy, five-minute periods. All 100 releases had native
forehand animation 07FC, full charge31, actual crit activation and measured
distance >=160. The long-shot goalie-miss flag activated18 times. This is an
isolated shooting experiment with an active goalie and other skaters held
away from the shot lane, not a prediction of ordinary match conversion.

## What was tested

Genesis Plus GX, the installed RetroArch core, headlessly. Native new-season
menus select the actual team, difficulty and period length before capture.
Native roster/rating loading supplies the real players; no substitute shooter
ratings or empty net. Each trial restores its private native fixture, gives
its real on-ice right defenseman the puck, positions the other skaters behind
the shooter with room between them, allows recovery, aligns a forehand and
holds C40 frames. The keeper retains native AI and ratings. Each new trial
uses its own deterministic PRNG seed immediately before the release frame;
input/output seeds are logged and >=95% unique outputs are required. No live
user SRAM, season progress or installed save was used or overwritten.

The immutable final suite contains **7,200 controller-driven shots per build**:
EDM Bouchard#2 / COL Cale Makar#8 / TOR Darren Raddysh#43; Easy, Normal, Hard;
5,7,10,20-minute periods; 100 shots from nominal distance120 and100 from200
per case. Five lateral positions(-40,-20,0,20,40) are used. Native measured
release distance must fall on the correct side of160. These36 combinations
were run on r31 and the fix, for **14,400 validated full forehand slapshots**.
Opponents are the native scheduled NYR, MTL and BUF respectively; cross-team
goal totals also reflect different goalie ratings and are not team rankings.
The period setting does not change the bonus thresholds or percentages.

Final per-case measurements: [CSV](/nhl94/download/shot-balance-audit-r32.csv).
Private evidence: `~/.config/retroarch/roms/claude/work/shot-audit-20261026/final/`.
Only its `baseline-results.json`, `fixed-results.json`, `controls-results.json`
and matching case directories are accepted measurements. Earlier exploratory
runs and preliminary numbers are superseded. In particular, FFF0B0 means
**eligible full forehand**, not a successful crit. Actual critical activation
is measured at release through FFF060, the flash/SFX boost path. Setting a PRNG
seed too early can be undone by native initialization/waits; do not count
identical restored random sequences as independent attempts. The final hooks
were frozen for the entire run; their SHA256 is
`b1f43ff3db6e414d42f832432a187b174979ba26ed84c06db25c21f7d3b692b5`.

## Percentages and separate bonus effects

* Easy: **2,400 actual crits / 2,400 full forehand slapshots**. Backhands and
  partial shots remain excluded by the existing eligibility gate.
* The long-shot goalie-miss roll activated **541 / 3,600 = 15.03%** beyond the
  threshold, and **0 / 3,600** below it. This is a chance that the goalie misses
  an intervention, not an extra15 percentage points of guaranteed goals.
* A native routine driver additionally tested10,000 calls at each distance
  100,159,160,200,300. Below160:0 activations. At/above160:1,491/10,000(14.91%)
  for each distance, using the same seed for an exact boundary comparison.
* Native crit drivers: rating50 yielded818/10,000(8.18%); rating100 yielded
  1,302/10,000(13.02%). The current formula includes r26's +3 percentage
  points. Crowd/difficulty scaling is applied before that addition. In the
  full native matrix, Normal measured240/2,400 and Hard256/2,400; finite
  samples and different native fixtures do not establish a Hard advantage.
* **Release power** is increased by `floor(power/4)`, preserving the intended
  +25% arithmetic. The probability of a goal is a separate outcome.

Three additional matched1,000-shot Easy/Bouchard experiments isolate effects
from distance200, with identical positions/seeds and active native goalie:

| Test copy | Goals | Long-shot flag |
| --- | ---: | ---: |
| r32 production physics |248/1,000|171/1,000|
| Only the goalie-miss roll disabled |187/1,000|0/1,000|
| Only the extra crit power disabled |161/1,000|171/1,000|

Controls are private ROMs; they retain the same native RNG calls. Shot by shot,
production power equals control power+floor(control power/4), and the crit
power control retains identical goalie-bonus decisions. No control ROM is
installed or served. Positions, fatigue, aim/spread and goalkeeper movement
still influence whether a particular shot scores.

## Actual fault found and fixed

For Raddysh on Easy, full native power393 becomes491 after crit. The original
C4B0/C4C0 coordinate multiplication takes signed WORD operands. Its preceding
`power*68` is33,388, which wraps to -32,148 as a signed16-bit operand. X is
reversed; Y triggers the native wrong-direction fallback(14,352). In the r31
far-shot sample his average was89.8km/h and23/400 goals. With the fix, same
400 attempts: **187.8km/h and195/400 goals**. This is why a harder crit could
previously perform worse than an ordinary shot. In Normal only his crit
releases hit the bad range; Hard's lower power in this sample stays below it.

`critical.s` now tails into `critical_velocity.s` at **2ED800**,90bytes. Power
<=481 jumps straight to the original C4B0 path with unchanged operands. Above
that, axis*power is calculated first and multiplied by68 using LONG shifts
and addition. Unsigned magnitude division avoids the signed quotient trap;
the original sign is restored afterward. Each component saturates at +/-32767,
the puck object's signed-word storage limit. This prevents overflow without
reducing the saved crit power or changing aim, RNG, crit chance, sound, lift,
goalie bonus, native fallback for other shots or the HUD. A near-straight
extreme shot may reach that representable component limit; diagonal speed
can be higher. Native C4C8 continues with the original stack/register contract.

```asm
mulu.w 0xFFBEDE,%d2  | |axis| * power, full LONG result
move.l %d2,%d0
lsl.l #6,%d2
lsl.l #2,%d0
add.l %d0,%d2        | x68, no intermediate signed WORD wrap
divu.w %d3,%d2
cmpi.w #32767,%d2
```

The native boundary/axis regression exercises11 powers192..600 and8 axis/
direction combinations. r31:56/56 below-threshold cases pass,0/32 high-power
cases pass. r32: **88/88 pass**. All low-power native full-shot outcomes and
measured speeds in the matched14,400-shot matrix remain identical. Only
releases in the overflow range change; power/crit/bonus decisions match
shot by shot across both builds.

## Reproduction and other verification

Sources: `experimental/critical_velocity.s`, `critical.s`, `critical.py`.
Permanent rigs: `verify_critical_velocity.py`, `verify_shot_balance.py`,
`shot_balance_hook.py`, `shot_balance_fixture_hook.py`. The native audit takes
explicit ROM/output/seed paths, freezes its hooks and only writes private saves.
For a paired build pass `--fixtures-from` the first audit's output directory.

```
python3 tools/experimental/verify_shot_balance.py current.bin /private/audit --seed copy.srm
python3 tools/experimental/verify_critical_velocity.py current.bin /private/velocity --baseline preceding.bin
```

PASS critical10 cases(including native velocity, probability and832 lookups),
critical flash, shot panel8 cases/161 VRAM checks(including pads1..4 and replay),
navigation12/228, menu teams18/240. One autonomous five-minute-period game,
CPU on both sides, completed regulation and saved its first season result
(EDM2–4NYR); the live game clock was not shortened. Public audit CLI smoke10
shots and Python compilation pass. Exact second rebuild; guarded **91 changed
bytes** only in title/checksum, critical tail target and new90-byte code bank.
Original-base IPS234,914bytes reconstructs the exact ROM. No UI/state layout,
roster, season save or controller remap change.

Release: **2026-10-26-r32**. SHA256
`8a7304d8fd5708907dced70ea016fffaa8f3096389be18c809f0ebc086423fd9`,
CRC32 `92065481`. Replaces r31 under the standing release policy; deployment
reports record content hashes for migrated saves/states/remaps/history.
