# Complete experimental build — current jdc release

## Separate penalty frequency and preserved native rules — 2026-10-31-r8

The new final start-menu row is Penaltys: 100% / 50% / Off, default 100%. The original
combined Penalties option is renamed Rules and keeps Off/On/On except offside.
The percentage affects ordinary foul admission and whole new fighting incidents;
icing, offside and penalty-shot eligibility retain their existing controls.
A suppressed injury foul uses the native injury-only stoppage instead of losing
its report. Existing pending calls are not rerolled or erased.

All five independent percentage/pass choices save per season and lock after the
first match; native play has separate preferences. No extra SRAM bytes are used.
Buffered human contact-to-shot setup now runs as one protected transition, so an
intervening native sprite update cannot change the measured stick hotspot. The
old42px failure is reproduced independently; native register/CCR/IRQ-mask/stack
contracts are compared at every IRQ level. All 89 exact-source suites execute: 88 pass, only the retained finisher_hold geometry
failure, strict gate FAILED. Independent rebuild, exact r7 rollback and original-
base IPS reconstruction pass. See PENALTYS_R8.md and PENALTYS_RESULT.json.

All 40 ordinary 600-frame scenes are recorded against fresh exact r7 controls;37 are raw-exact. Every difference and the complete earlier audit chain remain retained. No Hard win-rate investigation resumes.

ROM SHA256:501da8aaf8fc1fb4f5fdd22f4d8f8d893a8fe06de9ec28cb4ecaf0a6e18297eb; CRC32:F2C8334E.

## Optional directional passing and vector targeting cone — 2026-10-31-r7

Two new start-menu rows: Pass Mode Assisted/Directional (default Assisted), and
Target Cone Default/15 degrees/30 degrees/45 degrees (default Default).
Directional sends ordinary human B passes along the actual eight-direction aim,
without selecting a receiver or transferring control at release. The explicit
cone choices limit Assisted receivers using actual vector half-angles; Default
retains the previous native octant policy. P1–P4 and manual keepers are covered;
CPU keeper outlet requests retain their requested receiver. Height and Speed
remain independent. Seasons save all four choices and lock after the first game;
standalone preferences are separate. Hold/release charging remains research.

The first shared-path prototype reduced AI finish releases in the gameplay test
and was rejected. The final design hooks human B input and existing human-only
scan branches, preserving the CPU pass/scan/outlet instruction paths exactly.
All 40 ordinary 600-frame scenes match fresh r6 controls byte-for-byte; the earlier
comparison chain is retained. This is bounded regression evidence, not a claim
that every possible game is identical. No Hard win-rate investigation resumes.

All 88 shared suites execute: 87 pass and the retained receiver-hold geometry check
fails, so the strict release gate remains FAILED. No new regression is waived.
Independent rebuild, exact r6 rollback and original-base IPS reconstruction pass.
See PASS_MODE_R7.md and PASS_MODE_RESULT.json for code, tests and delivery.

ROM SHA256: 8172a7e4b3d55754076d4f31de2def263d423bad832b2057538fa3b5c1ca86c7; CRC32: 5DC93C86.

## Fair automatic co-op pickup control — 2026-10-31-r6

When a free CPU skater on your team takes possession, eligible active human pads
now share a random chance to control that skater. Native P1 priority no longer
wins every such co-op pickup. Only actual accepted pickups are considered.
Already controlled receivers, requested keeper outlets, manual keepers, pending Y,
locked skaters, single-human teams and special modes retain their native paths.
Other pads keep their existing skaters. There is no new menu setting here.

The hook at1538A calls a bounded helper at2EFC00. It uses the real controller-side
map, native RNG and native C0F8 handoff, with temporary eligibility data on the
stack. No RAM/SRAM field, forced pickup, rating or movement change is added.
Native tests cover316 assignment contexts,36 unchanged guards,112 paired ABI cases,
32 full collision contexts and48 physical-pad input branches.24 advancing-RNG series
exercise 144000 controlled native pickup draws, exactly matching the native RNG
control. These are automated fixtures, not 144000 matches or a hardware playtest.

All 87 shared suites execute;86 pass and the retained finisher_hold geometry failure
remains, so the strict gate is FAILED. All 40 current ordinary scenes and fresh r5
controls are recorded, including timing differences. Rebuild, exact r5 rollback
and original-base IPS reconstruction pass. No new Hard-strength claim.
See FAIR_PICKUP_R6.md and FAIR_PICKUP_RESULT.json.

ROM SHA256: 548615ad01079d6a93fc62a0409b4a8d6ec4e53676f8a629081f8cc34da712fa; CRC32: 26911EBB.

## Independent pass speed and safe standalone preferences — 2026-10-31-r5

The last main-menu row now offers Pass Speed: Normal / Slow, default Normal.
Slow reruns the native intercept calculation at80%of nominal pass power, keeping
receiver movement, native time bounds and catch timers in the calculation.
It never chooses an earlier arrival. Fast receivers outside that lower-power
range retain their native pass. This is not an exact20%reduction for every pass.
Pass Height Normal/Low remains independent, using the original arrival estimate
and preserving native fractional lift. Both teams and keeper targeted B passes
share these settings; A flips, C shots and blind-pass code retain their paths.

Each season saves both choices and locks them after the first completed match.
Standalone play has separate preferences. A paired native test also exposed
r4's standalone height record at2E20overlapping Full-season goalie data. Both
standalone preferences now use4D20in Light's excluded shared-profile gap; menu
writes preserve Full goalie bytes and survive slot circles and cold boots.

Native tests cover120direct clamp ABI cases,1152Normal/invalid full solver ABI
comparisons,384Slow comparisons against the original native lower-power solver,
34real menu/lifecycle cases, paired r4/current goalie-storage reproduction and
four co-op input games covering both speeds and heights. All86shared suites run;
85pass and the retained finisher_hold geometry failure remains, strict gate
FAILED. All40current ordinary scenes and fresh r4controls are recorded; additional
instructions may change native HV-counter decisions. No unchanged-gameplay,
pass-completion or new Hard-strength claim. Rebuild, exactr4rollback and original-
base IPS reconstruction pass. See PASS_SPEED_R5.md and PASS_SPEED_RESULT.json.

ROM SHA256:40f388dc0beaedbd0883fdd4101a30debfb0fbbe62aab9d7a382e61476d513e0; CRC32:F82519B6.

## Optional pass height — 2026-10-31-r4

The main menu now has Pass Height: Normal / Low, default Normal. Low lowers the
vertical lift of targeted B passes on both teams, including keeper passes.
Short passes retain their existing lift. The native horizontal velocity,
arrival-time estimate and receiver intercept arithmetic remain unchanged.
A flips, C shots and blind passes retain their code. Lower lift can change
landing and interception; no pass-completion or competitive balance claim.

Full, Light and Mini save their own setting, which locks after the first
completed game and survives a reboot. Native exhibition/playoffs have a separate
preference. Missing/corrupt records default Normal without unlocking a season.
Confirmed season restart resets only that season to Normal.

Native tests cover80paired register/CCR/stack/fraction contexts,64moving-target
contexts across5profiles,28controller/menu/slot/boot/lock/restart cases, and real
co-op directional/B input with both Normal and Low passes above the lower cap.
The full85-suite exact-source matrix has84pass and the retained finisher_hold
geometry failure; the strict gate remains FAILED. Forty ordinary native scenes
and fresh r3 controls are recorded; added dispatch timing may change HV-based
decisions, so no identical-gameplay claim. The earlier strict chain is retained.
Independent rebuild, exact r3rollback and original-base IPS reconstruction pass.
No Hard win-rate investigation is resumed. See PASS_HEIGHT_R4.md and
PASS_HEIGHT_RESULT.json for the exact evidence, scope and delivery status.

ROM SHA256:0f7f080a30b52e536d7107ffc75044c21c1c617aa5d0007ad6203349604e2a69; CRC32:4B4E78D6.

## Downed pass receivers — 2026-10-31-r3

Falling/downed skaters are no longer selected by the native B-pass search or
keeper fallback, and cannot take stick possession while the fall/recovery
animation remains active. A receiver knocked down after launch is protected too.
The puck can still hit the body and deflect. Standing windups/one-timers, keeper
dives/catches and skaters who have recovered retain their native eligibility.

Two pass-scan eligibility tests and the existing actual stick-contact gate call
one small helper. No general frame/animation hook, RAM field, forced movement,
physics, rating or RNG write is added. The original pickup/crit gate and native
body response remain. Additional instructions can alter native HV-counter timing,
so ordinary game trajectories are recorded rather than claimed byte-identical.

The paired native tests cover84CCR/register/stack contexts,114real receiver
searches and44full stick/body collision cases. A native co-op input test injects
only a synthetic receiver fall after an actual pass: the parent shows6downed
possession episodes (579observations); current has zero. Recovery and subsequent
legal possession occur naturally. These are not physical-controller recordings.

All84shared suites execute:83pass and the retained finisher_hold geometry failure;
the strict gate remains FAILED. All40current ordinary scenes and fresh exact
r2controls are recorded, including every difference. The complete earlier strict
comparison chain remains on those parent controls. Rebuild, exact r2 rollback
and original-base IPS reconstruction pass. No Hard win-rate search is resumed.
See DOWNED_RECEIVER_R3.md, DOWNED_RECEIVER_RESULT.json and QA_STATUS.json.

ROM SHA256:40b3a5700ae7c3ce6f0536b878a22b2e7284a8e418231ed6a6ea4ce891fb3548; CRC32:16DD6925.

## Shootout puck progress and automatic start — 2026-10-31-r2

Shootout attempts now start without a controller shortcut. The tied-OT fixture
reaches the first live human attempt with no input in180frames (about3seconds),
previously619frames. A player may react before starting the approach; the normal
attempt timeout still applies. Human and CPU countdowns both use60native ticks.

Before a shot, the puck must keep moving toward the opponent's goal. Substantial
retracement, a stopped approach or crossing the goal line outside the mouth ends
the attempt through the existing native miss event. Body orientation alone is
not a foul: an unshot loose puck still moving forward may be recovered. Stick
movement uses an8-world-unit retracement allowance and12non-forward native
observations of stop tolerance. These are game tolerances, not literal NHL units.

Native shot/windup, loose-shot retouch, keeper ownership and shot-flight handling
remain. The native post-shot retouch queues one ending; no general pickup or
physics hook was added. This is an attempt-control fix, not complete NHL roster,
spinorama or eligibility parity. Custom OT lengths and season formats remain.

All83 exact-source suites execute:82pass and the identical inherited
finisher_hold geometry failure; strict gate remains FAILED.54paired native
boundary/ABI contexts,28both-end native-input cases and the no-input starts pass.
All40 ordinary raw scenes match40fresh2026-10-31 controls exactly. Earlier net
impact differences versus r13 remain independently audited. Rebuild, exact
2026-10-31 rollback and original-base IPS reconstruction pass. No Hard search.
See SHOOTOUT_RULES_R2.md, SHOOTOUT_RULES_RESULT.json and QA_STATUS.json.

ROM SHA256:dc279e63f35b2607ebba4eb5fb439eeb47f36239bddcfaae7c3d24e29f99cb08; CRC32:90081498.

## Less sensitive goal nets — 2026-10-31

Goal nets now require a50%higher impact-speed component before the existing
native dislodgement can trigger:8192→12288 in either direction on either axis.
Ordinary collisions keep the solid-net response; a heavy burst can still move
it. Both ends and every skater/keeper use the same rule. Four threshold immediate
bytes change; instruction timing, random gate, puck proximity, native release
velocity, stoppage, scoring and reset code remain exact.

A genuine CPU-play sequence reproduced one old dislodgement with40moving-net
observations; the corrected sequence has none. A real human burst reaches13868,
above the new threshold, with its input/motion trace exact to r13. The native
helper passes368paired boundary/actor/end/direction/guard/ABI contexts. No user
recording or physical-controller playtest is claimed.

All82 exact-source suites execute:81pass, only the identical inherited
finisher_hold geometry failure. Strict gate remains FAILED. All40 scenes are compared with fresh r13 controls: raw-exact, or exact until
a verified native net release. Post-impact differences are recorded. Rebuild,
exact r13 rollback and original-base IPS reconstruction pass. No new Hard win-rate series. See NET_CONTACT_20261031.md,
NET_CONTACT_RESULT.json and QA_STATUS.json.

ROM SHA256:aa035e55db8b43d1d38f3b178a739cc0edfab1fb8ef7837de718248fde5721f1; CRC32:BF43956E.

## Manually controlled keeper passes — 2026-10-30-r13

A fresh pass press while your manually selected keeper holds the puck now
reaches the native keeper passing routine. The immediate B-switch correction
previously treated an owned keeper as a switch request. Owned skaters retain
their native pass path; without possession B still switches to an eligible
skater nearest the puck. Automatic keeper catches and P1–P4 CPU-outlet requests
retain their established behavior.

Only the early ownership branch in switch.s and fourgoalie.s changes. There is
no new RAM, global hook, pass target override, physics, AI or season change.
Mini remains seven games. Native control may move to a receiver when the pass
is released; this is separate from switching away before executing the pass.

All81 exact-source suites execute:80pass, only the identical inherited
finisher_hold geometry failure. Strict gate remains FAILED. All40 current raw
ordinary-game traces match40 fresh r12 controls. Independent rebuild, exact
r12 rollback and original-base IPS reconstruction pass. No Hard strength series.
See MANUAL_KEEPER_R13.md, MANUAL_KEEPER_RESULT.json and QA_STATUS.json.

ROM SHA256:3070c2bbac4ec9fe6cf578c7fd0a3260869371a232b05ed76f33e44ca71ea899; CRC32:4D62BA8E.

## Mini is seven games — 2026-10-30-r12

Mini now has seven regular-season games: one against each of the seven other
clubs in your division. The new-game menu shows1/7, the saved length is7 and
all32 clubs finish with7GP. The eighth return fixture is no longer scheduled.
Single-player home/away alternation and co-op home games retain their policy.

Qualification and the season-summary boundary use the seventh game. Mini
retains one game per playoff pairing; Full remains best-of-seven and Light
retains its own schedule. Existing played legacy eight-game saves retain their
original length, lock and eighth-game cursor limit. Changed releases still use
fresh progress; an already running older ROM is left alone.

All80 exact-source suites execute:79pass, only the identical inherited
finisher_hold geometry failure. Strict release gate remains FAILED. All40 raw
ordinary-game traces match40fresh r11 controls exactly. Rebuild, exact r11
rollback and original-base IPS reconstruction pass. No new Hard strength series.
See MINI_SEVEN_R12.md, MINI_SEVEN_RESULT.json and QA_STATUS.json.

ROM SHA256:2ca0be4ef69f1d875c5a25e1ef91e42bda6b87396c5471fa8a417eb652ec8d60; CRC32:FB7FA2F0.

## Fouled player keeps the penalty shot — 2026-10-30-r11

P1–P4 now retain the penalty shot awarded for a foul against their controlled
breakaway attacker. Native setup can move the shooter to another object slot;
the physical controller is captured when getBAplayerInfo accepts the award,
then used when the two penalty-shot participants are assigned. Other attacking
pads cannot claim the shooter first. Existing team-side settings are restored
before returning from setup; normal assignment resumes after the attempt.

This release addresses priority1 only. Manual-keeper passing, downed receivers,
fair random unassigned pickup ownership, Penaltys100%/50%/Off and seven-game
Mini remain queued in TODO.md. Mini currently still has eight divisional games.

The native capture/setup, both teams and P1–P4 input/return regressions pass.
The full79-suite exact-source matrix has78pass and the identical inherited
finisher_hold failure; strict gate FAILED. All40 current ordinary-game raw
traces match40 fresh r10 controls exactly. Independent rebuild, exact r10
rollback and original-base IPS reconstruction pass. No new Hard strength
series or39/50 qualification is claimed. See PENALTY_OWNER_R11.md,
PENALTY_OWNER_RESULT.json and QA_STATUS.json for exact evidence and limits.

ROM SHA256:8f84f537ab945ea849ffc69eeba6794e9e1651556d3f878648ed846f8ebc4703; CRC32:41B60F46.

## Human one-timer stick contact — 2026-10-30-r10

jdc reported that receiving a pass while repeatedly shooting launched the puck
far from the stick. Native co-op input tests reproduced buffered releases up to
36.8 game pixels from GetHot's stick hotspot, with the shot animation at0.
The human buffer now waits for actual stick contact (radius4), then starts the
native contact/follow-through phase24. A fresh owned C cannot start a competing
ordinary windup while that request waits, and another pad's C cannot erase it.

The request stays responsive to native movement and possession loss. No puck
teleport or pickup-radius change is used; AI and normal shot physics retain
their code. Mini's divisional schedule, OT, momentum, defaults and locks remain.
See HUMAN_CONTACT_R10.md, HUMAN_CONTACT_RESULT.json and exact QA_STATUS.json.
The inherited finisher_hold geometry limitation remains failed; no new Hard
strength series is claimed. SHA256:b28fa2c8f42bd792f3118bc3d71df2fed5f959957d4adc60f372f2d4863c2038; CRC32:F157EFEB.

## Division-only Mini season — 2026-10-30-r9

Mini now plays eight regular-season games exclusively within the selected club's
own division. Its first seven rounds cover every divisional rival once; round8
is a return against the round4 opponent. All four simulated divisions use the
same eight-team round robin. Every team still plays eight games. Existing
single-player home/away alternation, co-op home fixtures, season locks and NHL
playoff qualification remain. Full and Light retain their league-wide schedules.

jdc reported Edmonton meeting Detroit in Mini. Exact r8 reproduces this from a
saved first-opponent choice; r9 restricts the cursor and actual fixtures to the
Pacific division. See MINI_DIVISION_R9.md, MINI_DIVISION_RESULT.json and current
QA_STATUS.json. The pre-existing receiver-hold geometry limitation is retained;
no new Hard win-rate series is claimed. ROM SHA256:29672052309b0bcad831716cd0edf35b498a265f0063d99173c70dff4df47c3a; CRC32:FD1A8A3A.

## Selected-length overtime and whistle momentum — 2026-10-30-r8

Overtime now uses the selected period length:5,7,10 or20 minutes. Native sudden
death and the existing shootout after a tied overtime remain. Shots/checks while
the clock is stopped, during a faceoff, pause, foreign view, replay or Stars no
longer earn crowd momentum or rating boosts. Blocked counters are consumed
immediately; resuming cannot bank them. Existing earned momentum is retained.
Normal and Hard retain the same earned-meter rules; Easy retains fixed ratings.

