From b6cbacbd42dd6510483cc2fe94781afa25218bcd Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Wed, 11 Nov 2026 18:28:58 +0100 Subject: [PATCH] docs: handoff (rulings, corrections, open items and deferred minors after the v2 extensions) Co-Authored-By: Claude Fable 5.2 --- docs/HANDOFF.md | 192 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 docs/HANDOFF.md diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md new file mode 100644 index 0000000..f45de99 --- /dev/null +++ b/docs/HANDOFF.md @@ -0,0 +1,192 @@ +# 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.