Files
functional-programming-snake/e2e/lib/seed.mjs
T
Lucas WintherandClaude Fable 5.2 edd57f0148 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>
2026-11-11 12:05:29 +01:00

33 lines
1.7 KiB
JavaScript

// Technique 1: a fixed seed.
// Every track's glue asks the browser for its seed the same way: it makes a Uint32Array of length 1 and hands it to
// crypto.getRandomValues (JS: `new Uint32Array(1)`; Scala: `new Uint32Array(1)`; F#: `Array.zeroCreate<uint32> 1`,
// which Fable compiles to a Uint32Array). This init script replaces `getRandomValues` before any page script runs,
// writes the seed into such an array, and logs every call so a test can prove the page asked exactly once, in exactly
// that shape. Any other request (none of the pages makes one) falls through to the real generator and is logged too.
/** The seed every test uses unless it says otherwise. Above 2^31 on purpose: a track that read the seed as a signed
* 32-bit number would start a different game and every comparison with the model would fail. */
export const SEED = 0xdeadbeef
/** Runs in the page (Playwright serialises it), so it may use only its argument. */
const seedScript = (seed) => {
const real = Crypto.prototype.getRandomValues
const calls = []
Object.defineProperty(window, '__e2eSeedCalls', { value: calls })
Crypto.prototype.getRandomValues = function (array) {
const type = array?.constructor?.name
calls.push({ type, length: array?.length })
if (type === 'Uint32Array' && array.length === 1) {
array[0] = seed
return array
}
return real.call(this, array)
}
}
/** Install the fixed seed for every document the page loads from now on. */
export const fixSeed = (page, seed = SEED) => page.addInitScript(seedScript, seed)
/** The calls the current document made to crypto.getRandomValues: [{type, length}]. */
export const seedCalls = (page) => page.evaluate(() => window.__e2eSeedCalls ?? null)