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:
Lucas WintherandClaude Fable 5.2 committed 2026-11-11 12:05:29 +01:00
1 parent 876f5aabb1
commit edd57f0148
253 files changed
+21753 -503

No files matched your search

+31 -3
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Usage: scripts/check-stubs.sh <js|scala|fsharp>
# Runs each module M01..M11 that has tests against the stubs in src/. Fails if any module's tests pass,
# Runs each module M01..M13 (and the optional M07b) that has tests against the stubs in src/. Fails if any module's tests pass,
# and also fails (exit 2) on tooling/build errors, so a broken toolchain can never look like "stubs fail".
set -uo pipefail
track="${1:?usage: check-stubs.sh <js|scala|fsharp>}"
@@ -32,11 +32,39 @@ tests_failed() {
esac
}
# Modules in course order; "07b" is the optional Module 7b.
modules="01 02 03 04 05 06 07 07b 08 09 10 11 12 13"
# Scala's `./test.sh M07` (munit glob "*M07*") and F#'s (Expecto --filter-test-list, a substring) also select the
# M07b tests. Their failures would hide an M07 that wrongly passes, so for such a module run the tests in a temp
# copy without the longer-tagged modules' test files (F#: also without their line in tests/Snake.Tests.fsproj).
run_module() {
local n="$1" longer="" t f tmp
if [ "$track" != js ]; then
for t in $modules; do [[ "$t" == "$n"?* ]] && has "$t" && longer="$longer $t"; done
fi
if [ -z "$longer" ]; then (cd "$dir" && ./test.sh "M$n" 2>&1); return; fi
tmp="$(mktemp -d)"
tar -C "$repo" --exclude=.git --exclude=node_modules --exclude=bin --exclude=obj \
--exclude=.scala-build --exclude=.bsp --exclude=.superpowers -cf - . | tar -C "$tmp" -xf -
for t in $longer; do
case "$track" in
scala) rm -f "$tmp"/scala/test/M"$t"*.test.scala ;;
fsharp) for f in "$tmp"/fsharp/tests/M"$t"*.fs; do
rm -f "$f"; sed -i "/Include=\"$(basename "$f")\"/d" "$tmp/fsharp/tests/Snake.Tests.fsproj"
done ;;
esac
done
(cd "$tmp/$track" && ./test.sh "M$n" 2>&1); local rc=$?
rm -rf "$tmp"
return $rc
}
status=0
for n in 01 02 03 04 05 06 07 08 09 10 11; do
for n in $modules; do
m="M$n"
if ! has "$n"; then echo "$m: no tests"; continue; fi
out="$(cd "$dir" && ./test.sh "$m" 2>&1)"; rc=$?
out="$(run_module "$n")"; rc=$?
out="$(sed $'s/\x1b\\[[0-9;?]*[A-Za-z]//g' <<<"$out")"
if [ $rc -eq 0 ]; then
echo "$m: FAIL (tests pass against stubs)"; status=1