# Critical slapshots and Twin Cobra sound — 2026-10-25

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


Crit preference implementation: RAM FFF0EC word (0On/1Off), independent CT
tag/value/complement record at Full-bank logical2E1C..2E1F (physical205C39).
This is the shared-profile gap excluded by slot copies; Full-bank checksum is
recomputed on actual edits. Missing/invalid records default On without writing
SRAM. replaymenu.s loads both records independently and preserves registers,
IRQ state and mapper lifetime; menu mapping/writer entries remain2DE000/+4/+8.
Labels2DE800, values2DE900, count tables2DEF00/2DEF20 now contain12 rows.
seasonplayers.s dispatches logical row22 before locked/native writers.
critical_slap.s rejects Off before eligibility; critical.s also protects direct
entry without shifting existing flash/crowd patch offsets. WITH_CRIT_OPTION
and builder --no-crit-option preserve exact historical reconstruction.


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


The dated Season - jdc ROM now plays jdc's selected **19 - Digital effekt 13.wav**
on a critical slapshot. It replaces the preceding glass-break crit sound. Only
full forehand windups qualify; quick wrist releases and backhands have no crit
roll, bonus, flash or special sound. Forehand one-timers qualify through their
native full-charge value31. A non-critical slapshot keeps its normal shot sound.
CRT100 still gives10% probability and25% extra release power, CRT50 gives5%.
The same code runs for human/CPU skaters, either team, One and both Teammates pads.

## Native eligibility and hooks

Capture the unscaled charge at C378, **before** the player's shot-power/energy
multipliers. Animation must be07FC (SPAshotf), raw BEDE must be>=31. The native
SetShotMode starts at15; a full windup accumulates16 and stops charging at31.
The backhand animation is092E. In the measured live fixture, C held1/5/12/15/18
frames produces raw16/20/27/30/31. Both short and full animations can reach phase
1C at release, so testing only the final animation phase would misclassify wrists.
A high player's final puck velocity is likewise not a reliable shot-type test.

critical_slap.s:126 bytes at2AD000..2AD07D; entries+0 classify,+4 release gate,
+8 PCM descriptor lookup,+12 music/SFX classification. New transient byte
FFF0B0, rewritten for every actual shot; shootout reservationFFF090..0AF ends
before this byte. No save/SRAM or roster-layout changes. C378 JSR captures and
replays CLR.W D0 / MOVE.B6C(A3),D0. C4A8 JMP goes through the gate; ineligible
shots clear an earlier flash and execute original velocity math directly, without
calling RNG1000. Eligible shots enter the unchanged2FF400 code, preserving its
fixed offsets, stack, chance and25% boost. critical_sound.s still writes the shot
SFX at22(SP), then tail-chains the optional red flash; its default ID is now123.

## Original digital sample, adapted to NHL's existing sound engine

User-supplied Twin Cobra USA ROM SHA256:
23c1dd768c455067ad614c6c054db59176b0bda0f6531fff56da023e224cd512.
The selected local WAV corresponds to **PCM command0D** (decimal13), not SFX13.
Original Z80 descriptor at9E188: start1298,4496 nibbles,delay48; source bytes
9F298..9FB5F, high nibble first. Original driver directly outputs each nibble
shifted left4 to the YM2612 DAC. Convert to unsigned8-bit PCM; change DAC0 to1
because NHL reserves byte0 as the end marker, then append one terminator.
No soundtrack/music mixture or recorded emulator noise is embedded.

Asset experimental/critical_twin_cobra.pcm:4497 bytes at2AE000..2AF190,
SHA256ce014d721f2022444e19bcc8617f34fd79e2f16b6938745f8e3fdcaa4d456240.
prepare_critical_twin_cobra.py reproduces it from the local supplied ZIP and
checks both source/output hashes. Builds work offline from this small asset.
The source Twin Cobra ROM is never distributed or installed in NHL's place.

Preserve all existing samples, effects and music: copy the123 original sound
pointers unchanged to2AD200..2AD3EF and append SFX123. Raise1A294's bound7A→7B;
1A2C8 uses the extended table. The1A29E helper exempts only123 from the native
>=48 music-stop path; ordinary effects/music retain their classification.
Copy15 original PCM descriptors unchanged to2AD400..2AD477; append6F's descriptor
at2AD478. Replace the12-byte1A77E lookup with JSR+NOPs. Do not overwrite an old
sound-ID slot or either originally unused PCM68/69 entry. Z80 driver unchanged.

New20-byte event stream2AD480..2AD493: program6F, pitch bend19B0, note60,
velocity127, note-off after72 ticks, end. This gives1.133s measured native audio,
versus1.142s for the source clip's active signal (about0.8% rate difference from
NHL's quantized DAC stepping). One critical sound is emitted by the ordinary
C538→11132 shot exit; no second sound layer or glass/whistle event. The sample
uses the existing one PCM voice and may replace another PCM effect, as native
sound priorities do. It does not replace the driver or block the main CPU.

