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>
This commit is contained in:
Lucas WintherandClaude Opus 5.5 committed 2026-11-11 10:23:59 +01:00
1 parent 06a1b34c5d
commit 97c64ebee8
86 files changed
+1375 -55

No files matched your search

@@ -1,4 +1,5 @@
// What happened, in order. The engine records an event each time it changes the game.
// The world also keeps the nesting depth each event was recorded at (`World.logDepths`).
import type { TriggerOn } from "./card";
import type { EntityId, PlayerId } from "./world";
@@ -192,7 +192,10 @@ function react(world: World, events: Event[]): void {
event.type === "Destroyed" && event.id === source
? positionLeftOf(world, world.ownerOf(source), event.before)
: world.boardIndex(source);
// The trigger's consequences are one level deeper in the log tree.
world.openBlock();
runEffects(world, trigger.effects, new Ctx(world, world.ownerOf(source), source, undefined, position));
world.closeBlock();
}
}
}
@@ -46,6 +46,10 @@ export class World {
private readonly sides: [Side, Side];
private nextOrderOfPlay = 0;
private events: Event[] = [];
/** The nesting depth at which each event in `events` was recorded, index for index. */
private depths: number[] = [];
/** How many triggers are resolving right now, one inside another. */
private depth = 0;
/** The auras in force as of the last Aura Update. Changes only there (see `aura.ts`). */
private auras: ActiveAura[] = [];
/** Until a chooser is set, take the first option. */
@@ -212,10 +216,26 @@ export class World {
return this.events;
}
/** The nesting depth of each event in `log()`, index for index: 0 for the outermost action's own events. */
logDepths(): readonly number[] {
return this.depths;
}
// ---- Changes: each one records its event ----
record(event: Event): void {
this.events.push(event);
this.depths.push(this.depth);
}
/** A trigger starts resolving: the events it causes are recorded one level deeper. */
openBlock(): void {
this.depth++;
}
/** The trigger that last opened a block has finished resolving. */
closeBlock(): void {
this.depth--;
}
/** Add a card to the library, or replace the card with the same name. */
@@ -248,6 +268,7 @@ export class World {
clearLog(): void {
this.events = [];
this.depths = [];
}
damage(target: EntityId, amount: number): void {