What to build: Registries become the only dispatch path — the match-arm fallback is removed. The hand-maintained registry mirror and its frozen-fixture parity test are deleted (their port-guarding job is done, per ADR-0002); anything that still consumes a registry manifest reads live registry introspection instead. After this, adding a card touches one block, one test, one manifest flag.
Blocked by: 17 — Migrate: System Gateway cards; 18 — Migrate: Elevation + vp cards.
Status: fixed
The fallback was already gone — 03 emptied the last dispatcher — so this ticket is the deletion plus the introspection that replaces what the deleted files were checking.
Deleted outright:
rust/netrunner-core/src/registry_manifest.rs (the hand-maintained mirror of
the 45 Python registries) and its pub mod line
rust/netrunner-core/src/bin/registry_manifest.rs + the generated
rust/netrunner-core/registry_manifest.json — the bin existed only to feed
the Python parity test
tests/test_registry_parity.py and tests/fixtures/registry_manifest_golden.json
Introspection. Registry::inventory() returns `(registry name, sorted
codes) for all 46 registries, read off the live maps; registered_codes()`
flattens it to "every card this engine implements". The [Registry] struct is
now declared by a small registries! macro that generates the fields *and*
inventory() from one list, so a new hook kind cannot be added to the struct
and forgotten in the introspection — which matters because 05's honesty checks
trust the inventory to be complete. Registry names are the struct's field
names (on_play, playability_gate, …), the snake_case forms of the
game/effects.py names the card comments still cite.
Python reads it through a new netrunner_engine.registry_inventory()
(rust/netrunner-py/src/registry.rs) returning dict[str, list[str]]. Rust
never parses the pack manifests (ADR-0001), so 05's checks run Python-side and
this is the bridge they need; tests/test_registry_inventory.py (3 tests)
guards the bridge and its shape in the meantime.
Tests. cards/registry.rs's every_manifest_registration_is_in_the_registry
walked the deleted manifest, so it became inventory_reports_every_registration:
same guard numbers (46 registries, 133 registrations, 72 cards = 57 sg + 8 elev
+ 7 vp) taken from the live inventory instead, plus a sorted/de-duplicated check
on every registry's codes. It is a drift guard, not a truth check — with no
fallback arms left, a deleted or never-called block shows up as a smaller
count. Tying those codes to the manifests' implemented flags is 05's job.
Rust core unit tests go 56 → 53 (the three tests inside registry_manifest.rs
went with it); the Python suite goes 136 → 93 (test_registry_parity.py was
parametrized over 45 registries, so it was 46 of those, and 3 new ones land).
Doc pointers repointed rather than dropped: hooks.rs and cards/registry.rs
module docs, server/serialize.py's MU_BONUS/HAND_SIZE_BONUS source-of-truth
comment (now cards/*.rs), claude_todo.md's where-things-live/tests
paragraphs, and docs/adding-a-card.md's honesty-check section, which now
names registry_inventory() as the entry point. ADR-0002 and PORT-DELTA.md are
left alone — they describe the port as history, and their prediction ("deleted
once the registries are introspectable") is what just happened.
cargo fmt --all -- --check: clean. `cargo clippy --workspace --all-targets-- -D warnings`: clean.
cargo test --workspace: green — 53 core unit tests + 5 AI invariants.make rust-py, including the new
test_registry_inventory.py.
inventory_reports_every_registration verified non-vacuous by commenting outWildcat Strike's registration: fails with 132 ≠ 133.
selfplay 12 200 (24 games) byte-identical to HEADignoring the timing lines — expected, since nothing in the dispatch path changed, but cheap confirmation that the macro-declared struct and the pack registration order are unchanged.