# Player cards: portraits follow player identity (2026-10-25)

The current dated Season - jdc build fixes the incorrect face/name combinations
in both the main-menu slideshow and pre-game/pause Player Cards. The roster patch
had changed587 named roster slots while the832-entry portrait table still
referred to the original roster indices. For example, Washington swapped Charlie
Lindgren/Logan Thompson in the roster without swapping their portrait links.

There are832 corrected card associations:702 roster slots reuse existing original
portraits,123 receive real NHL headshots, and7 use the NHL neutral silhouette
because no usable official headshot was found. Three newly spelled names were
resolved to existing photos through their NHL IDs: Gabe/Gabriel Perreault,
Jake/Jacob Middleton and Will/William Borgen.586 actual image pointers changed;
one changed spelling already occupied the original correct portrait slot.
William Trudeau uses the verified2025/26 NHL archive photo because the current
2026/27 URL returns a placeholder. A checked older-season search did not find
usable headshots for the remaining seven names below.

| Neutral card | NHL player ID |
|---|---|
| Alexander Dergachyov |8478494|
| Axel Hurtig |8484481|
| Egor Velmakin |8484493|
| Jackson Hallum |8482512|
| Mason Langenbrunner |8482517|
| Oskar Jellvik |8482907|
| Yegor Spiridonov |8481603|

The current NYR roster already contains both Gabriel and Gabe Perreault. These
two existing slots now share their correct portrait/ID8484210;832 roster names
therefore resolve to831 distinct NHL IDs. This patch keeps those existing roster
slots, attributes and season statistics intact. Elias Pettersson is explicitly
resolved as the VAN forward8480012; Elias_Nils Pettersson is the VAN defenseman
8483678. Exact reviewed aliases avoid same-name or abbreviated-name mistakes.

## Source and preparation

- `tools/experimental/player_cards.py`: build-time association installer.
- `tools/experimental/prepare_player_cards.py`: explicit NHL fetch/asset preparation.
- `tools/experimental/player_card_aliases.json`: reviewed name/ID disambiguation.
- `tools/experimental/player_card_sources.json`: reviewed historical source override.
- `tools/experimental/player_cards/manifest.json`: all832 names, NHL IDs, original
  photo offsets/hashes or source URLs, canonical NHL names, crop rectangles,
  source-image and encoded-image hashes.
- `tools/experimental/player_cards/*.bin`:130 prepared874-byte native records
  (123 real headshots plus7 neutral cards). Builds require no network or OpenCV.
- `tools/experimental/verify_player_cards.py`: private native lookup/decoder test.

NHL identities come from the already downloaded official roster/prospect/bios
JSONs, requiring a unique ID for each reviewed name. New photos come from
`https://api-web.nhle.com/v1/player/{ID}/landing` and its NHL headshot URL;
canonical first/last name and playerId are checked before downloading. For an
example of the source response see
[Devon Levi's official NHL data](https://api-web.nhle.com/v1/player/8482221/landing).
No guessed/fuzzy face assignment is used. Archived sources must be explicitly
reviewed in player_card_sources.json. The seven placeholders are labelled as
`nhl-placeholder` in the manifest, not represented as real portraits.

Preparation composites transparency on white, crops the face using the locally
installed OpenCV frontal-face cascade (one standard crop, Bokondji Imama, was
visually reviewed), reduces to48x48 with Lanczos and dithers to the ROM's existing
eight-colour palette. A low-colour NHL default image becomes a full neutral card,
not a crop of a nonexistent face. Raw source responses/PNGs and the reviewed
contact sheet stay in the private evidence cache. The published encoded records
and hashes make the production build reproducible without fetching images again.

From the project root, to explicitly regenerate sources/assets:

    python3 tools/experimental/prepare_player_cards.py ORIGINAL_ROM CURRENT_ROM \
      --data tools/data --cache /absolute/private/headshot-cache

This preparation requires Pillow, numpy, OpenCV and its frontal-face cascade.
The normal complete experimental build command stays the same:

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

The builder uses the final ROM's roster names, resolves the reviewed NHL-ID
catalogue, validates the original image/table/palette hashes and updates only
the photo pointer for each existing roster index. Unknown names fail with an
instruction to prepare/review them, instead of silently displaying another face.
`--no-player-cards` exactly reproduces the preceding OT/shootout build.

## ROM layout and native rendering

- Team-to-photo-list pointers:32 longs at0038E, unchanged.
- Association lists:0040E..00180D, each26 `(photo pointer.l, roster index.w)`
  records plus a four-byte terminator. List ordering/indices/terminators stay.
- Existing832 source photos remain at their original addresses.
- New image records:113620 bytes at2B0000..2CBBD3, in previously unused ROM.
  Reserve2B0000..2EFFFF for these assets; roster/gameplay code starts at2F0000.
- Each native photo record is874 bytes:two zero relative pointers, tile count36,
  then36 tile-major8x8 tiles, three packed bytes per eight-pixel row (3bpp).
- Native FAE26 maps team/roster slot to the record. Native FE98A expands3bpp into
  4bpp, adding5 so pixels use colours5..12. The shared palette is atC63F8+36A+20;
  the existing6x6 tilemap is atC63F8+3EA. Neither shared asset changes.
- Main-menu F8948 and card-screen FAED4 both use the repaired association lists.

No new assembly, hooks, live RAM, SRAM or save schema. Native rendering and all
match/season controls/statistics/rosters keep their existing bytecode/data. The
only production differences are586 pointers, the130 new image records and ROM
checksum:109010 changed bytes in the guarded regions. No private probe remains.
The original card-format reference is
[the author's NHL94 player-card tutorial](https://forum.nhl94.com/index.php?/topic/216-how-to-player-card-tutorial-nhl94-nhl95/).

## Verification and release

All832 lookups and832 decodes were executed in the actual68000 core. The private
reset-vector probe calls native FAE26/FE98A, allowing the host to compare every
returned pointer and1152 decoded4bpp bytes to the expected identity/tile data.
The probe uses2FEA00 and RAMFFF0B0..0BB only in its private ROM; the production
ROM contains neither it nor an altered reset vector.

    python3 tools/experimental/verify_player_cards.py ROM /private/card-check

Real controller navigation captured all26 Washington player cards, a team card,
return/wrap, and the corresponding28 before/after screenshots. Every changed
pixel is inside the48x48 portrait area (8,64..56,112); all labels/statistics and
the saved battery data are identical. Another28 captures cover Edmonton,
including Devon Levi's newly fetched portrait. Ten paired main-menu slideshow
captures also differ only in the left/right48x48 photo area, with identical SRAM.
The completed CPU game's full battery image is byte-identical to the prior build. The123 real new source photos have distinct hashes; the7 neutral
cards are explicit. A complete clean CPU Mini1–4 saved valid team, player and
keeper totals/checksum. Source rebuild is byte-identical; --no-player-cards is
byte-identical to the preceding27349d... ROM. IPS reconstructs the exact output.

SHA256 `0e5c8ee01f69d7219456d414a15b58fe593c0b957c5630886d138027822d0461`, CRC32 `694C73C2`; IPS210164 bytes.
Dated filename remains2026-10-24 to retain save/remap matching. Updated canonical,
Downloads and public dated ROM/IPS, Bodin's existing playlist entry and website/
source/docs, with previous-ROM/playlist/save backups. Earlier release downloads
and existing battery/state/remap contents are retained. Private evidence:
`~/.config/retroarch/roms/claude/work/player-cards-20261025-110400/`.
