53 lines
3.5 KiB
JavaScript
53 lines
3.5 KiB
JavaScript
import { readFile, writeFile } from 'node:fs/promises';
|
|
import { createHash } from 'node:crypto';
|
|
import assert from 'node:assert/strict';
|
|
import { Game, solveRotations, validateLevel } from '../src/core.js';
|
|
import { canonicalLevelSignature } from '../src/campaign-evidence.js';
|
|
import { inspectCandidate } from '../src/production-generator.js';
|
|
import { countBends } from '../src/experience-generator.js';
|
|
|
|
const root = new URL('../', import.meta.url);
|
|
const read = async path => JSON.parse(await readFile(new URL(path, root), 'utf8'));
|
|
const [levels, manifest, analysis] = await Promise.all(['levels/v0.5.json', 'levels/v0.5-recipes.json', 'levels/v0.5-analysis.json'].map(read));
|
|
const digest = value => createHash('sha256').update(value).digest('hex');
|
|
assert.equal(levels.length, 1000); assert.equal(manifest.recipes.length, 1000); assert.equal(analysis.levels.length, 1000);
|
|
for (const [path, hash] of Object.entries(manifest.sourceHashes)) assert.equal(digest(await readFile(new URL(path, root))), hash, `Pipeline changed: ${path}`);
|
|
const signatures = new Set();
|
|
let actionCount = 0, replayCount = 0;
|
|
for (const [index, level] of levels.entries()) {
|
|
const row = analysis.levels[index], recipe = manifest.recipes[index];
|
|
assert.equal(level.id, row.id); assert.equal(level.id, recipe.id); assert.equal(row.level, index + 1);
|
|
assert.equal(row.policies.length, 20, level.id);
|
|
assert.deepEqual(validateLevel(level), [], level.id);
|
|
const hash = digest(canonicalLevelSignature(level));
|
|
assert.equal(hash, row.layoutHash); assert.equal(hash, recipe.layoutHash); assert.ok(!signatures.has(hash), level.id); signatures.add(hash);
|
|
const solution = solveRotations(level);
|
|
assert.ok(solution.solvable && !solution.exhausted, level.id);
|
|
assert.equal(solution.rotations, row.difficulty.requiredRotations, level.id);
|
|
assert.ok(!inspectCandidate(level, recipe.slot).rejected, level.id);
|
|
const witnesses = [{ actions: solution.actions, paths: row.reference.paths }, { actions: level.experience.actions, paths: row.authored.paths }, ...row.policies];
|
|
for (const { actions, paths } of witnesses) {
|
|
const game = new Game(level);
|
|
let departed = 0;
|
|
for (const action of actions) {
|
|
const result = action.type === 'rotate' ? game.rotate(action.direction) : game.tap(action.id);
|
|
assert.equal(result.status,
|
|
action.type === 'rotate' ? 'rotated' : 'exit', `${level.id} action ${JSON.stringify(action)}`);
|
|
if (action.type === 'tap') {
|
|
const recorded = paths[departed++];
|
|
assert.equal(recorded.id, action.id, level.id);
|
|
assert.equal(recorded.cells, result.steps.length, level.id);
|
|
assert.equal(recorded.bends, countBends(result), level.id);
|
|
}
|
|
actionCount++;
|
|
}
|
|
assert.equal(departed, paths.length, level.id);
|
|
assert.ok(game.won, level.id); assert.equal(game.moves, 0, level.id); replayCount++;
|
|
}
|
|
assert.ok(row.policies.filter(p => p.paths.some(path => path.bends >= 6)).length >= 16, level.id);
|
|
assert.ok(row.policies.filter(p => p.paths.some(path => path.id === level.experience.feature.id && path.bends >= recipe.slot.minHeroBends)).length >= 10, level.id);
|
|
}
|
|
const verification = { pipelineHash: manifest.pipelineHash, levels: levels.length, uniqueLayouts: signatures.size, independentlySolved: levels.length, completeReplays: replayCount, successfulActions: actionCount, result: 'passed' };
|
|
await writeFile(new URL('levels/v0.5-validation.json', root), JSON.stringify(verification, null, 2) + '\n');
|
|
console.log(JSON.stringify(verification, null, 2));
|