feat: add production campaigns, gameplay labs, and review documentation

This commit is contained in:
cabinw
2026-09-13 12:27:10 +08:00
parent d1dffa9338
commit 4644e975b0
214 changed files with 98994 additions and 134 deletions
+54
View File
@@ -0,0 +1,54 @@
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);
});