import test from 'node:test'; import assert from 'node:assert/strict'; import { Game, rotateBoard, solveRotations, trace, validateLevel } from '../src/core.js'; import { createRouteBoard, ROUTE_FAMILIES } from '../src/route-grammar.js'; import { createStageSeed } from '../src/stage-seed.js'; for (const { id: family } of ROUTE_FAMILIES) { test(`${family}: two-turn lock is witnessed by actual state changes and reversible insertion`, () => { for (const radius of [3, 4, 5]) { const seed = family === 'switchback' ? ({ 3: 2, 4: 0, 5: 3 })[radius] : family === 'spiral' && radius === 3 ? 1 : 0; const base = createRouteBoard(seed, { family, radius }); const snapshot = JSON.stringify(base); const lock = createStageSeed(base, { seed }); assert.ok(lock, `${family}, radius ${radius}, seed ${seed}`); assert.equal(JSON.stringify(base), snapshot, 'The helper must not alter its input route'); assert.equal(lock.minimumRotations, 2); assert.ok(lock.heroRoute.bends >= 7); assert.ok(lock.heroRoute.featuredPairs >= 6); assert.ok(lock.heroRoute.featuredRatio >= .6); const level = { ...base, tiles: lock.tiles, moveLimit: lock.tiles.length }; assert.deepEqual(validateLevel(level), []); assert.equal(solveRotations(level).rotations, 2, 'Both one-turn directions must fail to clear the lock'); const [a, b, c] = lock.tiles; assert.ok(lock.tiles.every(tile => trace(level, tile).status === 'blocked')); const turned = rotateBoard(level); assert.equal(trace(turned, b).status, 'exit'); assert.equal(trace(turned, a, [a, c]).blocker, c.id); assert.equal(trace(turned, c, [a, c]).blocker, a.id); assert.equal(trace(level, a, [a, c]).status, 'exit'); assert.equal(trace(level, c, [c]).status, 'exit'); let inserted = []; for (const { id, orientation } of lock.reverseInsertion) { const tile = lock.tiles.find(candidate => candidate.id === id); inserted = [...inserted, tile]; assert.equal(trace(rotateBoard(base, orientation), tile, inserted).status, 'exit'); } const game = new Game(level); for (const action of lock.actions) { if (action.type === 'rotate') game.rotate(action.direction); else assert.equal(game.tap(action.id).status, 'exit'); } assert.equal(game.tiles.length, 0, 'The authored two-turn witness must fully clear the board'); } }); } test('stage search is deterministic and an unmet constraint returns no lock', () => { const base = createRouteBoard(0, { family: 'orbits', radius: 3 }); assert.deepEqual(createStageSeed(base, { seed: 17 }), createStageSeed(base, { seed: 17 })); assert.equal(createStageSeed(base, { minHeroBends: 999 }), null); assert.equal(createStageSeed(base, { maxChecks: 0 }), null); });