What to build: The engine builds hook registries at startup, keyed by card code, with a registration builder so one contiguous block declares all of a card's hooks. Every hook dispatch site consults the registry first and falls back to the existing match arms, so the game plays identically with nothing migrated. Seamless Launch (a multi-registry card: on-play + playability gate) moves into the new form as the tracer bullet.
Blocked by: None — can start immediately.
Status: resolved
card_30040_* test)
rust/netrunner-core/src/cards/registry.rs (new) holds the infrastructure:
HookMap<T>: code → hook, plus registration order for the four registries the
engine applies unconditionally instead of looking up (INSTALL_COST_MODS,
ACCESS_BONUS, POST_PROGRAM_INSTALL, BASIC_DRAW_BONUS — Python looped
over .values() in insertion order, and hooks() preserves that).
Registry: one HookMap per hook kind, covering all 45 registries in
registry_manifest.rs, so migrating a card in 02/03 never has to add
infrastructure — only a block.
CardRegistration, the chaining builder returned by Registry::card(code),so one card is one contiguous block. Registering the same hook twice for a code panics at startup.
fn pointers and &'static data, so dispatch is a map lookup
with no allocation. Built once by registry() (a OnceLock).
Dispatch is registry-first everywhere: every dispatcher in hooks.rs plus the
two inline sites in engine.rs (RUN_EVENT_PRE in _play_event,
SUBROUTINE_OVERRIDE in fire_subroutine). Each consults the registry, then
falls back to its existing match arm, so nothing changes for the 71
unmigrated cards.
Seamless Launch (30040) moved to cards/sg.rs — on_play + playability_gate
in one block, with seamless_candidates/seamless_launch moved out of
hooks.rs and both match arms deleted. Six card_30040_* tests in that module
cover the gate (no advanceable card, advanceable card, installed-this-turn
exclusion, non-advanceable content), on-play candidate enumeration, the
no-candidate no-op, and that 30040 really dispatches through the registry.
Layout note: the pack modules live at src/cards/<pack>.rs alongside the
existing src/cards.rs (the 2018 module path style), which is the path
ADR-0002 and docs/adding-a-card.md name — no file move was needed.
registry_manifest.rs is unchanged apart from its doc comment: a migration
moves *where* a code is registered, not whether, so the frozen-fixture parity
test should still pass untouched.
cargo test --workspace green (56 core unit tests incl. the six
card_30040_* tests + 5 AI invariants) and the Python suite green
(136 passed) after make rust-py.
Reviewed and approved by Tomáš Musil; merged as bb576cd. Marked resolved.