Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
9.3 KiB
Handoff — Pure Snake course
Written 2026-11-11 for whoever works on this repository next. It assumes you know nothing about the earlier sessions. Read this page, then docs/contract.md.
State in one paragraph
The course is complete and green. It teaches functional programming by building a purely functional Snake (then Pong, then Conway's Life) in three language tracks: JavaScript (the reference), Scala 3 and F#. Modules 0–13 plus an optional Module 7b exist as text, student stubs, reference solutions, tests and shared fixtures. The work was merged into main as c67d81b (feature commit edd57f0), followed by small commits that added this page and the fixture generators (git log is authoritative); main is pushed to origin (https://gitea.lucaswinther.info/lucasw89/functional-programming-snake). The feature branch audit-and-extensions was merged and deleted. Nothing is in progress.
The one thing nobody has done: play the games by hand. Every check is automated.
Where things are
| Path | What |
|---|---|
README.md |
For students: what the course is, module map, checkpoints. "For maintainers" lists every check script. |
course/ |
The text: 00-setup.md … 13-capstone-life.md, 07b-stack-safe-io.md, method.md, reference.md (all names in three languages). |
<track>/src, <track>/solutions, tests |
Stubs, finished code, tests, for js, scala, fsharp. Same file names in src and solutions. |
fixtures/ |
Shared JSON test cases, read by all three tracks. |
e2e/, scripts/e2e.sh |
Playwright end-to-end tests (maintainers only; the tracks have no dependencies). |
scripts/fixtures/ |
The small scripts that generated the 2026-11-11 fixtures, the bot behind the replay sample, and a README saying how to use them safely. |
scripts/ |
verify.sh, check-stubs.sh, check-progression.sh, typecheck-js.sh, mutate.mjs (+ mutate-allow.txt), smoke.mjs, check-links.mjs, serve.mjs. |
docs/contract.md |
The canonical contract: data shapes, every module's rules, constants, pinned values, fixture inventory, required checks. |
docs/superpowers/specs/ |
2026-10-28-…-design.md (original design), 2026-11-11-audit-pass.md, 2026-11-11-course-extensions.md (twelve items, with the rules every builder followed). |
docs/superpowers/notes/2026-10-29-build-log.md |
Every decision made on the owner's behalf, verification results and open items, in three dated sections. |
docs/superpowers/notes/2026-11-11-session-log.md |
What happened on 2026-11-11, in order, and what was learned about working this way. |
docs/superpowers/plans/2026-10-28-pure-snake-course.md |
The original build plan. Its "contract" section is superseded by docs/contract.md. |
MENTOR.md, AGENTS.md, CLAUDE.md |
Instructions for an AI helping a student (Socratic mentor) and the pointer for maintainers. |
Before you change anything
Run these from the repository root and expect them to pass. If one fails on a clean checkout, that is your first task.
scripts/verify.sh js && scripts/verify.sh scala && scripts/verify.sh fsharp # 728 / 689 / 691 tests
for t in js scala fsharp; do scripts/check-stubs.sh $t && scripts/check-progression.sh $t; done
scripts/typecheck-js.sh # needs network (npx typescript); exits 2 without it
node scripts/mutate.mjs # ~30 s; exit 0 = no surviving mutant outside the allow-list
scripts/e2e.sh # ~25 s after the first build; 176 tests, Chromium + WebKit
node scripts/check-links.mjs # online; two okmij.org PDFs sometimes time out and pass on retry
Environment on the machine this was built on: Node 24; scala-cli at ~/.local/bin/scala-cli (fetches its own JDK and Scala 3.5.2); .NET SDK 10 at ~/.dotnet/dotnet (Fable 5.24.0 as a local tool); Google Chrome at /usr/bin/google-chrome; Playwright 1.65.0 with Chromium and WebKit builds in ~/.cache/ms-playwright (no Firefox; the e2e config skips missing browsers and never downloads). The first Scala and F# builds need the network and are slow. scripts/serve.mjs binds 127.0.0.1:8000 by default and takes a port argument.
How a change is made here
- Decide the behaviour and write it into
docs/contract.md(and a spec underdocs/superpowers/specs/if it is more than a fix). - JavaScript first: stub with its doc comment, solution, tests, fixtures (generated from the JS solution with a small script — see
scripts/fixtures/README.md— then read by hand), course text. - Port to Scala and F#, touching only that track's directory. Use the same seeds and run counts so pinned values match; if a pinned value differs, the draw order differs — find out why before changing a test.
- Run every check above for every track you touched, and the e2e suite for anything a page shows.
- Record what you decided in the build log.
Rules that are easy to break:
- Course text never explains an idea by reference to Scala and assumes no FP background. Bridges come from everyday programming, in blockquotes starting
> **From everyday programming:**. (Owner's rule, 2026-10-29.) - Every exercise has signatures in all three languages, three folded hints (nudge, stronger hint, near-solution skeleton with holes — never the solution; capstones have two), a "Tests"/"Proved by" list, and "Check your understanding" questions.
- A stub must compile and fail per test, not at import or module load.
fsharp/src/Snake.fsprojandfsharp/solutions/Snake.fsprojstay identical.- Existing fixture cases are contract: do not change one to make something pass.
- Features arrive in the module that teaches them. A field or platform method may exist earlier but dormant (
frame,queued,onClick), and the tests of earlier modules must pass for a student who has not built the later feature.check-progression.shcannot prove that (it copies finished solution files), so check it by hand in a temp copy when you add something cross-cutting. - F# on Fable does not wrap
uint32arithmetic: all seed arithmetic goes throughRandom.fs. - Expecto silently drops plain
testCaseitems in a list that also hasfor … do yield; useyield! [ for … ]and compare test counts with the JS suite. - In Scala and F#, a module tag must select only its own module (
./test.sh M07must not run M07b). Bothtest.shfiles handle this; keep it working when adding tags.
How the owner likes to work
- Once a design is approved, carry it through spec, plan and execution without extra review stops, and report the decisions you made at the end.
- "Skip the questions" means: decide, and say what you decided.
- For large builds: the main session writes the specs and verifies; Opus-class subagents do the coding (one JS reference builder per wave, then one Scala and one F# porter, each confined to its directory, never two builders on the same files at once). This is how the twelve extensions were built.
- Commit, merge and push only when asked. Commit messages end with the co-author line used in the existing history.
- Reports lead with what was found and what is not verified.
What is not verified
- Play by a person, in a real browser: whether speed and controls feel right, a real hidden tab, browser zoom with Life clicks, key auto-repeat, touch, Safari and Firefox proper.
- The JS type errors as an editor shows them. Only the
tsccommand line was run (TypeScript 7.0.2 and 5.9.3); the version is not pinned (TS_PACKAGEcan pin it). - Rendered pixels and fonts. The e2e suite compares draw calls with the model, not images.
- Firefox in the e2e suite (no build installed).
- After the last change (the stored-best rule in the three browser glue files) the Scala and F# unit suites were not re-run, because they do not compile the glue; the e2e suite exercised it in both.
Known small gaps
- Scala lacks the JS test "play without a best answers LoadBest with 0".
- Skipping optional Exercise 4.7 (
State) leaves red M04 tests unless the student copies the solution file; the text says so. Giving it its own tag, like 7b, would be cleaner. check-progression.shandcheck-stubs.shstill carry a workaround for the old M07/M07b tag collision; harmless, could be removed.web/Web.fsprojdoes not listState.fs,SafeIO.fs,Check.fs(the browser does not need them).- The Module 9 and 10 tests on
mainaccept the program with or without the stored-best load, so a student between the two modules stays green; the exact sequence is pinned by an Exercise 10.7 test. - Earlier open items still stand in the first section of the build log (e.g.
check-links.mjsignores reference-style links; the purity audits exclude the whole runtime file).
Ideas not yet built
- A third capstone from
course/method.md's list (Tetris or Breakout). - An auto-pause policy for long gaps instead of dropping time (Module 10, question 9 sketches it).
- Drag-to-draw in Life (needs pointer-move events).
- Exporting a recording from a played game, so the replay viewer can show the student's own game (needs a new
Op). - Visual regression snapshots in the e2e suite.
- Pinning the TypeScript version and running the type check in the editor matrix.
If something looks wrong
Trust, in this order: the fixtures, the stub doc comments, docs/contract.md, the course text. The build log explains why things are the way they are; search it for the function or fixture name before "fixing" a surprising rule.