feat(course): audit fixes and twelve extensions (Modules 7b, 12, 13)
Audit pass: - Frame rule counts frames since the last move, so every step lasts exactly movesEvery(score) frames, also right after a speed-up - clampTime + MAX_GAP_MS: a hidden tab no longer replays the whole gap - Pong Restart keeps held keys - Fact corrections (mulberry32 output coverage, Haskell IO, uniform) - New fixture cases from a mutation analysis Extensions (all three tracks, shared fixtures, course text): - Turn queue (Exercise 10.6), persisted high score (10.7), replay viewer (10.8), play() end-to-end helper (9.5) - Optional State (4.7) and optional Module 7b, stack-safe IO - Module 12, property-based testing with generators, shrinking, invariants and a model-based oracle - Module 13, Conway's Life with a Click event - JS type checking via jsconfig, JSDoc and types.d.ts - Maintainer tools: scripts/mutate.mjs, scripts/typecheck-js.sh, Playwright end-to-end suite (e2e/, scripts/e2e.sh) Docs: docs/contract.md is the canonical contract; specs and build log under docs/superpowers. Verified: JS 728, Scala 689, F# 691 unit tests; stubs and progression M01-M13 and M07b; 176 end-to-end tests on Chromium and WebKit. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
1 parent
876f5aabb1
commit
edd57f0148
253 files changed
+21753
-503
No files matched your search
@@ -49,3 +49,85 @@ Record of how the course was built, the decisions made on the owner's behalf, an
|
||||
## Environment notes
|
||||
- scala-cli installed at `~/.local/bin/scala-cli` (downloads its own JVM 17); dotnet at `~/.dotnet/dotnet`; Fable 5.24.0 via `fsharp/.config/dotnet-tools.json`; headless Chrome at `/usr/bin/google-chrome`.
|
||||
- First `scala/build-web.sh` / `fsharp/build-web.sh` run needs network (Scala.js linker / Fable restore). `scripts/serve.mjs` binds 127.0.0.1:8000; run from the repo root.
|
||||
|
||||
---
|
||||
|
||||
# Audit and extension pass (2026-11-11)
|
||||
|
||||
Owner request: audit the course, fact-check, add sources, knowledge and exercises, test edge cases, extend the project; no questions asked. Decisions below were made on the owner's behalf.
|
||||
|
||||
## Behaviour changes (all three tracks, fixtures regenerated)
|
||||
1. **Frame rule (Module 10).** `frame` now counts frames since the last move and resets on each step (`n >= movesEvery(score)` → `tick` with `frame: 0`). The old rule (`totalFrames % movesEvery == 0`) made the snake take two steps as little as 25 ms apart at every speed-up. The old rule lives on as "Puzzle 3" in Module 10 and as a mutant in the M12 tests. Cost if wrong: `m10-update.json` frame cases were renamed/replaced.
|
||||
2. **`clampTime(s, maxGap)` + `MAX_GAP_MS = 250` (Module 10, Exercise 10.5; also used by `pongMain`).** A hidden tab stops `requestAnimationFrame`; on return `interval` caught up on the whole gap at once and the snake died unseen. Fixed in pure stream code, not in the browser glue. Module 8's "the game should catch up" text now says catching up has a limit.
|
||||
3. **Pong `Restart` keeps `held`.** The text claimed a key held across a restart "behaves sensibly"; the code cleared `held`.
|
||||
|
||||
## New content
|
||||
- **Module 12 (bonus), property-based testing**: `check.js` / `Check.scala` / `Check.fs` (`elementOf`, `listOf`, `forAll`, `removals`, `minimize`, `violations`, `moveGaps`), fixture `m12-violations.json`, tests that run the library against three mutants (double-turn, no walls, old frame rule). Scripts, README, reference and setup tables know M12.
|
||||
- New fixture cases from a mutation analysis of the JS solutions (212 killed, 14 survivors, 6 of them real gaps): Pong first paddle row, ball entering rows 0/49 and columns 0/79, `eating-the-last-free-cell-wins`, JS `deepFreeze` with `null`.
|
||||
- Challenge questions in Modules 3, 4, 7, 8, 10; new pitfalls in method.md; primary sources added (Wadler, Moggi, Peyton Jones, Kiselyov & Ishii, Steele/Lea/Flood, Lemire, Czaplicki, Elliott, Claessen & Hughes, Zeller, Kleppmann).
|
||||
|
||||
## Fact corrections
|
||||
- Module 4: mulberry32's output step is not a bijection; measured over the full period it emits 1 893 145 848 distinct values (44 %). The modulo-bias remark was reworded accordingly. Haskell `uniformR` → `uniform`.
|
||||
- Module 7: Haskell's `IO` has no `Suspend` case (laziness does that job); only cats-effect/ZIO do.
|
||||
- Module 10: "for a game this is invisible" (about uneven steps) was false; removed.
|
||||
|
||||
## Verification (2026-11-11)
|
||||
- `verify.sh`: JS 446, Scala 423, F# 425 tests pass (F# has one extra stack-safety test for `forAll`).
|
||||
- `check-stubs.sh` and `check-progression.sh`: M01–M12 ok in all tracks.
|
||||
- `smoke.mjs`: all 6 pages paint with no console errors, built from solutions.
|
||||
- `check-links.mjs`: all links OK after replacing one dead Moggi URL.
|
||||
- Not done: a human playing the games; the tab-switch fix is covered by stream tests only.
|
||||
|
||||
## Open follow-ups (added)
|
||||
- Changes are uncommitted in the working tree.
|
||||
- `Check.fs` is not in `fsharp/web/Web.fsproj` (not needed in the browser).
|
||||
- A turn queue (honouring Up-then-Left within one step) is still only a question in Module 3.
|
||||
- Resolved from the earlier list: backwards `Time` is now harmless behind `clampTime`.
|
||||
|
||||
---
|
||||
|
||||
# Extension pass (2026-11-11, later the same day)
|
||||
|
||||
Owner approved eleven extensions; spec: `docs/superpowers/specs/2026-11-11-course-extensions.md` (corrected in place where the build differed). Specs and verification by the orchestrating session; code and text by Opus 5.5 builders: a JS reference builder per wave, then one Scala and one F# porter.
|
||||
|
||||
## What was added
|
||||
1. **Turn queue** (Exercise 10.6): dormant `queued` field in `GameState`; `steer`; every game-state fixture gained `"queued": null`. From Module 10 on, Up-then-Down with Up pending is ignored (Module 3's `turn` alone would replace it).
|
||||
2. **Persisted high score** (Exercise 10.7): `LoadBest`/`SaveBest` ops, `Platform.loadBest/saveBest` (`localStorage` key `pure-snake-best`), a `Session {game, best, improved}` around the game; `GameState`, `update`, `view` untouched.
|
||||
3. **Replay viewer** (Exercise 10.8): `replay.*`, pages `replay.html`, sample `fixtures/replay-sample.json` (seed 1, 226 actions, 19 apples), loaded with `fetch` in all tracks.
|
||||
4. **Stack-safe IO** (optional Module 7b): `safeio.*`, tag `M07b`; it answers only Module 7's two ops and throws for others.
|
||||
5. **`play(main, seed, events)`** (Exercise 9.5) and golden fixture `m10-golden.json`.
|
||||
6. **`State`** (optional Exercise 4.7): `state.*`, tests under M04. A student who skips it must copy the solution file to keep M04 green.
|
||||
7. **Module 12**: `genSnake`, given `genState`, `advanceSpec` as an oracle; invariant `queued-not-reversed`; six mutants in the tests.
|
||||
8. **`scripts/mutate.mjs`** with `scripts/mutate-allow.txt` (13 reasoned equivalent mutants; exit 0).
|
||||
9. **JS type checking**: `js/jsconfig.json`, `types.d.ts`, JSDoc; `scripts/typecheck-js.sh` (needs network for `npx`; exits 2 without it).
|
||||
10. **Module 13, Life with the mouse**: `life/`, `Click` event, `clicks` source, `Platform.onClick`; clicks toggle cells whether running or paused; canvas 640×504.
|
||||
11. **`scripts/play.mjs`**: scripted browser session over all pages of all tracks.
|
||||
|
||||
## Decisions made on the owner's behalf
|
||||
- Two earlier tests on `main` were loosened (they accept the program with or without `LoadBest`) so a student between Modules 9 and 10 stays green; the exact sequence is pinned by a 10.7 test.
|
||||
- Click support reaches students in Module 13; M09–M12 pass with a runtime that does not register clicks.
|
||||
- `./test.sh MNN` in Scala and F# now selects exactly that module (M07 no longer pulls in M07b).
|
||||
- `smoke.mjs` picks the tab it opened (Chrome sometimes lists an extension page first).
|
||||
- One builder ran `git checkout` on `fixtures/m00-sanity.json` after its script reformatted it; the file had no pending changes.
|
||||
|
||||
## Verification (2026-11-11, final)
|
||||
- `verify.sh`: JS 728, Scala 689, F# 691 tests pass. Counts differ because a few tests are track-specific (JS browser-glue tests, JS-only constructors, one F# stack test).
|
||||
- `check-stubs.sh`, `check-progression.sh`: M01–M13 and M07b ok in all tracks.
|
||||
- `typecheck-js.sh`: stubs and solutions clean (TypeScript 7.0.2; also checked with 5.9.3).
|
||||
- `mutate.mjs`: 404 mutants, 391 killed, 13 allow-listed, exit 0.
|
||||
- `smoke.mjs`: 12 pages paint with no errors. `play.mjs`: 66 checks pass on all tracks.
|
||||
- `check-links.mjs`: all resolve; two okmij.org PDFs timed out once and returned 200 on retry.
|
||||
|
||||
## Not verified / open
|
||||
- No person has played any of it. `play.mjs` cannot see overlay text, the speed-up, the turn queue at real speed, Pong scoring, replay autoplay timing, a real hidden desktop tab, or blocked storage.
|
||||
- VS Code's own display of the JS type errors was not checked, only `tsc`.
|
||||
- TypeScript version is not pinned (`TS_PACKAGE` can pin it).
|
||||
- Scala lacks the JS test "play without a best answers LoadBest with 0".
|
||||
- Everything is uncommitted in the working tree.
|
||||
|
||||
## Playwright end-to-end suite (2026-11-11, owner's suggestion; spec Item 12)
|
||||
- `e2e/` (own `package.json`, `@playwright/test` 1.65.0 pinned) and `scripts/e2e.sh`; `scripts/play.mjs` deleted, `smoke.mjs` kept. The course tracks stay dependency-free.
|
||||
- Technique: fixed seed (stubbed `crypto.getRandomValues`), fake clock driving `requestAnimationFrame`, recorded `fillRect`/`fillText` calls; every frame a page draws is compared with the pure JS model for the same inputs, for all three tracks on Chromium and WebKit.
|
||||
- Result: 176 tests pass in about 20 s. No frame of any track differs from the model.
|
||||
- Found and fixed: the three tracks read an unusual stored best differently (`"1e2"`, `"7.0"`, `"2147483648"`). Ruling: only plain decimal digits with a value of at most 2147483647 count; anything else reads as 0. Applied to `js/solutions/browser.js`, `scala/web/BrowserPlatform.scala`, `fsharp/web/BrowserPlatform.fs`.
|
||||
- Still not covered: real animation timing and real hidden-tab throttling (the clamp is tested with a fake 10 s frame), rendered pixels and fonts, zoom, touch, key auto-repeat, Firefox (no build installed), and a person playing.
|
||||
Reference in new issue
Block a user