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>
75 lines
4.1 KiB
Markdown
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.
|