chore(js): polish pong keys, held mapping and m09 main test

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
Lucas Winther
2026-10-29 00:02:23 +01:00
co-authored by Claude Sonnet 5.5
parent 3146bb630a
commit 59bece5443
10 changed files with 209 additions and 16 deletions
+6
View File
@@ -1,6 +1,9 @@
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
import { states, classicActions, render } from '../src/app.js'
import { map as mapStream } from '../src/stream.js'
import { map as mapIO, randomSeed } from '../src/io.js'
const here = dirname(fileURLToPath(import.meta.url))
@@ -17,3 +20,6 @@ export const freeze = (x, seen = new WeakSet()) => {
}
return x
}
/** The m09 program built from classicActions (ticks every 150 ms). The shipped `main` switches to speedActions in m10. */
export const classicMain = mapIO(randomSeed, (seed) => mapStream(states(seed, classicActions), render))
+12 -4
View File
@@ -1,12 +1,12 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { states, classicActions, render, main, TICK_MS } from '../src/app.js'
import { run, map as mapStream } from '../src/stream.js'
import { trace, map as mapIO, randomSeed } from '../src/io.js'
import { run } from '../src/stream.js'
import { trace } from '../src/io.js'
import { view } from '../src/view.js'
import { newGame } from '../src/spawn.js'
import { Action } from '../src/model.js'
import { freeze } from './helpers.js'
import { freeze, classicMain } from './helpers.js'
const time = (t) => ({ tag: 'Time', t })
const down = (key) => ({ tag: 'KeyDown', key })
@@ -42,7 +42,6 @@ test('m09 main asks for a random seed, then builds the stream from it', () => {
// `main` itself switches to speedActions in m10, so the 150 ms behaviour is checked on the same program built from classicActions.
test('m09 the program built from classicActions draws the new game first, then the ticked game after 150 ms', () => {
const classicMain = mapIO(randomSeed, (seed) => mapStream(states(seed, classicActions), render))
const { result: stream } = trace(classicMain, 42)
const [first, next] = stream.step(time(0))
const [second] = next.step(time(150))
@@ -50,3 +49,12 @@ test('m09 the program built from classicActions draws the new game first, then t
assert.equal(second.length, 1)
assert.notDeepEqual(trace(second[0], 0).ops, trace(first[0], 0).ops)
})
// Holds before and after m10: whatever `main` ticks with, the clock must be wired in, so time passing produces more drawings.
test('m09 main draws again once time has passed', () => {
const { result: stream } = trace(main, 42)
const [first, next] = stream.step(time(0))
const [later] = next.step(time(150))
assert.equal(first.length, 1)
assert.ok(later.length >= 1)
})
+3 -4
View File
@@ -2,10 +2,9 @@ import test from 'node:test'
import assert from 'node:assert/strict'
import { runIO, runApp } from '../src/runtime.js'
import { pure, draw, randomSeed, map, sequence } from '../src/io.js'
import { states, classicActions, render } from '../src/app.js'
import { map as mapStream } from '../src/stream.js'
import { view } from '../src/view.js'
import { newGame, tick } from '../src/spawn.js'
import { classicMain } from './helpers.js'
// A fake platform: records every call and lets the test fire events by hand.
const fakePlatform = (seed = 42) => {
@@ -25,8 +24,8 @@ const fakePlatform = (seed = 42) => {
}
}
// The m09 program: ticks every 150 ms. (The shipped `main` uses speedActions after m10, so these tests build their own.)
const main = map(randomSeed, (seed) => mapStream(states(seed, classicActions), render))
// The m09 program (classicMain) ticks every 150 ms; the shipped `main` uses speedActions after m10.
const main = classicMain
// What the platform should have been asked to draw for a scene.
const expectedCalls = (scene) =>
+2
View File
@@ -41,7 +41,9 @@ test('m11 clamp keeps a number inside [lo, hi]', () =>
test('m11 pongUpdate does not modify the state it is given', () => {
const s = freeze(initialPong(0))
const before = structuredClone(s)
assert.doesNotThrow(() => pongUpdate(s, PongAction.Frame))
assert.deepStrictEqual(s, before)
})
// 3000 frames with no one touching the keys: the ball must always stay on the court and the match must end.