# Player count in saved seasons and playoffs — 2026-10-26-r20

> r21 extends the player menu to original standalone playoffs too; see [NATIVE_PLAYOFF_PLAYERS.md](NATIVE_PLAYOFF_PLAYERS.md).

Choose Continue Full/Light/Mini or Continue Full/Light/Mini PO, move down to
**Players** and use Left/Right to select One, Two, Three or Four - Teammates.
The choice is saved immediately for that season, including before a match has
started. Each season remembers its own choice when switching slots or closing
and reopening the emulator. Both RetroArch ports need device1281 (six-button
4-Way Play) for Three/Four, as before.

With ordinary two-pad hardware, a saved Three/Four choice runs as Two; it is
remembered and restored when the adapter returns. Explicitly changing Players
updates that season's saved preference. Results, qualified clubs, playoff tree,
round, series wins and statistics stay unchanged while changing player count.
The existing goalie hold/B request/Y behavior remains for all teammates.

## Why r19 could not do this

Three/Four was kept only in session RAM TM (FFF07C). Season playoffs locked all
their initial rows in seasonmenu.s. In addition, the native Continue Playoffs
cursor skipped Players/Home/Away entirely. Unlocking the writer alone did not
make Players reachable. The native playoff player value also has only one
cooperative encoding, so separate Three/Four labels need their own preference.

## Source and storage

New `experimental/seasonplayers.s` at **2EB000**, **622 bytes**, exports entries
every four bytes: load (+0), store (+4), menu step (+8), label (+12), league
settings (+16), playoff settings (+20), native cursor predicate (+24).

- Preferred count **1..4** lives in RAM **FFF0D0..FFF0D1**. The existing native
  season-playoff game flag at FFF0D2 is untouched. Effective Three/Four also
  sets TM; One/Two clears TM. Adapter absence clamps only the effective count.
- The optional four-byte record **logical SRAM1EF0..1EF3** contains ASCII
  **PC**, count and its byte complement. This is unused space after the
  existing D046..D087 snapshot (1EAC..1EED) and before native settings at1EF6.
  It is included in the existing native8KB checksum and each season snapshot.
  SRAM size, S3SV version, season headers and all existing offsets stay intact.
- Missing/invalid PC records fall back to the native saved player setting;
  merely opening an old season does not rewrite it. Selecting Players creates
  the optional record. Legacy three/four choices that were never saved by an
  older ROM may need to be selected once after upgrading.
- `season/slots.s` loads the preference on initialization and slot restore.
  Restart keeps the current slot's count. A fresh slot gets a record for its
  inherited settings. Store masks interrupts, writes only the active record
  and recalculates its checksum; the slot manager snapshots it on switching.
  Store closes the SRAM mapper; clearseason reopens it before its own writes.

## Menu and native playoff encoding

`season/seasonmenu.s` permits only Players among the locked playoff rows.
Changed league/playoff player selections call the same count step, cycling
One↔Two↔Three↔Four with the adapter and One↔Two without it. The displayed label
uses the effective count even though Three/Four share the native co-op value.
Native unchanged-row writes are ignored, retaining the r18 menu-freeze fix.
Other locked fixture fields remain protected.

Hook **F7782** (native compare Play Mode=Continue) dispatches to the cursor
predicate. It bypasses the native row skip only for Players in an active season
playoff; Home/Away and standalone native modes keep their existing behavior.

After native17CA0/17D16 restores/seeds the playoff, the count is reapplied:
CEF0=0 for One,1 for two-pad co-op,3 for adapter co-op. Players is7/6 without
the adapter and11/8 with it. The existing mtap.s side hook expands co-op to
Three/Four using TM. The qualified club and native bracket/password decode
remain authoritative; changing count does not reseed or erase the playoff.
League values remain1/3/5/6. seasonmenu2002 bytes fits below2FA000; slots1556
bytes fits below2AB000.

## Verification

`experimental/verify_season_players.py` uses real menu input and private copied
batteries; it never loads a state or forces the match's control-side words.

- **24 cases**: Full/Light/Mini × league/playoff × counts1/2/3/4. Forward and
  reverse selection circles, distinct Three/Four labels and immediate saves.
- **48 cold match starts** in fresh core processes. Counts3/4 also boot with
  ordinary two-pad hardware and then back with the adapter, carrying the actual
  newly written battery through each reboot. Checks physical active sides,
  effective count, preferred count, user club and checksums.
- Mixed **Full4 / Light2 / Mini3** preferences survive slot roundtrips, native
  mode detours and a fresh boot. Active and inactive season data, bracket,
  series wins and qualified clubs remain unchanged during selection.
- **18 native playoff result/save cases**: three formats × counts2/3/4 × home/
  away winning result. `verify_shootout.py` now selects the count through the
  real playoff Players row in r20, instead of injecting its old native value.
  Native OT/shootout goals/results/statistics/bracket advancement/checksums
  pass, and the preferred count survives the native playoff save.
- Existing regressions pass: goalie88 adapter +41 ordinary, slots20, adapter
  Play Mode navigation14/266, completed-season9/108, club labels18/240, font14
  cases/3124 native calls/zero wrong-font calls including season playoffs.

```sh
python3 tools/experimental/verify_season_players.py /private/current.bin /private/player-tests --seed /private/preferences.srm --playoffs /private/three-playoffs.srm
```

The three-playoff fixture is produced by verify_season_slots.py. All test
batteries, traces and migration reports are private in
`~/.config/retroarch/roms/claude/work/season-players-20261026-134646/`.
No installed save was used for gameplay tests. Two full source builds are
byte-identical. Guarded r19→r20 diff3579 bytes: header/checksum, relinked menu
hooks, cursor hook, seasonmenu/slots, and the new module only. Original-base
IPS230941 bytes, reconstructed and verified.

ROM SHA256 `a67ecaffe77beb7c13071caee9f507767a4efb7471af3c9ff9487d3b3fb9ae3e`;
CRC32 `086E5C8E`. r20 replaces r19 locally, in Downloads, on the website and
in Bodin's existing playlist item. Matching saves, states, settings, remaps
and history are migrated by unchanged content hashes; preferences are written
by the game, not injected into installed user saves during deployment.
