Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
21 KiB
Course v2 Extensions Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Close the course's admitted gaps, add two modules and one seam, and add the tools, levels and site page described in the spec.
Architecture: Engine changes are made in the cumulative Rust and TypeScript references, earliest module first, so later tasks copy finished engines. New tools reuse tools/play-spellgrid.mjs's staging. New modules 11 and 12 are full modules; the old Module 11 becomes 13.
Tech Stack: Rust (cargo, serde), TypeScript (vitest, tsx), Node 22+ tooling (node --test), Astro/Starlight site.
Spec: docs/superpowers/specs/2026-11-11-course-v2-extensions-design.md. Every task implements the spec sections it names; the spec's text is the requirement and this plan adds order, files and steps.
Global Constraints
Spec §1, all eight points, bind every task. In short: both tracks, every cumulative reference copy; test names identical across Rust, TS and scenarios; engine names no card or noun; the full verification list of §1.4 passes before reporting; lessons follow §1.5 (fetched sources, exact quotes of at most 125 characters, course choices labelled, counts and quoted code match the files); harness.md, catalog.json, README.md and tool tests travel with the code; commit on extend/course-v2, never push, commit trailer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>; no subagents.
Review Focus
- A change made in one track or one module copy and missed in another: the differential fuzzer and
verify-referencesmust both run on the final tree of every engine task. - A random level or script that crashes a CLI (out-of-range coordinates, a card not in hand, a chooser asked with no options): both tracks must print the same error line and carry on; Task 1 tests this.
- An existing learner test that silently changes meaning: Armor absorption (Task 3) and the nesting guard (Task 4) must not alter any existing test's expected log; if one would, stop and report.
- Undo after a turn that destroyed objects and created others: ids must not be reused or renumbered; Task 5's test asserts ids.
- A lesson that quotes a source nobody fetched, or a test count that no longer matches: reviewers check every new quotation has a URL and every count against the test files.
Task 1: Rename Module 11 to 13; differential fuzzer; fix what it finds
Implements spec §4 (first paragraph, rename only) and §2.1.
Files:
- Move:
course/11-break-your-engine/→course/13-break-your-engine/(title becomes "Module 13"); update every link and mention incourse/**/lesson.md,README.md,mentor/SKILL.md. - Create:
tools/diff-tracks.mjs,tools/test/diff-tracks.test.mjs - Modify:
tools/start-module.mjs(clear message for a module with no tests),package.json(scriptdiff:tracks),README.md - Modify as needed: references and tests of any module where a divergence is fixed
Interfaces:
-
Produces:
node tools/diff-tracks.mjs [--seeds N] [--seed S] [--module NN] [--dir D] [--keep], exit 0 on agreement. ExportsparseArgs(argv),generateCase(seed, vocab) -> { level, script }(pure, deterministic) andrng(seed)for tests. Later tasks run it as verification. -
Step 1:
git mvthe module, fix links and the H1, runnode tools/check-links.mjs(relative links only matter) andnpm run test:site. -
Step 2: Write
tools/test/diff-tracks.test.mjs:generateCaseis deterministic for a seed and differs across seeds; every generated level validates againstcourse/schema/level.schema.json; every script line matches the CLI grammar (w|a|s|d,c <card> [x y], a chooser answer);parseArgsdefaults. Run: fails (no module). -
Step 3: Implement the tool per §2.1. Compare the two CLIs' output formats first; where the Rust and TS CLIs print the same state differently, fix the CLI (both must print identical text for identical state) and add a CLI test.
-
Step 4: Run
node tools/diff-tracks.mjs --seeds 500. For each distinct divergence: minimise by hand to a scenario, decide the right behaviour (spec §2.1), write the regression test in both tracks andscenarios.mdin the module that owns the behaviour, fix every affected reference copy. Repeat until 500 seeds agree. Record each divergence and its ruling in the report. -
Step 5: Full verification (§1.4). Commit.
Task 2: Log tree
Implements spec §3.1.
Files:
- Modify:
engine/event.*,engine/queue.*,engine/world.*,src/harness.*in the references of Modules 02–10, both tracks;cli.*in Module 10 - Modify:
course/02-action-queue/tests/*,scenarios.md,lesson.md;course/03-death-step/tests/*,scenarios.md,lesson.md;course/harness.md; workspace templates' harness stubs if they list functions
Interfaces:
-
Produces: harness
logTree(): string(TS) /log_tree(&self) -> String(Rust), onGamefrom Module 2 and onSpellGridin Module 10. Format per §3.1; event text exactly asscenarios.mdwrites events. Tasks 10 and 11 consume it. -
Step 1: Write
test_02_log_tree_shows_nesting(Echo, Armorsmith, Reactor;dealDamage(armorsmith, 1)) asserting the exact string:Damagedat depth 0; Echo'sTriggeredat 0 with its heroDamagedat 1 and Reactor'sTriggeredat 1 withStatsChangedat 2; Armorsmith'sTriggeredat 0 withArmorGainedat 1. Writetest_03_log_tree_death_phase(the chained Golem/Ghoul scenario):DestroyedandDeathPhaseat 0, each Deathrattle'sTriggeredat 0, its effects at 1. Add both to scenarios. Run: fail. -
Step 2: Add depth to engine events and
logTreeto the harness, Module 2 first, then propagate to every later copy. -
Step 3: Module 10 CLIs print
Log:and the tree of the last command's Sequences (both tracks, identical text); update the CLI tests. -
Step 4: Update Lesson 2 (Spec table, test list and count, Stress it question 3), Lesson 3 (test list and count),
harness.md. -
Step 5: Full verification including
diff-tracks --seeds 200. Commit.
Task 3: Replacement effects in Module 4
Implements spec §3.2.
Files:
- Modify:
engine/aura.*,engine/queue.*,engine/card.*,engine/event.*,src/harness.*, content, in the references of Modules 04–10, both tracks - Modify:
course/schema/card.schema.json,course/catalog.json,course/cards/(the redirect card, from Module 5's data format on),course/04-interceptors/tests/*,scenarios.md,lesson.md,course/02-action-queue/lesson.md,course/harness.md,tools/test/schema.test.mjsif it enumerates modifier types
Interfaces:
-
Produces: aura data
{ type: "damageRedirect", from: "friendlyHero", to: "self" };GameEventArmorLost{player, amount}; engine listDAMAGE_STEPS = [redirect, armor](TSreadonlyarray / Rustconst). Task 9 (combat) and Task 10 (mutant) consume these names. -
Step 1: Fetch the Bolf Ramshield page; settle the card per §3.2 and record the quotation.
-
Step 2: Write the four tests of §3.2 with exact logs, and their scenarios. Run: fail.
-
Step 3: Implement in Module 4 (card as code there, as that module's cards are), then propagate; from Module 5 the card is a JSON file and the interpreter needs no new effect.
-
Step 4: Confirm no existing test's expected log changed. If one must, stop and report (Review Focus 3).
-
Step 5: Lesson 4: new section "A second interceptor: replacement" after Name it, test list and count, further reading; Lesson 2: the "How the real game differs" sentence about Armor and Stress it question 4.
-
Step 6: Full verification including
diff-tracks. Commit.
Task 4: Robustness in Module 5
Implements spec §3.3.
Files:
- Modify:
engine/queue.*,engine/assemble.*,engine/effect.*,engine/card.*,src/harness.*in the references of Modules 05–10, both tracks - Modify:
course/05-assemble/tests/*,scenarios.md,lesson.md; Stress it answers incourse/02-action-queue/lesson.md,course/03-death-step/lesson.md;course/harness.md - Create: a test in
tools/test/that every shipped card is well-formed (reimplement the rule in the tool, in JS, against the JSON)
Interfaces:
-
Produces: engine
wellFormed/well_formed; error text containingDeath Phase limit;DEATH_PHASE_LIMIT = 64. Nothing later depends on these names except lessons. -
Step 1: Write
test_05_trigger_does_not_nest_in_itself,test_05_death_phase_limit,test_05_assemble_rejects_ill_formed_text,test_05_multi_summon_order(cards registered inline, astest_05_own_aura_misses_own_battlecrydoes) and scenarios. For the Bomb test, work the expectedTriggeredsequence out by hand in the scenario text before running anything. Run: fail (the first two crash or hang: use a timeout). -
Step 2: Implement the four changes in Module 5, propagate.
-
Step 3: Confirm Module 10's Graft level and every existing Assemble test still offer the options their tests expect.
-
Step 4: Lessons 2, 3, 5 per §3.3; test counts.
-
Step 5: Full verification including
diff-tracks. Commit.
Task 5: Undo and stackAt
Implements spec §3.4, the stackAt half of §2.3, and one leftover.
Files:
- Modify:
engine/puzzle/grid.*,engine/puzzle/level.*,src/harness.*in the references of Modules 08–10 (stackAt) and 09–10 (undo), both tracks - Modify:
course/08-push-chains/tests/*,scenarios.md,lesson.md;course/09-turn-pipeline/tests/*,scenarios.md,lesson.md;course/harness.md - Fix:
course/08-push-chains/lesson.mdquotestry_move/tryMovewith an inline comment the reference lacks: make the quote match the reference exactly
Interfaces:
-
Produces:
stackAt(x, y): string[]/stack_at(&self, x, y) -> Vec<String>(kinds on the tile, oldest first) onBabafrom Module 8 and onSpellGrid;undo(): boolean/undo(&mut self) -> boolonBabafrom Module 9; SpellGrid'sundoerrors withnot supported. Task 7's solver consumesstackAt. -
Step 1: Write
test_08_stack_is_age_orderedandtest_09_undo_restores_everything(§3.4) andtest_09_undo_walks_back_and_stops(two steps, three undos, third returns false). Scenarios. Run: fail. -
Step 2: Implement; propagate.
-
Step 3: Lessons 8 and 9 (Spec tables, test lists and counts; Lesson 9's Stress it question 3 now compares the learner's undo with the reference's snapshot contents),
harness.md, the Lesson 8 quote fix. -
Step 4: Full verification including
diff-tracks. Commit.
Task 6: Summoned minions enter the grid
Implements spec §3.5.
Files:
- Modify:
engine/spellgrid.*,engine/event.*,engine/queue.*(the summoner on the internal event),src/harness.*,src/content/words.jsonin the Module 10 reference, both tracks - Create:
course/10-spellgrid/cards/clay-golem.json,clay-shard.json; catalog entries; wordsGOLEM,SHARD - Modify:
course/10-spellgrid/tests/*,scenarios.md,lesson.md,course/harness.md
Interfaces:
-
Produces: cards
Clay Golem(nounGOLEM) andClay Shard(nounSHARD) available to Task 7's levels. -
Step 1: Write
test_10_summoned_minion_enters_gridandtest_10_summon_without_noun_stays_off_gridwith scenarios. Run: fail. -
Step 2: Implement the seam; the order inside "a Sequence ends" is: write spawned text, place summoned minions, remove the dead, reparse, sync rule auras.
-
Step 3: Lesson 10: three seams become four everywhere they are counted (Spec, Build diagram and hints, Name it, "How the real game differs", Stress it question 3), card table, test list.
-
Step 4: Full verification including
diff-tracks(the generator now sees the new cards). Commit.
Task 7: Solver upgrade, level proofs, seven new levels
Implements spec §2.3 and §3.6.
Files:
- Modify:
tools/solver/solve.ts(age-aware key viastackAt),tools/solve-spellgrid.mjsif needed - Create:
tools/verify-levels.mjs,tools/test/verify-levels.test.mjs,course/10-spellgrid/levels/proofs.json,course/10-spellgrid/levels/l4-….json…l10-….json - Modify:
course/10-spellgrid/tests/*,scenarios.md,lesson.md,course/schema/level.schema.jsononly if a level needs nothing new (it should not),package.json(scriptverify:levels),README.md, both CLIs' tests if they enumerate levels,tools/test/play-spellgrid.test.mjs(it lists level names)
Interfaces:
-
Produces:
node tools/verify-levels.mjsexit 0 when every proof inproofs.jsonholds;proofs.jsonentries{ "level": "l4-…", "shortest": N, "neverRule": "X IS Y" }or"naiveUnsolvable": true. Task 10 adds it to CI; Task 11 lists the levels. -
Step 1: Solver key uses
stackAt; re-run onl1–l3and confirm the same results as recorded in Lesson 10's Stress it question 4 (10 inputs; exhausted without the rule; 4; 6; naive exhausted). Update the numbers in Lessons 10 and 13 if position counts changed. -
Step 2: Write
verify-levelsand its test (arg parsing; a fixture proof that must fail is reported as failing). Add proofs forl1–l3. -
Step 3: Design
l4–l10, one per idea in §3.6. For each: write the level, run the solver with--alland read every solution, run it with the idea forbidden, and iterate on the layout until the proof holds and no cook exists. Keep each level's intended solution and proof in the report. -
Step 4:
test_10_level_N_solutionfor N = 4…10 in both tracks, scripted from the solver's shortest solution, with one assertion mid-script that shows the idea happening; scenarios; Lesson 10's level list (ideas, no solutions) and test count. -
Step 5: Full verification,
node tools/verify-levels.mjs,diff-tracks. Commit.
Task 8: Module 11, Conditions
Implements spec §4 (module shape) and §4.1.
Files:
- Create:
course/11-conditions/withlesson.md,scenarios.md,tests/{rust,ts}/test_11.*,reference/{rust,ts}/(copied from Module 10's final reference, then changed),expansion/(README.md,tests/{rust,ts}/test_11x.*,solution/withwords.jsonand the content file that registersNEAR) - Modify:
course/harness.md,course/catalog.json(wordsON,NEARwith aconditionrole),tools/coupling-audit.mjsonly if its word-role handling needs the new role,tools/lib.mjs/verify-references.mjsonly if a module after 10 needs staging changes (levels and SpellGrid cards must still be staged for 11+)
Interfaces:
-
Consumes: Module 10's final reference (Tasks 1–7).
-
Produces: word role
"condition"; content registry of condition functions(level, objectId, nouns) -> bool; rule text formatBABA ON LAVA IS MELT;Rule.conditions. Task 9 copies this reference; Task 10 adds a mutant "conditions evaluated at parse time instead of at use" against it. -
Step 1: Fetch the Baba Is You Wiki pages for
ON,NEARand Order of Operations (the course'sdocs/research/baba-is-you.mdlists how earlier fetches were done); append what was fetched, with quotations, to that research file. -
Step 2: Create the module skeleton and reference copy; make
verify-references 11pass with zero new tests. -
Step 3: Write the tests of §4.1 (at least the seven listed) and scenarios. Run: fail.
-
Step 4: Implement: lexer role, parser, rule conditions, evaluation at use in
hasProperty, conversion, hazards'sharing-based queries and the win check, resolution pairing, rule text. -
Step 5: Expansion Drop
NEAR: test, README scenario, solution;verify-references --expansionpasses and the solution touches no engine file. -
Step 6: Write the lesson (§4.1 theme; all beats; three hint tiers; "Decisions in the reference"; "How the real game differs"; a Stress it section with at least three questions, one of which asks which existing mutant-style change the new tests catch).
-
Step 7:
harness.md, mentor/README mentions of the module list, full verification,diff-tracks --module 11. Commit.
Task 9: Module 12, Combat
Implements spec §4 (module shape) and §4.2.
Files:
- Create:
course/12-combat/with the full module shape; reference copied from Module 11's final reference - Modify:
course/harness.md,course/catalog.json,course/cards/orcourse/12-combat/cards/for any new card (follow how Module 10 scopes its cards so earlier modules never see them),course/01-handles/lesson.mdandcourse/02-action-queue/lesson.md("defender triggers first" is no longer unmodelled: point forward)
Interfaces:
-
Consumes:
DAMAGE_STEPS,ArmorLost(Task 3); Module 11's reference. -
Produces: harness
attack(attacker, defender),canAttack(attacker, defender): string | null,endTurn(player)onGame; a per-queue-entry priority inreact. Task 10's mutant list may add "defender priority removed". -
Step 1: Fetch the Advanced rulebook's combat and end-of-turn passages and the Drop card's page; append to
docs/research/hearthstone.md. -
Step 2: Skeleton and reference copy;
verify-references 12green with no new tests. -
Step 3: Write the tests of §4.2 (at least the six listed) and scenarios. Run: fail.
-
Step 4: Implement: per-target damage amounts, the attack action and its legality, queue priority,
endTurnand theendOfTurnevent. -
Step 5: Expansion Drop: one card file, one test, README; content only.
-
Step 6: Lesson (§4.2 theme; all beats; hints; decisions; differences; Stress it with at least three questions).
-
Step 7:
harness.md, earlier lessons' forward pointers, full verification,diff-tracks --module 12. Commit.
Task 10: Mutation check, scenario runner, designer capstone, CI, closing docs
Implements spec §2.2, §2.4, §2.5, §5.2 and the Module 13 updates.
Files:
- Create:
tools/mutation-check.mjs,tools/mutants.json,tools/test/mutation-check.test.mjs,tools/run-scenario.mjs,tools/scenario/run.ts,tools/test/run-scenario.test.mjs,course/10-spellgrid/designer-capstone.md,course/10-spellgrid/designer-example/(cards, word,scenario.json,output.txt) - Modify:
.gitea/workflows/ci.yml,package.jsonscripts (mutation:check,run:scenario),README.md(tools; Designer path),mentor/SKILL.md(module list, designer capstone),course/13-break-your-engine/lesson.md,course/10-spellgrid/lesson.md(link to the capstone)
Interfaces:
-
Consumes:
logTree(Task 2),verify-levels(Task 7),diff-tracks(Task 1), final references (Task 9). -
Step 1:
mutation-check: tests first (a fixture mutant that must be killed; a stalefindis an error; a surviving mutant exits 1). Implement. Fillmutants.jsonper §2.2 and run it; a mutant that survives gets a test in the owning module (both tracks, scenario) in this task. -
Step 2:
run-scenario: tests first (schema error is readable; merged words appear in the rules; output has the tree). Implement. -
Step 3: Designer capstone document and worked example;
output.txtis the tool's real output, regenerated in a tools test so it cannot go stale. -
Step 4: CI jobs per §2.5; run each job's commands locally and paste the results in the report.
-
Step 5: Module 13: the mutation table matches
mutants.jsonand names the tool; "Two engines, one specification" reports whatdiff-tracksfound in Task 1 and how to run it; "State, undo and replay" and "Solving your own levels" match Tasks 5 and 7; the course outline in README and mentor lists Modules 11–13. -
Step 6: Full verification,
mutation-check,verify-levels,diff-tracks. Commit.
Task 11: Playable SpellGrid on the site
Implements spec §5.1.
Files:
- Modify:
site/scripts/sync-content.mjs(generate the game sources),site/scripts/sync-content.test.mjs,site/package.jsonif a script changes,site/.gitignore(generated directory) - Create:
site/src/game/content.browser.ts(static imports of cards, words, seams, levels, and the reference's hazards and movement),site/src/game/play.ts(DOM glue),site/src/pages/play.astroor a Starlight page embedding it,site/scripts/game.test.mjs - Modify:
site/astro.config.mjs(sidebar entry "Play SpellGrid"),course/10-spellgrid/lesson.md,course/11-conditions/lesson.md,course/12-combat/lesson.md(link),site/README.md
Interfaces:
-
Consumes: the Module 12 TypeScript reference's
src/engineandsrc/harness.tsunmodified (if the harness imports./contentin a way that cannot be redirected by the bundler, the sync step may rewrite that one import line, and nothing else);logTree; all levels. -
Step 1:
site/scripts/game.test.mjs: the generated game module, loaded under Node, playsl3with its scripted inputs towon(). Run: fail. -
Step 2: Sync step and browser content module; test passes.
-
Step 3: The page per §5.1. Build the site, serve
distlocally, and exercise the page in a headless browser if one is available (load, playl3to the win, open the log tree, Undo, switch level, at 375 px and 1280 px, light and dark); if none is available, say so in the report and describe what was checked instead. -
Step 4: Links from the lessons;
site/README.md. -
Step 5:
npm run test:site, site build, full verification. Commit.