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>
31 lines
3.1 KiB
Markdown
31 lines
3.1 KiB
Markdown
# Audit pass — spec, written after the fact (2026-11-11)
|
||
|
||
**Status: built and verified.** This pass was done without a written spec (the owner asked for an audit with no questions). This document records what was decided, so that it can be read like the other specs. The current rules are in `docs/contract.md`; the later extension pass is in `2026-11-11-course-extensions.md`.
|
||
|
||
## Request
|
||
Audit the finished course: find improvements, fact-check, add sources, knowledge and exercises, test edge cases, extend the project.
|
||
|
||
## Findings and the decision for each
|
||
|
||
| # | Finding | Decision |
|
||
|---|---|---|
|
||
| 1 | **Uneven steps at every speed-up.** `Frame` stepped when the *total* frame count was a multiple of `movesEvery(score)`. When the interval changes, the next multiple can be one frame away: two moves 25 ms apart. The text called this invisible. | `frame` counts frames since the last move and resets on each step (`n >= movesEvery(score)`). The old rule became "Puzzle 3" in Module 10 and a mutant in the Module 12 tests. `m10-update.json` frame cases were replaced. |
|
||
| 2 | **A hidden tab killed the snake.** `requestAnimationFrame` stops in background tabs; on return `interval` emitted every missed beat at once. Module 8 said that catching up was wanted. | New pure combinator `clampTime(s, maxGap)` and `MAX_GAP_MS = 250` (Exercise 10.5), wrapped around `main` and `pongMain`. Fixed in stream code, not in the browser glue, so it is testable and identical across tracks. Module 8's text now says catching up has a limit. |
|
||
| 3 | **Pong restart cleared held keys**, although the text said a key held across a restart behaves sensibly. | `Restart` keeps `held`. Fixture updated; new case `restart-keeps-the-held-keys`. |
|
||
| 4 | **mulberry32's output is not one-to-one.** Measured over the full period: 1 893 145 848 distinct values (44 %). | Stated in Module 4; the modulo-bias remark was reworded. |
|
||
| 5 | Module 7 said Haskell's `IO` has a `Suspend` case. | Corrected: only cats-effect and ZIO do; Haskell is lazy. |
|
||
| 6 | Module 4 cited `uniformR :: g -> (a, g)`. | Corrected to `uniform`. |
|
||
| 7 | **Test gaps.** A mutation analysis of the JS solutions (212 killed, 14 survived) showed six real gaps. | New fixture cases: Pong first paddle row, ball entering rows 0/49 and columns 0/79, `eating-the-last-free-cell-wins`; JS test for `deepFreeze` with `null`. |
|
||
|
||
## New material
|
||
- **Module 12 (bonus), property-based testing:** `elementOf`, `listOf`, `forAll`, `removals`, `minimize`, `violations`, `moveGaps`; fixture `m12-violations.json`; tests that run the library against deliberately broken games.
|
||
- Challenge questions in Modules 3, 4, 7, 8, 10; three pitfalls in `method.md`; primary sources in several modules.
|
||
|
||
## Rules followed
|
||
- JS first, then Scala and F# ported from it, each porter confined to its track directory.
|
||
- Fixtures regenerated from the JS solution and read by hand.
|
||
- No Scala bridging in the course text (owner's rule of 2026-10-29).
|
||
|
||
## Verification at the end of this pass
|
||
JS 446, Scala 423, F# 425 tests; stubs and progression M01–M12 ok; six pages painted; 128 links resolved. (Superseded by the extension pass; current numbers are in the build log and the contract.)
|