## Build controls and regression

Default experimental builder enables this change. --legacy-crit-shots --version 2026-10-24 reconstructs
the preceding all-shot/glass profile exactly (portrait-ROM SHA0e5c8ee0...).
--no-crit-sound retains slap-only power/flash with ordinary shot audio and omits
the asset/tables/audio hooks. --no-crit-flash retains slap-only power/new sound.
--no-crit omits all critical features. Do not use the old normal tools/build.sh.

    python3 tools/experimental/build_test.py \
      'download/NHL 2026 - 94 Edition v5.1W.bin' /absolute/private/test.bin
    python3 tools/experimental/verify_critical_slap.py \
      /absolute/private/test.bin /absolute/private/slap-checks

11 native reset-vector cases/110,000 shots check raw15/30/31 boundaries, forehand,
backhand/other animation,0/50/100 rating, away/reversed direction, invalid slot,
RNG unchanged on exclusions, exact power/velocity, one SFX123/ordinary24 and stack
return. The same110,000 pass without flash. Existing verify_critical_sound.py
now accepts --sound-id (default123, legacy14) for its independent stack checks.
Eight live pad/audio cases show wrists stay173 power/24, critical slap336/123,
ordinary slap269/24; both Teammates pads emit123 once at power357. Preceding ROM
incorrectly crits the wrist216/14, while its full slap has the same336 power.
Live coop forehand cases set only the fixture shooter's initial facing to0;
inputs, shot charge, release, physics and playback are native. Baseline fixture's
backhand was also observed with native20 and is excluded by the new gate.

Isolated native-engine audio has nonzero unclipped output. Music/SFX fixture
retains every pre-existing active music track across the critical trigger.
A clean uninstrumented full CPU Mini game completes with valid season checksum,
all32 team totals and player/keeper records. Private tests never load/write the
user's installed battery file. Source rebuild is byte-identical; the previous
profile is reproduced exactly. Guarded5297-byte diff includes only the new
code/data, listed hooks, sound immediate and checksum; private test region2FEA00
remainsFF. IPS reconstruction against the original base is verified.

Sound-change snapshot SHA2560312bd6009741447726088115801a9e2cd0bd9f385b616743e393cadd3396a1a,
CRC32FC0D6B4F. The subsequent version25 rename changes only header dates, yielding
SHA2562d4d53a50b2e153be6f5ebfe4b12b9e31d6b391d87dc75ecef57dd0cefed3f47,
CRC32FEF0867F. Saves/remaps migrate with the basename; see RELEASES.md.
Private evidence: work/critical-twin-cobra-20261025/ (verification.json,
slap-tests/,no-flash-tests/,live-audio-results.json,audio-native-verification.json,
charge-probe.json,cpu/,sample-source.json and exact rebuilt ROMs).

Reference used to cross-check labels/engine format, followed by direct native
ROM verification: [NHL94Genesis input94.asm](https://github.com/abdulahmad/NHL94Genesis/blob/main/src/input94.asm),
[sound94.asm](https://github.com/abdulahmad/NHL94Genesis/blob/main/src/sound94.asm).
The target is jdc's expanded32-team ROM; addresses above are measured in it.

## r11 colour note

The red phase now uses rink palette 1 index A, patched to pure red $000E
(was dark green $0062, only a few crowd pixels), instead of palette 0 index A
($064C, the dull rink-line red). See EXPERIMENTAL_BUILD.md.

## Measured: do crits score more? (2026-10-25)

jdc asked. Two copies of r16 differing only in the crit roll at $2FF470 (bcc ->
nop = every qualifying shot crits, -> bra = never). GPGX hook: from one live-play
state, P1 skater teleported with the puck to x -60..60 and 60/100/140/180 px from
the goal line, other skaters moved away (goalie only), facing 6/7 (forehand), C
held 40 frames, aim none/Left/Right, 40 start delays; 4800 shots per ROM, all
qualifying full forehand slapshots ($FFF0B0 set).
No crit: speed 18364, goal 4.9%, save 76.4%, over 1.0%, wide 17.7%.
Crit:    speed 22912 (+25%), goal 6.2%, save 67.6%, over 1.7%, wide 24.5%.
Goals +1.31 points (+27% relative), z = 2.8. Faster pucks beat the goalie more
often but also miss the net/bar more. By distance: 60 1.4->2.2, 100 2.8->1.4,
140 2.0->7.1, 180 13.4->14.1 (180 is inside the blue-line bonus zone). Artificial
setup (empty ice, one goalie, teleport); real games differ. Rig and raw results:
work/crit-goal-rate-20261025/.

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

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

