The writing job, done. 89 new philes takes the store to 200, and every one
of them is now reachable in play — which turned out to be the harder half.
Where they live follows from the voice rule. Each phile is written by
somebody, in the year it happened, who did not know they were in a game —
so the board is 1991 and the board cannot carry a file about WannaCry.
the board seven read-online areas: g-philes, the law, the scene,
v i r u s, the iron, telephony, unix. Reading online is free
and downloading costs MTU, which is how a board actually
worked, and is the only way ~60 files fit inside an economy
built to make you choose.
the climb everything after 1992, because the climb is the only level
that travels forwards in time. Thirty-six years bolted to the
shaft wall going past at speed. You get the file for having
been there whether or not you turned your head.
the shell man(1) and cat(1) on kruuna, for the unix and network ones.
ctx.phile(id, ..., quiet) collects without a toast — thirty-six
notifications in fifty seconds is a wall, not a level. The count lands once
at the top of the climb.
Two real bugs found on the way:
handshake exit() guarded .tone instead of .b, and there is a window
between S.i++ and enter() where a boss is current but its
state does not exist. Leave via the traceroute map during a
title card and unmount throws. Async continuations now bail
on a dead level too.
the climb the camera does not travel, the shaft does. The new signs
were nailed to the origin and never came into view. They also
needed dark ink for the one zone whose walls are white.
tools/philecheck.mjs: duplicate ids in an object literal overwrite
silently, and that cost an hour. It also reports anything written but not
placed. 200/200 written, 200/200 findable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
30 lines
1.4 KiB
JavaScript
30 lines
1.4 KiB
JavaScript
// duplicate ids in an object literal overwrite silently. this is the only place
|
|
// that would ever notice. run it after writing philes.
|
|
import { PHILES, TOTAL } from '../src/philes.js';
|
|
import { readFileSync, readdirSync } from 'fs';
|
|
|
|
const src = readFileSync(new URL('../src/philes.js', import.meta.url), 'utf8');
|
|
const ids = [...src.matchAll(/^ {2}'([a-z0-9-]+)': \{/gm)].map(m => m[1]);
|
|
const dupes = ids.filter((id, i) => ids.indexOf(id) !== i);
|
|
|
|
const used = new Set();
|
|
// ids live in a lot of shapes now: phile('x'), phile: 'x', and bare entries in
|
|
// data tables. so: does the quoted id appear anywhere outside philes.js at all.
|
|
for (const d of ['../src/', '../src/levels/']) {
|
|
const dir = new URL(d, import.meta.url);
|
|
for (const f of readdirSync(dir)) {
|
|
if (!f.endsWith('.js') || f === 'philes.js') continue;
|
|
const t = readFileSync(new URL(f, dir), 'utf8');
|
|
for (const id of ids) if (t.includes(`'${id}'`)) used.add(id);
|
|
}
|
|
}
|
|
|
|
const missing = [...used].filter(id => !PHILES[id]);
|
|
const orphan = ids.filter(id => !used.has(id));
|
|
|
|
console.log(`${ids.length}/${TOTAL} written · ${ids.length - orphan.length} findable`);
|
|
if (dupes.length) console.log('DUPLICATE IDS (silently overwritten):', dupes.join(' '));
|
|
if (missing.length) console.log('REFERENCED BUT NOT WRITTEN:', missing.join(' '));
|
|
if (orphan.length) console.log(`not yet placed in a level (${orphan.length}):`, orphan.join(' '));
|
|
process.exit(dupes.length || missing.length ? 1 : 0);
|