← all tickets

57 — Implement: Empiricist (35052)

Status: done

What to build: Implement 35052 — Empiricist (elev ice, Sentry - AP - Observer, rez 7, str 5). Subroutines: 1. "Draw 1 card. You may add 1 card from HQ to the top of R&D." 2. "Do 1 net damage. Give the Runner 1 tag." 3. "Do 2 net damage."

Follow docs/adding-a-card.md: hook block in rust/netrunner-core/src/cards/elev.rs, ≥1 card_35052_* test, flags in game/cards_impl/elev.json.

Watch out for: sub 1 is draw (mandatory) then an optional Corp decision with an HQ-card target — per-target action keys apply (ADR-0003; redact the chosen card from the Runner's view like Hansei's hansei_trash handling in server/serialize.py:52-57). Empty R&D / empty HQ edge cases. Subs 2–3 are stock effects — follow existing sentry patterns in sg.rs (e.g. Karunā 30047 for net damage subs).

Comments

Review follow-up — the tag trigger did not hold the encounter. The first pass paused engine::pass_ice on a two-name allowlist (bran_install, empiricist_top), so subroutine 2's tag firing NBN: Reality Plus (30051) left the Corp's gain-2-or-draw-2 window open while subroutine 3's 2 net damage resolved past it. Fixed generically, as the review asked: the loop now pauses on *any* growth of the decision stack during a subroutine and records it in RunState::sub_decision_pause, and engine::apply_action re-enters pass_ice once the last pending decision is answered. Resuming from apply_action rather than from a decision handler is what puts the resumed subroutines *after* the answer's own effects — reality_plus_choice credits the Corp before the damage lands, which the old handler-driven resume_pass_ice could not have done without every such handler knowing about encounters. The four handlers that used to call resume_pass_ice now just restore_priority, and resume_pass_ice is gone.

An allowlist could only ever name what a subroutine asks for *itself*; the Reality Plus case arrives from a trigger two calls below the subroutine, so every effect that can reach a trigger would have had to be enumerated. The generic test is cheap and has no false positives here: a decision pending at this point is by construction one the subroutine just opened, and legal_actions will not offer PassIce while any decision is on the stack.

Covered by card_35052_a_tag_trigger_holds_the_last_subroutine and card_35052_chains_its_own_window_into_the_tag_trigger (both fail against the old allowlist, verified). Subroutine 2 also now logs the tag *before* taking it, so the trigger's own log lines come after the line that explains them — the order every other tag in the engine uses.