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

## Behavior

jdc reported that a free CPU teammate taking the puck almost always becomes P1.
The native 15394 first-pad preference reproduces this:64 equal-opportunity native
pickup assignments in r5 all go to P1. The same initial fixture and evolving native
RNG in r6 yield17/23/10/14 assignments across P1–P4. This small diagnostic is not a
physical playtest or a guarantee of equal counts in a short game.

The new policy applies only after the engine has accepted stick possession by an
unassigned CPU skater on a team with at least two active human pads. It chooses
randomly among that team's eligible pads. Two ordinary controllers and3/4-Way Play
use their actual physical controller-side table. In mixed2v2only friendly pads
participate. An eligible pad currently without a skater can participate too.

Already controlled receivers retain their pad, including explicitly requested
keeper outlets. Manual keeper control, pass/recovery locks, pending Y requests,
invalid/inactive/wrong-side old actors and duplicate actor references are excluded.
Single-human/head-to-head/CPU-only teams, shootouts, awarded penalty shots, pause
and stopped play retain the native policy. If only one pad is eligible, it gets
the skater without consuming RNG. If none is eligible, the original path runs.
Native C0F8 releases the old actor and assigns the new one; other pads' skaters are
not detached. B/Y switching and keeper requests are not hooked by this feature.

## Hook and state

| Site | r5 bytes | r6 bytes |
|---|---|---|
|1538A|B07C00065DC1|4EF9002EFC00|

`experimental/fair_pickup.s` lives at2EFC00, bounded below 2F0000. The installed
native accepted-pickup path still performs the existing catch/downed/crit/body
checks and goalie handoff first. Its already-human tests precede this hook.
The helper verifies D0/A2 index, puck ownership, role and team. Physical C320..C326
controller assignments and C328..C32Eside values determine the eligible subset;
D046 limits ordinary input to two pads. Pending Y is read from F0E0 ordinary or
F0B2 adapter flags. The C0F8 old-actor lock and native one-timer semantics are retained.

The helper saves fifteen registers and SR, masks interrupts while using the
controller table, then restores them with a balanced stack. Its8-byte eligibility
list exists only on the stack. It adds no RAM or battery-save fields. With two or
more eligible pads, exactly one call to native 11086 selects an index; no host RNG,
seed reset, repeated rolls or favorable outcome selection is used in the ROM.
The fallback replays CMP/SLT then resumes 15390 with the native CCR/register result.
General C0F8, movement, pickups, pass physics, AI and ratings remain byte-exact.
Extra instructions can still change timing-dependent native HV choices.

Build with `--version 2026-10-31-r6`. `--legacy-fair-pickup --version
2026-10-31-r5` reconstructs the exact released r5 image. All previous features,
Pass Height/Speed choices and played-season locks remain. The planned optional
Pass Mode Assisted/Directional is separate research and is not implemented here.

## Evidence and limits

`verify_fair_pickup.py` requires all five sections for a complete run:

- 316 physical subset/eligibility contexts enumerate every eligible draw on both
 sides, including ordinary input,2v2, absent pads, manual keepers, Y, locks,
 unassigned actors, duplicates and native one-timer flags. A private finite RNG
 stub supplies each index; the original native C0F8 executes as the pad oracle.
- 36 guard contexts preserve the parent's complete registers/SR/SP/controller,
 actor flags, owner and RNG observation. Already-human receivers are included.
- 112 paired direct hook ABI contexts cover12 handled and100 fallback cases,
 both CCR extremes, valid/invalid indices and guard boundaries.
- 24 series of6000 actual native pickup assignments each cover2 ordinary,2 adapter,
 3 adapter and4 adapter pads, both teams and three initial seeds. The RNG evolves
 within each series. r5always chooses its first pad and does not draw. Current
 counts and final seed match an original native RNG-only control exactly; every
 active bucket is within10%of equal share. This is 144000 controlled assignment
 decisions per current/control profile, not144000games or natural collisions.
- 32 full native14E10 stick/body contexts use real GetHot and loose-puck inputs,
 then require actual accepted possession and the original C0F8controller/flag
 result. Explicit human receivers and keepers also match the parent directly.
- 48 declared live-game fixture branches place a loose puck at GetHot and execute
 the actual collision in a private preserving game-loop wrapper. Native RNG,
 pickup and controller selection produce the result; no owner or winning pad is
 injected. The actual selected physical pad then holds Right for55 frames and
 must move that carrier more than 3 world units. All P1–P4 winning pads are exercised.
 Private bank probes distinguish temporary4-Way Play mirrors and verify bank0
 at arbitration. Each event assigns once; subsequent movement must not reroll it.
 These are synthetic fixture branches with actual emulator input, not unforced
 matches or recordings from physical controllers. Native screenshots are inspected.

Early host-only failures remain private: an oversized RNG stub shifted the ROM;
its first address encoded ROM instead of RAM; a byte flag was initially supplied
as a word. Runtime diagnostics initially loaded an adapter state for ordinary
input, missed native pickup from a stale actor timer, used a scratch field touched
by rendering, and allowed too little movement time. Corrected fixtures use the
matching ordinary/adapter states, a declared native collision, dedicated observer
scratch and55 frames of input. Production ROM code did not change for these fixes.
No failed run is represented as a success.

The first full matrix also retained a net-test failure: its matched legacy/current
CPU sequence produced zero moving-net observations in both images, so the old
positive-event assertion was no longer reproduced. No ROM code changed for the
correction. The affected net test was rerun; the other 86 suite sources and
receipts remained unchanged and every original failure/source hash is retained.
The exact r13 sequence still produces 40 moving-net observations; changing only
its four threshold bytes removes all 40. The current matched pair is separately
recorded as zero/zero, with no claim of a corrected event in that pair. All 368
native threshold/ABI cases and identical physical-input burst motion still pass.
This isolates the threshold proof from unrelated native instruction timing.
QA_STATUS.json includes the initial failed matrix, changed test hash and rerun receipt.

All 87 shared exact-source checks run on the release image. 86 pass; the inherited
finisher_hold receiver-position geometry check remains failed in the accepted38-win tactical base, so the strict gate remains FAILED. This is a retained known
limitation, not a qualified39/50 or 40/50 claim. The40 ordinary 600-frame scenes are
recorded against fresh exact r5 controls with every divergence disclosed. Earlier
parents retain their full strict comparison chain. No Hard win-rate search resumes.
Independent rebuild, exact r5 rollback, matched AI/gap/net controls and original-base
IPS reconstruction pass. Hashes and results are in FAIR_PICKUP_RESULT.json and
QA_STATUS.json; sources and exact hook/full-byte exports are on the website.

## Delivery

The dated r6 replaces the current Downloads, canonical and public ROM/IPS. Fresh
progress follows jdc's release policy; remap/config/history hashes are preserved.
A running RetroArch is left open and save cleanup waits for normal exit. Bodin's
existing playlist item receives the same image when reachable; queued work is
not described as completed synchronization. FAIR_PICKUP_RESULT.json records the
actual installation and remote verification state. No forum message is posted.
