Status: done
What to build: Implement 35049 — Sericulture Expansion (elev agenda, adv 3, 2 points, Expansion). Card text: "Dividends 1 *(When you score this agenda, place 1 agenda counter on it for each excess advancement counter.)* When your discard phase ends, you may remove 1 hosted agenda counter to place 2 advancement counters on 1 installed card. *(You cannot score that card this turn.)*"
Follow docs/adding-a-card.md: hook block in rust/netrunner-core/src/cards/elev.rs, ≥1 card_35049_* test, flags in game/cards_impl/elev.json.
Watch out for: (1) Dividends counts *excess* advancement counters at score time — score with exactly 3 → 0 counters. (2) The discard-phase-end trigger is optional and targets any installed card — Jinteki: Restoring Humanity (30043) already hooks on_discard_phase_end (sg.rs:256), so verify multiple triggers in that window coexist and order sanely. (3) The "cannot score that card this turn" restriction needs a turn-scoped flag on the target; if the engine has no such restriction mechanism yet, raise it in a comment before approximating.
Targeting widened to the full installed board (review follow-up to 4d49fce).
The first pass offered only advanceable cards in remote roots, which is
narrower than "1 installed card". Nothing in the state model justified the
narrowing: InstalledCard::advancement_tokens exists in every zone, and
placing advancement counters is not advancing — is_advanceable gates the
click action and Seamless Launch's *install* clause, not what an ability may
put counters on. So no target class is excluded now.
Action::SericultureTarget therefore carries a target_id: String
(InstalledId) instead of a (server_index, slot_index) pair, which could
not name central ice, remote ice or the Runner's rig at all. The _id suffix
puts it on the existing wire contract — snapshot::normalize_action_ids swaps
the live decimal id for a "#N" walk ordinal leaving the engine and
denormalize_action_ids reverses it on the way in, matching the id field
server/serialize.py already gives each installed card, so static/game.js
resolves the target across the whole board. Per ADR-0003 this is a case-3
*public position* key (sericulture_target:{target_id}): board positions and
installed ids are visible to both players, so both trees share the key and no
observed_action_key arm is needed.
New coverage for the reachable classes: `card_35049_offers_every_installed_
card_on_either_side` (central ice, remote ice, remote root and rig, in
walk_installed order, plus the snapshot's normalized candidate list),
..._places_counters_on_unadvanceable_central_ice,
..._places_counters_on_a_runner_program,
..._places_counters_on_a_non_advanceable_asset, and
..._refuses_targets_outside_the_frozen_list.
One consequence worth naming: the log names a Runner target but still writes "an unrezzed card" for an unrezzed Corp one, since only the Corp's own facedown board is hidden from the Runner.