A trigger is never queued while that same (minion, trigger) is resolving;
more than DEATH_PHASE_LIMIT = 64 Death Phases is an error ('Death Phase
limit'); Assemble refuses and never offers ill-formed text (wellFormed /
well_formed); summon count > 1 places tokens in summon order. Modules 05-10,
both tracks. Four new Module 5 tests, a tools test that every shipped card
is well-formed, lessons 2, 3, 5, 13 and harness.md updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Applying a Damage to one target now runs DAMAGE_STEPS = [redirect, armor]
first. A damageRedirect aura (Bolf Ramshield, fetched from the wiki) sends
damage meant for its owner's hero to the oldest such minion; Armor absorbs
damage to a hero before Health, logged as the new ArmorLost{player, amount}
just before a full-amount Damaged. Four new Module 4 tests; propagated to the
references of Modules 4-10 in both tracks; card schema, catalog, card JSON,
harness.md, Lessons 2, 4 and 5 updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
- Rename course/11-break-your-engine to 13-break-your-engine and update every
link and mention; start-module explains that Module 13 has no tests to start.
- tools/diff-tracks.mjs (npm run diff:tracks): stages both references of a
module, generates a seeded random SpellGrid level and script per seed, pipes
it to the Rust and TypeScript CLIs and diffs their output; crashes on one or
both sides fail. stagePlay takes a module number.
- register_assembled: the TypeScript track compared only the text of an
existing Assembled card, so a base re-registered with a new cost (or a
different split) silently rewrote the earlier card. It now compares every
field the engine reads, as Rust does (Modules 5-10).
New test_05_assemble_never_rewrites_an_earlier_card.
- SpellGrid CLIs: coordinates and chooser numbers are 1-9 plain digits in both
tracks (Rust accepted signs, TypeScript accepted "+2", "0x2", "1e0"); Rust
quotes card names verbatim instead of with Debug escapes; Rust joins the
minion list with "; " instead of rewriting ", ". Two new CLI tests per track.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- lessons 8 and 9 updated for the once-per-move rule, per-object negated
conversion and the new ordering tests
- tools/solve-spellgrid.mjs: breadth-first search over a SpellGrid level
with the TypeScript reference; --never-rule and --naive
- README, harness.md and the mentor skill know the new beat and module
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Mutation runs on the references showed six surviving mutants: a live aura
scan, two turn-phase reorderings, a dropped final parse, and two hazard
reorderings. Each now fails a named test.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Bug 1 (modules 08-10): a YOU object pulled by an earlier mover took a second step of its own. Level movement now keeps the ids that stepped in a committed plan this phase; such a YOU object starts no move, and Planner try_move answers no for it. Regression: test_08x_pulled_you_moves_once (Module 8 Expansion Drop).
Bug 2 (modules 09-10): convert honoured negation only per rule, so ALL IS ROCK beat BABA IS NOT ROCK. A target Y is now skipped for an object if any active negated rule IS NOT Y names it. Module 9 and 10 references (and their drop solutions) now carry ALL, the Module 7 Expansion Drop. Regression: test_09_negated_conversion_beats_group.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
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>
- verify-references --expansion overlays expansion/solution on each reference and runs the
cumulative tests plus the drop test; refuses a solution that touches src/engine/
- CI job verify-expansions
- README: workspace-template + git init step, tool list, repo disclaimer link
- Cargo.lock for every Rust reference (staged with the reference, pins serde for rustc 1.85.1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lesson 10 and both M10 reference READMEs now give the real recipe
(node tools/play-spellgrid.mjs <rust|ts> [l1|l2|l3]); root script play:spellgrid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-verify Teikari article quotes and GDC speaker, soften unsourced
claims (lessons 01, 06, 07, 10), add course-simplification bullets to
lesson 07's differs box, qualify OPEN obstruction wording in research,
and add tools/check-links.mjs (report-only) with npm run check:links.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- SpellGrid cards move to course/10-spellgrid/cards; start-module and
verify-references stage course/cards plus module-local cards up to the module,
so M5-M9 workspaces get exactly the pre-M10 card set.
- cast() rejects a chosen-target spell without a target (checkCast/checkPlay);
new test_10_targeted_spell_needs_target.
- Rust CLI pre-validates instead of swapping the global panic hook.
- Catalog: CRIER noun; DEATHRATTLE/BATTLECRY note on lexicon vs catalog role.
- Lesson: design note on mid-movement reparse.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SpellGrid capstone: Part I's card engine and Part II's grid engine joined at
three seams (rules -> aura data, hazard victims -> one destroy Sequence,
Sequence end -> write text, remove the dead, reparse). Three levels with
scripted solutions, a naive-timing test via a harness-only hook that
reverses the END_OF_PHASE list, CLIs in both tracks, the BATTLECRY IS DOUBLE
Expansion Drop, SpellGrid cards and words in course/cards and the catalog,
level schema cards/hand, and tools staging/copying of module levels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Module 9 lesson, scenarios, tests (8, both tracks), reference engines
(TURN phase list, conversion, content-registered hazards, win/lost),
WEAK Expansion Drop, harness.md M9 semantics, research notes (WEAK page,
baba-is-auto Game::MovePlayer, X IS NOT X placement reading).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Movement planned in full, then committed: a Planner builds a plan
(moving and destroyed ids) by running content's movement responses
(OPEN/SHUT, PUSH, STOP) for every step, rolling back failed branches.
Edge of level blocks; YOU objects move furthest-ahead first; rules are
reparsed once after movement. Harness: move/move_dir(Dir), posOf,
kindsAt. Expansion Drop: PULL as one word plus one content response.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds IS NOT to the Part II parser and a resolution pass (negation, then
identity lock) that turns parsed rules into the rules in force; harness
gains activeRules() and hasProperty(). A leading NOT now breaks the
sentence instead of being ignored (carried from M6 review). Nouns say
which objects they name ("selects"), so the ALL Expansion Drop is one
line of data. Corrects lesson 06's NOT wording; harness.md M7 details.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
First Part II module. The cumulative reference adds a second engine
(src/engine/puzzle: grid, lexer, parser) beside Part I's; nouns and
properties are data in src/content/words.json, and the engine names only
IS/AND/NOT/TEXT and the base rule TEXT IS PUSH. Rules are recompiled from
the board on every change; rules() is a sorted set. Adds the M6 scenarios
and tests (rows, columns, shared IS, AND distribution, stacked text, base
rule), the HOT word as a data-only Expansion Drop, the lesson with the
Part II bridge, and the Rust LevelDef shape in harness.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cards become JSON data in course/cards (one file per card, validated
against card.schema.json and the catalog); the M5 references load them
via registerCards and interpret effects, replacing content closures.
Adds assemble(base, donor, half), the assemble/buffHand effects with an
injected chooser, Fleshshape as the data-only Expansion Drop, and stages
course/cards in verify-references and start-module.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Trigger multipliers as aura data on cards, snapshotted by an Aura Update
(Other) after each Death Creation Step and on summon, and applied by one
generic routine (activations) when each queued trigger starts. Baron
Rivendare is content only (stacking "max"); Brann Bronzebeard is the
content-only Expansion Drop. Adds play (now required) and Battlecry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deferred death: damage and destroy only mark minions; the Death Creation
Step removes them when the outermost Phase ends, and Death Phases resolve
Deathrattles in order of play until no new deaths occur. Deathrattle
summons use the board position the Death Event remembered. Harness adds
destroy(target). Expansion Drop: Twisting Nether as content only.
test_02_mortally_wounded_still_triggers no longer asserts the Acolyte is
still on the board: from Module 3 on it leaves at the end of the Sequence,
and verify-references runs Module 2's tests against the Module 3 reference.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
verify-references ran a bare `npx vitest run` in the staged reference, which
fetched an unrelated vitest that could not load the reference config. It now
installs the reference's devDependencies first. sync-check also checks each
module's expansion/tests against ### headings in expansion/README.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Naive engine in Rust and TypeScript (immediate damage, inline triggers,
recursive Deathrattles, cards defined in the engine) with
test_00_naive_runs and the expected-fail test_00_real_rules_whirlwind_order,
plus the Module 0 lesson.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>