feat: summoned minions enter the grid in Module 10 (a fourth seam; Clay Golem, Clay Shard)

Summoned records its summoner (the minion whose trigger is resolving) inside
the engine; the harness log is unchanged. When a Sequence ends, SpellGrid
writes spawned text, places each summoned minion with a noun on its
summoner's tile, removes the dead, reparses and syncs rule auras.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5.5 committed 2026-11-11 11:35:59 +01:00
1 parent 820b378929
commit 4eaf24eed2
23 files changed
+361 -42

No files matched your search

@@ -1,7 +1,7 @@
//! Part III: SpellGrid, the two engines in one level.
//!
//! Part I (the card engine) and Part II (the grid engine, `puzzle`) don't use each
//! other. This file is the only place that knows both, and they meet at three seams:
//! other. This file is the only place that knows both, and they meet at four seams:
//!
//! 1. Rules become modifiers. After every reparse, the rules in force that join a
//! trigger word to a multiplier word (`DEATHRATTLE IS DOUBLE`) are compiled into
@@ -15,6 +15,10 @@
//! ends, the text its effects asked for (`TextSpawned` events) is written, the
//! minions that died (`Destroyed` events) leave the grid, and the rules are parsed
//! again, before anything else happens.
//! 4. Summoned minions enter the grid. In the same place, before the dead leave: a
//! minion that a minion's ability summoned (`Summoned` events with a `by`), whose card
//! has a noun, becomes an object of that noun on its summoner's tile. One summoned by
//! a spell, or with no noun, is in play but not on the grid.
//!
//! One id space: a minion's id is its grid object's id. Every other card (a hero, a
//! card in hand) gets an id from the same counter that no object will have.
@@ -338,13 +342,13 @@ impl CardSide {
self.sequence(level, Action::Destroy { targets: minions });
}
/// Run one Sequence in Part I, then seam 3: apply its grid changes and reparse.
/// Run one Sequence in Part I, then seams 3 and 4: apply its grid changes and reparse.
fn sequence(&mut self, level: &mut Level, action: Action) {
let from = self.world.log().len();
run_sequence(&mut self.world, action);
let events = self.world.log()[from..].to_vec();
// Text first, while every minion that wrote it is still on its tile. A minion
// with no id (summoned by this Sequence) is not on the grid: nowhere to write.
// with no id (summoned by this Sequence) is not on the grid yet: nowhere to write.
for event in &events {
if let Event::TextSpawned { source, word } = event {
let tile = self
@@ -358,6 +362,27 @@ impl CardSide {
}
}
}
// Seam 4: summoned minions, in summon order, on their summoner's tile. The dead are
// still on the grid here, so a Deathrattle's summoner is found where it died; a
// minion summoned earlier in the list is already placed, so it can be a summoner too.
for event in &events {
let Event::Summoned { id, by: Some(by) } = *event else {
continue;
};
let card = self.world.card_name(id).to_string();
if self.ids.contains_key(&id) || self.world.card(&card).def.noun.is_none() {
continue;
}
let tile = self
.ids
.get(&by)
.and_then(|&object| level.board().find(object))
.map(|o| (o.x, o.y));
if let Some((x, y)) = tile {
let object = level.board_mut().place(self.noun_of(&card), x, y);
self.link(id, object);
}
}
for event in &events {
if let Event::Destroyed { id, .. } = event {
if let Some(&object) = self.ids.get(id) {