fix(reference): Death Events remember a right-hand neighbour, not a board index

A Deathrattle summon used the dying minion's board index from before the
Death Creation Step removed anything. That index is stale when a minion to
its left dies in the same step, so the token landed one place too far right.

Each Death Event now records `before`: the nearest minion to the right that
is not dying in the same step. The position handed to the effect context is
resolved when the trigger resolves: the current index of `before`, or the
right end. Applied to the Rust and TS references of Modules 3-10.

Module 3 gains two tests, documented in scenarios.md:
test_03_summon_position_after_simultaneous_deaths (fails before the fix) and
test_03_simultaneous_summons_keep_board_order.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Fable 5.2 committed 2026-11-11 09:15:48 +01:00
1 parent 638549161f
commit 429137a6ef
79 files changed
+808 -236

No files matched your search

@@ -110,8 +110,8 @@ pub struct Ctx<'a> {
pub source: Option<EntityId>,
/// The chosen target, if any.
pub target: Option<EntityId>,
/// The source's board position. For a Deathrattle, the source has left play,
/// and this is the position its Death Event remembered.
/// The source's board position. For a Deathrattle, the source has left play, and
/// this is where it stood: immediately left of the minion its Death Event remembers.
pub position: Option<usize>,
}
@@ -270,7 +270,9 @@ fn react(world: &mut World, events: &[Event]) {
on: trigger.on,
});
let position = match *event {
Event::Destroyed { id, position } if id == source => Some(position),
Event::Destroyed { id, before } if id == source => {
Some(position_left_of(world, world.owner_of(source), before))
}
_ => world.board_index(source),
};
let owner = world.owner_of(source);
@@ -279,6 +281,16 @@ fn react(world: &mut World, events: &[Event]) {
}
}
/// The board index, right now, immediately left of `before` on `owner`'s board; the
/// right end if `before` is `None` or no longer there. A Death Event remembers the
/// minion, not an index, so this still holds after other minions have left or arrived.
fn position_left_of(world: &World, owner: PlayerId, before: Option<EntityId>) -> usize {
let board = world.zone(owner, Zone::Board);
before
.and_then(|before| board.iter().position(|&id| id == before))
.unwrap_or(board.len())
}
/// Who may respond to `event`, in order of play: every minion in play and, for a
/// Death Event, the minion that died. It has left play, but its Deathrattle still answers.
fn listeners(world: &World, event: &Event) -> Vec<EntityId> {