// The prepare step must be a cache, never a parallel pipeline: a song rebuilt // from a prepared artifact has to produce a world bit-identical to one analysed // from the WAV, for every frame. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { resolve, dirname } from 'node:path'; import { loadSong } from '../tools/load.js'; import { serializeSong, deserializeSong } from '../src/song/serialize.js'; import { Run } from '../src/film.js'; const HERE = dirname(fileURLToPath(import.meta.url)); const BANK = resolve(HERE, '../..', 'flow-state/test/songs'); test('prepared song is bit-identical to the live WAV path', () => { for (const name of ['drone', 'runner', 'dusk']) { const { song, analysis } = loadSong(resolve(BANK, `${name}.wav`), { fps: 30, seed: 12345 }); const artifact = serializeSong(song); const rebuilt = deserializeSong(artifact); assert.equal(rebuilt.seed, song.seed); assert.equal(rebuilt.name, song.name); assert.deepEqual(rebuilt.summary, song.summary); assert.equal(rebuilt.events.length, song.events.length); assert.equal(rebuilt.laws.length, song.laws.length); const frames = [0, 31, 217, 1200, 3599]; const a = new Run(song, { fps: 30, width: 480, height: 270, seed: 12345, field: 64, genesis: 0 }).renderAt(frames); const b = new Run(rebuilt, { fps: 30, width: 480, height: 270, seed: 12345, field: 64, genesis: 0 }).renderAt(frames); for (const f of frames) { const pa = a.get(f).data; const pb = b.get(f).data; assert.equal(pa.length, pb.length, `${name} frame ${f} size`); for (let i = 0; i < pa.length; i++) { assert.equal(pa[i], pb[i], `${name} frame ${f} pixel ${i}: live=${pa[i]} prepared=${pb[i]}`); } } } });