Files
systemic-game-design-course/docs/HANDOFF.md
T
Lucas WintherandClaude Fable 5.2 b6cbacbd42
CI / Tool tests and sync-check (push) Successful in 13s
CI / Verify references (all modules, Rust and TS) (push) Successful in 2m1s
CI / Verify Expansion Drop solutions (all modules, Rust and TS) (push) Successful in 1m59s
CI / Coupling audit on references (push) Successful in 6s
CI / Differential fuzzing of the Rust and TS references (300 seeds) (push) Successful in 1m0s
CI / Mutation check (tools/mutants.json against the highest TS reference) (push) Successful in 41s
CI / Verify the SpellGrid level proofs (solver) (push) Successful in 6m13s
CI / Site build (push) Successful in 13s
CI / Naive engine tests (Module 0) (push) Successful in 20s
docs: handoff (rulings, corrections, open items and deferred minors after the v2 extensions)
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 18:28:58 +01:00

9.9 KiB
Raw Blame History

Handoff: state of the course after the v2 extensions

Written 2026-11-11. This records what the two revision rounds changed, the decisions made along the way that the spec and plan do not show, and what is still open. The code, lessons and commit history are the record of what exists; this file is the record of why and what next.

Where things stand

main is at 85959ba and contains everything below. Two branches fed it and still exist on the remote:

  • revise/edge-cases-and-sources (round 1): bug fixes, ordering tests, fact-checking, a "Stress it" beat in every lesson, the closing module, the level solver.
  • extend/course-v2 (round 2, built on round 1): Modules 11 and 12, engine extensions in Modules 2, 4, 5, 9 and 10, seven new levels, new tools, the playable site page, three CI jobs.

Design documents:

  • docs/superpowers/specs/2026-11-11-course-v2-extensions-design.md (spec)
  • docs/superpowers/plans/2026-11-11-course-v2-extensions.md (plan)

The code departs from the spec and plan text in a few places. Those documents were not updated afterwards. Where they disagree with the code, the code is right; see "Rulings" below.

Verification at the time of the merge: npm run check ok; 93 tool tests and 13 site tests pass; verify-references and --expansion ok for Modules 01–12; coupling audit ok; all 10 level proofs hold; all 13 mutants killed; the Rust and TypeScript tracks agree over the fuzz seeds; the site builds (20 pages).

Round 1: what was found

Three bugs in the reference engines, all with every test green at the time:

  • Deathrattle summon position. The Death Event remembered a board index, which went stale when a minion further left died in the same step. It now remembers the surviving right-hand neighbour by id.
  • Negated conversion lost to a group noun. ALL IS ROCK plus BABA IS NOT ROCK turned Baba into a rock.
  • A pulled YOU object moved twice. A key that was YOU and PULL behind Baba was dragged one tile, then took its own step.

Six ordering decisions that no test pinned were found by mutating the engines' order lists: two turn-phase reorderings, a dropped final parse, two hazard reorderings, and a live aura scan in place of the snapshot. Each now fails a named test.

Fact-check results:

  • Two Branns: the lesson said the sources do not say how they combine. The wiki does ("Brann's effect does not stack with itself"). Fixed.
  • The rulebook's Acolyte of Pain example predates the 2023 change from 3 to 4 Health (Patch 27.2.0.183876). The card data was right; the example is flagged as stale.
  • HearthSim's mirror of the game's tag names has EXTRA_DEATHRATTLES_BASE next to EXTRA_DEATHRATTLES_ADDITIONAL, which looks like the same split as Module 4's stacking field. The lesson presents this as a reading of the names, not a documented rule.
  • Level 1 cannot be solved without DEATHRATTLE IS DOUBLE, and Level 3 has no solution on the naive-timing engine (both by exhaustive search).

Decisions made in round 1:

  • Module 9 requires the Module 7 Expansion Drop (ALL), the way Module 4 already requires earlier drops. Without it the conversion bug has no test.
  • An object steps at most once per move, and a stepped object does not block a later mover. This keeps stacked YOU AND PUSH objects together. It is the course's rule; no source covers it.
  • Two dying neighbours summon their tokens in order of play, even if that reverses their board order. Unsourced, and labelled so.
  • Module 9 went from 8 tests to 14.
  • "Stress it" is a new optional lesson beat.

Round 2: where each idea landed

