Files
systemic-game-design-course/course/02-action-queue/scenarios.md
T
Lucas WintherandClaude Opus 5.5 97c64ebee8 feat: log tree from Module 2 (logTree / log_tree); SpellGrid CLIs print Log:
Engine events carry the nesting depth they were recorded at; the harness
prints the log as an indented tree. New tests test_02_log_tree_shows_nesting
and test_03_log_tree_death_phase; both SpellGrid CLIs print the tree of the
last command's Sequences under a Log: line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 10:23:59 +01:00

75 lines
4.1 KiB
Markdown

# Module 2 scenarios: The action queue, depth-first
Each heading is a test name. The Rust test lives in `tests/rust/test_02.rs` and the TypeScript test in `tests/ts/test_02.test.ts`; both state exactly what is written here. Player 0 is the first player, player 1 the second. Every minion in these tests is summoned for player 0, in the order listed, so the first one listed has the lowest order of play. Each test calls `clearLog()` after its setup, so `log()` holds only the events of the action under test.
Cards used (text from `course/catalog.json`):
- `Armorsmith`, 1/4: "Whenever a friendly minion takes damage, gain 1 Armor."
- `Acolyte of Pain`, 1/4: "Whenever this minion takes damage, draw a card."
- `Whirlwind`, spell: "Deal 1 damage to ALL minions."
- `Test Echo` (fictional), 1/5: "Whenever a minion takes damage, deal 1 damage to the enemy hero." Player 0's enemy hero is `hero(1)`.
- `Test Reactor` (fictional), 1/5: "Whenever a hero takes damage, gain +1 Attack."
- `Test Filler` (fictional), 1/1, no text. Used to fill decks.
Heroes have 30 Health and 0 Armor at the start of a game.
### test_02_aoe_damage_then_triggers
- Setup: `newGame` with a deck of 5 `Test Filler` for each player. Summon `Armorsmith` (id S), then `Acolyte of Pain` (id A).
- Action: player 0 casts `Whirlwind`.
- Assertions:
- The log has two `Damaged` events, for S then A (area damage is created in order of play), each of amount 1.
- Every `Damaged` event comes before every `Triggered` event in the log.
- `Triggered` events: S appears twice (once for each damaged friendly minion, itself included), A once. The test does not check how those three are interleaved.
- `armor(0)` is 2.
- `hand(0)` has 1 card.
### test_02_mortally_wounded_still_triggers
- Setup: `newGame` with a deck of 5 `Test Filler` for player 0. Summon `Acolyte of Pain` (id A). `dealDamage(A, 3)`, which leaves A at 1 Health and draws one card. Record `hand(0)`'s length as N.
- Action: player 0 casts `Whirlwind`.
- Assertions:
- The log contains `Triggered{source: A, trigger: "damaged"}` and a `Drew` event for player 0.
- `hand(0)` has N + 1 cards.
- `health(A)` is 0.
- The test does not check `zoneOf(A)`. Module 2 has no death step, so A stays on the board at 0 Health. Module 3 adds the step that removes A once the Sequence's reactions have resolved, and this test must keep passing then.
### test_02_depth_first_nesting
- Setup: an empty game. Summon `Test Echo` (id E), then `Armorsmith` (id S), then `Test Reactor` (id R).
- Action: `dealDamage(S, 1)`.
- Assertions:
- The sources of the `Triggered` events, in log order, are E, R, S. E and S both respond to S taking damage. E resolves first (older), and the hero damage it deals makes R trigger and resolve before S gets its turn.
- The `StatsChanged` event for R comes before the `ArmorGained` event in the log.
- `attack(R)` is 2.
- `armor(0)` is 1.
- `health(hero(1))` is 29.
### test_02_log_tree_shows_nesting
- Setup: an empty game. Summon `Test Echo` (id E), then `Armorsmith` (id S), then `Test Reactor` (id R). Call `hero(1)` H.
- Action: `dealDamage(S, 1)`.
- Assertions:
- `logTree()` is exactly these seven lines, each ending with a newline, with two spaces of indent per level of nesting:
```text
Damaged{target: S, amount: 1}
Triggered{source: E, trigger: "damaged"}
Damaged{target: H, amount: 1}
Triggered{source: R, trigger: "damaged"}
StatsChanged{id: R, attack: 2, health: 5}
Triggered{source: S, trigger: "damaged"}
ArmorGained{player: 0, amount: 1}
```
The outermost action's own event is at depth 0, and so is each trigger in the Queue it fills. A trigger's consequences are one level deeper than its `Triggered` line, including any trigger they cause in turn: Reactor's line is inside Echo's block.
### test_02_empty_deck_draw_is_fatigue
- Setup: an empty game (no decks). Summon `Acolyte of Pain` (id A).
- Action: `dealDamage(A, 1)`.
- Assertions:
- The log is exactly `Damaged{target: A, amount: 1}`, `Triggered{source: A, trigger: "damaged"}`, `Fatigue{player: 0}`.
- `hand(0)` is empty.
- `health(hero(0))` is 30: in this course, Fatigue is only an event.