# Hard defensive pair and earlier pressure — 2026-10-28-r8

On Hard, the nearer available defender contains an opposing puck carrier from
the goal side. The partner covers a dangerous central receiver; without one,
he protects the weak post. Human defenders remain manually controlled. A human
partner may be the nearer defender, in which case the CPU back supplies cover.
This complements the existing Hard off-puck attack and weak-side winger work.

## Native implementation and guard contract

Source: experimental/ai_gap.s (590 bytes at 2DC000), installer ai_gap.py.
Hook CF8A replaces `207C0000E594 61003436` (load EvadePC callback, call skateto)
with a JSR plus padding. The helper restores the native callback and tail-calls
native skateto; the original following check4check path still runs. Coordinates
are targets, not teleported player positions. No new persistent RAM/RNG usage.

Only live Hard CPU defenders in positions 1/2 are eligible. Friendly, loose or
invalid possession; forward/goalkeeper positions; human, unavailable or locked
actors; whistles, menus/replays, offside, penalty-shot/shootout scenes; puck
outside X ±144 or own attack-oriented depth -264..-80 all retain native targets.
Normal and Easy always retain native target decisions.

The nearer back is selected by squared distance to the carrier, with stable
native object-order ties; unavailable or animation-locked backs are excluded.
His target is X = carrierX minus one eighth, depth = carrierDepth -20, clamped
at -238. The partner scans five opposing skaters, excluding the carrier and
boxed/unavailable players. Candidate |X| <=92, depth -252..-140; risk cost is
|X| + 2*|depth+224|. Cover sits one quarter inside the receiver and 12 units
goal-side (minimum depth -236). With no receiver: opposite post X ±30 and
carrierDepth -32 clamped -224..-140.

Skating, checking, shot/pass physics, ratings, speed, shot odds, goalkeepers,
human controls, crowd momentum and save layout retain their existing rules.
Both teams benefit from smarter CPU backs on Hard, including AI teammates.

## Measured comparisons and limits

Immediate reference is exact r7. All observations run Genesis Plus GX headlessly
with copied fixtures and scripted native pad inputs; no physical-controller
playthrough is claimed. Nine setup frames settle the captured engine/input
state. There are no position/velocity resets during the measured frames.
The moving fixture asserts that its carrier actually advances toward the
protected goal; earlier wrong-direction diagnostic probes are excluded.

- verify_ai_gap.py: 44 paired native target/guard cases through the actual
  patched hook, including near/cover pair roles, both sides/ends, human partner,
  single available back, receiver exclusion and zone boundaries. D2..D7,
  A0..A3/A6 and native RNG-word contracts are checked.
- verify_ai_gap_gameplay.py: 24 spacing cases (16 Hard; eight Normal/Easy
  controls) plus 24 controller-driven rush/shot situations. Native AI,
  collisions, game clock, keeper, puck and scoring run throughout observations.
- Hard goal-side pressure gap improves in 15/16 spacing cases; average reduction
  33.33 rink units. The remaining case is 4.11 units farther from the carrier,
  with better slot-receiver cover. The suite bounds individual gap/cover
  regressions rather than selecting only favourable comparisons.
- In the 24 rush scenarios, C is attempted each time; possession loss/native
  state can prevent a release. Observed attacker releases are 11 ->7, not 24
  successful shots. Attacking-team goals are 7 ->6. The suite verifies actual
  running clocks and sprite bounds. These small staged samples do not establish
  a win rate, a statistically supported goal reduction or universal superiority.
- Normal/Easy target outputs match the native path in the exact call tests.
  Live trajectories can differ slightly because the added guard changes CPU
  timing and the engine samples hardware randomness; the existing bounded
  trajectory tolerance remains explicit.

The shared release wrapper now requires 31 exact-ROM/source checks, including
these two new suites and all previous control/keeper/shootout/season/menu/HUD/
shot/AI regressions and a complete Hard CPU match. Current completed results:
QA_STATUS.json. Full command adds --gap-baseline /private/r7.bin.

Independent r8 builds match byte-for-byte. `--no-ai-gap --version 2026-10-28-r7`
reconstructs exact r7. The guarded difference is 571 changed bytes confined to
CF8A..CF93, 2DC000..2DC7FF and ROM title/checksum. Original-base IPS verified.
SHA256 `27248d4097dae5f2af9e5f73d02f1811befad2f034a615f2951bf2ff810813c9`; CRC32 `33EB70DA`.
Private evidence: work/hard-ai-20261028-175329/.

Native assignment/skating contracts were checked against the existing local
[abdulahmad/NHL94Genesis](https://github.com/abdulahmad/NHL94Genesis) research
copy, particularly assign94.asm. No external trained controller is embedded.

Release delivery follows RELEASES.md, including a fresh basename/progress and
preserved remaps. A still-running older game is never closed by this update;
its matching save/history migration waits for normal exit. Remote delivery is
reported separately when its host is reachable.

## Goalkeeper regression fixture finding

The first shared run stopped at four_goalie: the old Y/hold fixture let a CPU
carrier make a random pass. A free teammate then picked up the loose puck at
frame172, invoking the unchanged native skater-control assignment at15322.
That sequence mixed Y takeover with a separate native possession transition.
The final fixture uses a loose puck at center for Y/steal hold cases, keeping
all original no-stealing and ownership assertions. It does not change the
production control code. Both predecessor and candidate must pass the full
80-case isolated keeper suite. Earlier failed reports remain private and are
not counted as passes. Full exact-build QA is rerun after the fixture change.

The crowd_modes private reset driver was also relocated: its old2DB000 origin
plus4128 bytes overlapped the first32 bytes of the new AI module. The free-byte
guard caught this before emulation. Its new2D9000 origin passes12 profiles and
84 observations. The exact-ROM QA run retained actual completed receipts for
unchanged checks, reran the only changed probe and executed all remaining
checks. QA_STATUS records the changed source hash and retained-check list;
no old-ROM success or unexecuted check is credited.