Changes are confined to shootout.s and crowd.s, plus release strings/checksum.
Rebuild, exact r7 rollback and original-base IPS reconstruction pass. All shared
checks are freshly run against this image; see OVERTIME_MOMENTUM_R8.md and
QA_STATUS.json for the exact results and retained AI limitation. No new Hard
strength matches are claimed. ROM SHA256:083f45ffbfff3986d55fbe2ea13384916c4c5b4f1d0b6c5e21e39aa80af80e02; CRC32:D813665F.

## Icing On default restored — 2026-10-30-r7

jdc clarified that **Icing must default On**, despite Off in the previous menu
screenshot. Fresh boot, empty Full/Light/Mini formats and explicit restarts now
use On. All other r6 menu defaults and keeper/current-skater outlet behavior stay
unchanged. Manually chosen saved settings and played-season locks still apply.

Only three default immediate bytes change1→0 outside version/checksum fields;
all other game code is byte-identical to r6. The 75 shared checks ran:74pass,
only the known inherited finisher_hold geometry failure; strict gate FAILED.
All40 current raw no-B scenes match fresh r6 controls exactly. The older r6/r5
observation audit remains separately disclosed. Rebuild, exactr6 rollback and
original-base IPS reconstruction pass. See ICING_DEFAULT_R7.md and QA_STATUS.json.
SHA256 48af31f9f7c85766951bf42745360571d6e7bf025bdce936363a58a178ab05f6; CRC32 273334B0.

## Keeper catch handoff and requested defaults — 2026-10-30-r6

A keeper already selected before catching the puck now returns to CPU control
on the accepted catch, and that pad receives an eligible free skater. The native
AI marker follows this ownership change. P1–P4 request an outlet to their current
skater; pass no longer switches that requester first. Explicit Y after the catch,
CPU-only keepers, opposing human teams and special modes retain their rules.

Fresh defaults follow jdc's latest screenshot: Auto lines, Fighting Off, Hard,
Auto Replays Off, Crit Shots Off and Icing Off; Edmonton remains the default club.
The screenshot's Icing Off supersedes the earlier r5 On request in this release.
Existing valid saved choices and played-season locks remain readable.

All75 shared checks ran:74pass,only the inherited finisher_hold geometry failure.
Strict gate remains FAILED. New coverage includes44 native ABI/object guards,
148 four-pad keeper cases (68 added handoff/current-requester cases),5 branches
of the reported r5 catch and6 native default/menu-render cases. All40 current puck/clock/action timelines match fresh controls.35 raw traces
are exact;15 isolated position/cache/render observations across5 traces are
recorded separately with exact whole rows before and after each difference. Rebuild, exactr5 rollback and original-base IPS reconstruction pass.
No new Hard win-rate series. See GOALIE_CATCH_R6.md and QA_STATUS.json.
SHA256 74f099b10459e000b4b50870e41617983641445f83c813328dba019b6b02fd7d; CRC32 25BBB406.

## Icing switch and Edmonton defaults — 2026-10-30-r5

Icing has its own **On / Off** row at the bottom of the start menu, after
Crit Shots. **On is the default**, explicitly confirmed by jdc. Off suppresses
the native touch-icing award. It follows the ordinary season-settings lock
after the first completed match and is saved independently for Full/Light/Mini.
Fresh startup, new empty formats and an explicit restart default to Edmonton;
manual team selection and the identity of an ongoing saved season still work.

All 72 shared checks executed: 71 pass; only the inherited finisher_hold geometry
failure remains. The strict gate remains FAILED. The new suites cover 34 native
menu/persistence/default-team cases and 56 synthetic native rule cases. Forty
current no-B scenes match fresh r4 and r3 controls exactly; all40 fresh r2 controls
still reproduce the immutable original traces. Rebuild, exact r4 rollback and
original-base IPS reconstruction pass. No new Hard win-rate series was run.
See ICING_MENU_R5.md and QA_STATUS.json for scope and exact-source evidence.
SHA256 a7e8bef0e8c78da6d37eacb432769af3f6d4b99e0196e451875c7991656ca182; CRC32 33894753.

## Keeper pass request at the possession deadline — 2026-10-30-r4

A timely pass request could switch the skater yet lose the keeper outlet to the
native possession timeout before it was processed. The first accepted request
now reserves up to four native processing ticks when the counter is still
nonnegative. Holding B does not refresh that reservation. An already queued
native timeout, negative counter or actual whistle retains the native outcome.
On jdc's controller this is physical A, the game's logical B. Y keeps its outlet
behavior. Native passing, target choice, physics, ratings and the r3 keeper
correction remain. No new Hard-versus-Normal strength series was run.

Paired timing tests: 1,792 valid requests per ROM; r3 passed 1,776 and r4 passes
all 1,792. All 128 late/stopped requests remain without a pass. The 1,920 cases
per ROM vary two native catches, timing and 1/2/5/12-frame taps; they are not
independent matches. Shared regression: 38 native ABI/object/guard cases and
108 paired input contexts pass. All 70 matrix checks executed: 69 pass; only the
same inherited finisher_hold geometry failure remains. Strict gate: FAILED.
The affected replay check was rerun after declaring the input modules; all
40 current no-B trajectories exactly match fresh r3 controls, and all 40 fresh
r2 controls still match the immutable original scenes. The original rejected
scope check is retained. Five rebuild/rollback comparisons and original-base
IPS reconstruction pass. See OUTLET_DEADLINE_R4.md and QA_STATUS.json.

SHA256 4dd53440a7a4b4b7920a432b1e70a18e0ddb43e2a57a553b65cd052a0a0dca50;
CRC32 4884D486. Fresh progress for this release; remaps/config/history retained.
The intermittent faceoff alignment report is still open.


## Hard cross-ice passing and shot openings (2026-10-30)

User-requested playable test release: Edmonton Hard38/50versus Chicago Normal,
goals132–66,25home each; matchedr3control32/50and immediateparent36/50.
Stable39/50plus independent confirmation remains unfinished; all results retained.
Hard now plans accepted native passes, supports real receivers, checks the
keeper opening on the actual catch and selects only more open native post
targets while retaining height/accuracy/power/physics/ratings. Coverage continues
through real passes. Native scorer cache and keeper attacking/defensive counters
are accounted correctly. See AI_TACTICS.md and HARD_STRENGTH_RESULT.json.

All63 exact-ROM/source checks executed:62pass; finisher_hold fails its live
geometry improvement criterion (slot distance1.90to2.78; early releases2to2).
The strict release gate remains FAILED. This exact38image is provided only as
the interim playtest requested by jdc, with that AI regression retained. Independent current-source rebuild, exact
r3rollback, feature ablations and original-base IPS reconstruction pass. Normal/
Easy/human eligibility and physical-shot contracts pass; two Easy live controls
retain sampled frame-timing differences. No physical-controller session claimed.
SHA256 fdf88b7f1933304dd08917b88e1b2e4805072355c1538ff41d876e6e015688c4; CRC32 853481AF.
Fresh progress each release, controller/remap/config/history retained.


## Crit On/Off value visible on every redraw (2026-10-29-r3)

User reported the Crit Shots label without its On/Off value. Native full value
redraw stopped after logical row20, skipping the new row22. Extend F7B44 from
0014 to0016. Both On and Off now render on initial selection, cold boots and
full redraws, in the same column as Auto Replays. The preference already changed
and persisted in r2; those tests missed the actual initial drawing call.

All40 exact-ROM/source checks pass. New native renderer regression reproduces
r2's missing initial value and verifies8 ordinary/adapter cases,32 observations:
default/saved/corrupt values, Left/Right, row changes, exact strings and column.
Existing43 Crit behavior and7 replay cases, plus full natural Hard CPU game,
also pass. Independent rebuild, exact r2 rollback (--legacy-crit-value-draw)
and original-base IPS reconstruction verified. Only4 ROM bytes differ: two
version-header bytes, checksum byte and native draw-bound byte. No AI, power,
controller behavior, save format or gameplay changes. SHA256
0d196f206294ebf6c8bda449a41aa7cf4f6702585a57cc9ecceaee960915a8db;
CRC32 3493DFFC. Evidence: .workspace/work/crit-render-fix-20261029/.
Normal fresh-progress/remap policy applies after normal exit of an older game.
Hard AI35/50 target remains unfinished; no hold/timing/point prototype included.


## Global Crit Shots On/Off (2026-10-29-r2)

The twelfth main-menu row is Crit Shots: On / Off, default On. This global
preference survives cold boots, Full/Light/Mini slot switches and native modes.
It stays editable during a locked season without unlocking match settings.
Off disables the critical roll, extra power, red puck, flame trail and special
sound for CPU and human shots on every difficulty, including Easy's otherwise
guaranteed critical forehands. Auto Replays is saved independently. Ordinary
shots and the separate older long-range goalie-miss rule retain their behavior.

All39 exact-ROM/source checks pass. The new Crit suite covers43 cases: locked
menu edits, cold boots/slot/native-mode persistence, absent/corrupt independent
records, read-only boots,24 direct native CPU/human/team/difficulty On/Off
cases (10,000 shots each, native SFX-entry instrumentation), and six real
controller Off releases including all four Easy pads. Seven strict replay
preference cases still pass. A timing-dependent season-slot fixture now skips
native other-game highlights with ordinary C inputs while keeping the exact
bracket/password/checksum assertions; no gameplay code was changed for it.

Independent rebuild, exact preceding29 rollback with --no-crit-option and
original-v5.1W IPS reconstruction pass; guarded1,311-byte ROM diff. Current
SHA256 fd9f76a4e29dcbd904b898aaba2018ceb1d4f0eff7e8d46f940435c55981b78f,
CRC32 44FEF889. Full evidence: .workspace/work/crit-option-20261029/.
The separate Hard AI35/50 target remains unfinished. No additional AI prototype
is included. Controller mappings retained; progress clears after normal exit
if a preceding user game is still running.


## Hard CPU coaching, native crash fixes and keeper statistics (2026-10-29)

CPU Hard chooses stronger real keepers and its best first forward unit, with
unchanged player attributes, physics and true fatigue. Normal/Easy AI decisions
retained; human teams protected. Native rewind wrap and powerplay-display team
pointers corrected; reserve/third/multiple keeper season statistics use actual
individual counters. All38 exact-build checks pass, including46 line/46 keeper/
12 accounting/6 rewind/4 PP cases and a full Hard CPU game. Same failing0-2 game
passes with identical goals. Rebuild, exact r9 rollback and original-base IPS
verified. SHA256 b81bbd5510edc8496d9a50cce269dee653067977f6ae05b3ca89cdf3ad20bd8c;
CRC32 B999E29D. See HARD_COACH.md. User requested this playable copy before the
unfinished35/50 strength goal; later experiments and requested Crit toggle
are not included. Fresh progress; controller mappings retained.


## Crit flames follow the moving camera (2026-10-26-r33)

Reproduced Mini/Easy/5min/Fighting Off,EDM-CHI: red puck+bomb sound but
the six flame circles overlapped because HIST stored old screen coordinates.
trail.s now stores camera-independent anchors and projects at draw time;
other views normalize their native puck sprite.20 native crit shots show
median span11->42px;1424/1424 world-path checks pass(previous0/1424).
Same art,VRAM,RAM,6 sprites,80-sprite cap and24-frame afterglow. Crit physics
and goalie bonus unchanged. Critical10,velocity88,flash,panel8/161,navigation
12/228 and full CPU game pass. Exact rebuild,guarded115-byte UI/title/checksum
diff,IPS verified. See [CRITICAL_TRAIL.md](CRITICAL_TRAIL.md).
SHA25607c63ff6583b8edd235f7b2cf22b4584ace23174c7ba1009385a37b115ea62e9;
CRC7BB0CADE. Replaces r32 with save/remap hashes preserved.

## Strong crits and shot balance audit (2026-10-26-r32)

Fixed signed-word overflow above release power481: the strongest crits could
reverse sideways and trigger the native slow-Y fallback. New90-byte
critical_velocity.s at2ED800 uses wide math and signed-component saturation;
low powers retain the native path. Paired14,400 controller shots,3 clubs,all
3 difficulties/4 period lengths: lower-power outcomes unchanged. Requested
Bouchard Easy run32/100; goalie bonus541/3600(15.03%),0/3600 below160;
Easy2400/2400 actual crits. Matched1,000-shot controls248goals production,
187without goalie bonus,161without crit power. Native boundary88/88(fixes32
previous failures),crit/flash/panel/navigation/menu and full CPU game pass.
Exact rebuild; guarded91-byte diff; IPS verified. See [SHOT_BALANCE_AUDIT.md](SHOT_BALANCE_AUDIT.md).
SHA2568a7304d8fd5708907dced70ea016fffaa8f3096389be18c809f0ebc086423fd9;
CRC92065481. Replaces r31 with saves/remaps preserved.
## Shot panel retained through replay, above the score box (2026-10-26-r31)

The64x40px shot panel now matches the score box and sits immediately above it,
with the charge bar inside. Recorded km/h/mph and charge stay visible throughout
the automatic replay and disappear on return (also on skip). Live bar remains
smooth/red; replay uses native HUD segments so REPLAY keeps its borrowed tiles.
No new pattern memory, palette or DMA. UI full-charge point/sampling now matches
native31; physics/crit/sound and all other game modules remain unchanged.
PASS8 native cases/161 VRAM checks,critical10,flash,navigation14/266,finished9/108,
menu18/240;80 standard goalie cases pass, old home-dive capture fails equally on
r30/r31. Rebuild exact,guarded1099-byte diff,IPS verified. See [SHOT_PANEL.md](SHOT_PANEL.md).
SHA2568e45bb86e1728c5933bbe4614a6a89007459b0a88839570f4415b90cde8c48da;
CRC1A35E68D. Replaces r30 everywhere with save/remap hashes preserved.

## Three/four in original playoffs (2026-10-26-r21)

Original BO1/BO7 and Continue Playoffs now offer Three/Four - Teammates, save
that choice separately from the seasons and retain Two - Head to Head.
PASS10 native result cases/18 cold resumes, independent Full1/Light2/Mini3/
native4 roundtrip/reboot, Continue player edits with unchanged password;
season24/48starts,slots20,nav14/266,font14/3124,goalie88+41 and3 four-pad
season shootout results. Exact rebuild, guarded1691-byte diff, IPS verified.
Source/storage/tests: [NATIVE_PLAYOFF_PLAYERS.md](NATIVE_PLAYOFF_PLAYERS.md).
Replaces r20 everywhere with saves/remaps carried unchanged.
SHA25637feaa90d54806517026d26e67e5fb77ff4992408f3ce26c458f2302f0c38759;
CRCADE85330.

## Saved player counts, including Continue Playoffs (2026-10-26-r20)

Players is now reachable and editable in a season's Continue Playoffs menu.
Choose One/Two/Three/Four - Teammates; each Full/Light/Mini slot remembers its
own count across slot changes and cold boots, in both league and playoffs.
The optional PC/count/complement record at logical SRAM1EF0..1EF3 follows
existing snapshots/checksums; old saves remain compatible. Without an adapter,
a saved Three/Four runs as Two and returns when the adapter is restored.

New seasonplayers.s at2EB000 (622bytes), cursor hookF7782, seasonmenu2002 and
slots1556. Only Players is unlocked; club/results/tree/statistics stay intact.
PASS24 native player-choice cases/48 cold match starts, mixed Full4/Light2/
Mini3 roundtrip and reboot,18 played playoff OT/shootout result/save cases,
goalie88+41,slots20,navigation14/266,finished9/108,club labels18/240,font14
cases/3124 calls/zero wrong fonts. Exact rebuild, guarded3579-byte diff,
original-base IPS230941 bytes verified. See [SEASON_PLAYERS.md](SEASON_PLAYERS.md)
for source, RAM, storage and reproduction. Replaces r19 everywhere with matching
save/state/remap contents preserved.
SHA256a67ecaffe77beb7c13071caee9f507767a4efb7471af3c9ff9487d3b3fb9ae3e;
CRC086E5C8E.

## Four-pad goalie hold, B request and Y control (2026-10-26-r19)

The goalie patch now handles all four physical pads in cooperative 4-Way Play:
AI holds on pickup, B requests a pass to the requester, Y explicitly takes the
keeper. Native P3/P4 reuse D4=0/2 with temporary control banks; requests now
retain physical IDs 0/2/4/6. Short hardware B taps survive the catch/recovery;
first valid request wins, and pause/replay input cannot queue an outlet.
Also fixed: switching from Four into a One-player season could still start
four humans (stale TM), and shootouts left pads 3/4 active with only two actors.
Ordinary two-pad switch/Y code is byte-identical. No SRAM format change.

PASS 88 adapter goalie +41 ordinary goalie cases; 7 two-match lifecycles
(14 games, 14 cold boots, P3/P4 movement, pause/resume, menu circles); a complete
natural two-game Mini run produced 27 keeper passes/17 receptions. PASS 50
OT/shootout cases across ordinary/three/four pads and all playoff formats;
slots20, adapter navigation14/266, completed-season9/108, club labels18/240,
menu font. Exact rebuild, guarded2870-byte diff, original-base IPS verified.
Full source/RAM/test details and the known RAM-only playoff player-count
limitation: [FOUR_PAD_GOALIE.md](FOUR_PAD_GOALIE.md).
User-requested private r18 ZIP backup and hash manifest verified. r19 replaces
r18 locally/site/Bodin's same playlist item, carrying save/state/remap hashes.
SHA2567262f8e4a39d6970b660e24803f1d7212b29aed26a933202296b788aed8d9ec6;
CRC D82B8F78.

## Completed slots open results (2026-10-26)

See Old Full/Light/Mini Season replaces Continue for finished slots. Start
opens saved results; after leaders, view again, start a new selected-slot
season (Xbox X+B confirms, Start cancels), or return. Finished slots retain
the saved club and show Season finished. Fixes fictitious/unrecorded mirror
matches; r30 fixed only the stale Team text. finished.s1104 at2E6000, Start
hook F843E before settings save; seasonmenu1966, results3926 with +8 browser
export and both-pad Start-release wait. No SRAM format change.9 native flows /
108 checks,18/240 menu,12/228 navigation,347 browser pass; source rebuild,
guarded3547-byte diff and IPS verified. Actual user saves unchanged. New dated
2026-10-26 replaces25-r30 locally/site/Bodin, remaps carried. See
FINISHED_SEASONS.md and release.json for full implementation and hashes.


## Completed-season team label (2026-10-25-r30)

