What to build: Every implemented System Gateway card (~57) plays through the registry system: each card's hooks move into one contiguous block in the pack's card module, and its match arms are deleted as it moves so no card is registered in both worlds.
Blocked by: 16 — Expand: registry infrastructure + pilot card.
Status: resolved
All 57 implemented System Gateway cards (every non-vanilla implemented
entry in game/cards_impl/sg.json, verified against that file) now live in
cards/sg.rs as one contiguous reg.card("…") block each, in NRDB code
order, followed by their hook implementations in the same order. 107
registrations across 36 registries.
Hook functions moved verbatim from hooks.rs; the only edits were signature
adjustments where a port-era function was narrower than its registry type
(e.g. sure_gamble(state) → sure_gamble(state, _card)), and guards that
used to sit in the match arm moving inside the hook that owns them
("30008" if ic.counters > 0 → the check at the top of leech_actions).
send_a_message is registered on both on_score and on_steal, which is
what the two arms did.
Two cards needed more than a move:
SUBROUTINE_OVERRIDE was inline in
engine::fire_subroutine because diviner_sub needs deal_net_damage /
end_run. Both are pub(crate), so the hook moved to cards/sg.rs
unchanged and fire_subroutine now consults only the registry.
registration and the two documented engine approximations moved with it.
hooks.rs is now dispatchers plus the Elevation/Voices-of-the-Past arms
(1643 → 824 lines). Twenty-nine of them, plus the SUBROUTINE_OVERRIDE site
in engine::fire_subroutine, had no arms left afterwards and collapsed to a
registry lookup with their default (get_ice_subs keeps its "End the run."
fallback, playability_gate its true, and so on). Of the registries behind
them, only ON_TURN_START still holds an unmigrated code (Azimat, 35029) —
that one is a runner program, so trigger_corp_turn_start is registry-only
while trigger_runner_turn_start keeps its arm. all_server_targets became
pub(crate) so the RUN_EVENT_SPEC hooks in pack modules can share it.
Ordering: INSTALL_COST_MODS and ACCESS_BONUS are applied in registration
order rather than looked up. Code order preserves the old order for
INSTALL_COST_MODS (Carmen, DZMZ, then the still-inline Principia). For
ACCESS_BONUS it reorders Jailbreak and Conduit, which is behavior-preserving:
the three hooks are a sum over disjoint cards, and Docklands is the only one
that mutates state (its own once-per-turn flag).
registry_manifest.rs is unchanged — a migration moves *where* a code is
registered, not whether — so the frozen-fixture parity test passes untouched.
Testing: the pilot's card_30040_dispatches_through_the_registry assertion
was replaced by every_system_gateway_registration_is_in_the_registry, which
walks registry_manifest.rs, and for every 30xxx key asserts the live
registry contains it. That is the machine-checked form of "none remain in
match arms", and it covers 30040 as a special case of the general rule. The
six card_30040_* behavior tests are unchanged. No new per-card tests were
added — the card_<code>_* bar for these ~57 grandfathered cards is issue
05's subject, not this one.
cargo build --workspace: clean, no warnings. (cargo fmt / clippycomponents are not installed in this toolchain, so neither was run.)
cargo test --workspace: green — 56 core unit tests + 5 AI invariants.make rust-py, including
test_registry_parity.py against the frozen golden manifest.
selfplay 12 200 plays 24 fullgames (both MCTS engines) of the two System Gateway starter decks from fixed seeds, exercising most of the migrated pool. Its per-game winner and action-count output is byte-identical before and after the migration.
Reviewed and approved by Tomáš Musil. Marked resolved.