Idea Result
Differential fuzzing of the two tracks tools/diff-tracks.mjs. No divergence in 5,000 seeds. Three real track differences were found by reading side by side and fixed: the TypeScript registerAssembled, and two CLI parsing differences.
Replacement effects Module 4: DAMAGE_STEPS = [redirect, armor], the damageRedirect aura, the ArmorLost event, the card Bolf Ramshield.
Nesting guard and Death Phase cap Module 5: a resolving stack in World; DEATH_PHASE_LIMIT = 64.
Well-formed Assemble Module 5: wellFormed / well_formed.
Conditions Module 11: NOUN ON NOUN IS …, evaluated per object at use; NEAR is the drop.
Fourth SpellGrid seam Module 10: a summoned minion with a noun enters the grid (Summoned.by); cards Clay Golem and Clay Shard.
Combat and turns Module 12: orderAttack, canAttack, endTurn, Taunt, defender-first as a queue priority; Bonechewer Brawler is the drop.
More levels, solver-verified Levels l4–l10 with course/10-spellgrid/levels/proofs.json and tools/verify-levels.mjs.
Log tree printer logTree() / log_tree() from Module 2; both SpellGrid CLIs print it.
Designer capstone course/10-spellgrid/designer-capstone.md, designer-example/, tools/run-scenario.mjs.
CI Three new jobs (diff-tracks, mutation-check, verify-levels). Never run on a real runner; see "Open items".
Interactive site /play/ runs the Module 12 TypeScript reference unmodified in the browser.
Undo undo() on the grid engine from Module 9. SpellGrid deliberately reports it as not supported.
Solver position key Age-aware, via stackAt.
Leftovers from round 1 The Lesson 8 quote and the count > 1 summon order are fixed.

The closing module, "Break your own engine", was renumbered from 11 to 13.

Rulings

Decisions made during round 2 that the spec and plan do not record, each with what it costs if it was the wrong call.

  1. Worked on a branch in the main checkout, not a separate worktree. No lasting cost.
  2. The CI jobs were validated by running their commands locally. There is no runner. A workflow syntax error would only show on a real runner, and CI pins Rust 1.85.1 where the local runs used 1.99.
  3. The plan gave tests as scenario prose, not code in two languages. Reviewers had to check tests against scenarios more carefully.
  4. The redirect test uses Armorsmith's reaction as "the minion's damage trigger fires", since Bolf has no damage trigger.
  5. wellFormed is stricter than the spec's wording. It also rejects an effect after an aura or keyword. A future card with a keyword between trigger and effect would be rejected.
  6. Summoned.by is set in World.enter from the innermost resolving trigger, not threaded through the queue. A future summon outside a trigger block would have no summoner.
  7. The fuzzer's generator was extended for conditions in the Module 11 task, outside that task's file list. Earlier modules' cases are unchanged.
  8. The attack action is orderAttack / order_attack, not attack. attack(id) has returned a character's Attack since Module 2. The spec and plan say attack.
  9. No course test for closing a trigger block on error. It is covered by reference-only tests. Learners' engines are not asked to do it.

Where the course knowingly differs from the real games

Both are stated in the lessons as the course's choice.

  • Nesting guard. The course drops the skipped activations. Hearthstone's rulebook says: "Instead it works the skipped times as a compensation after all other consequences of it are resolved."
  • ON ALL. The course means "any one other object". The Baba Is You wiki says "ALL in the noun2 part requires one of every object in ALL".

Corrections made during round 2

Claims that were wrong in a draft and fixed before the merge. They are listed because each is easy to reintroduce.

  • Lesson 2: two Bombs do not "hit each other once each"; the correct count is six activations.
  • Lesson 10: level 2 has 18,601 reachable positions, not "some 31,000".
  • Lesson 11: a wiki page does cover ON ALL (see above).
  • Lesson 12: several Hearthstone claims were not in the research log; they were re-fetched and logged in docs/research/hearthstone.md.
  • Module 13: the mutation table's rows follow tools/mutants.json, and the survivor count is five in both places it appears.

The final whole-branch review found three important issues, all fixed in 85959ba:

  • An engine error in the middle of a Sequence left a corrupt game in run-scenario and the TypeScript CLI. It is now fatal ("The Sequence did not finish, so the game cannot go on."), and trigger blocks close on error in Modules 2–12 (try/finally; catch_unwind in Rust).
  • The nesting guard was presented as Hearthstone's rule. It is now labelled as the course's.
  • The Node floor is 22.15, because the site's game test needs it.

Changes to the learner contract

  • Module 5 has a new test, test_05_assemble_never_rewrites_an_earlier_card.
  • Module 9 requires the Module 7 drop (ALL).
  • package.json engines is >=22.15.

Open items

  • CI has never run on a real runner. The Gitea workflow needs the ubuntu-latest label and access to GitHub for the actions it uses.
  • The deploy subdomain is not set (COURSE_HOST in deploy/.env).
  • The owner's read-through of the course is still to come.

Known minor issues, deliberately deferred

  • Level 9 is the weakest puzzle: its chain push is forced, not a decision.
  • The solver, verify-levels, play-spellgrid and run-scenario stage the Module 10 engine, while the site, fuzzer and mutation check use Module 12. It is the same game for the shipped levels, and the lessons say so.
  • The differential fuzzer rarely puts an Assembled minion on the grid and never draws Bolf.
  • Play page: focus is lost after a tile click, and arrow keys are captured page-wide.
  • No tests for: SpellGrid undo erroring, a conditional rule giving no aura, third-party defender priority, two Taunts.
  • site/scripts/sync-content.mjs duplicates cardDirs from tools/lib.mjs.
  • Lesson 11's hazard-board bullet has nested parentheses.
  • From v1: the coupling audit flags PascalCase names and string-literal noun words; the chooser type in course/harness.md does not match the code; the registerCards test is weak.