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>
4.1 KiB
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 ishero(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:
newGamewith a deck of 5Test Fillerfor each player. SummonArmorsmith(id S), thenAcolyte of Pain(id A). - Action: player 0 casts
Whirlwind. - Assertions:
- The log has two
Damagedevents, for S then A (area damage is created in order of play), each of amount 1. - Every
Damagedevent comes before everyTriggeredevent in the log. Triggeredevents: 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.
- The log has two
test_02_mortally_wounded_still_triggers
- Setup:
newGamewith a deck of 5Test Fillerfor player 0. SummonAcolyte of Pain(id A).dealDamage(A, 3), which leaves A at 1 Health and draws one card. Recordhand(0)'s length as N. - Action: player 0 casts
Whirlwind. - Assertions:
- The log contains
Triggered{source: A, trigger: "damaged"}and aDrewevent 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.
- The log contains
test_02_depth_first_nesting
- Setup: an empty game. Summon
Test Echo(id E), thenArmorsmith(id S), thenTest Reactor(id R). - Action:
dealDamage(S, 1). - Assertions:
- The sources of the
Triggeredevents, 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
StatsChangedevent for R comes before theArmorGainedevent in the log. attack(R)is 2.armor(0)is 1.health(hero(1))is 29.
- The sources of the
test_02_log_tree_shows_nesting
- Setup: an empty game. Summon
Test Echo(id E), thenArmorsmith(id S), thenTest Reactor(id R). Callhero(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: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
Triggeredline, 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.
- The log is exactly