import test from 'node:test'; import assert from 'node:assert/strict'; import { canRotate, opposite, rotateBoard, trace, validateLevel } from '../src/core.js'; import { createRouteBoard, ROUTE_FAMILIES } from '../src/route-grammar.js'; const pairKey = (q, r, a, b) => `${q},${r}:${Math.min(a, b)},${Math.max(a, b)}`; const boardKey = board => board.cells.map(cell => `${cell.q},${cell.r}:${cell.tracks.join('')}`).sort().join('|'); const featuredKey = board => board.routeDesign.featured.map(pair => pairKey(pair.q, pair.r, pair.a, pair.b)).sort().join('|'); for (const { id: family } of ROUTE_FAMILIES) { test(`${family}: complete rotating boards contain real open hero paths at every supported size`, () => { for (const radius of [2, 3, 4, 5]) for (const seed of [0, 1, 7, 42, 71, 95]) { const board = createRouteBoard(seed, { family, radius }); const label = `${family}, radius ${radius}, seed ${seed}`; assert.deepEqual(validateLevel(board), [], label); assert.equal(board.cells.length, 1 + 3 * radius * (radius + 1), label); assert.equal(canRotate(board), true, label); assert.equal(boardKey(rotateBoard(board, 6)), boardKey(board), label); assert.deepEqual(board.tiles, [], label); assert.ok(board.routeDesign.heroStarts.length > 0, label); const boardCells = new Map(board.cells.map(cell => [`${cell.q},${cell.r}`, cell])); const covered = new Set(); let longestBends = 0; for (const [index, hero] of board.routeDesign.heroStarts.entries()) { const tile = { ...hero, id: `hero-${index}`, color: 0 }; // Exercise the production tracer, including swept geometry, not the // grammar's own inexpensive route scan used to propose candidates. const route = trace(board, tile, [tile]); assert.equal(route.status, 'exit', label); assert.equal(route.steps.length, hero.steps, label); const bends = route.steps.filter(step => step.enter !== null && opposite(step.enter) !== step.exit).length; assert.equal(bends, hero.bends, label); longestBends = Math.max(longestBends, bends); for (const step of route.steps) if (step.enter !== null) covered.add(pairKey(step.q, step.r, step.enter, step.exit)); } assert.ok(longestBends >= 7, `${label}: actual open path should sustain a long curved run`); assert.ok(board.routeDesign.featured.length > 0, label); for (const pair of board.routeDesign.featured) { const cell = boardCells.get(`${pair.q},${pair.r}`); assert.equal(cell?.tracks[pair.a], pair.b, `${label}: emphasized rail must be an actual pair`); assert.ok(covered.has(pairKey(pair.q, pair.r, pair.a, pair.b)), `${label}: emphasized rail must have a supplied open-path witness`); } } }); test(`${family}: variant changes the authored route and remains deterministic`, () => { const original = createRouteBoard(42, { family, radius: 3, variant: 0 }); const variant = createRouteBoard(42, { family, radius: 3, variant: 1 }); assert.deepEqual(createRouteBoard(42, { family, radius: 3, variant: 0 }), original); assert.notEqual(featuredKey(original), featuredKey(variant), 'A variant changes real featured routing, not only background fill'); assert.notEqual(boardKey(original), boardKey(rotateBoard(original)), 'The grammar must allow rotation to change paths'); }); } test('the six route families are geometrically different even after whole-board rotation', () => { const boards = ROUTE_FAMILIES.map(({ id }) => createRouteBoard(42, { family: id, radius: 3 })); const canonical = boards.map(board => Array.from({ length: 6 }, (_, turn) => boardKey(rotateBoard(board, turn))).sort()[0]); assert.equal(new Set(canonical).size, ROUTE_FAMILIES.length); }); test('route grammar rejects unsupported board requests', () => { assert.throws(() => createRouteBoard(1, { family: 'unknown' }), /Unknown route family/); for (const radius of [1, 6, 2.5]) assert.throws(() => createRouteBoard(1, { radius }), /radius/); assert.throws(() => createRouteBoard(1, { variant: 0.5 }), /variant/); });