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

193 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.