Main-menu Team text now follows native Home when the completed season has
FORCED=0; USERW is used only for locked/pregame season selection. This fixes
logos changing while the old name stayed. Active away games retain the user's
club. No new RAM/save field; seasonmenu1894 bytes. 18 native cases / 798 club
checks, 12 navigation cases / 228 checks, exact rebuild and IPS pass. Current
user SRAM stays byte-identical after ordinary menu selection. Statistics code
unchanged. New r30 replaces r29 everywhere, with remaps/saves/states carried.
Full cause, source, evidence and hashes: MENU_TEAM_LABEL.md and release.json.


## Statistics alignment and footer spacing (2026-10-25-r29)

Club skater and goalie headings move one character left to match the values:
column ends23/27/31/35. Leader column ends26/30/34/38 remain correct. Both
screens move headers to row6 and all18 skater rows to7..24, leaving row25
completely free of text above Xbox help26 and footer27. Page2's goalie block
also moves up one row. All players, numeric positions, inputs and saves stay
unchanged. Orange selection now uses row6 C600 and club PTS29/G23/A27;
leaders use +3 columns. seasonend3926 bytes,2E3000..2E3F55,170 bytes below trail.
347 native browser checks pass, including selected glyphs, both planes' empty
spacer row, boundary redraw counters and unchanged SRAM. Fresh production-safe
champion preview, exact rebuild, guarded591-byte UI/title/checksum diff and
original-base IPS reconstruction pass. Source/site/screenshot/docs updated;
r29 replacesr28 locally, Downloads, site and Bodin's same item with saves/remaps
carried. Private evidence:work/stats-spacing-20261025-234630/.
SHA256bd4eb6465d81e72b7a07dcffcee1093719a370eb049307ecf5d100007946bcc7,
CRC8170380A. release.json is authoritative for the current version.


## Xbox sorting-button help (2026-10-25-r28)

Both end-of-season statistics pages now explicitly show
"Sort: Xbox B / Mega Drive C" above the original footer. This is the same
sorting action on the existing six-button remap; the input code is unchanged.
Retains r27's orange G/A/PTS heading and no page-boundary redraw. Module3928
bytes,2E3000..2E3F57,168 bytes below trail.347 native browser checks, fresh
production-safe champion preview, exact rebuild and original-base IPS pass.
Guarded UI/title/checksum diff:1896 bytes vsr27,3215 vsr26. Details and memory
map:STATS_DISPLAY.md. Newr28 replacesr27 locally, Downloads, site and Bodin's
same item; saves/remaps carried.
SHA256990a751d77fe5b7a4b2fdb46c606ce36e9a604ba34ffe6a75e1c3346be505e81,
CRCF96D9E75. release.json is authoritative for the current version.


## Statistics heading colour and page boundaries (2026-10-25-r27)

Selected G/A/PTS is orange in the end-of-season club browser and scoring
leaders. Up at page1 / Down at page2 no longer redraw/fade the same screen.
seasonend3880 bytes at2E3000..2E3F27; cloned glyphs use disabled-window VRAM
B040..B09F, not the live front plane. No SRAM or gameplay change.347 native
browser checks, VRAM glyph/palette assertions and redraw-count regressions
pass; paired capture changes only selected glyphs. Exact rebuild, guarded3170
byte UI/title/checksum diff and IPS verified. Fresh local Dallas champion
preview tested against production code; battery progress/remaps carried.
Replacesr26 locally, Downloads, site and Bodin's same item. STATS_DISPLAY.md
has implementation, memory map and reproduction details.
SHA25668f8f60bd60e9058eb2a9d277ada8672ca5bca8fa66b05dfc23b9a2b368ce7e6,
CRCB7B97FA0. release.json is authoritative for the current version.


This is the continuation reference for Claude/Codex. Read WORKFLOW, HACKING,
CHANGELOG and SESSION_LOG too. The experimental code is now published on this
site, including the earlier private goalie, warning, penalty and simulation
changes. The current versioned test ROM/IPS are separate downloads; see RELEASES.md. The earlier release
ROM/IPS remain available unchanged.




## Goalie catch input and all-team end statistics (2026-10-25-r26)

B held across a native goalie catch now reaches the outlet request before the
previous switch's HELD latch. Reproduced r25's skater4/1→0 then goalie5 catch,
with no pass; r26 serves the pending B in One and both Teammates pads. No B still
means hold. switch510/outlet750, no new RAM/save field. After Season Summary,
Left/Right cycles32 teams; A changes combined/league/playoffs tab, C sorts,
Up/Down pages; Start opens leaders, B there returns to the selected club.
seasonend3618 bytes; BTEAM FFE6FC/SCREEN FFE6FE, actual TEAM and all saves intact.
41 goalie cases,682 native browser checks with exact source totals/unchanged
SRAM,14/266 navigation, clean CPU Mini0–1, exact rebuild/guarded3313-byte diff/IPS
pass. Full cause, controls, sources and fixtures:GOALIE_AND_STATS.md.
SHA2564b42e4b6fa9079bb3bdb40376ff473bed4a0ef6116d9df98f5fef123d1047c8a,
CRC4ACB9735. Read release.json for the current version; older hash claims below
refer to their historical snapshots.

## Current release naming policy —2026-10-25

Current **Season - jdc -2026-10-25**, SHA256
2d4d53a50b2e153be6f5ebfe4b12b9e31d6b391d87dc75ecef57dd0cefed3f47,
CRCFEF0867F. The user now requires a fresh name/version for EVERY changed
release, replacing the preceding current file/playlist entry on Bodin, the
website (ROM+IPS) and Downloads. First daily versionYYYY-MM-DD, then-r2/-r3.
Migrate matching saves/states/remaps/configs and histories with content hashes;
do not retain an old filename just to match saves. release.json supplies the
builder default; --version overrides it for historical reproduction. Current
rename changes only the two header title date bytes147/177; gameplay/assets and
save contents are unchanged. See RELEASES.md for locations and complete process.
This policy supersedes historical filename-preservation text below.

## Current: critical slapshots and Twin Cobra sound (2026-10-25)

Only full forehand windups (native animation07FC, unscaled charge>=31) now roll
Crit Chance; quick wrist releases and backhands do not. Forehand one-timers
qualify. Critical shots play the selected Twin Cobra Digital effect13 (local
clip19) once, alongside the existing red puck. CRT chance/25% bonus, ratings and
saves remain unchanged for qualifying shots. New critical_slap.s126 bytes at
2AD000, extended sound/PCM tables2AD200/2AD400, stream2AD480 and4497-byte PCM
asset2AE000..2AF190; transientFFF0B0 is reserved. Existing sound IDs, samples,
Z80 driver and music are retained. See CRITICAL_SLAP.md for exact hooks, original
sample extraction, source links, native playback and all validation.
110,000 native boundary/probability/sound/physics trials +110,000 without flash,
eight live audio/pad cases, music continuity and a clean full CPU Mini game pass.
Exact rebuild/legacy baseline and guarded5297-byte diff/IPS reconstruction pass.
Current dated SHA2560312bd6009741447726088115801a9e2cd0bd9f385b616743e393cadd3396a1a,
CRCFC0D6B4F. --legacy-crit-shots reproduces the preceding portrait build; older
entries/hashes below describe historical feature snapshots. Dated filename and
three test seasons carry over; no additional backup requested by jdc.

## Bright red crit puck (2026-10-25-r11)

jdc: the red puck looked only slightly tinted. It used palette 0 index A ($064C,
the dithered rink-line red; GPGX RGB 200,68,96). Every palette-0 entry is visible
on the ice (15 green-substitution test ROMs during CPU play), so no rink colour was
changed. Rink palette 1 (ROM $59944) index A ($0062 dark green) is only used by a
few crowd shirt pixels: critical_flash.py patches $59958 $0062 -> $000E (pure red)
and critical_flash.s sets sprite palette 1 (andi #$9FFF, ori #$2000) for the red
phase; glint index 1 = pal1 $CCC. Test driver expects $2923. verify_critical_flash
PASS; forced-flash capture shows a pure red puck (232,0,0). Side effect: those
crowd shirts are red. Backhand question: classify already requires animation $07FC
with raw charge >= 31 (backhand $092E never qualifies); no change made.
SHA256 161efbdcee0cff042bc9903b89ad54b511c1c77348efce4abcbe00f02b80dae6, CRC FFD6745D.

## Season Edition intro, no EA logo (2026-10-25-r10)

jdc's own picture (Downloads/33.png -> tools/data/intro-season-edition.png) converted
by introimg.py to data/intro-season.bin (1120 tiles, 38118 bytes, same format as fix
12) and written by build_test.py over the plain ROM's intro at $2A0000 (asserted
equal to data/intro.bin first; the normal/plain release keeps the old picture).
EA logo screen $17A18 (called from $770C) keeps its VDP/plane set-up and
interrupt state, but $17A74 `movea.l #$FFAC0,a2` -> `bra.s $17AA0` skips drawing,
and $17AAA wait count 80 -> 0 (one 4-frame wait). NOP-ing the whole call blacks
the boot. Boot capture: frames 5-60 black, intro fades in ~80, menu ~550. Full CPU
season game afterwards (2-3) recorded. SHA256 b1288a0ff59e9fbbf983d55ec55dbb6d6d9b201e014801c8fd50bfbaf0efb071, CRC 57CD2624.

## Standalone native playoffs have their own context (2026-10-25-r9)

jdc asked for the thorough fix of the r8 limitation (a standalone New Playoffs
tournament could not coexist with a season in its playoffs). Native playoff state
= RAM D088..D175 (238 bytes: 10-byte password D088..D091, $18192 encode / $1803E
decode, plus packed playoff player stats unpacked by $1833A), inside the D076
settings block (SRAM 1EF6). Every 8KB image was full, so:
- User profiles at logical D20..E1F (CF36 and D45A, 2 x 128 bytes) are now shared:
  slots.s copybank copies 0..D1F and E20..1FFF only. Banks' D20..E1F are free.
  backup recomputes the bank checksum (new `sumbank`); restore ends with the
  active checksum. Old banks stay valid (restore ignores their profile copy).
- Standalone context lives in the Full bank's free range, logical 2D20:
  "NPO1", 238 bytes, sum, ~sum (`ctxsave`/`ctxload`); writing it re-sums the
  Full bank. Missing/invalid magic = no standalone playoff.
- suspend (season view -> native modes) calls ctxload after backing up the season:
  RAM D088.. and active SRAM 1EF6 get the standalone context (or zeros).
- New slots +20 `ctxhook` replaces `jsr $1A206` at $FE6B2 (end of the native
  settings/playoff save $FE696): outside the season view (TAG set, VIEW 0) it also
  saves the context, so it survives season switches and power-off. Boot always
  resumes the season view with the season's own context (no ctxload in init).
