import assert from 'node:assert/strict'; import { readFile, mkdtemp, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { spawnSync } from 'node:child_process'; const root = new URL('..', import.meta.url); const htmlPath = new URL('index.html', root); const html = await readFile(htmlPath, 'utf8'); assert.match(html, //i, 'index.html must be a complete HTML document'); assert.match(html, /const LEVELS = Object\.freeze\(\[/, 'level configuration must be present'); assert.doesNotMatch(html, /(?:https?:)?\/\/(?:unpkg|cdn\.jsdelivr|cdnjs|fonts\.googleapis|fonts\.gstatic)/i, 'the game must not load external runtime dependencies'); assert.doesNotMatch(html, /<(?:script|link|img)[^>]+(?:src|href)=['"]https?:/i, 'the game must be self-contained'); const scriptMatches = [...html.matchAll(/]*)?>([\s\S]*?)<\/script>/gi)]; assert.equal(scriptMatches.length, 1, 'the game should have one inline script'); const script = scriptMatches[0][1]; const tempDir = await mkdtemp(join(tmpdir(), 'echo-loop-')); const scriptPath = join(tempDir, 'game.mjs'); await writeFile(scriptPath, script); const syntax = spawnSync(process.execPath, ['--check', scriptPath], { encoding: 'utf8' }); assert.equal(syntax.status, 0, `browser script has syntax errors:\n${syntax.stderr}`); const levelsMatch = script.match(/const LEVELS = Object\.freeze\((\[[\s\S]*?\])\);\s*\n\s*const state/); assert.ok(levelsMatch, 'could not extract level definitions'); const levels = Function(`"use strict"; return (${levelsMatch[1]});`)(); assert.equal(levels.length, 4, 'the game must contain four experiments'); for (const [index, level] of levels.entries()) { assert.ok(level.id && level.name && level.title, `level ${index + 1} must have identity metadata`); const width = level.map[0].length; const symbols = level.map.flatMap((row) => [...row]); assert.ok(level.map.length >= 3 && width >= 7, `level ${index + 1} is too small to be meaningful`); assert.ok(level.map.every((row) => row.length === width), `level ${index + 1} rows must have equal width`); assert.equal(symbols.filter((symbol) => symbol === 'S').length, 1, `level ${index + 1} needs one start`); assert.equal(symbols.filter((symbol) => symbol === 'G').length, 1, `level ${index + 1} needs one goal`); const plates = new Set(symbols.filter((symbol) => /[a-z]/.test(symbol))); const gates = new Set(symbols.filter((symbol) => /[A-Z]/.test(symbol) && symbol !== 'S' && symbol !== 'G').map((symbol) => symbol.toLowerCase())); assert.ok(plates.size >= 1, `level ${index + 1} needs a memory node`); assert.deepEqual([...plates].sort(), [...gates].sort(), `level ${index + 1} node/gate pairs must match`); } function simulateFirstExperiment(level) { const map = level.map; const start = { x: 1, y: 1 }; const plate = { x: 4, y: 1 }; const gate = { x: 5, y: 1 }; const goal = { x: 9, y: 1 }; const echo = { ...start }; const stepRight = (entity, allowGate) => { const next = { x: entity.x + 1, y: entity.y }; const tile = map[next.y][next.x]; if (tile === '#' || (next.x === gate.x && !allowGate)) return false; entity.x = next.x; return true; }; for (let index = 0; index < 3; index += 1) assert.equal(stepRight(echo, false), true, 'the recorded route must reach the first node'); assert.deepEqual(echo, plate, 'the first echo must remain on node A after recording'); const player = { ...start }; for (let index = 0; index < 8; index += 1) { const passed = stepRight(player, echo.x === plate.x && echo.y === plate.y); assert.equal(passed, true, `player pulse ${index + 1} should be legal`); } assert.deepEqual(player, goal, 'the player must reach the goal through the powered gate'); } const first = levels[0]; simulateFirstExperiment(first); assert.match(script, /function resetEchoPositions\(\)/, 'multi-echo timelines must reset every echo to the start'); assert.match(script, /resetEchoPositions\(\);\s*state\.actions = \[\];/, 'recording a new echo must restart the existing echo fleet'); console.log('ECHO//LOOP verification passed'); console.log(`- ${levels.length} level definitions validated`); console.log('- self-contained resource checks passed'); console.log('- inline JavaScript syntax check passed'); console.log('- echo-powered gate simulation passed');