# 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 one documentation commit that added this page; `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). | | `/src`, `/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/` | `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. ```sh 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 1. **Decide the behaviour** and write it into `docs/contract.md` (and a spec under `docs/superpowers/specs/` if it is more than a fix). 2. **JavaScript first**: stub with its doc comment, solution, tests, fixtures (generated from the JS solution with a throwaway script, then read by hand), course text. 3. **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. 4. **Run every check above** for every track you touched, and the e2e suite for anything a page shows. 5. **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.fsproj` and `fsharp/solutions/Snake.fsproj` stay 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.sh` cannot 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 `uint32` arithmetic: all seed arithmetic goes through `Random.fs`. - Expecto silently drops plain `testCase` items in a list that also has `for … do yield`; use `yield! [ for … ]` and compare test counts with the JS suite. - In Scala and F#, a module tag must select only its own module (`./test.sh M07` must not run M07b). Both `test.sh` files 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 `tsc` command line was run (TypeScript 7.0.2 and 5.9.3); the version is not pinned (`TS_PACKAGE` can 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.sh` and `check-stubs.sh` still carry a workaround for the old M07/M07b tag collision; harmless, could be removed. - `web/Web.fsproj` does not list `State.fs`, `SafeIO.fs`, `Check.fs` (the browser does not need them). - The Module 9 and 10 tests on `main` accept 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.mjs` ignores 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.