# Stage Drop sources and cast thresholds

Checked 2026-11-01 against rules `0.2.0` and effects `0.1.0`. Two seed-2
traces compare playing **Encore Spark** before **Final Chorus Bloom** with
playing Bloom first. Spark is a **Hex Line**, despite its name. Bloom is an
**Encore**. Stage Drop adds one Trend for the former or two for the latter,
once per turn. No engine, manifest, policy or balance change was needed.

## Rebuild and replay

```sh
python3 tools/build_tutorial_traces.py
python3 tools/play_seeded_game.py \
  --replay traces/tutorials/stage-drop-hex-first-seed-2.v0.json
python3 tools/play_seeded_game.py \
  --replay traces/tutorials/stage-drop-encore-first-seed-2.v0.json
python3 -m unittest discover -s tests -p 'test_stage_drop.py' -v
python3 -m unittest discover -s tests -p 'test_tutorials.py' -v
```

Both traces start from unmodified seeded setup and use only legal actions.
They share **45 decisions** and end in Comeback's turn-10 main phase, still
`in-progress`. Hex-first takes **56 decisions**; Encore-first takes **54**.
Decision counts below mean actions already applied. Replay a trace prefix with
`final_digest=None` to inspect it.

## Shared setup

Rooftop (player 1) goes first. Both players pass until Comeback's fourth turn.

| After decision | Checkpoint |
| --- | --- |
| 31 | Turn 8 main: Comeback has 4 Fandom from its normal refill. |
| 32 | Stage Drop declared for 2; Rooftop has first response priority. |
| 34 | Stage Drop is in play. It grants no Trend just for entering. |
| 42 | Turn 10 main: Comeback refills to 5. The prior turn's Scene cast is outside the current cast history. |
| 45 | Lightstick resolves: 8 Fandom, 0 Trend, one current-turn cast, Lightstick in discard. Stage's use remains available. |

No Stars enter in these tutorials. All four required cards are drawn normally.

## Play-order comparison

| After decision | Hex first | Encore first |
| --- | --- | --- |
| 46 | Spark declared for 3; Lightstick remains in discard until resolution. | Bloom declared for 5 as this turn's second cast. |
| 48 | Spark returns Lightstick and gains 2 Trend; Stage adds 1. Total 3 Trend, 5 Fandom. Return adds no cast event. | Bloom gains 5 Trend; Stage adds 1 then 1. Total 7 Trend, 3 Fandom. Two casts do not enable Bloom's damage. |
| 51 | Lightstick is recast for zero, returning Fandom to 8. Three cast events now include Lightstick twice. | Spark returns Lightstick and gains 2 Trend. Stage is already spent: total 9 Trend, zero Fandom. |
| 52 | Bloom declared for 5 as the fourth cast. | Lightstick declared for zero as the fourth cast. |
| 54 | Bloom has granted 5 Trend, taking the total to 8. Stage adds nothing. Its 3 damage is pending; Rooftop is still at 20 Composure. | Lightstick resolves, leaving 3 Fandom. The tutorial ends. |
| 56 | Damage resolves, leaving Rooftop at 17 Composure. The tutorial ends. | — |

Both endpoints have 3 Comeback Fandom and four cast events from three distinct
card identities. Lightstick, Spark and Bloom are in discard; Stage remains in
play. All stacks, pending events and resolution frames are empty. Stage has
used its trigger once. Rooftop has 5 Fandom and 0 Trend in both branches.

Bloom counts its own declaration. Returning Lightstick is not a cast; recasting
it is. In these starter histories, the recast is the third event but Bloom
would also be a third cast if played immediately after Spark. The seeded pair
therefore demonstrates a recast without proving that a distinct-card-counting
implementation would fail. A separate controlled test isolates that boundary.
These branches show a trade between one extra Trend and three damage; they
establish neither optimal play nor a deck-balance result.

## Controlled boundaries

`tests/test_stage_drop.py` explicitly moves cards between zones, sets resources
and injects resource changes where necessary. Those setups are separate from
the seeded tutorials.

- Stage follows its controller in either seat. An opponent's gain, another
  resource, a loss, zero change, a missing cause, Fandom cause or Scene cause
  does not consume its trigger. Stage's own bonus cannot trigger itself.
- The first qualifying gain consumes the limit. A second qualifying spell
  that turn gets no bonus. The limit resets at the next player's turn, and
  Stage can trigger again on the next own turn.
- Countered Chorus and fizzled Spark remain cast events but produce no Trend
  and leave Stage's trigger available for Bloom. Discard-target removal while
  Spark is pending is a controlled boundary unavailable to starter responses.
- Bloom's own declaration changes the count from one to two or two to three.
  Only the latter opens its damage window. A controlled return of Lightstick
  produces three cast events from two identities, catching accidental distinct
  counting. Normal starter recursion requires Spark as an additional cast.
- Opponent casts and previous-turn casts do not satisfy Bloom's threshold.
- Starting at 5, 4 or 3 Trend, Bloom wins respectively on its own gain, Stage's
  first bonus or Stage's second bonus. Later bonus actions and otherwise
  eligible damage stop immediately. The terminal snapshot retains interrupted
  frames and Bloom in the stack zone, and offers no legal actions.

## Follow-up measurement

Track the triggering card and the triggered Scene separately when attributing
resource gains. Existing resource events identify Stage as the source of its
bonus, but do not link that bonus event back to Spark or Bloom. Record actual
bonus delivered and any actions interrupted by victory, rather than assuming
every Encore supplies two bonus Trend. Use these branches and terminal tests
to check attribution before changing policy sequencing heuristics.

The [coverage matrix](coverage.v0.md) retains the Scene-occupancy and Fandom
history gaps. The [pre-tuning protocol](pre-tuning-gates.v0.md) still requires
policy attribution, seed-43 replication and table evidence.

## Source provenance

Inputs and tutorial traces, SHA-256:

| File | SHA-256 |
| --- | --- |
| `data/starter-deck.v0.json` | `8e27a0102790a6203092cd3975ecded2456e26bb1993cc353da2803bf9404aac` |
| `hallyu_hex/engine.py` | `7f8627648e163ddd6a728054e0509f72ee4ca5d54f13181f41e4a9bda5000852` |
| `traces/tutorials/stage-drop-hex-first-seed-2.v0.json` | `cf9c3d85f52faa47b497c50d0b1e134de2b212515ab6a5211cdec70c8edca635` |
| `traces/tutorials/stage-drop-encore-first-seed-2.v0.json` | `3cec09312a550e2369bb8ae505607e0a1a451d0af76ee6f98ed52927780da967` |
