← all tickets

39 — Replace no-window choice approximations with real decisions (Carnivore, Longevity Serum, Hansei Review, Anoetic Void)

Status: fixed

What to fix: Four cards landed in tickets 10/27/31/32/34 with documented engine approximations where a player choice is silently auto-resolved instead of being offered as a decision. These are genuine rules divergences — the printed cards give the player a choice of *which* cards, and the auto-resolution changes game outcomes (e.g. Longevity Serum never trashing from HQ, Carnivore burning cards the runner wanted to keep). Route each through the existing out-of-band decision mechanism (state.push_decision(side, DecisionData::…) in rust/netrunner-core/src/state.rs:360, with legal actions derived per-variant in rust/netrunner-core/src/legal.rs — see DecisionData::BranInstall / ChooseSearchCard for multi-card-pick precedent).

The four approximations, all in rust/netrunner-core/src/cards/sg.rs:

1. Longevity Serum (30044) — on-score, two choices are auto-resolved: the optional "trash any number of cards from HQ" always resolves as zero, and "shuffle up to 3 cards from Archives into R&D" takes the first 3 found (faceup, then facedown). Both should be corp decisions: pick any subset of HQ to trash, then pick up to 3 Archives cards to shuffle back. 2. Hansei Review (30048) — "trash 1 of them [HQ]" trashes hq.remove(0) (hansei_review, ~sg.rs:1388). Which card is the corp's choice. 3. Anoetic Void (30050) — the "pay 1 credit and trash 2 cards from HQ: end the run" cost takes the top 2 of HQ. Which 2 is the corp's choice. 4. Carnivore (30003) — the "trash 2 cards from your grip" cost (use_carnivore, rust/netrunner-core/src/engine.rs:2377) takes the top 2 grip cards. Which 2 is the runner's choice.

Definition of done:

Out of scope: Spin Doctor (30011) and Sprint (30041) share the same shape of approximation and were cited as precedent when these four landed. Fixing them is a natural follow-up once the decision plumbing exists, but is not required here — file a separate ticket if this one lands cleanly.

Comments

2026-08-01: Five new decision kinds (carnivore_trash, longevity_trash, longevity_recur, hansei_trash, anoetic_trash), each asking for one card per window: options in legal.rs (hq_trash_options is shared by the three HQ pickers), resolution in engine.rs, and the card hooks in cards/sg.rs reduced to pushing them. Costs stay costs — Carnivore does not trash the accessed card and Anoetic Void does not end the run until the last card is chosen — and Longevity Serum pushes its two decisions in reverse so the HQ trash resolves first and feeds the Archives recursion. Corp AI: decline the HQ trash, take every recursion card offered (the old auto-behavior). Also added client prompts in static/game.js and log redaction in server/serialize.py for the newly named HQ/grip cards. Tests: non-default picks per card (Carnivore pays with grip[2] and grip[0], Longevity Serum trashes 2 chosen HQ cards and recurs 2 chosen Archives cards, Hansei/Anoetic trash off the back of HQ) plus a recursion cap test. Follow-up for Spin Doctor and Sprint filed as ticket 41.