- r8's pocont hook at $F7AD4 is removed (original bytes restored).
slots.s grows (BORG 2AA000, < 2AB000). verify_season_navigation.py expectation
updated: season playoffs no longer show native Continue Playoffs (graph [2,3,4]).
Tests (GPGX headless, copy of jdc's save with Mini PO): standalone NP/7 CPU game
2-1 -> Continue Playoffs (CEEA/CEEC 0001/0000); Left to Mini PO 0007/0001 intact;
through Light, Full, Shootout, New Playoffs and back -> Continue Playoffs again;
reboot -> Mini PO 0007/0001 and Continue Playoffs 0001/0000 both present. All
four images' checksums valid, ctx magic in Full bank. verify_season_slots.py
PASS 20; verify_season_navigation.py PASS 18 / 342 checks. SHA256 5bb02972d6ea8d2a4e698f507bd7ddb6c9e4efba8219601110c0e517343de44f, CRC 9D205009.
Evidence: work/npo-ctx-20261025/.

## Native Continue Playoffs hidden for season playoffs (superseded by r9) (2026-10-25-r8)

jdc: after a Mini series the circle showed both "Continue Mini PO" and native
"Continue Playoffs" — the same season playoff (native password shared by the
active slot) offered outside the season. New `season/pocont.s` (2ABC00): hook at
$F7AD4 (jsr $17CA0 -> jsr $2ABC00) runs the native decode, then, with SLOTVIEW
$FFF06C == 0 and the active slot's SRAM $1210 playoff flag 1 or 2, clears
CEEA/CEEC so native validation skips Continue (as for no saved playoff).
Season view unchanged. Tests (GPGX headless): jdc's save (Mini PO active) r7
circle Mini PO -> Continue Playoffs -> New Playoffs..., r8 Mini PO -> New Playoffs
-> NP/7 -> Shootout -> Full -> Light -> Mini PO. Fresh save, standalone New
Playoffs/7 game, one CPU game: r8 shows Continue Playoffs. With Mini PO active, a
standalone New Playoffs game left Mini PO intact (CEEA/CEEC 0007/0001 before and
after, same fixture). SHA256 b0b82332af37f3737c41cd6fc3d1b2e512c17501f1f5c29f4c0e79e5768a57e5, CRC 1252F70E.

## Abandon dialog shows Xbox buttons (2026-10-25-r7)

jdc (Xbox pad) read "A + C" literally. newseason.s confirm line is now
"A + C (Xbox X + B) = yes, abandon" (fits at col 4). Headless check: dialog renders,
A+C still clears the slot (Light 0 GP, checksum OK). SHA256 c448ab56ec959b5bd5988b5b0677249491dc310cd0ebcea5d3ddc70e0627a4a8, CRC 2E5FC2C0.

## Abandon Season in the in-game pause menus (2026-10-25-r6)

jdc wanted to abandon a season from inside a game. build_season.py copies the
pause lists $19700 (normal, ref $7D1C), $1988C (after timeout, refs $7D44/$9D84)
and $19C04 (intermission table refs $131D0/D4/D8/E4/E8/EC) to $2AB100.. with an
appended "Abandon Season" entry -> $2FA800, and repoints the refs (asserted).
Shootout list $19664 and post-game Exit lists unchanged. newseason.s now starts
with a guard: proceed if $D048==0, or 1..3 with SLOTVIEW $FFF06C != 0; otherwise
jmp $9CD8 (shared lists also serve standalone playoffs). Headless on a copy of
jdc's save (Light 29 GP): pause -> Down x16 -> Abandon -> Start returns to the pause
menu, Resume continues (clock 141 -> 133 s); A+C -> main menu New Season Light 1/31.
Light slot 0 GP, Full/Mini statistics identical, checksums OK. Remap carried.
Controller note: Xbox pad A/B/X/Y = Genesis B/C/A/Y with device 513.
SHA256 84d77895c38452f5e96c8ed3ae43397b4a2445a0abe44c70353ac2b5e479c8bf, CRC 1B9CF260.

## Restart Season renamed Abandon Season (2026-10-25-r5)

jdc: "Exit Season" was considered but rejected as misleading (the entry erases the
slot); chose **Abandon Season**. Same 18-char pre-game item in build_season.py
(league and playoff lists), dialog title, "Abandon this season?" and
"A + C  = yes, abandon it" in newseason.s. Behaviour unchanged: A+C clears only the
selected slot and returns to the main menu on New Season <format>, where Team (and
the first opponent) can be chosen; Start cancels. Headless check on a copy of jdc's
save: Light -> Abandon -> New Season Light 1/31, Team Edmonton -> Florida.
Controller remap renamed with the ROM (local + Bodin). SHA256 8960475c5570a3fc6dbed5ccb2bae272c83243cbdcbb0134bb6728be8cc2561f, CRC E3F44C8D.

## Simulated games scale with Per. Length (2026-10-25-r4)

jdc asked that simulated fixtures match the period length he plays. Measured 118
complete headless CPU-vs-CPU games (GPGX, r3, both pads CPU via $C320/$C322, Start
at intermissions; hook/runner work/simlength-20261025/cpu_hook.py, one.sh), 30 per
length (2 seven-minute runs timed out). Regulation goals/team (sd) and SOG/team:
5 min 1.32 (1.15) / 11.7; 7 min 1.93 (1.27) / 16.8; 10 min 2.82 (1.78) / 24.8;
20 min 5.70 (2.31) / 49.5. Scoring is ~0.28 goals per team-minute.
`season/season.s`: `goals` tries = 4/6/8/16 by $FFD050 (0=5,1=7,2=10,3=20; other
values -> index 2 = previous 8-try model); chance formula 60+strength/4 unchanged.
`dist` shots = base 5/9/15/39 + rnd(14/16/20/22) (was 22+rnd 16). New `lenix`,
tables `tries`/`shotb`. season.s 3938 -> 3994 bytes (limit $2F9000). Applies to
league simulation and Simulate Match (shared GOALS_FN/DIST_FN). Per. Length is
locked after game 1, so a season uses one model. Played games unchanged.
Check: fresh Mini, 8 Simulate rounds per length (256 team games each): GF/team
1.50/2.14/2.80/5.55 (incl. OT goal), shots 11.5/16.6/24.1/48.6; checksums OK and
readsram problems 0. Played CPU games at 5 and 20 min completed and recorded.
Saves not migrated (jdc). SHA256 0d16d6e10d3e0b677f60af5d9d3878a748ab92d885530af0c8fb73e703c3a7bc, CRC EF17AFA2.

## Team-stats SOG column cleared (2026-10-25-r3)

jdc saw LAK shots like `141%`, `811`, `1289` (stored 143/82/131 — save data correct).
In WITH_CRIT, skater rows write SOG at cols 33-35 and CRT at 37-39; col 36 was never
written, so after page 2 (goalie header `SV%` and SV% values reach col 36) returning to
page 1 left those characters on display rows 10-12. `season/teamstats.s` now writes a
space at col 36 after SOG. Block $2FB000 grows by 4 bytes (diff $2FB25D..$2FB6DF, end
$2FB6DF). Headless GPGX on a copy of jdc's save: standings -> COL -> page 2 -> page 1:
r2 shows `49%`/`240`, r3 clean. Saves not migrated for r3 at jdc's request ("not
important"). SHA256 1a2d72de66d8919e997fbe3263539dcc114a247777f98c1b56e1bec93f2ed6d0, CRC AC761309.

## Opponent choice only before game 1 (2026-10-25-r2)

jdc clarified the 2026-10-23 request: Left/Right on the main-menu Game row was meant
to vary the FIRST opponent of a new season only; after that the schedule is fixed.
`season/opponents.s` `change` (+4 entry, $2FD600) now returns without touching SRAM
unless the season header has a pending new format (+12 != 0) or zero games played
(+8 == 0). Get/capture/commit, the round mask, schedule, simulation and SRAM layout
are unchanged; block grows 630 -> 646 bytes (ends $2FD885, below switch.s $2FD900).
Only $2FD600..$2FD885 plus header/checksum differ from 2026-10-25.
Tests (Genesis Plus GX headless, private save copies): jdc's Light 30/31 Right/Left —
old ROM EDM->FLA, r2 stays EDM; new Mini pending and empty Full GP=0 — both ROMs
COL->SEA->NJD; Mini after one simulated game 2/8 — old NJD->BUF, r2 stays NJD.
Exact baseline rebuild of 2026-10-25 matched before the change. Full CPU game not
rerun (menu-only change). Note: verify_season_teams.py's mid-season opponent changes
now expect the lock. SHA256 c6c08289c13ef5b82ba66b87e69182e0f417a1321ba4f2fec33db00ff9fe60af, CRC 98017587.
Evidence: ~/.config/retroarch/roms/claude/work/lock-opponent-20261025/.

## Build exactly the current test ROM

From the NHL94 project root, with Docker and the cached SGDK image available:

    python3 tools/experimental/build_test.py \
      "download/NHL 2026 - 94 Edition v5.1W.bin" \
      "/absolute/private/work/NHL94-test.bin"

Input is the **plain patched ROM without season mode**. The builder applies the
experimental gameplay changes, calls the normal season builder, links simulation
against the newly assembled season symbols, and writes only the explicit output.
It does not install a ROM, touch saves, or refresh public downloads. Do not use
`tools/build.sh` for a private test update: that script installs/publishes the
normal release profile and does not include the experimental gameplay patches.

Verified input SHA256:
`f84e2f834b258b7f3b5f2254061b4f00a31ccf5652d1d33e79ba210ae02f0e97`.
Current complete output SHA256:
`0e5c8ee01f69d7219456d414a15b58fe593c0b957c5630886d138027822d0461`.
The build from the site's sources was compared byte for byte with the working
test build: zero differing bytes. Recheck hashes after any later source change.

The normal season builder regenerates its .inc/.bin assets, but the experimental
builder never invokes the public release install/copy script. Source baseline
patterns and free-space assertions fail if given an incompatible ROM.

## Correct player-card portraits (2026-10-25)

The dated build now maps all832 cards to the reviewed player identity, instead
of retaining the pre-roster-update face.702 slot associations reuse existing
portraits,123 use new real NHL photos and7 have an explicit neutral silhouette.
586 pointers repaired;130 new874-byte records at2B0000..2CBBD3. Shared palette,
all gameplay code, roster/stat/save data and SRAM layout are unchanged.
Source: experimental/player_cards.py, prepare_player_cards.py, player_cards/
manifest.json and native verify_player_cards.py. Builds are offline; optional
--no-player-cards reproduces the preceding OT/shootout ROM exactly. Unknown
identities fail rather than assigning an unrelated face. Detailed IDs, sources,
seven missing photos, aliases, ROM format and validation: PLAYER_CARDS.md.
832 native lookups/decodes,28 paired WSH and28 EDM screenshots, byte-identical
rebuild and clean CPU1–4 pass. Current SHA256
0e5c8ee01f69d7219456d414a15b58fe593c0b957c5630886d138027822d0461,
CRC694C73C2. Dated filename/save matching and earlier releases are retained.

## Ten-minute overtime and alternating co-op shootouts (2026-10-25)

Played league and playoff games now use one10-minute sudden-death overtime.
If it stays tied, the existing on-rink shootout starts: three attempts per club,
early clinch when the opponent cannot catch up, then paired sudden-death rounds.
In Two - Teammates, P1/P2 alternate their own club's attempts, beginning with P1,
for either home or away. Only the current shooter is assigned; during a CPU
attempt P1 defends the keeper using the native shootout controls, with P2 idle.
Single-player retains its own pad. Standalone Play Mode → Shootout retains its
native format. Abstract Simulate Match/other-league results keep their existing
weighted overtime result model.

Source `experimental/shootout.s`:900 bytes at2AC000..2AC383, reserved scratch
FFF090..FFF0AF; no new SRAM field or save migration. Entry offsets:
+0 OT timer,+4 tied expiry,+8 frame,+12 attempt setup,+16 attempt completion,
+20 goal statistics,+24 shot statistics,+28 new match,+32 Three Stars guard.
Builder hooks7742,7814,7C3E,7B30,ECE2,F3DC,1485E,12F30,18642 preserve the
original instructions/continuations and fall back to native standalone behavior.

On OT expiry save controller sides, tied scores, rink orientation, line index,
manpower and pulled-goalie state. Native FC47C resets only the16 rink actors and
prepares shootout rosters; match G/A/SOG/PIM arrays are elsewhere and retained.
Clear old referee events and pending penalties/occupancy, normalize both clubs
to a valid full line, clear transient goalie-pass/critical-flash requests and
enter native7866 rink initialization. A late penalty can otherwise leave line5,
five skaters and an unassigned keeper (roster255), causing a native1592C/15AA4
Address Error on the next attempt. The normalized line prevents that crash.

Attempt setup temporarily assigns only the selected co-op shooter, using the
completed own-side attempt parity. Keeping one defending human during CPU
attempts prevents the native demo-input exit; a second co-op defender would be
assigned to a referee because no second skater exists. Native keeper, shooting,
miss/timeout, sound and shootout score panels remain. Native first-line five
shooters rotate, wrapping after five; pad rotation continues independently.

Shootout goals increment D574/D576 only. Bypass normal G/A, scoring log, goalie
GA and shot counters, while retaining the correct team pointers for native goal
cleanup/celebration. Native Three Stars' last-OT-scorer bonus is bypassed for a
shootout decider because it has no normal goal-log entry. A winner is finalized
at the next safe main-frame boundary after the attempt/goal handlers return.
Restore original sides, normal tied scores, line/manpower/goalie state and rink
orientation, add one deciding goal only to the winner's final team score,
restore period3 and enter native7C16 exactly once. Existing league OTW/OTL/points
and native playoff result/password handling then save the result normally.

Team GF/GA include that deciding goal; skater/keeper stats exclude shootout
attempts and goals. Therefore team GF minus summed skater goals is the number
of shootout deciders, bounded by OTW. `season/readsram.py` understands this;
keeper GA also excludes native empty-net goals. Existing ties/statistics and all
three season slots remain intact. Full still uses best-of-seven playoff series;
Light/Mini still use single-game pairings, each with the new OT/shootout rule.

Validation:18 headless native integration cases in `verify_shootout.py` and
`testcases/shootout_hook.py`, including real regulation→600-second OT→shootout,
normal sudden-death OT without a shootout, both co-op sides and pads, early
clinch, equal response in sudden death, two extra rounds, six rounds with roster
wrap, a preserved2–2 score becoming3–2, unchanged normal statistics, checksums,
Full BO7 win counts and correct qualified clubs/rounds in Light/Mini. Goals are
pucks crossing the native goal line; misses use the native shot-clock timeout.
Only fixture clocks, positions and controller sides are injected; the tested
ROM has no instrumentation. Native playoff checks account for home/away swaps
and eliminated clubs returning to a fresh bracket after password clearing.

Also passed20 existing season-slot cases,38 goalie request cases,14 menu-font
circles/3190 native prints with zero wrong-font calls, and a complete CPU Mini
1–4 game with valid league/player/keeper statistics and checksum. Exact source
rebuild, guarded959-byte diff and IPS reconstruction pass; the900-byte routine
and the nine hooks/checksum are the only ROM changes. No private probes remain.

Reproduce (private outputs only):

    python3 tools/experimental/verify_shootout.py ROM /private/shootout-test \
      --seed /private/season-slots-20261025-010418/fresh.srm \
      --playoffs /private/season-slots-20261025-010418/regression/three-playoffs-after-reboot/battery.srm

The playoff fixture is generated by `verify_season_slots.py`. Private evidence:
`~/.config/retroarch/roms/claude/work/overtime-shootout-20261025-025233/`.
Final SHA256 `27349dff9ef46db0b1115f607535c72e03573a91896af03fa25df1b16f6443f7`, CRC32 `57008214`, IPS89870 bytes.
The dated2026-10-24 filename is retained for save/remap matching. Local/Downloads/
public ROM/IPS, Bodin's existing entry, website and notes are updated with backups;
earlier release downloads and existing battery/save-state/remap files are retained.

## Sharp native text while cycling leagues/cups (2026-10-25)

Reproduced the reported blurry/corrupt original main-menu values when cycling
from Mini to native New Playoffs, seven-game playoffs and Shootout. The first
season screen and other screens were correct. RetroArch filters were already
disabled. Native F78A2 sets C2F8 byte bit3 to use the small main-menu font before
its F7B20 value pass. With SLOTKEY set and SLOTVIEW0, even an unchanged row0
callback entered pmwrite→relabel. Native LABELS/F8070 cleared that font bit on
return; subsequent original value strings used the large AAC52 glyph table in
place of BE26A. Old/new glyphs looked superimposed or blurred in the small rows.

Move the unchanged D3-versus-PLAYMODE comparison ahead of SLOTKEY handling in
`seasonmenu.s::playmode`. Unchanged writes now take the native WRITEVAL path
without relabelling, retaining the caller's font state. Real mode changes still
use the corrected empty-Continue validation and one-slot-per-key handling.
Menu remains1836 bytes at2F9800; no allocation, font art, palette, save, filter or
gameplay change. This is a27-byte guarded menu/checksum diff from bf489... .

New regression `tools/experimental/verify_menu_font.py` instruments only private
ROM copies: a JSR wrapper at2FEA00 counts C2F8 bit3 at native F7B66, then tail-
jumps11B92 with the original inline-data return address, SR and registers intact.
Runtime countersFFF080..085 are private only. The release debug region staysFF.

    python3 tools/experimental/verify_menu_font.py /private/current.bin \
      /private/font-checks --baseline /private/preceding.bin \
      --seed /private/preferences.srm --empty-save /private/user-save.srm \
      --three-playoffs /private/three-playoffs.srm

It uses the existing complete menu-circle regression and optional probe checks
in testcases/season_navigation_hook.py.16 One/Teammates/Full/Light/Mini/native-mode/
reverse/user-save/three-playoff cases pass:3608 native text calls, zero incorrect
font selections. The preceding ROM reproduces66 bad selections before its cases
stop. Direct rendered-image comparison shows the initial screen is pixel-
identical and all changed pixels after New Playoffs selection are confined to
text rectangle(16,120)–(296,208); backgrounds/logos are unchanged. Published
before/after screenshots show the original New Playoffs/Home/Away/value text.

Existing20 slot/migration/playoff/restart cases and615 all-club screens pass.
A clean full CPU Mini1–2 OT records valid league/player/keeper totals/checksum.
Exact source rebuild and original-v5.1W IPS reconstruction pass. Current SHA256
`d6483fb99c858b6531f8ca4217e0e5ae0bb85fba5a655b5eedb413d582b34e77`,
CRC533E9A86. Dated filenames remain unchanged for existing save/remap matching.
Private evidence:work/menu-font-20261025-021645/ (native old/new screenshots,
font counter regression, slots/clubs/CPU results, exact byte diff and deployment
reports). Existing battery files are backed up and preserved during deployment.

## Startup/menu freeze at empty Continue Playoffs (2026-10-25)

Reproduced with jdc's exact pre-launch16KB save: an empty selected Mini season
with no native playoff password. Right from Mini suspended the slot, then tried
native Continue Playoffs(mode1). Native F7AD4 decodes the empty password and
branches F7AE2→F7A6E to retry the mode writer with another candidate. The new
SLOTKEY latch blocked every retry. VBlank frames still ran, but the menu stayed
inside validation, ignored further input and retained the old Mini display;
Home/Away could briefly change. The user's post-freeze64KB save confirms active
Mini3, backup mask4, suspended view0 and intact checksum/archive.

Fixed only `seasonmenu.s`: a repeated writer callback with SLOTVIEW1 still blocks
another slot transition for that key. With SLOTVIEW0 it instead enters
original_modes, allowing native validation to skip an unavailable Continue to
New Playoffs. A retry reaching Regular Season0 also uses the slot-selection
path to restore Mini; simply calling WRITEVAL would leave a non-season Regular
Season view. Kept both protections: one slot transition per key, working native
validation/re-entry. Menu grows1826→1836 bytes at2F9800; allocations/storage and
all gameplay code are unchanged. The next main menu automatically resumes the
user's valid suspended Mini snapshot; no external SRAM rewrite is required.

New published regression:

    python3 tools/experimental/verify_season_navigation.py /private/current.bin \
      /private/navigation --seed /private/preferences.srm \
      --empty-save /private/user-before.srm --empty-save /private/user-after.srm \
      --three-playoffs /private/three-playoffs.srm

`testcases/season_navigation_hook.py` verifies two complete circles and one
reverse circle from Full/Light/Mini, One/Teammates, both directions, both captured
user saves, and three concurrent playoffs. All18 cases/348 checkpoints pass,
including native modes, season view, distinct Home/Away clubs and checksum.
The previous9a009... ROM fails the same test at the first empty native Continue.
Direct before/after replay of the user's save confirms it was previously stuck
on native1 after all subsequent buttons; the fixed ROM reaches native2, returns
to Mini and accepts a Players toggle. Existing20 save-slot/three-playoff cases
and615 all-club menu checks pass unchanged. A full clean CPU Mini4–1 records
valid league/player/keeper totals and checksum.

Current SHA256 bf489dc5c47f26d22f0f3f3a01ee25977d42daa7604453e5b8d00abffc09c068,
CRC131A9424. Exact rebuild; guarded551-byte diff is confined to menu2F9800–2F9FFF
and ROM checksum18E/18F. IPS reconstruction against original v5.1W verified.
Evidence: private `work/slots-menu-freeze-20261025-014252/` (captured user saves,
old/new replay, navigation regression, slots/clubs/CPU results, byte verification
and deployments). Same dated2026-10-24 filename for existing saves/remaps.

## Three independent season saves (2026-10-25)

Full, Light and Mini now have separate battery-save slots. Left/Right on Play
Mode selects the next format and automatically resumes its own team, fixture,
standings, player/goalie statistics and playoffs. Empty slots show New Full
Season / New Season Light / New Season Mini; started slots show Continue Full
Season / Continue Light / Continue Mini. Played playoffs identify the format as
Continue Full/Light/Mini PO; the initial last-league-game transition retains
Continue to Playoffs. New Full remains84 games, legacy Full82 remains82, Light31
and Mini8 remain single-game playoffs. Full playoffs remain best-of-seven.

Abandon Season (formerly Restart Season) asks for A+C and resets only the selected slot; Start cancels.
It is also available in the season-playoff pre-game list. The circle still
includes the native non-season playoff and Shootout modes. Leaving the season
view suspends its context; re-entering a format restores its saved context.
Restarting the emulator while outside the season view resumes the suspended
season. Existing native records/settings are included in each snapshot, so the
native playoff password and per-playoff player statistics follow that slot too.

Storage and source:

- New `tools/season/slots.s`:1182 bytes at `$2AA000–$2AA49D`. Entries+0 initialize/
  migrate,+4 select(D0=1/2/3,0 success/-1 unavailable),+8 suspend,+12 restart,
  +16 native pre-game list selector. Builder asserts free space and4KB boundary.
- ROM SRAM descriptor end `$1B8.l`: `$203FFF`→`$20FFFF`; start stays `$200001`.
 32KB logical, odd-addressed bytes in64KB physical save. Legacy8KB logical active
  window `$0000–$1FFF` is unchanged; Full backup `$2000–$3FFF`, Light `$4000–$5FFF`,
  Mini `$6000–$7FFF`. Active data is authoritative and may be newer than its bank.
- Formerly unused active logical `$1EA0–$1EF5`: magic S3SV(+0),version1(+4),
  active slot1..3(+6),valid-backup mask(+8),season view1/suspended0(+10).
  +12 stores66 bytes from native RAM `$D046–$D087` for live settings restoration.
  These fields are covered by the existing active8KB native checksum. Each
  snapshot retains its own native checksum and self-identifying slot metadata.
- On switching, validate an existing destination first; synchronize RAMD076's
 256-byte native settings/password into SRAM1EF6; capture live settings, update
  metadata/checksum and snapshot the old active window. Restore the destination
  or initialize only the new format's league data, then restore the current
  directory mask/selected slot and recompute the native checksum. A damaged
  destination is rejected before either current data or snapshot is overwritten.
- Copies use full32-bit pointers and byte writes to odd SRAM addresses, with
  SRAM enabled and interrupts masked only for the transaction. The old helpers
  sign-extend doubled word offsets and cannot address all new banks. Native
  helpers still operate on their original8KB window. Checksum work does not
  reload SRAM into FF0000/replay RAM.
- `seasonmenu.s`:1836 bytes at `$2F9800` (was1894); removed obsolete destructive
  setsel helper. Slots are forced before native club/logo comparisons. Treat
  native direction as a sign and latch one transition per main-menu key event:
  native validation can amplify its step and call the writer repeatedly.
- New runtime RAM `$FFF068–$FFF06F`: initialization tag5333,slot ID,season-view
  flag,menu-key latch (four words). Scratch `$FFE800–$FFE85F` reserved for slot
  metadata/header. Existing goalie request `$FFF064–67` and crit `$FFF060–63`
  remain untouched.
- `newseason.s`:340 bytes at `$2FA800` (was514); shared slot reset and revised
  confirmation text. `playoffs.s`:1074 bytes at `$2FC800` (was1066); active-season
  gate prevents standalone native playoffs from modifying a suspended season.
- Native pre-game list hook `$13134–$1313B` (8 bytes)→slots+16. Only season
  playoff pre-game index5 uses the9-entry copy at `$2AB000–$2AB0E3` (228 bytes).
  Other native lists stay intact; the regular9-entry list remains `$2FAC00`.
- `build_season.py` installs the manager/list/header/hook and relinks changed
  menu symbols. `readsram.py` accepts legacy/expanded saves and `--slot full`,
  `--slot light`, `--slot mini`. `test_core.py` writes the header-declared SRAM
  span rather than truncating every fixture to16KB. RetroArch may keep a compact
 16KB file until backup banks contain data; its real save writer was verified
  to retain a populated64KB file and every snapshot byte.

Migration is automatic in the ROM: an existing running season maps by saved
length to Full(82/84), Light31 or Mini8, with all season/statistics bytes retained.
An old pending New choice beside a running season is cleared so it cannot erase
that saved season. Empty legacy preferences map by their pending format, otherwise
Full. No live battery file is externally rewritten during deployment.

Verification:

    python3 tools/experimental/verify_season_slots.py /private/current.bin \
      /private/slot-tests --fixtures-root /home/jdc/.config/retroarch/roms/claude/work \
      --seed /private/read-only-preferences.srm

All20 cases pass: different clubs/GP in three native-simulated leagues, exact
statistics restoration, power cycles, cancel/confirm restart of Light only,
legacy82/31/8 migration and final games, stale legacy New choices, damaged-slot
rejection, Full84's last three games, a native best-of-seven playoff game and
restored1–0 series wins, Light/Mini round2 resumes, playoff-menu restart, and
three concurrent independently played playoffs before/after reboot. The late
playoff test shortens the native match clock and sets a result; ordinary native
playoff progression/password saving executes. Native highlights are dismissed
before selecting Exit Game. No debug driver is installed in the ROM.

Also passed615 all-club menu/logo/opponent/One/Teammates checks,38 goalie B-request
cases, and a complete clean CPU Mini1–2 in overtime with valid league/player/
keeper totals and checksum. The preceding one-save ROM fails the same slot
regression at its first save-size/slot check. Exact rebuild is byte-identical;
4420-byte guarded diff covers only slot manager/list, changed season/menu/reset/
playoff code/hooks, SRAM descriptor and ROM checksum. Private test region2FEA00
remainsFF; gameplay/crit/glass/goalie code is byte-identical to the previous ROM.
IPS reconstruction verified against the original v5.1W base. Initial three-slot CRC32 **8B08E457**; the menu-freeze correction above is newer.
Private evidence: `work/season-slots-20261025-010418/` (20-case regression,
all-club/goalie checks, CPU final save, native frontend save test, byte diff,
backups and deployment reports). Dated2026-10-24 filename stays for existing
save/remap matching; only the dated downloads are updated. Deployment preserves
34 local save/state/remap files and19 other Bodin files, with the same19 playlist
entries. Dated battery-save/remap backups are stored privately on both machines.

## Goalie holds until P1/P2 requests a pass (2026-10-25)

The human team's AI goalie now holds the puck until a **fresh B press** from a
human skater. In One, P1 requests the pass. In Teammates, P1 passes to P1's
currently controlled skater and P2 to P2's; the other pad's skater and CPU skaters
are not substituted. No automatic proximity/ranking outlet remains. A request
is explicit even with an opponent nearby, so the native pass can be intercepted.
A tap during diving/catch recovery waits until the keeper can pass. First valid
request wins if both pads ask during recovery. B is consumed through hold/release
so it neither changes the requester nor immediately passes again on reception.
The ordinary finite possession timer still produces a faceoff after a prolonged
hold. Y/manual goalie controls, CPU-only keeper AI and special-mode fallbacks
retain their previous native paths; no save format or roster change.

Implementation:

- `outlet.s`:748 bytes at `$2FDC00–$2FDEEB`. Entries+0 decision,+4 exact target,
  +8 exact goalie vector,+12 new request handler. Existing hooks `$D75C`(8),
  `$BCDA`(12), `$BDBA`(10) remain. Only our synchronous native `$BC64` call uses
  the guarded `OUTL` exact-target/vector override; normal pass physics continue.
- `switch.s`:500 bytes at `$2FD900–$2FDAF3` (was512). Calls `$2FDC0C` before
  ordinary B switching; reuses its existing per-pad pending/HELD state. Equivalent
  sign-extended `$FFFFxxxx` constants allow absolute-word addressing to fit below
  glass `$2FDB00`. Builder now asserts `len <= $200` to enforce that boundary.
- `goalie_ai.s`:90 bytes at `$2FE400–$2FE459` (was86). Each accepted native
  pickup clears the new request, preventing reuse on a later possession.
- New scratch `$FFF064.w`=pending pad offset+1 (1=P1,3=P2), `$FFF066.w`=keeper
  index5/11. Clear on successful pass, invalid request, changed owner, native
  fallback or accepted pickup. `$FFF058–5F` remains the transient pass descriptor;
  `$FFF060–63` remains reserved for the critical-puck flash. No SRAM additions.
- The requester is revalidated against current pad side/object and active human
  skater flags when served. Recovery/pass lock or unavailable target waits within
  the native possession window. Clear all32 bits before DIVU6: a word-only copy
  leaves native register residue and incorrectly rejects real-pressure requests.

Headless regression (normal6-button device513):

    python3 tools/experimental/verify_goalie_request.py /private/current.bin \
      /private/request-tests --fixtures-root /home/jdc/.config/retroarch/roms/claude/work

`goalie_request_hook.py` sets pad assignments and injects real button presses;
it does not warp positions or bypass native pass/recovery. Captured gameplay
states remain private. Required relative fixtures beneath `--fixtures-root`:
`goalie-outlet-20261024-173340/capture/gk5-clear.state` and `gk11-clear.state`,
`goalie-recovery-20261024-145816/scenario-home/before-dive.state` and the matching
`scenario-away` path, and
`goalie-auto-20261023-142924/native6-coop-baseline/before-catch.state`.

All38 hold/request cases pass: both nets, One/Teammates, P1/P2-only, native Auto
setting,1/5/25-frame B, no-B holds, both pads requesting during recovery, actual
dives and close pressure, manual fallback and CPU-only outlets. Exact receiver,
unchanged requester, no early pass and cleared transient/pending state checked.
Existing26 old/new switch-input cases also pass, including own-carrier passing,
whistle/shootout paths and both pads' Y selection. A clean complete CPU Mini
match ended1–3 and saved valid league/player/keeper totals and checksum (including
native empty-net differences). Exact source rebuild is byte-identical; guarded
1276-byte diff is limited to switch/outlet/pickup routines and ROM checksum.
Private instrumentation region `$2FEA00–$2FEFFF` remains allFF. IPS reconstruction
against original v5.1W verified. Current CRC32 **B3EF6EF5**.
Evidence: private `work/goalie-request-20261025-004956/`, including the published
regression output, switch matrix, CPU save, verification and deployment reports.
The dated2026-10-24 filename remains for existing save/remap matching.

## Glass-break sound on critical releases (2026-10-25)

A critical shot now plays the existing glass-break sound, once at release,
in place of its ordinary shot sound. The red flash, chance, power bonus, physics,
ratings and save format are unchanged. This reuses the existing native sound ID;
it does not invoke the glass-break animation, whistle or collision handler.

The real glass collision at$14C18 pushes SFX14 and calls$11132 at$14C1C.
`experimental/critical_sound.s` uses that same ID at the actual-shot exit$C538.
Its12-byte helper is at$2FF600..$2FF60B; the critical-success hook at$2FF472
changes from JSR$2FF504 to JSR$2FF600. The helper writes14 into the pending
shot's stack argument, then JMPs$2FF504 so the existing flash/power-read hook
sees exactly the original stack. The native shot sound call and argument cleanup
run once as before. Ordinary/alternate shot sounds remain native.

The sound word is at **22(SP)** in this helper:4-byte JSR return,12 bytes of
critical.s saved D0/D1/A0, then6 bytes of target coordinates. There is no added
call frame in the tail chain. Do not call this helper outside that shot context.
No register or RAM allocation is added. `critical_sound.py` guards the hook/free
bytes. With flash disabled, a14-byte helper replays MOVE.W BEDE,D0;RTS instead
of chaining to the absent flash hook.

Build switches: default includes sound. At the sound-change snapshot,
`--no-crit-sound` reproduced the preceding menu-fixed ROM SHA256
`ddb73d35bada5a140ac6b1d1f12099ed06a7284b0e36bf836963969a4b7e32ca`.
Current feature flags also retain the later goalie-request change, so that
historical hash is not the current feature-off output.
`--no-crit-flash` keeps this sound and crit power without flashing; `--no-crit`
omits all critical features. The existing dated2026-10-24 filename is retained
so Downloads/Bodin saves and remaps keep matching their current game entry.

Headless sound-stack regression (private output only):

    python3 tools/experimental/verify_critical_sound.py /path/current.bin \
      /absolute/private/sound-checks --baseline /path/preceding.bin

50,000 native shot-tail trials passed: crit0/50/100, an alternate native shot
sound20, and preceding-ROM comparison. Every crit emitted exactly one SFX14,
every ordinary shot retained24/20, with no unexpected sound IDs/callers; native
power/velocity and stack restoration passed. Repeated40,000 trials with flash
disabled also passed. The existing90,000-shot/832-identity crit regression and
136-render flash lifecycle/cache regression passed unchanged.

Six live gameplay/audio fixtures cover old/new crit and ordinary single-player
shots plus both Teammates pads. The native$C53C return trace emits14 once for a
crit,24 for ordinary shots. Power remains216 for old/new crit and173 for old/new
ordinary in the single-player fixture. Captured WAV output is nonzero, the crit
wave differs from the preceding shot sound, and ordinary WAVs are byte-identical.
Private sound taps/forced RNG were used only in those controlled fixtures, never
in the published ROM. A clean final-ROM full CPU Mini ended0–1 with valid native
team/player/goalie totals and SRAM checksum. The source rebuild is exact; only
16 ROM bytes differ (checksum, two hook operand bytes and12 helper bytes).

Evidence:`work/critical-sound-20261025-001208/` (sound-regression/,
no-flash-sound-regression/, shots/, flash-regression/, audio-verification.json,
wave-verification.json, cpu/, verification.json and deployment reports).
Final dated SHA256:
`a328835ff33de5ca65545553f542b77856d4b1f208e4c498e62d4e7c14ff4ed2`.

## Correct team/opponent logos in every season format (2026-10-24)

The user could see Edmonton on both sides while choosing a Mini team. Reproduced
with a private copy of the current save: menu settings and the pre-game actual
clubs were EDM10/DET9, but both main-menu logo caches were10, so two Oilers
logos appeared. The Game row already said1/8 vs DET. The same shared menu bug
reproduced in Full, Light and Mini, both One and Two - Teammates.

Cause: native Left/Right handling at$F75D2 captures old clubs, invokes the value
writer, copies HOME/AWAY to$C330/$C332, and updates logos using$D428 and caches
$D42A/$D42C. `force` previously recalculated the scheduled opponent during the
later menu text redraw. Changing HOME therefore let native logo loading see the
new user club with the previous opponent. For selected round10 (offset31), a
Left press even made those transient clubs equal, producing identical logos.
The actual fixture was recalculated before entering the game; this report was
confirmed as a stale-logo defect, not two copies of a club in the league table.

Fix in `season/seasonmenu.s`: permitted setting writes now call native WRITEVAL,
then `force` immediately when SELV is set, then return with `TST.W D2` as before.
The Players special path joins that same write/force continuation. Thus both
scheduled clubs are ready before the original caller compares/loads the logos.
This also handles One/Teammates toggles in a continued odd-numbered fixture,
where the user's club moves between away and home. Existing season team locks,
opponent masks, schedule, scoring and all save fields stay unchanged.

`seasonmenu.s` grows1880→1894 bytes, still at$2F9800 and within$2FA000. Public
entry offsets+0/+8 are unchanged. The builder relinks its teamrow/labelhook/
mainlr/skipmatch symbols; no other allocation/RAM change. New guarded binary
diff is1642 bytes, limited to ROM checksum, season-menu code/data and four
symbol-based native hook targets. Crit/flash/goaltending/shot code is byte-identical.

Headless regression (reads seed only, writes private case saves, no window):

    python3 tools/experimental/verify_season_teams.py /path/current.bin \
      /absolute/private/menu-checks --seed /path/copy-of-valid-16KB.srm

Use a valid Genesis Plus GX seed with default One-player preferences. The runner
creates pending Full/Light/Mini saves with round10 selected, and drives normal
controller input. Its hook checks menu settings, actual clubs, opponent text,
native logo identities, checksum and one fully simulated league round. Logo
cache high bytes may beFF, a native invalidation tag after Players changes;
club IDs are in the low bytes, not the complete tagged word.

All615 screen checks passed:32 team changes in each of six format/player cases,
64 opponent changes (both directions and wrap), first-game entry/simulation,
continued odd-round One/Teammates toggles and saved-team locking. All32 clubs
have1 GP with matching team/player/goalie totals after each first simulation.
The preceding ROM fails at the first Team Left press in all six cases (both
logo IDs29 while actual clubs29/28). An actual legacy82-game save at81 GP
passed five menu/pre-game checks, both player modes and unchanged season data.
Full CPU Mini game ended2–5 with valid saved totals/checksum, including native
empty-net keeper differences. Exact clean source rebuild matched byte for byte.

Private evidence:`work/distinct-teams-20261024-233701/` (old-build-failures.json,
matrix/, legacy82/, fixed-repro/, cpu/, verification.json, deployment reports).
The live emulator was left open; installed dated files take effect on restart.
No live save/remap/season was rewritten. Older normal releases remain unchanged.
The homepage now shows the corrected EDM–DET team-selection screenshot.

The feature switches `--no-crit`/`--no-crit-flash` still disable those features,
but this menu correction applies to current source builds with either switch.
Earlier sections' exact historical profile hashes refer to their source version
at that time; they no longer describe the output of this updated menu source.
Final dated SHA256:
`ddb73d35bada5a140ac6b1d1f12099ed06a7284b0e36bf836963969a4b7e32ca`.

## Red flashing puck on critical shots (2026-10-24)

A critical release briefly alternates only the puck between its normal appearance
and red. Four visible puck renders are red, then four ordinary, repeating until
possession, a dead puck, another shot or expiry (maximum127 visible renders).
The first phase is red. This adds no RNG, changes no shot power/chance/ratings,
and does not modify shared CRAM/palette entries. The shadow remains native.

Source: `experimental/critical_flash.s` (234 bytes at$2FF500..$2FF5E9),
`critical_flash.py` (guarded installation and tile generation). Fixed entries:
+0 begin-shot reset, +4 mark critical, +8 successful pickup, +12 frame/cache,
+16 DMA source, +20 sprite attributes. RAM$FFF060.w is the visible-render
lifetime; $FFF062.w is the last rendered red phase. These four bytes are now
occupied; older private traces that usedFFF060 must be relocated before reuse.
No SRAM layout/normal ratings changes or save migration.

Installation hooks:

- Critical code$2FF43E (6): reset lifetime, replay original roster-slot read.
- Critical code$2FF472 (6): arm127 only on a successful crit, replay power read.
- Successful blue-line pickup path$2FF39C (6): clear effect and replay BCLR.
  Failed goalie pickups take the preceding native abort and preserve the effect.
- Native render$167F8 (8): original frame mask, then for puck A3=$FFFFB74A,
  choose phase, invalidate its +8 frame cache only when colour changes, and
  resume the native comparison at$16800. Skaters, nets and shadow are excluded.
- Native DMA source$1686A (6): replay base addition; on red puck frames map
  sources$7C324..$7C423 to the corresponding red copies at$2FFE00..$2FFEFF.
- Sprite attributes$168FC (8): replay cache-index/tile-base arithmetic and clear
  only palette bits$6000 for the red puck. Priority, flips, geometry and tile
  number stay native. The ordinary phase keeps the original palette.

Puck frames$18B..$194 all have one8x8 sprite, using eight contiguous tiles
$F25..$F2C. Their256 pixel bytes are copied with identical transparency/shape;
nontransparent pixels use fixed rink palette0 indexA (reddish$064C), with white
index1 retained for the native glint. Palette0 is independent of team uniforms.
The separate shadow frame$18A and its object are untouched. The frame cache
must be invalidated at both red→ordinary and ordinary→red, otherwise native
sprite DMA would keep old pixels for an unchanged animation frame. Native DMA
can delay the displayed phase by one video frame; cache state and VRAM were
checked separately.

The dead-puck test is **byte$FFC2EA bit0**, also used by the native clock gate.
Do not use$C3F4: that is the last penalty-report ID, can remain nonzero during
live play and would suppress flashes after a penalty. Owner$FFB7AA>=0 also
ends the effect, and accepted pickup clears it before the next puck render.

`build_test.py --no-crit-flash` reconstructs the preceding critical-only ROM
SHA256 `95a05f76f358ae94a1259d57ca266d4edf0d4ce4b0166be81fd88eabeee53b7a`.
`--no-crit` still reconstructs the pre-critical dated profile. Current default
includes the visual effect. Rating table/JSON and native statistics UI are unchanged.

Regression (headless, all generated test ROMs stay in the chosen private folder):

    python3 tools/experimental/verify_critical_flash.py /path/current.bin \
      /absolute/private/flash-checks

The reset-vector driver executes136 frame-hook calls, verifying exact4/4 phase
cadence, cache invalidation only at transitions and expiry. Checks also cover
register preservation, all eight DMA mappings, palette/flip/tile preservation,
shadow exclusion, ordinary-shot reset, possession/whistle reset and the real
native blue-line pickup gate (failed goalie attempt vs accepted pickup).
The existing critical-shot regression passed again with identical90,000-shot
probability/velocity results and all832 native/shared lookups. A natural critical
shot captured from a clean CPU game showed multiple red/ordinary phases in
actual VRAM tiles, sprite palette bits and RGB screenshots without physics or
input overrides. A separate controlled stationary-puck render test verified
restoration on a real subsequent pickup. Full final-ROM CPU Mini game reached
overtime and ended1–0 with valid team/player/goalie totals and SRAM checksum.
No normal palette entry or other-object colour was rewritten. Exact source
rebuild and --no-crit-flash prior-profile reproduction passed; guarded binary
diff is511 bytes, limited to checksum, these hooks/code and copied puck pixels.
Private test drivers at2FEA00 are excluded from the final ROM.

Evidence:`work/critical-flash-20261024-230827/` (flash-regression/, shot-regression-final/,
natural-fixed/, visual/, cpu-fixed/, rom-verification.json and deployment reports).
The animated website screenshot `img/critical-puck-flash.gif` uses only frames
from the natural shot. Final dated SHA256:
`4bb1a229251ed1699a1be11040530d15505e204eb310a29bf2772cd00a885c69`.

## Critical shot rating, native attribute and season statistics (2026-10-24)

Each of the 832 roster slots has a separate ROM byte, 0..100; goalies have zero.
One native RNG(1000) draw per actual released skater shot is compared to that
rating. Thus 100 means 10%, 50 means 5%, and zero skips the draw. A successful
roll adds `floor(native_release_power / 4)` to release power: +25%, rounded down.
This is power, not an automatic goal or a goalie-miss effect. The native velocity,
height, charge, fatigue and aiming routines remain in use. The hook runs after
native aim/spread and before velocity/lift, so the bonus does not reroll accuracy.
Human and CPU skaters use the same rule. Passing and goalie attributes are not
changed. The statistical league-result generator does not emulate shots and
retains its documented model; no season-stat migration is needed.

`experimental/critical.py` computes an offensive quality score from native roster
ratings: `2*Off + 2*Power + 2*Accuracy + Stick + Passing + Agility + Speed`.
Across the current skaters (quality 8..53), the lowest gets20 and the highest100;
intermediate values use `20 + round_half_up(80*(quality-low)/(high-low))`.
An explicit per-player override can be added to `experimental/crit_overrides.json`
as `"EDM:Connor McDavid": 100` (empty by default). Native ratings, names and
lineups remain untouched. Rebuilding recalculates the table from that ROM.
Current examples: Nathan MacKinnon100, Connor McDavid96, Nikita Kucherov96,
Anton Frondell93. All assigned values are in `experimental/crit_ratings.json`
and the builder's private `<output-stem>.crit.json` sidecar.

- `critical.s`: 208 bytes at `$2FF400–$2FF4CF`, fixed entry offsets +0 shot,
  +4 native attribute, +8 shared lookup (D0=team, D1=zero-based roster slot).
- Rating table:832 bytes at `$2FF800–$2FFB3F`, offset `team*26 + slot`.
- Extended native attribute table:398 bytes at `$2FFC00–$2FFD8D`, copies all17
  original attributes and adds `Crit Chance`, mask0/mode12. Native pointers at
  `$84DC/$8BD8` point to the start; `$FC85E/$FC890` to start+22 (skip Status).
- Actual-shot JMP hook:`$C4A8` (8 bytes), replays `MOVE.W BEDE,D2; MULS #68,D2`,
  then resumes `$C4B0`. Identity uses shooter +$66 (roster slot), +$62 bit6
  (home/away membership), and `$FFC331/$FFC333` team IDs. Bit7 is attack
  direction and must not select the roster: it changes at period ends.
- Native attribute JMP hook:`$8D68` (8 bytes). For mode12, matches A2+$1E's
  team-record pointer against the32 entries at$30E, then looks up the selected
  roster slot and uses the native integer renderer at$8E4C. Other modes replay
  the original comparison/branch at$8D70/$8D78.
- `season/teamstats.s` is now1756 bytes (was1670), still within$2FB000..$2FBFFF.
  Skater SOG moves to column33 and CRT is column37 (three digits, ends39).
  Reads the same lookup table after sorting; goalie rows clear the extra column.
  Footer explains `CRT 100 = 10% chance, +25% power`.
- `season/build_season.py --crit` generates `features.inc` with WITH_CRIT=1;
  the experimental builder passes this flag and installs the hooks/table.
  Normal builds use WITH_CRIT=0 and retain their original display/profile.
  `experimental/build_test.py --no-crit` reproduces the preceding dated ROM
  SHA256 `bb08ce17e7c760920dbded861387bfbce5711c581fad495ef03bcc0056db3322`.
- No persistent RAM, SRAM field, or normal rating nibble was repurposed.
  `$2FEA00..$2FEFFF` and `$FFF060...` were used only by private test drivers/
  sound/shot traces; those test hooks are excluded from release.

Reviewable regression command (all generated ROMs and output go to the chosen
private directory, no window, no live saves):

    python3 tools/experimental/verify_critical.py /path/current.bin \
      /absolute/private/crit-checks --baseline /path/preceding.bin

Its reset-vector fixtures construct the native shot stack and execute the real
power/velocity tail. 100,000 shots across ten old/new cases passed: zero0/10000,
50:522/10000,100:1028/10000, plus away membership, reversed attack directions,
another skater, goalie and invalid-slot exclusions. Native puck velocity was
exactly ordinary/1.25x in these fixtures; zero chance consumed no RNG. All832
slots matched the ROM table through both shared and native attribute paths,
with lookup register preservation checked. Normal UI navigation opened all32
teams, both pages, goalies and return paths, verified every rendered CRT against
its actual sorted roster slot, and preserved the season data byte for byte.
An instrumented full CPU Mini game observed39 shots,1 critical and maximum
release power359. A clean final-ROM CPU game ended1–3 with valid team/player/
goalie totals and SRAM checksum (including native empty-net differences).
Clean source reconstruction was byte-identical; --no-crit matched the preceding
ROM exactly. Team website IDs match all832 ROM slots. Public site adds the Crit
column and native attribute screenshot. Private evidence:
`work/critical-shot-20261024-220545/` (verification/, all-teams/, cpu-release/,
build logs, ROM diff checks and deployment reports).

## Extra warning beep from penalty expiry removed (2026-10-24)

The user reported an unnecessary beep at 1:00 in addition to the intended 0:40
warning. The period IRQ was already restricted to exactly 40, but another caller
also used **SFX 2**: penalty expiry at `$1295E`. The earlier warning patch replaced
that shared sound's data with three louder pulses, so penalty release could play
the same warning at other clock times. The earlier test only checked the clock
path and missed this dynamic stack-argument selection.

Fix: `$1295E` instruction `MOVE.W #2,(SP)` becomes `MOVE.W #-1,(SP)` (immediate
word at `$12960`). The native sound wrapper `$11132` treats a negative ID as
silent and still cleans its stack/restores registers. Player release runs before
this selection and is unchanged. The ordinary final-five-second penalty ticks
(SFX 1), whistles, passes, music and period horn remain. Only the expiry's duplicate
warning is silenced; the clock's SFX 2 at exactly 0:40 still plays three pulses.
No added ROM routine, RAM/SRAM allocation or save migration.

Reproduced a real penalty release at match clock60: old build requested SFX2 at
frame101 and produced three warning pulses; fixed build passes the silent ID and
produces none. Penalty arrays before/after are identical old/new. Ten audio cases
check 40/60 in periods1/2/3/overtime plus old/new penalty release at60. Every 40
case has exactly three pulses; 60 cases have no clock warning. Full CPU match
finished with one result and consistent native keeper/team/player totals/checksum.
Canonical-source build and IPS reconstruction match the tested ROM. Only checksum
byte `$18F` and the two immediate bytes `$12960–61` differ from the preceding ROM.

Private evidence: `work/warning-only40-20261024-192406/`. Debug-only sound taps
at `$11132/$11156` recorded all sound IDs, callers and clock values, exposing the
penalty caller `$12966`. They are NOT in the release. The third-period music cue
at `$7A34` was investigated and left unchanged; it is a separate scheduled song.
Dated local/Downloads/site ROM/IPS and Bodin updated with previous ROM/playlist
backups and saves/remaps retained.

## Historical automatic goalie outlets (2026-10-24; superseded 2026-10-25)

This section records the previous automatic policy. The current B-request policy
and allocations are documented above.

The user reported that the automatic goalie held the puck instead of passing to
his controlled skaters. Native AI picks a random teammate, may reject that one
and quit the attempt, waits until <=90 possession ticks, and does not prioritize
human targets while the goalie is AI-controlled. Keeping pickup control with the
AI therefore needed an explicit human-outlet decision as well.

`tools/experimental/outlet.s` handles an AI goalie holding the puck for a team
assigned to either ordinary pad. After native recovery and about 24 possession
ticks (timer <=156), it actively chooses the on-ice skater controlled by P1/P2.
An active opponent within **40 pixels of the goalie** makes him hold the puck.
When clear, it prefers an unmarked, forward outlet; nearer candidates break ties.
A receiver marked within 24 pixels gets a cost penalty. Safe side/back outlets
are allowed; a segment crossing the own goal line inside +/-36 pixels is rejected.
Wrong-team, goalie, penalized, unavailable/action-locked and unassigned targets
are excluded. With no eligible human, the goalie holds rather than dumping to a
CPU skater. Normal possession expiry/faceoff still applies under prolonged pressure.

The pass uses native BC64/BE26 physics, target leading, receive state, goalie
softening, animations and sound. A guarded target selector stops BC64's rescan
from substituting a CPU skater in the same aim direction. A second guarded hook
retains the actual checked pass vector; the original goalie Y-velocity flip
would otherwise turn a safe side/back pass away from its selected receiver.
Ordinary manual goalie passing/Y, CPU-only goalies, shootout, penalty shot and
multitap use the original paths. A pass can still be intercepted or bounce in
normal play; this chooses the human outlet rather than guaranteeing possession.

Allocation: **800 bytes `$2FDC00–$2FDF1F`**, below the existing `$2FE000` hook.
Entries +0 decision, +4 exact target, +8 goalie vector. Hooks `$D75C–$D763`
(8 bytes), `$BCDA–$BCE5` (12), `$BDBA–$BDC3` (10). Decision exits `$D880`
after handling; original timing exits `$D764/$D76A`. Selector exits `$BD64`
or native `$BCE6`; vector exits `$BDD8` or native `$BDC4`. The latter restores
TST velocity / BTST direction so original N and Z flags survive correctly.
RAM `$FFF058–$FFF05F`: target+1 word, passer word, signature long `OUTL`.
Only our synchronous BC64 call enables the target/vector overrides; all eight
bytes are cleared after return. No save/SRAM change. `$FFF056` glass pane is intact.

Verification: **44 old/new cases**, both goalies, single/co-op, only either pad,
wrong-side pointers, Auto goalie, no available target, manual goalie, CPU,
shootout and multitap. Clear co-op home: new pass at frame105 to P2/object1,
received at148; away: pass104 to P1/object10, received126. Only-P2 away also
received the outlet on object7. Six real close-pressure/recovery cases confirm
no pass inside the 40-pixel radius or before recovering from a dive; passing
starts when clear. Seven extra cases check swapped pads, avoiding a marked
receiver, opposing controller membership and explicit Y on both co-op pads.
Complete old/new CPU games counted once and passed native keeper/team/player
statistics and checksum checks. Canonical source rebuild matches the tested ROM;
IPS reconstruction verified. No player coordinates were moved in these tests;
fixtures retain the native sorted collision structures.

Evidence: private `work/goalie-outlet-20261024-173340/` (natural clear fixtures,
44-case results, pressure/recovery and extra checks, old/new CPU, exact rebuild,
backups and delivery report). Dated local/Downloads/site ROM/IPS and Bodin's
existing dated entry updated; older ROMs and playlist backed up, saves/remaps
preserved. The user-facing build remains Season - jdc - 2026-10-24.

## Breakable glass: 50% on qualifying impacts (2026-10-24)

The user requested glass breaking more often, approximately half the time.
The original ROM did not use a probability roll here: its trigger required a
puck at height 19..31, Y >=280, X only 11..18, outward Y speed >=`$1F00`.
That narrow slot always broke its single centre pane; almost all other impacts
could never break it. The existing glass art fits the **far-end straight panes**.

`tools/experimental/glass.s` now tests X -64..64 across the flat far-end glass,
retaining the original height/speed requirements. Each qualifying hard outward
puck impact gets one native `RND(100) < 50` draw. Failure takes the original wall
rebound path; success uses the original glass animation/sound, crowd reaction,
out-of-play event and faceoff. This is 50% of qualifying glass impacts, not 50%
of all shots/body checks. Near-end, side boards, corners and low/soft shots keep
native behavior. It is not a new glass effect on every section of the rink.

Original glass/siren positioning forces X=0. The new glass-only position hook
uses the nearest 32-pixel pane centre (-64/-32/0/32/64), so shards and the missing
pane appear at the impact pane. Other shadow/siren frames retain X=0 and native
near/far Y, height and priority logic. The native animation is `$1078`, frames
`$28D..$291`. Source interpretation also used the author's annotated
[Chaos81 NHL94 disassembly](https://github.com/Chaos81/nhl94-disassembly), private
reference commit `b53cbd01245473325c12a7eeb13889bab33cedfe`.

Allocation: **140 bytes at `$2FDB00–$2FDB8B`**. Entry +0 is impact, +4 is
position. Replace `$14BE0–$14C03` with JMP + NOPs (36 bytes), retaining earlier
object/height checks; returns `$14C04` on break, `$14C70` on rebound, `$14C2C`
for the native side/near out-of-play path. Position hook `$10320–$10327`
replaces CLR.W (A3) / MOVE.W #300,20(A3), then returns `$10328`.
RAM **`$FFF056.w`** stores the pane centre, directly after the player-switch
magic at `$FFF054–55`; it is written before each break and read only for glass
frames. No SRAM/save format change. Native RNG preserves D1/D2; the impact hook
saves/restores D0–D2 and leaves the wall-normal inputs intact.

Verification: 256 seeded impacts at each of six X positions (-64/-32/0/14/32/64)
gave 130 breaks per position (**50.78%**). Same seeds are reused across positions;
this is 256 distinct seeds, not 1,536 independent random draws. Every break's
sprite X matched its pane and Y=300. Baseline native slot: 64/64; baseline other
pane: 0/64. Low, soft, corner and near-end exclusions: 0/16 each. Native shatter,
whistle, faceoff and live-clock resumption verified; off-centre pane screenshot
checked. Full old/new CPU games reached post-game standings, one recorded result
and valid team/player/goalie/checksum data. Goal sirens retain native positioning.
Canonical-source rebuild matches the tested ROM exactly; IPS reconstructs it.

Test accounting note: the new CPU fixture naturally produced one empty-net goal.
The first validator assumed keeper GA == team GA and flagged this incorrectly.
Native keeper GA was 1 while team GA was 2, and native keeper SA was 9 versus
10 opposing SOG. The corrected test requires saved played-game keeper counters
to match native keeper counters, and reconciles the league shot difference with
native shots without a keeper; simulated clubs still have equal GA/SA totals.
No game/statistics code was changed for this test correction.

Evidence: private `work/glass-50-20261024-170737/` (source, seeded trials,
`glass-verification.json`, screenshots, after-break resume, old/new CPU totals,
exact rebuild, backups and delivery report). Dated local/Downloads/site ROM and
IPS and Bodin's existing dated entry updated; saves/remaps preserved.

## Empty Mini header showed Full Season as 8 games (2026-10-24)

The user's actual save had magic SESN, team=0, GP=0, stored length=8,
pending=0, format=3. The label correctly chose New Full Season (no saved team),
but `gameinfo` incorrectly used the leftover length 8. Reproduced the exact save
on the preceding ROM: 1/8; the fixed ROM shows 1/84 with the same save bytes.

After explicit pending-format checks, `gameinfo` now calls `hasseason` and uses
the stored length only when the header magic and saved team indicate a real
season. Empty old Mini/Light headers use the full default 84. Continue still
shows active Mini/Light/legacy82 lengths correctly; choosing a new format takes
priority. No save reset/migration is required. `seasonmenu.s` grows six bytes,
1874 -> **1880** at `$2F9800`, still below `$2FA000`. The builder relinks its
symbol-based hooks, so later routine/string addresses follow the new layout.

24 old/new menu cases cover the user's exact save, empty headers with lengths
8/31/82/84 and all four pending choices, real continued 8/31/82 saves and new
Full selection from each. Opening the menu preserves season data. Full -> Light
-> Full, then Mini -> Light -> Full displays 84/31/84/8/31/84 correctly. A first
simulated match from the user's empty-Mini header starts a real 84-game season,
shows 2/84 afterwards and passes all team/player/goalie totals/checksum checks.
Private evidence: `work/full-menu-length-20261024-170153/` (exact user-save copy,
24-case menu results, initial/next-game screenshots, simulation and CPU checks).
Updated local/Downloads/site ROM, IPS and Bodin's dated entry with old ROM
backups and no user-save changes. The fixed Downloads copy is the launch target.

## New Full Season: 84 games (2026-10-24 follow-up)

The main Play Mode option is now **New Full Season**, starting 84 league games
followed by the existing best-of-seven playoffs. NHL changed from 82 to 84 games
per team for 2026/27 ([official schedule announcement](https://www.nhl.com/news/nhl-releases-2026-27-regular-season-schedule)). The game's opponent generator
is still its existing 31-round cycle/shuffle, not the official NHL fixture list.
New Season Light remains 31 and New Season Mini remains 8, with one-game playoffs.

Only new full seasons default to 84: `season.s` new-season initialization writes
84 to header +10; `seasonmenu.s` uses 84 for pending-full/default Game text and
prints the 20-character padded `New Full Season` label. Continue still reads
stored +10, so an active legacy 82-game save ends at 82 without migration.
Restarting that full season starts a new 84-game one. The generic pre-game
restart entry/dialog now says **Restart Season**, because it retains whichever
format is selected (Full/Light/Mini). The `cmpi #82` in simulated assist chance
is an unrelated percentage and remains unchanged. No routine sizes or addresses
changed; no extra RAM/SRAM is used.

Verification: all 84 rounds through real menu/controller simulation, no duplicate
opponents within each 31-game cycle, and all 32 teams' results/player/goalie stats
and SRAM checksum checked every round. Rechecked complete 31/8 seasons. The
84th-game transition in single-player and teammates gives 16 qualified teams and
best-of-seven; an old 81/82 save still advances to playoffs after game 82. Mini
single-game playoffs and completed-Light guard remain correct. A full Mini CPU
game also passed recorded totals/checksum. Menu screenshot shows 1/84 and the new
label. Evidence: private `work/full-season-84-20261024-151721/` (build, schedules,
endings, screenshot, CPU checks, previous ROM backups and remote delivery report).

Updated the dated local ROM, Downloads copy, website ROM/IPS and Bodin's existing
dated playlist entry, preserving save/remap contents. The already running local
RetroArch session keeps its loaded previous build until restarted; it was left
running. Earlier undated release downloads remain the older 82-game profile.

## Diving goalie catch recovery (2026-10-24 follow-up)

The user reported a faceoff immediately after a diving goalie catch, before he
could get up and play the puck. Confirmed native special case: pickup `$1530C`
initializes goalie object `+$48` to 180 ticks, then `$15312` checks animation
`$02F4` (sprawl/dive). The MOVE at `$1531C` overwrote that with **5 ticks**.
This change sets its immediate word at **`$1531E` from `$0005` to `$00B4`**,
so diving and standing catches receive the same possession window. No new ROM
routine, RAM, SRAM field or save migration is needed. The experimental builder
checks all six original instruction bytes before patching the duration.

Native goalie logic remains: recovery/action locks pause most of the countdown;
AI passes are considered once the remaining timer is <=90, and a nearby opponent
can block that pass. With only 5 ticks, that blocker caused an almost immediate
whistle once the goalie got up. The ordinary timer decrement/whistle paths are
`$D4C6` (controlled) and `$D6B8` (AI). Event 8 at `$11F62` causes the frozen-puck
faceoff; permanent blocking still eventually whistles with the new timer.

Controlled in-game fixtures use the real AI dive animation and native loose-puck
pickup. Puck placement and a temporarily nearby opponent stage a repeatable
catch; these are not an unstaged recording of the user's precise incident.
Home single-player old/new: catch at frame 101, standing at 146; old whistles at
151, fixed passes at 237 with no preceding whistle. Teammates: old whistles at
153, fixed passes at 250. The 24-case old/new matrix covers both goalies,
single-player/co-op/CPU, with/without a temporary nearby opponent. Every fixed
case passes before a freeze whistle. Some original cases do pass successfully;
the old bug depends on whether the native AI has an immediate safe outlet.

Manual goalie B at frame 180: old already whistled (home 152 / away 163), fixed
passes at 186 for both sides. Continuous pressure still produces the normal
finite hold whistle (old 151, fixed 326 in the home fixture). Standing-catch
traces are byte-identical before/after. A full Mini CPU game passed saved
team/player/goalie totals and checksum, and the source rebuild is byte-identical.
Evidence: private `work/goalie-recovery-20261024-145816/` (metadata, fixtures,
24-case `recovery-verification.json`, six extra manual/timeout checks, standing
comparison, CPU totals, previous-ROM backups and reconstruction).

The dated download/IPS and local dated ROM are updated with an unchanged save;
the preceding dated build is backed up privately. Bodin's separate dated entry
also received the fix while no game was running; his older entries/saves/remaps
are unchanged, and his preceding dated ROM/playlist are backed up. This is a small revision to
the 2026-10-24 version; the complete output hash above identifies the new bytes.

## Dated version and player switching

The file/download/playlist name now includes **Season - jdc**. Its matching IPS
uses the same basename. This rename preserves the ROM bytes and hash; Bodin's
existing save/remap were renamed together with his game and retained intact.

The ROM header and filename identify **2026-10-24**. Its full name is
`NHL 2026 - 94 Edition v5.1W - Season - jdc - 2026-10-24.bin`. A separate test download and IPS
are linked on the homepage. The IPS applies to the original unpatched v5.1W
base (MD5 `8f863ecb108c1d213953bada9b98f4f2`), not to the plain patched input
used by the source builder. Reconstructing the ROM from that IPS was verified.

Original B selection waits for the release edge and may select the current
nearest skater again; holding B eventually selects the goalie. In ordinary
single-player and Two - Teammates play, **B without the puck now selects another
available skater on the press edge**. It chooses the teammate closest to the
predicted puck position, excluding the current skater, goalies, the other human,
penalized slots and unavailable actions. B with the puck uses native passing.
After a successful switch, B is consumed through physical release, preventing
repeat changes or an accidental pass if the new skater gains possession.
Explicit Y remains the goalie command. Existing pass-animation locks remain;
this patch does not promise immediate switching during every native locked state.

Original switching snapshot: `tools/experimental/switch.s` used512 bytes at
`$2FD900–$2FDAFF`. Current source is500 bytes with goalie requests; see above.
Entry +0 replaces `$B1B6–$B1BF` (pass-lock check) and +4 replaces
`$B2DA–$B2E1` (old B hold/release handling). The legacy path retains original
shootout, penalty-shot, fight, stoppage and real multitap control. Ordinary
eligible play skips the old release/hold selection through `$B42E`.
RAM `$FFF048/4A` = request countdowns (30 input calls), `$FFF04C/4E` = requesting
object IDs, `$FFF050/52` = consume-B latches, `$FFF054` = initialization magic
`SW`. No SRAM allocation or season migration is added. MOVEM restores preserve
flags at the eligibility branch; a MOVE restore there would corrupt the test.

Measured on the same live-game fixture: B held 1/5/12/25 frames originally
changed at frames 112/117/123/132 (press at 110); the dated version changes at
111 for all four. The longest original hold selected the goalie; the new code
selects a skater. Both co-op pads, away-team single-player, repeated taps,
Y on both pads, native puck passing and shootout fallback were checked in a
26-run old/new matrix. Four successive taps actually select different skaters
without taking player 2's skater. Pending-selection retry was also exercised.
A complete Mini CPU game recorded one result with consistent team/player/goalie
totals and SRAM checksum using an isolated valid preference/season seed.

Installed locally and delivered to Bodin as a separate dated playlist entry,
with separate Mini save and ordinary six-button remap (libretro device 513).
His older entries, ROMs, remaps and saves were preserved and hash-checked; the
playlist was backed up before the append. No global controller setting changed.
Private evidence: `work/player-switch-20261024-142323/` (metadata, old/new traces,
CPU checks, exact public-source rebuild and deployment report). Do not publish
machine addresses or credentials in these notes.

## Current behavior

- New Full Season: 84 league games (legacy saved full seasons retain 82), then the existing best-of-seven playoffs.
- New Season Light: 31 league games, then one game per playoff pairing.
- **New Season Mini: eight league games against distinct opponents**, then the
  same 16-team NHL qualification/seeding, with one game deciding every playoff
  pairing. All 32 teams play eight league games. League/playoff transitions and
  a one-win advancement to the next playoff round have been verified.
- On the main menu's **Game** row, **Left/Right changes the next opponent only
  before game 1** of a new/pending season (2026-10-25-r2); afterwards it is locked.
  Already played opponents are skipped within a 31-round cycle; the current
  choice survives power-off. For Mini/Light the entire league plays that chosen
  circle round, so both teams' records and all other pairings remain consistent.
  The full schedule resets its used-round mask after games 31 and 62.
- New-season selection cycles in both directions through the formats and the
  original game modes. Selecting/restarting a format uses that explicit choice
  before the old saved season's length. The Game row displays 1/8, 1/31 or 1/84 (1/82 for a legacy save)
  correctly even while switching from a different saved format.
- **Simulate Match** is directly below Start Game in the league pre-game menu.
  C gives an instant result with team/player/goalie stats and the rest of that
  league round; Start returns to the next fixture. It refuses an extra simulation
  after a season ends. Start Game is still the first/default entry.
- Manual goalie setting: a goalie who collects the puck stays AI in single player
  and Two - Teammates; explicit Y still takes control. Co-op Y's doubled pad
  offset was corrected. Shootout/opposing humans/real third and fourth players
  retain the native behavior.
- Warning at exactly 0:40 in every period: three short beeps, about +2 dB measured
  first-pulse RMS, no global-volume change.
- Five-minute periods: ordinary penalties one minute, fighting/injury-causing
  Elbowing two minutes, Fight Instigator two minutes. Other period lengths keep
  the original times. Original coincidental-major classification is preserved.

## Statistics and Bodin's report

The user clarified that Bodin's one-off freeze was in **single-player**, not
Two - Teammates. His exact incident has not been reproduced. Do not describe the
co-op input fix as its confirmed cause.

Three separate findings/fixes are documented:

1. Confirmed Address Error on standings → team players: palette helper `unlite`
   overwrote the selected team in D0 with a colour. Save/restore D0 around it.
   All 32 teams, both player pages and goalie rows were checked.
2. The optional old main-menu statistics path left `EXITM/EXITV` marker/address
   checks in standings/leaders. That path is no longer used; a stale marker could
   send Start to an obsolete/invalid address. Removed the indirect exit from both
   screens; Start always returns through the native menu path `$9CD8`. A controlled
   stale-marker regression crashed the old core process and returns normally with
   the fix. This is a possible failure mechanism, not proof of Bodin's incident.
3. Co-op statistics previously listened only to the native active pad. Both
   ordinary pads may now navigate/exit in Two - Teammates (also its playoff value).
   Single-player and multitap retain the native active-controller reader.

Final single-player stress test: 64 repeated cycles / **192 screen entries**
through leaders, standings and team stats, with page changes, correct rankings,
returns and unchanged saved season data. Also checked away-team single-player
pause → leaders → standings → team players → resume. Earlier co-op stress had
192 entries too. No additional spontaneous freeze was observed in these runs.

## Sources and memory layout

| Source | Purpose / ROM allocation |
|---|---|
| `tools/season/season.s` | League results, player/goalie stats, 84/31/8 formats; legacy 82 saves; `$2F8000`, 3938 bytes |
| `tools/season/seasonmenu.s` | Circular format selection, Game row and opponent input; `$2F9800`, 1836 bytes |
| `tools/season/playoffs.s` | Seeding/persistence, single-game Mini/Light; `$2FC800`, 1074 bytes |
| `tools/season/newseason.s` | Restart selected format; `$2FA800`, 340 bytes |
| `tools/season/standings.s` | Division/player entry and direct exit; `$2F9000`, 1748 bytes |
| `tools/season/leaders.s` | Scoring leaders and direct exit; `$2FA000`, 972 bytes |
| `tools/season/teamstats.s` | Selected team's skaters/goalies; `$2FB000`, 1756 bytes |
| `tools/season/statsinput.s` | Shared co-op input; `$2FD500`, 78 bytes |
| `tools/season/opponents.s` | Persistent unplayed round selection; `$2FD600`, 630 bytes |
| `tools/experimental/simulate.s` | Instant user-fixture result/stats; `$2FD000`, 964 bytes |
| `tools/experimental/outlet.s` | AI keeper holds until B and passes to requester; `$2FDC00`, 748 bytes |
| `tools/experimental/glass.s` | 50% far-end glass impacts and pane position; `$2FDB00`, 140 bytes |
| `tools/experimental/switch.s` | Press-edge skater switching and goalie request; `$2FD900`, 500 bytes |
| `tools/experimental/ygoalie.s` | Corrected six-button Y handlers; `$2FE040`, 310 bytes |
| `tools/experimental/goalie_ai.s` | Keep AI goalie pickup automatic; reset pass request; `$2FE400`, 90 bytes |
| `tools/experimental/penalty.s` | Duration helpers/award normalization; `$2FE500`, 112 bytes; table `$2FE600–$2FE92D` |
| `tools/experimental/build_test.py` | All experimental patch wiring, warning data, exact baseline checks and linking |

Warning sequence is 60 bytes at `$1960–$199B`; sound pointer `$2C650`. Do not move
it into high free ROM: the driver identifies music by pointer address and would
stop it when music changes. Clock comparisons: `$15DFE/$15E08`, both exactly 40.

Season header logical SRAM `$1000`: +8 games played, +10 length (84/31/8; legacy 82 retained), +12
pending new format (0 continue, 1 full, 2 Light, 3 Mini), +14 stats format (3).
Playoff flag remains `$1210–$1211`. New round record uses only the previously free
**`$1212–$121F`**: magic `SC` (word), played-round mask (long), selected round
(word), matching game count (word), pending-new-season round (word), pending mode
(word). Player rows still start at `$1220`. No save migration is required: a
missing/old record derives the already played rounds from the original schedule.

Round routines at `$2FD600`: +0 get choice, +4 change (D2=-1/+1), +8 capture before
season initialization, +12 commit after a recorded user game. RAM: cached round
`$FFF00C`, new-season flag `$FFF00E`, scratch `$FFF020–$FFF045`. The 14-byte record
updates the existing SRAM checksum incrementally. A pending new choice keeps the
active season's mask/choice, so returning to Continue does not discard progress.

Main-menu input hook `$F75C2` calls linked `mainlr`. Team forcing is applied in
the Away value-write callback after the native caller saved old home/away values;
this makes the original redraw refresh both the text and team logos. Mini/Light
simulation uses the cached selected round for all 16 pairings, not merely a
renamed opponent. Simulated and played user games commit the same round record.

## Verification / private evidence

Private work: `~/.config/retroarch/roms/claude/work/mini-season-20261024-133459/`.
It contains the tests, screenshots, linked symbols and fixtures; save files and
ROMs are intentionally outside the web source folder.

- Eight, 31 and 82 chosen-opponent rounds through real controller input; all 32
  teams' GP, wins/losses, GF/GA, player goals/assists/shots, goalie GP/W/GA/SA and
  checksum checked. No opponent repeats within each 31-round cycle. Mini/Light
  GF/GA deltas match the correct whole-league circle pairings each round.
- Power-off after choosing Montreal restored Montreal and actually played it.
- Mini final game in single/co-op, Light final game, full final game: correct
  16-team tree and single-game / best-of-seven format. One Mini playoff win
  advanced round 0 → 1 with `$CEEA=7`.
- Empty/saved circular menu checks; restart/cancel choices preserve format.
- A complete single-player CPU league game with Mini and the new round recorder
  counted once and passed all season totals/checksum checks.
- Headless MAME: opponent changes, simulation, pad-2 page/exit, stale-return
  regression, no exception breakpoint hit, valid NVRAM totals/checksum.
- Published-source reconstruction matched the tested ROM byte for byte.

For ad hoc headless tests, `tools/experimental/test_core.py` supports the same
frame commands plus load/save state, two pads and an isolated `GPGX_SRM` path.
Set `GPGX_CORE=/path/to/genesis_plus_gx_libretro.so` to test another installed core.
Use `GPGX_DEV=513` for normal six-button pads. 1281 means 4-WayPlay, not a normal
six-button pad. Never point a test at a live save. MAME must use `tools/run.sh`
(or its isolated copy) with dummy video/audio and no display.

## Crowd momentum meters (2026-10-25-r12)

jdc asked whether a loud crowd raises ratings. Native: no. $B8A2 (the crowd meter
shown as Current/Average/Peak Level, Arena Record) is only read by $7A76 (peak/
average) and the stats page $9A9E; $B89C drives crowd sound selection ($7A9E,
table $18CFC) and PSG volume ($16976 -> $B044). Neither touches ratings.
New experimental/crowd.s (+crowd.py, --no-crowd reproduces r11 exactly) at
$2E0000, 27 tiles at $2E0400. One momentum M (-1000..1000, + = home) in RAM
$FFF700..$FFF7FF (menu-only RAM, empty in games; stack is at $FFFFF6):
+200/-200 per home/away shot on goal (team block +$00), +150/-150 per body check
(+$10); blocks $FFC6CE home, $FFCA32 away (+$364). Counters going down = new game
-> M=0. Each change of clock seconds $C468 subtracts M/128 (at least 1 toward 0),
so the pace follows the period length. PCT = M/100 (-10..+10). Ratings: the native
per-player loader $15AA4 (called when a player comes on) ends with jmp $1FF002
(hack joke-player nerf); hook $15D7C -> $2E0004 replays it, then stores +$66..+$75
as that object's base and applies the current percent. Each frame, objects 0..11
(goalies included) get base*(100+p)/100 (trunc, clamp 0..max(30,base)), p = PCT
for home, -PCT for +$62 bit6 (away). Scaled: $68..$6D, $6E, $70..$73, $75; $6A/$6B
are inverted timings (lower = better) so they get -p. Kept: $66 roster slot, $67
weight, $6F jersey, $74 flags. Only rewritten when p changes or after a reload.
Native temporary $69/$75 tweaks ($EA0E/$EAA0) save/restore inside one call, so no
clash. Display: hook $15ED4 (movea.w #$C018,a6 / moveq #1,d6) -> $2E0000 sets them
and puts 8 priority sprites first in the list (tick, fill, partial, 32x8 black bg
per team) at x 222, y 191 home / 206 away, left of the COL/WAS score box (which is
plane A, not sprites). Hidden when $C2EC bit7. Tiles go to VRAM $FC20 (tile $7E1;
H-scroll is full-screen, only $FC00..$FC03 used; reg 11 is only ever $8B00) through
the native VBlank DMA queue (a5: source.l, words, VRAM) every 32 frames. White fill
pal0 idx1, red pal1 idxA (the r11 crit red), black pal0 idx4. Tests: headless CPU
game, M reached -797 with away 5 checks vs 1 and home ratings 20 -> 19 at -7%;
verify_goalie_request PASS 38, verify_critical_flash PASS. verify_shootout: 12/18
then its fixture assertion "no normal goal before injection" fails because the
rating change alters play; the --no-crowd build passes 18/18 (shootout code is
untouched). SHA256 7280773be04f9424cf2910f20b9d020f1749d3add11737e05ce289c0247d4e91,
CRC 21A1898A.

## Crowd meter rows swapped (2026-10-25-r13)

jdc (Downloads/34.png): playing EDM, he scored and his bar dropped. The score box
lists the $CA32 team on the TOP row and the $C6CE team (team id $C331, called
"home" in crowd.s) on the BOTTOM row; r12 drew them the other way round. Ratings
were always applied to the right team; only the display was mirrored. crowd.s now
draws M (the $C6CE team) at y 206 and -M at y 191. Verified: test game C6CE = WSH
(id 30, bottom WAS row); poked WSH shots fill the bottom bar.
SHA256 6f9ff97b8b41e7dd6f6f4658237730ce29d5c89a827a4a7eb162854ef17880d1, CRC 8E50844E.

## Team stats screen: no CRT, no frame notch (2026-10-25-r14)

jdc (Downloads/3.png, CHICAGO): a black notch in the title frame above "CH", and
CRT/footer need not be shown there. Notch: teamstats.s padded odd-length title
strings with a space AFTER the closing big-font code BD 07 01, which drew a black
tile in the frame; every odd-length city (CHICAGO, SEATTLE...) had it. The pad now
goes before BD 07 01. CRT: teamstats.s has its own SHOW_CRIT 0 (WITH_CRIT still
drives critical.s and the native attribute screens); SOG returns to column 34 and
column 37 is cleared after SOG so goalie SV% text cannot remain. Headless SEATTLE
capture (stats-freeze all-teams seed): no notch, no CRT, clean columns.
SHA256 a8b64f0f144b6e67a78f2ab16d817899f21b2b958a7cfa480264627c4e2ddc86, CRC 960E84A8.

## Difficulty Normal/Hard (2026-10-25-r15)

jdc: a start-menu choice Normal/Hard; Hard = the crowd meter is always at the
bottom for human players and full for the computer. Main menu row 9 (User Records,
hidden by fix 11) is now "Difficulty" with values Normal/Hard (strings at $209700,
offset table $2091FE+18). The fix 11 row map at $2FE232 sends row 9 to $FFF07A
(move.w #$2032,d1; no other ROM reference to $F07A) instead of User Records $D054,
which stays Off and unseen. Ten menu rows again: $F777A $14, $F7758/$F7762/$F7C08 4.
Power-on: $17C86 -> crowd +8 (fix 10 defaults, then clr $FFF07A = Normal). Not
saved to SRAM: after power-on it is Normal. seasonmenu.s lets row 9 through the
season lock (all other locked rows stay locked; tested on a running Light season).
crowd.s: Normal keeps one shared M; Hard scans objects 0..11 (+$62 bit3 human,
bit6 side) and remembers human sides for the game (cleared on a new game); human
side bar -1000 / -10%, CPU side +1000 / +10% (both human or both CPU: equal).
Ratings are now rescaled only when a side percent changes (the loader hook applies
it to new players). Code grew past 1 KB, tiles moved to $2E0800. RAM $FFF72A..735.
Tests: menu toggles Normal/Hard and back; Hard game: human WSH 20 -> 18, CPU COL
20 -> 22, bars empty/full; verify_season_navigation PASS 18/342;
verify_critical_flash PASS. verify_goalie_request is cycle-chaotic: the game takes
randomness from the VDP HV counter, so any change in main-loop cycles changes the
fixture's play from frame ~103. Proof: r14 with only 20 NOPs added before the crowd
hook fails clear-11-p2only "request leaked"; r15 fails clear-5-coop-p2-d10-hold1
"no requested pass" (a 1-frame B tap). Not a crowd logic fault; the fixtures need
to become timing-robust. SHA256 491fc96f9e810ac48cdf00054a5fa1faee45f1c4e4bc59407debc66cf5d7e6d8, CRC 27A68F5F.

## Team Roster shows the meter effect; reset after every game (2026-10-25-r16)

jdc: with the meter up, McDavid's numbers in Team Roster had not changed; show the
effect, reset after each game. Roster values come from $FA9F8 (18 x rating nibble +
per-player adjustment byte at team block +$1A2, limit 99) and never read the object
ratings. Hook $8D70 (jsr $FA9F8, reached through the crit hook for every non-Crit
mode) -> crowd +12: for mode $0A (Overall and the 0..99 ratings; not Handed, Weight,
Energy, Fighting, Crit) and a2 = $C6CE/$CA32 it shows value + value*p/100 (trunc,
0..limit-1), only while MAG is valid. Game end: $172FA (bsr $169D0 / move #$2700,sr,
reached after every game via $172E0 and at boot, right before the B000..D03E reset)
-> crowd +16, which clears MAG; the meter, percents and stored bases start over and
Roster outside a game shows base values. Test: Hard, Washington human -10%: Overall
Chychrun 76 -> 69, Roy 55 -> 50, Kyrou 70 -> 63 (r15 showed 76/55/70).
verify_season_navigation 18/342, verify_critical 10, verify_critical_flash PASS.
SHA256 b6d72438d540cb6c81c96a4183744f0ebef5040e7a4796b28651fff718124c56, CRC AA7610A9.

## Buffered one-timer for human receivers; meter scales Crit (2026-10-25-r17)

jdc: in co-op, when P2 presses/holds C while P1's pass is on its way, P2 stops and
the pass slides by. Cause: receiver input $B6C8 (C new press, puck loose, we are
$BEE0) queues action $23 (one-timer) at once; the windup ($C1B8, anim $07FC) stops
the skater and clears $BEE0, and the lead pass misses (probe: speed 0, puck past).
New experimental/onetimer.s at $2E1000 (+0 input hook $B6C8 -> $B6D0/$10750, +4
frame hook replacing jsr $FD78A at $15EF2): while the pass is in flight a press is
remembered in $FFF736 (object+1) and the receiver keeps skating; when that object
owns the puck the frame hook sets +$64 bit3 (native one-timer: power $1F at $C35E),
anim $07FC via $1073A and calls the native shot $C2F2 = immediate full-power
forehand one-timer (crit-eligible: $FFF0B0 set). A press is forgotten when the
pass is cut off. Probe: receiver catches at full speed (3902) on the same frame as
without C, puck leaves at 20264 with slap flag set; hold and mash identical. Only
human receivers (input path); passer-C native one-timer for AI receivers unchanged.
--no-onetimer keeps the native behaviour. NEAR_PX (0) can release early instead.
Crit: crowd +20 replaces move.w d0,d1/move.w #1000,d0 at $2FF462 (rating scaled
by the shooter side's percent before RNG(1000)), +24 replaces jmp $8E4C at $2FF4B8
(Team Roster Crit column, same scaling). Hard WSH -10%: Crit 77->70, 48->44, 79->72.
verify_critical 10, verify_critical_flash, verify_season_navigation 18/342 PASS;
verify_goalie_request still the known cycle-chaotic clear-5-coop-p2-d10-hold1.
SHA256 ff1ec02d17173e9a91496bd8d35ceae557f618ebbdb0a99a0e1fad34cd4507b7, CRC B71C5B22.

## Season-to-playoffs test run; finished season takes no more games (2026-10-25-r18)

jdc: test that every season reaches the playoffs, the playoffs run to the end.
GPGX driver (work/season-end-test-20261025/po_hook.py): Simulate Match through
menu input for the league, then real playoff games (clock held at 1 s, score set,
Start outside play), plus fully natural games without any pokes. SRAM validated
after every game (checksum, GP, W/L, scorers, goalie GP/GA).
- Full 84/84, Light 31/31, Mini 8/8 simulated: all checks ok, Continue to Playoffs.
- Playoffs: Mini and Light single games won all four rounds (round 0..4, flag 3);
  Full best-of-seven advanced round by round to the final; Mini/Full loss in
  round 1 ends the playoffs (bracket shown, flag 3).
- After a Cup win NO champion screen appears (Stars of the Game, Exit Game, then
  the main menu with New Playoffs); after elimination the bracket is shown.
- After the season's playoffs the menu shows native New Playoffs; starting it
  sets the season's playoff flag $1210 from 3 back to 2 (standalone and season
  playoff state still interact; to be fixed).
- BUG fixed: a season that missed the playoffs still counted real Start Game
  games (natural Light run reached 45/31, team GP 44..59; Mini 10/8 in r17).
  season.s haveuser now returns when games >= length. r18 natural Mini: 8/8, all
  teams GP 8. The menu after such a season still shows "Continue <slot>" with a
  meaningless "Game 1/8" line (to be replaced by the end-of-season flow).
- Expected, not bugs: natural games show team GF > scorer goals (shootout winners
  add a team goal only) and team GA > goalie GA (empty-net goals).
SHA256 33baa63541d44a71f2e43d88a0dfcb8bcf752611598462794feaece74c7f13ef, CRC BE3CEBD9.

## End-of-season summary and whole-run player statistics (2026-10-25-r19)

jdc: after the Cup (won) or the bracket (out), go on with a button to a screen with
all the statistics of the whole run. New experimental/seasonend.s at $2E1400 (+0 hook
$172E4 after the post-game tree/table and before the $172FA RAM reset, +4 from the
Simulate Match result screen, whose single jmp $172FA seasonend.py redirects).
classify (season view, valid finished season): Regular Season last game and the team
not in SEEDS -> missed (flag 5); the season's playoffs on (flag 2) and the game has
switched back to New Playoffs (D048 2/3) -> out (flag 3, round = $CEEC-1, the game
advances the round before) or champion (flag 4, your score > theirs in this game).
The flag is written once (playoffs.s new +28 setflag), so the summary shows once.
Screens use table.s's set-up (new table.s +4 = setup: background $2000, palettes,
scroll, VSRAM, no window/sprites); the native panel ($9BD8, simulate/teamstats) is
invisible in this post-tree context (wrong palette; $11594 clears VRAM). Season
Summary: banner, record W-L-OTL, points, GF-GA, result; Start -> "Season + Playoffs"
list: skaters by points (as teamstats build), G A PTS SOG, page 2 rest + goalies GP
W GA SA, Left/Right page, Start -> main menu. Start waits release/press (no skip).
A Cup win first shows the game's own Playoff Stats and Champions page (now reached).
Whole-run stats: season.s playoff games of the season (playoffs.s +32 poseason: in
progress, your team, flag 1/2) go through the normal save path (addreal/gkreal +
checksum) without round marking and league simulation; team table rows unchanged.
So teamstats/leaders also include playoff points after the season.
playoffs.s: played no longer reopens finished playoffs (flag >= 3; the post-season
standalone New Playoffs set 3 -> 2 before); spcheck treats >= 3 as over.
Tests (GPGX, work/season-end-test-20261025): Mini win -> Champions page, summary
"Stanley Cup champions", players, page 2 goalies (Thompson 11 GP = 8 + 4); Mini
loss -> bracket, "Out in the first round"; Mini missed via Simulate Match -> summary
"Missed the playoffs" (Vancouver 0-6-2); Full BO7 champion flag 4, goalie GP 84+16.
verify_season_navigation 18/342, verify_critical 10, verify_critical_flash PASS.
Known: after a season the menu still reads "Continue <slot>" (games no longer count).
SHA256 a877c327a8c66674172fce3a72359d24185ee37aeb99494efaf9febb561e7291, CRC C8CC833B.

## Co-op game row without at/vs (2026-10-25-r20)

jdc: switching Players One <-> Two - Teammates changed the Game row from "at" to "vs"
(co-op always plays at home), which looked misleading. seasonmenu.s gameinfo now
writes "84/84 FLA (co-op)" when Players = 3 (Two - Teammates); One keeps "at"/"vs".
Tested from a Full save at 84/84 (Edmonton, 83 simulated games): One "84/84 at FLA",
Two - Teammates "84/84 FLA (co-op)", back to One "at FLA".
SHA256 f073a6928cf8e9ade13c003d6ef94fcbe72c4030aa188a651f2d9a0c1d3828df, CRC 85089A9A.

## Playoff statistics apart, three tabs, sorting, scoring leaders (2026-10-25-r21)

jdc: see the scoring leaders too; with playoffs, one tab with ALL stats, one with only
the league, one with only the playoffs; if you go out, the playoffs are simulated to the
end in the background so all stats are there; filter goals / assists / points.
- Playoff block: SRAM $0002 "PO", $0010 + 100*slot player rows (slot = place in SEEDS,
  16 = scratch), $06B4 + 15*slot goalies, to $07A2. $0002..$0D1F is free: native code
  only writes the whole 8 KB on a format, User Records are off; the slot banks copy and
  checksum it with the rest. Cleared by playoffs.s setflag(1) (playoffs set up).
- season.s rowload/rowsave/gkput take their offset from postats.s ($2E2000 +0 ROWOFF,
  +4 GKOFF; same size as the replaced mulu/addi): POGAME set -> playoff block. So the
  season's own playoff games now go there, not into the league rows (r19 merged them).
- postats.s +12 others: after each of your season playoff games one game for every
  other pairing of that round; +16 finish: when you are out, every later round plays its
  series to the winners the game has already drawn ($CEF4 holds the whole drawn tree,
  re-drawn each round), one game or best of seven (0..3 losses). Uses season.s
  goals/dist/gksim via season-symbols.inc; checksum via DELTA (fixsum).
- seasonend.s moved to $2E3000 (hooks $172E4 / Simulate exit updated). After the summary:
  your team (banner, tab title, sort, skaters, Up/Down page 2 with goalies) and then the
  league's top 18 Scoring Leaders (rank, player, team, G A PTS SOG). Left/Right = tab
  (Season + Playoffs / Regular Season / Playoffs), C = sort (points, goals, assists),
  Start = next / main menu. ALL = league + playoff rows added on display.
Tests: Mini out in round 1: 15 playoff games (30 goalie GP), league rows/goalies
unchanged, checksum ok; tabs/sort/pages/leaders captured (Thompson 7 league + 1 playoff
= 8 GP). Full best-of-seven champion: 60 playoff games (16 yours + 28 + 12 + 4), league
GP 84 for all, checksum ok. verify_season_navigation 18/342, verify_critical 10,
verify_critical_flash PASS. The font has no < > (footer: "L/R tab").
SHA256 3115bde11904a3a00cb4201fa11cef8f7eff810573aed220d14417f46a803e87, CRC 3693B1E0.

## B no longer switches skaters while your goalie's pass travels (2026-10-25-r22)

jdc: mashing the pass button while the goalie holds the puck gives no pass and the cursor
jumps between skaters as if switching. Probe (GPGX, gk5-clear fixture, B every 6 frames):
the first B is the outlet request and the goalie passes at once (frame 101 -> 103); every
further B while the puck travels hit switch.s ordinary_switch and flipped control 4 <-> 3
(the pass target stayed 4). Fix: onetimer.s +8 gkflight (puck loose, last passer = a
goalie of your team -> Z, and switch.s Q cleared); switch.s calls it before the ordinary
switch and consumes B (switch.s must stay <= $200 bytes before glass at $2FDB00, now 510).
After the fix control stays on the receiver until the catch (frame 103..135); later B
presses pass/switch natively. verify_season_navigation 18/342, verify_critical 10 PASS;
verify_goalie_request: the known cycle-sensitive clear-5-coop-p2-d10-hold1 (1-frame tap).
SHA256 1a1ce11286b94377e228fbfd939474369c7d8b63920391028c4e3055b92b53cd, CRC 05BAFC54.

## Flame trail behind the crit puck (2026-10-25-r23)

jdc: cool speed streaks / flames behind the puck on a crit. experimental/trail.s at
$2E4000 (tiles at $2E4100), hook $15F06 at the end of the sprite list build
(cmpa.w #$C018,a6 / bne.w $15F12 -> jsr / bne.s $15F12; the routine ends with the same
compare). While $FFF060 (critical_flash lifetime) is set it finds the puck's sprite in
the list (tile = puck +$12; the shadow is +1), keeps 9 screen positions (RAM $FFF600..$FFF633,
menu-only RAM) and appends four sprites at 1/2/3/4 frames ago (big, big, medium, small)
behind everything: palette 1 colours A (crit red), C (orange), 6 (yellow-orange core).
Tiles $7FC..$7FE (VRAM $FF80..$FFDF) via the native DMA queue every 32 frames.
Pitfall: the first try at $FFA0..$FFFF (DMA ending exactly at $10000) wrapped onto tile 0
and covered the whole rink with dots. Tests: forced-crit capture shows a continuous red
tail; verify_critical_flash, verify_critical 10, verify_season_navigation 18/342 PASS.
SHA256 e6d7d5143bdd3352fa6a10a92d89edfcceb8d4599d9abaa80a7937e9e13de087, CRC 88B2C78F.

## Crit flame trail always shows (2026-10-25-r24)

jdc: the crit trail only showed sometimes. Cause: the trail sprites were appended at the
END of the sprite list, which already holds about 51 native sprites + 8 meter sprites;
the native list stops at 64 ($40, RAM after $C218 is in use, no room to extend), so the
trail was often dropped. trail.s now has +0 record (end hook $15F06: puck position into
HIST) and +4 draw, called by crowd.s at the START of the next list, drawing HIST 2..5
frames ago. The flames now draw in front of skaters and always get a slot.
verify_critical_flash, verify_critical 10, verify_season_navigation 18/342 PASS.
SHA256 46db9ac09de9a90f866e4f0ecb8eef60e47e4c3f420938d5d77180be4af7d237, CRC 6BD98C96.

## Automatic goal replay (2026-10-25-r25)

jdc: automatic replays after every goal, skippable with X + B (Genesis A + C, like Abandon
Season). experimental/replay.s at $2E5000:
- +0 tick (called every frame by crowd.s): score $C6DA + $CA3E rose (not in a shootout,
  $C2FA bit0) -> 90-frame countdown -> ARST = 1 and set $C2EC bit0 (pause request).
- +4 replaces bsr $7CF8 / bra $7938 at $7948 (main loop pause call). ARST = 1: the pause
  routine's prologue ($10F32, VBlank wait, $1A264, push $C2EC, $7E0E), then the native
  Instant Replay $9FD0 instead of the menu, then the pause routine's own exit $7D66 (pops
  $C2EC, restores the game screen). Otherwise the ordinary pause menu.
- +8 replaces tst.w $D046 / bne.w $A434 at $A41E (the replay's pad read: $11340/$11358
  give d0 dir, d1 new, d2 changed, d3 held). During the auto replay it returns "hold C"
  (play) for 540 reads (the buffer takes about 520 frames), then Start (options), release,
  Start (return to game). A + C held on either real pad jumps to the Start sequence.
- The replay starts rewound (native $A4F6 loop), A/B/C = rewind/stop/play, Start = options.
- RAM $FFF738 ARST, $FFF73A ARCNT, $FFF73C CNTDN, $FFF73E LASTG; crowd.s gameend clears them.
Tests: poked goal -> replay 402..1073, clock frozen during it, then resumes; A + C at
frame 520 -> back in play at 605. Natural Mini run: 4 games, every goal (periods 1-3 and
late in the third) replayed, games and season (4/8) continued normally.
verify_goalie_request 38, verify_critical 10, verify_critical_flash,
verify_season_navigation 18/342 PASS.
SHA256 cffacb9192269424bdcb136e60db64f9ab10d0bb0be5648376eebcee545c1f84, CRC 7B803E38.

## Longer crit flames, crits in shootouts (2026-10-26-r2)

jdc: the red crit animation was too short and did not show the flames; does crit work in
shootouts? Measured: a natural crit flash lasted 18 frames (goal / save ends it); the trail
needed the puck's sprite in the 64-sprite list. trail.s now takes the puck's position from
its x/y/height and the camera (screen x = x - $BD1C + 285, y = $BD18 - y + 233 - height,
sprite coordinates; constant over 35 samples), list search only if $C2EC bit7; six flames
(2..7 frames back: big, big, medium, medium, small, small) and a 24-frame afterglow after
the flash ends (the tail dies out: 6 -> 0 flames). RAM $FFF634 TL. Code grew past 256 bytes:
tiles moved to $2E4200.
Shootouts: the crit roll and +25% power always worked, but $C2EA bit0 (clock stopped) is
set throughout a shootout, so critical_flash ended the red puck after one frame.
critical_flash.s now skips that check when $C2FA bit0 (shootout); possession still ends it.
Shootout test (native Shootout mode, forced crit): flash 127 -> lasts about 160 frames,
red puck and flames up to the goalie.
verify_critical_flash, verify_critical 10, verify_season_navigation 18/342 PASS;
verify_shootout: 12 pass, then the known playoff fixture case (also on 2026-10-26).
Site: version and Light/Mini now in the header, goalie section updated to today's controls.
SHA256 78b36c6e3f9a925a49d92d756ad40174f7d9b494004b04544c2fe7d258ef77df, CRC 3DBBCA6E.

## Start Game hung after viewing season results (2026-10-26-r3)

jdc: deleted the finished Mini, started a new one (Edmonton vs Montreal) and the game hung
when starting the match. Reproduced from his pre-delete save in one session: See Old Mini
Season -> results/leaders -> Start New Mini Season (X+B) -> Game row to MTL -> Start ->
"Resume Game" pre-game menu, period 0 / 5:00, frozen; from a cold boot of the deleted
save the same match starts. Cause: replay.s kept its state in $FFF738.. (crowd block),
which the results/leaders/team-stats screens overwrite with text; a stale nonzero ARST made
the pad hook at $A41E (also used by the pre-game/pause menus) feed scripted input, so the
menu never saw the player's buttons. Fix: replay states are $A500..$A506 (anything else is
idle, the pad hook scripts only $A502..$A506), and crowd.s valid() (first frame of every
game) clears onetimer PEND $FFF736, replay $FFF738..$FFF73F and trail HN/HT/TL
$FFF630..$FFF635 (stale afterglow could have drawn flames from menu text).
Same flow now starts Montreal vs Edmonton and the clock runs. verify_finished_season 9/108,
verify_menu_team 18/240, verify_season_navigation 18/342, verify_goalie_request 41,
verify_critical_flash PASS; auto replay still runs ($A502..$A506).
Rule: never keep in-game state in $FFF100..$FFF7FF without a magic/validity check and a
reset at game start: menus use it as scratch.
SHA256 b6fb71635631a24f1c84c81a4802ff0ccfc821e891cb42ae63a5fe9a2c359239, CRC 4009ADA6.

