diff --git a/README.md b/README.md
index f73cf56..fc94673 100644
--- a/README.md
+++ b/README.md
@@ -85,6 +85,28 @@ is the ANSam beat from hop 47: `SPACE` on the phase reversal, 450 ms, four clean
Terminal levels opt out with `noRetrain: true`, because stealing `SPACE` from someone
mid-sentence isn't a mechanic, it's a bug with a story attached.
+## The philes
+
+All 200 are written, in [`src/philes.js`](src/philes.js), and all 200 are findable.
+Read them with `P`.
+
+The voice rule is the whole thing: **each one is written by somebody, in the year it
+happened, who did not know they were in a game.** Nobody explains anything for the
+player's benefit and nothing is written in hindsight.
+
+Where they live follows from that. The board is 1991, so the board cannot carry a
+file about WannaCry — it carries the seven read-online areas (g-philes, the law, the
+scene, virus, the iron, telephony, unix), and reading is free while *downloading*
+costs MTU, which is how a real board worked. Everything after 1992 is on **the
+climb**, 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, and they arrive quietly — thirty-six
+notifications in fifty seconds is a wall, not a level, so `ctx.phile(id, …, quiet)`
+collects them and the count lands once at the top.
+
+`node tools/philecheck.mjs` — duplicate ids in an object literal overwrite silently,
+which cost an hour once. It also reports anything written but not placed in a level.
+
## Audio
[`src/audio.js`](src/audio.js) — every tone is oscillators, nothing is sampled, and
@@ -148,5 +170,4 @@ to zero in a hidden tab and that makes the game look broken when it's only aslee
- **Multiplayer (`CHECKSUM`)** — the Journey-style anonymous pass-by. Needs a server; add when there's something to connect to.
- **Playing both fragments** — fragmenting costs TTL, it doesn't split you into two packets you alternate between. Add when a level has a corridor narrow enough to need it.
-- **The 200 philes** — 74 written in [`src/philes.js`](src/philes.js), all readable with `P`. Adding the next 126 is a writing job, not a code job: put the text in that file, call `ctx.phile('id')` wherever it should be found.
-- Levels run 3–8 minutes against a design doc that asks for 5–20. The genres, mechanics and set pieces are all in — the *duration* is still short. See [STAGES.md](STAGES.md) for a per-hop breakdown of what's in and what's next.
+- Levels run 5–12 minutes against a design doc that asks for 5–20. The genres, mechanics and set pieces are all in — the *duration* is still short. See [STAGES.md](STAGES.md) for a per-hop breakdown of what's in and what's next.
diff --git a/STAGES.md b/STAGES.md
index 70963e3..588d57b 100644
--- a/STAGES.md
+++ b/STAGES.md
@@ -358,7 +358,7 @@ TIME_WAIT with FIN saying *told you*, then it loops to hop 1.
| | |
|---|---|
-| **The philes** | 75 of 200 written. All in `src/philes.js`, all readable with `P`. Pure writing. |
+| ~~**The philes**~~ | **DONE** — 200 of 200 written and all 200 placed. The board carries everything up to 1991 in seven read-online areas; the climb carries everything after, as signs on the wall. `node tools/philecheck.mjs` verifies. |
| ~~**Audio**~~ | **DONE** — `src/score.js`. Every bed derived from a real number: 1 kHz reference tone, 50 Hz mains, 0x90 as a pitch, 45.45 baud, the 1200 Hz carrier, 20 Hz ringing current, the six MF tones, 2600 folded six times. The handshake is scored silent on purpose. |
| ~~**The retrain**~~ | **DONE** — `src/retrain.js`. Fires once per run after hop 51, drops you a notch, and the recovery is the 450 ms ANSam beat from hop 47. Terminal levels opt out. |
| **Difficulty** | Nothing scales. TTL is the only pressure and it's linear. |
diff --git a/card.html b/card.html
index 599749c..d91a7ba 100644
--- a/card.html
+++ b/card.html
@@ -68,6 +68,7 @@ the older it gets the more it trusts you.
| Audio | Required. Several mechanics are inaudible without it, and one boss is unbeatable if you cannot hear the beat. |
| Runs on | Any browser with WebGL2 and Web Audio. No install, no account, no server. Progress is in localStorage. |
| Data | None leaves your machine. There is no analytics, no telemetry, no network call after the page loads. |
+ | Reading | 200 philes — text collectibles, all of them findable, none of them required. Roughly 30,000 words, written as if by someone in the year it happened who did not know they were in a game. |
| Built with | ~9,000 lines of hand-written JavaScript and one vendored copy of three.js. No engine, no build step, no dependencies. |
@@ -123,8 +124,8 @@ on a wire.
What it does not do
- - It is short for what it is. Each hop runs 3–8 minutes against a design document that asks for 5–20. The genres, mechanics and set pieces are all in; the duration is not.
- - The philes are incomplete. 75 of a planned 200 text collectibles are written. The rest is a writing job, not a code job.
+ - It is still short for what it is. A hop runs 5–12 minutes now against a design document that asks for 5–20, and the shorter ones are short. The genres, mechanics and set pieces are all in.
+
- There is no multiplayer. The design calls for anonymous pass-by contact between players. It needs a server and there isn't one.
- It is not accessible. There is no colourblind mode, no remappable input, no subtitles for the non-speech audio cues, and at least one mechanic is gated behind hearing a 450 ms rhythm. This is a real limitation and not a stylistic choice.
- Performance is untuned. It runs on a modern laptop. It has not been profiled on anything else, and one level puts 4,200 instanced meshes on screen.
diff --git a/src/levels/board.js b/src/levels/board.js
index bb2060a..c359c8b 100644
--- a/src/levels/board.js
+++ b/src/levels/board.js
@@ -162,12 +162,100 @@ const TW_MAP = {
const TW_PORTS = { 2: 'BBS', 3: 'SBB', 5: 'SSB', 6: 'BSS', 7: 'SBS', 8: 'BBB', 9: 'SSS', 10: 'BBB' };
const TW_BASE = { ore: 18, org: 32, equ: 61 };
+// the rest of the board. everything here was written before the board closed —
+// nothing after 1991 exists yet, because nobody has done it yet. reading online
+// is free; only area 1 gets carried, and carrying is what costs you.
+const AREAS = [
+ { name: 'g-philes', note: 'general files. somebody sat down and wrote these.', items: [
+ ['mentor', 'CONSC.TXT', 'somebody wrote it the week he got arrested'],
+ ['conscience', 'HACKER.TXT', 'the one everybody prints out'],
+ ['2600mag', '2600-SUB.TXT','a quarterly, and it costs a quarter'],
+ ['cdc', 'CDC-001.TXT', 'a text file group out of a slaughterhouse'],
+ ['defcon', 'DC1-FLYR.TXT','somebody is throwing a party in Las Vegas'],
+ ['mod-lod', 'THE-WAR.TXT', 'New York against Texas, on the switch'],
+ ['jargon-file', 'JARGON.TXT', 'a dictionary of about four hundred people'],
+ ['bofh', 'BOFH-01.TXT', 'a story about the person who has root'],
+ ['rtfm', 'RTFM.TXT', 'rude, and nearly always correct'],
+ ['the-well', 'THE-WELL.TXT','you own your own words'],
+ ]},
+ { name: 'the law', note: 'read these before you need them.', items: [
+ ['sundevil', 'SUNDEVIL.TXT','forty-two machines in fourteen cities'],
+ ['cfaa', 'CFAA-86.TXT', 'exceeding authorised access, whatever that is'],
+ ['eff', 'EFF-FND.TXT', 'somebody had to be on the other side'],
+ ['captain-zap', 'ZAP-1981.TXT','he moved the clocks'],
+ ['poulsen', 'KIIS-102.TXT','caller one hundred and two'],
+ ['the-414s', '414S.TXT', 'a boy on the cover of a magazine'],
+ ['wargames', 'WARGAMES.TXT','that is where the word comes from'],
+ ['ccc-btx', 'BTX-HH.TXT', 'they gave it back at a press conference'],
+ ['mitnick', 'KM-FILE.TXT', 'the one they made a story out of'],
+ ['l0pht', 'L0PHT.TXT', 'we can take it down in half an hour'],
+ ]},
+ { name: 'the scene', note: 'not written by us. moved by us.', items: [
+ ['courier', 'CRIER.NFO', 'zero day means today'],
+ ['cracktro', 'INTRO.TXT', 'the bit before the game'],
+ ['nfo', 'HOWTO.NFO', 'why every one of these is drawn the same'],
+ ['lenslok', 'LENSLOK.TXT', 'the protection you hold up to the screen'],
+ ['keygen-music', 'TRACKER.TXT', 'why the generator has a tune'],
+ ]},
+ { name: 'v i r u s', note: 'do not run any of this on the family machine.', items: [
+ ['elk-cloner', 'CLONER.TXT', 'a fifteen year old and a boot sector'],
+ ['brain', 'BRAIN-C.TXT', 'their real address is in it'],
+ ['aids-trojan', 'AIDS-DSK.TXT','twenty thousand floppies in the post'],
+ ['fork-bomb', 'FORKB.TXT', 'it is thirteen characters'],
+ ['morris', 'RTM-88.TXT', 'one in seven'],
+ ]},
+ { name: 'the iron', note: 'how any of this got built.', items: [
+ ['punch-card', '80COL.TXT', 'eighty columns, and why your screen is'],
+ ['core-memory', 'CORE.TXT', 'it was woven, by hand, under a lens'],
+ ['pdp11', 'PDP11.TXT', 'the machine the language is shaped like'],
+ ['altair', 'ALTAIR.TXT', 'you enter the program with switches'],
+ ['homebrew', 'HBCC-LTR.TXT','the open letter'],
+ ['tape-load', 'TAPELOAD.TXT','the noise is the program'],
+ ['vt100', 'VT100.TXT', 'escape, bracket, two, J'],
+ ['hayes', 'ATCMD.TXT', 'three pluses and a second of silence'],
+ ['coupler', 'COUPLER.TXT', 'why it is a rubber cup and not a plug'],
+ ['isdn', 'ISDN.TXT', 'ready twenty years early, priced for a business'],
+ ]},
+ { name: 'telephony', note: 'the network is a machine and it is documented.', items: [
+ ['esquire', 'ESQUIRE.TXT', 'the article that told everybody'],
+ ['joybubbles', 'JOY.TXT', 'perfect pitch and a lot of time'],
+ ['inward', 'INWARD.TXT', 'the operator has an operator'],
+ ['silver-box', 'SILVER.TXT', 'the fourth column, and who it is for'],
+ ['payphone', 'COINTONE.TXT','the box knows by the sound'],
+ ['cocot', 'COCOT.TXT', 'a payphone that belongs to a petrol station'],
+ ['wats', 'EXTENDER.TXT','four digits is not a lot'],
+ ['operator', 'SOCIAL.TXT', 'she is not a security system'],
+ ['busy-signal', 'TONES.TXT', 'sixty a minute, and reorder is twice that'],
+ ['call-waiting', 'STAR70.TXT', 'the click that eats forty minutes'],
+ ]},
+ { name: 'unix', note: 'somebody typed all of this out for us.', items: [
+ ['pipes', 'PIPES.TXT', 'the vertical bar'],
+ ['everything-file','EVERYFIL.TXT','the disk is a file, the terminal is a file'],
+ ['devnull', 'DEVNULL.TXT', 'the bit bucket'],
+ ['editors', 'VIVSEMAC.TXT','why the commands are one letter'],
+ ['man-page', 'MANPAGE.TXT', 'read the section at the bottom'],
+ ['hosts-equiv', 'RHOSTS.TXT', 'trust by name'],
+ ['telnet', 'CLEARTXT.TXT','it is all in the clear'],
+ ['anon-ftp', 'ANONFTP.TXT', 'the password is your address, as a courtesy'],
+ ['uucp', 'BANGPATH.TXT','you do not have an address, you have directions'],
+ ['salt', 'SALT.TXT', 'why two people with the same password differ'],
+ ['toctou', 'RACE.TXT', 'between the check and the use'],
+ ['sendmail-debug','SMDEBUG.TXT','it was left switched on'],
+ ]},
+];
+
const FILES = [
- { name: 'PHRACK24.ZIP', size: '84,102', note: 'issue 24. the one with the E911 document in it.' },
- { name: 'TAP-42.TXT', size: '11,904', note: 'youth international party line, later TAP.' },
- { name: 'BLUEBOX.EXE', size: ' 6,288', note: 'MF tones through a PC speaker. it will not work. try it anyway.' },
- { name: 'LOD-TJ-1.TXT', size: '38,441', note: 'legion of doom technical journal, volume one.' },
- { name: 'TONELOC.ZIP', size: '52,010', note: 'wardialer. give it an exchange and go to bed.' },
+ { name: 'PHRACK24.ZIP', size: '84,102', note: 'issue 24. the one with the E911 document in it.', phile: 'e911' },
+ { name: 'TAP-42.TXT', size: '11,904', note: 'youth international party line, later TAP.', phile: 'tap' },
+ { name: 'BLUEBOX.EXE', size: ' 6,288', note: 'MF tones through a PC speaker. it will not work. try it anyway.', phile: 'redbox' },
+ { name: 'LOD-TJ-1.TXT', size: '38,441', note: 'legion of doom technical journal, volume one.', phile: 'blackbox' },
+ { name: 'TONELOC.ZIP', size: '52,010', note: 'wardialer. give it an exchange and go to bed.', phile: 'loop' },
+ { name: 'CBBS-HIST.TXT', size: ' 4,102', note: 'how the first one got built in a blizzard.', phile: 'cbbs' },
+ { name: 'XMODEM.DOC', size: ' 9,540', note: '128 bytes and a checksum. the whole protocol.', phile: 'xmodem' },
+ { name: 'NODELIST.297', size: '61,338', note: 'every board in the world. updated weekly, by hand.', phile: 'nodelist' },
+ { name: 'ARTPACK.ZIP', size: '78,220', note: 'this month\'s ansi. somebody drew all of it a cell at a time.', phile: 'ansi-art' },
+ { name: 'SJGAMES.TXT', size: '15,776', note: 'they raided a company that makes boardgames.', phile: 'sjgames' },
+ { name: 'CRACKING.TXT', size: '22,110', note: 'weak bits, lenslok, and why the copy works better.', phile: 'weak-bits' },
];
export default {
@@ -376,6 +464,7 @@ export default {
S.elite = true;
S.ratioOff = true;
ctx.game.flags.lastCall = true;
+ ctx.phile('no-carrier');
ctx.phile('lastcall');
ctx.say('nine years. forty-four users. one phone line.', 5600);
await term.ask('|08 [enter]|07');
@@ -499,6 +588,45 @@ export default {
},
async files(ctx, term) {
+ // area menu first. area 1 is the one you can carry away.
+ while (true) {
+ term.clear();
+ term.writeln('|11 file areas\n');
+ term.writeln(`|08 1 |15${'text'.padEnd(12)}|07${String(FILES.length).padStart(3)} files|08 the notable ones. downloadable.`);
+ AREAS.forEach((a, i) => term.writeln(
+ `|08 ${String(i + 2).padStart(2)} |15${a.name.padEnd(12)}|07${String(a.items.length).padStart(3)} files|08 ${a.note}`));
+ term.writeln('');
+ term.writeln('|03 you can read anything here online for nothing. downloading is what');
+ term.writeln('|03 costs you — a file you carry is a file you have to fit down the next hop.');
+ term.writeln('|08 [|15#|08] area [enter] back');
+ const sel = parseInt((await term.ask('|07area: |15')).trim(), 10);
+ if (sel === 1) { await this.filearea1(ctx, term); continue; }
+ const A2 = AREAS[sel - 2];
+ if (!A2) return;
+ await this.readarea(ctx, term, A2);
+ }
+ },
+
+ // reading online: free, and the only way most of this is ever going to be seen
+ async readarea(ctx, term, area) {
+ while (true) {
+ term.clear();
+ term.writeln(`|11 ${area.name}\n`);
+ area.items.forEach(([id, name, note], i) => term.writeln(
+ `|08 ${String(i + 1).padStart(2)} |15${name.padEnd(14)}|08${ctx.game.philes.has(id) ? '|02 read ' : ' '}|08${note}`));
+ term.writeln('');
+ term.writeln('|08 [|15#|08] read online [enter] back');
+ const n = parseInt((await term.ask('|07: |15')).trim(), 10);
+ const it = area.items[n - 1];
+ if (!it) return;
+ await term.paint(`|07 reading |15${it[1]}|07 ...\n`, 2200);
+ A.blip(1200, 0.01, 0.05);
+ ctx.phile(it[0]);
+ await ctx.sleep(500);
+ }
+ },
+
+ async filearea1(ctx, term) {
term.clear();
term.writeln('|11 file area 1 — text\n');
FILES.forEach((f, i) => term.writeln(`|08 ${String(i + 1).padStart(2)} |15${f.name.padEnd(16)}|07${f.size.padStart(8)} |08${f.note}`));
@@ -532,7 +660,7 @@ export default {
ctx.game.cargo.push(f.name);
ctx.game.mtu = Math.max(576, 1500 - ctx.game.cargo.length * 180);
ctx.refreshHud();
- ctx.phile('file-' + n, f.name, f.note);
+ ctx.phile(f.phile || ('file-' + n), f.name, f.note);
ctx.say(`you are carrying ${ctx.game.cargo.length}. your MTU is down to ${ctx.game.mtu}.`, 3600);
await ctx.sleep(1000);
},
diff --git a/src/levels/climb.js b/src/levels/climb.js
index 2c378f5..8369613 100644
--- a/src/levels/climb.js
+++ b/src/levels/climb.js
@@ -10,6 +10,51 @@ import * as A from '../audio.js';
import * as In from '../input.js';
import { sprite, panel, glow, rng, clamp, damp } from '../kit.js';
+// ── the years, on the wall, going past
+//
+// The climb is the only level that travels forwards in time, so it is the only
+// place the rest of this can be. Each one is a sign you pass at speed; you get
+// the file whether or not you turn your head, because you were there for it.
+const MARKS = [
+ [ 60, '1969', 'the first message got two letters out', 'first-message'],
+ [ 110, '1977', 'fifty-six bits, and an argument about why', 'des'],
+ [ 160, '1983', 'the whole network changed protocol on one day', 'flag-day'],
+ [ 200, '1987', 'they moved every newsgroup and invented alt', 'great-renaming'],
+ [ 300, '1987', 'somebody wanted to see the route, not the end', 'traceroute'],
+ [ 360, '1991', 'a label on a machine at CERN: do not power down', 'first-web'],
+ [ 420, '1992', 'the sixth of march, and almost nothing happened', 'michelangelo'],
+ [ 540, '1993', 'they announced a licence fee, and lost', 'gopher'],
+ [ 600, '1993', 'september stopped ending', 'eternal-september'],
+ [ 660, '1993', 'a chip with a spare key in it', 'clipper'],
+ [ 780, '1993', 'they classified mathematics as a munition', 'pgp'],
+ [ 840, '1994', 'a stateless protocol was given a basket', 'cookies'],
+ [ 900, '1995', 'the backbone was switched off and nothing broke', 'nsfnet'],
+ [1020, '1995', 'source code is speech', 'bernstein'],
+ [1080, '1995', 'the shirt is a munition', 'munitions'],
+ [1140, '1995', 'thirteen, because fourteen does not fit', 'root-servers'],
+ [1260, '1996', 'the word arrives for something much older', 'phish'],
+ [1300, '1996', 'a packet slightly too big to be reassembled', 'ping-death'],
+ [1340, '1997', 'the refusals are not all the same refusal', 'nmap'],
+ [1380, '1998', 'the quote closes early', 'sqli'],
+ [1420, '1998', 'the lock is protected, not the thing behind it', 'dmca'],
+ [1460, '1996', 'the argument that ends with a second phone line', 'second-line'],
+ [1500, '1998', 'fifty-six down and thirty-three up, and here is why', 'v90'],
+ [1540, '1999', 'two digits, and everyone stayed up', 'y2k'],
+ [1580, '2000', 'nobody could charge anybody', 'iloveyou'],
+ [1620, '2000', 'the page says it, so the browser believes it', 'xss'],
+ [1700, '2003', 'three hundred and seventy-six bytes, ten minutes', 'slammer'],
+ [1740, '2004', 'you cannot hide in an empty room', 'tor'],
+ [1790, '2010', 'four unpublished holes, for one room', 'stuxnet'],
+ [1840, '2014', 'the code was twenty-five years old', 'shellshock'],
+ [1890, '2016', 'sixty-one factory passwords', 'mirai'],
+ [1900, '2013', 'he downloaded papers. thirteen felony counts.', 'swartz'],
+ [1940, '2017', 'the patch had been out for two months', 'equifax'],
+ [1965, '2017', 'somebody registered the domain for ten dollars', 'wannacry'],
+ [1990, '2017', 'somebody kept it, and then lost it', 'eternalblue'],
+ [2040, '2018', 'the guess it threw away left a mark', 'meltdown'],
+ [2090, '2020', 'they did not break in, they got built in', 'solarwinds'],
+];
+
// in reverse, because that is the point. `gate` is the verb that era taught you.
const ZONES = [
{ at: 0, name: '1971 · the trunk', bg: 0x030a14, fog: 0x0a1930, col: 0x2a5a8a,
@@ -51,7 +96,7 @@ export default {
async mount(ctx) {
const { scene, camera } = ctx;
- S = { t: 0, z: 0, x: 0, speed: 44, zone: -1, rings: [], done: false,
+ S = { t: 0, z: 0, x: 0, speed: 44, zone: -1, mark: 0, picked: 0, rings: [], done: false,
spec: null, hammer: 0, hammered: false, log4: false, gateOpen: false,
gates: [], invert: 0, embers: [], stall: 0, passed: 0, failed: 0 };
window.__S = S; // debug handle
@@ -92,6 +137,23 @@ export default {
S.gateTag.position.set(0, -6.0, -30); scene.add(S.gateTag);
S.gateTag.visible = false;
+ // the years themselves, bolted to the shaft wall, alternating sides
+ const MODERN_AT = ZONES[ZONES.length - 1].at;
+ S.marks = MARKS.map(([at, year, line], i) => {
+ const g = new THREE.Group();
+ const side = i % 2 ? 1 : -1;
+ // the last zone is the white one. pale blue on near-white is not a sign,
+ // it is a rumour — so the ink flips where the walls do.
+ const ink = at >= MODERN_AT;
+ const yr = sprite(year, { color: ink ? '#123c4e' : '#8fb6c6', size: 1.5 });
+ yr.position.set(side * 9.5, 3.2, 0); g.add(yr);
+ const tx = sprite(line, { color: ink ? '#41606e' : '#4d6a78', size: 0.72 });
+ tx.position.set(side * 9.5, 1.4, 0); g.add(tx);
+ g.visible = false; // moved into place each frame, like the gates
+ scene.add(g);
+ return g;
+ });
+
// ── one gate per era, built from that era's furniture
ZONES.forEach(Z => { if (Z.gate) S.gates.push(this.makeGate(ctx, Z)); });
@@ -228,6 +290,19 @@ export default {
// ── zone crossfade
let zi = 0;
+ // the camera does not travel — the shaft does. so the signs come to you.
+ for (let i = 0; i < S.marks.length; i++) {
+ const zz = -MARKS[i][0] + S.z;
+ S.marks[i].position.z = zz;
+ S.marks[i].visible = zz > -190 && zz < 10;
+ }
+
+ // you get the file for having been there, whether or not you looked
+ while (S.mark < MARKS.length && S.z >= MARKS[S.mark][0]) {
+ const m = MARKS[S.mark++];
+ if (ctx.phile(m[3], null, null, true)) S.picked++; // quiet — counted at the top
+ }
+
for (let i = 0; i < ZONES.length; i++) if (S.z >= ZONES[i].at) zi = i;
const Z = ZONES[zi];
if (zi !== S.zone) {
@@ -238,6 +313,10 @@ export default {
document.body.classList.toggle('ink-dark', zi === ZONES.length - 1);
A.burst(220 + zi * 90, 330 + zi * 90, 0.25, 0.14);
if (Z.note) ctx.say(Z.note, 4200);
+ // each era drops what it is known for on the way past
+ const DROP = { 'the board': 'ansi-art', '1988': 'first-message', 'efnet': 'ping-death',
+ 'the stack': 'traceroute', 'modern': 'heartbleed' };
+ if (DROP[Z.name]) ctx.phile(DROP[Z.name]);
}
S.rings.forEach((r, i) => {
@@ -277,6 +356,7 @@ export default {
ctx.game.flags.climbClean = S.passed;
ctx.game.flags.climbGates = S.gates.length;
if (S.passed >= 6) ctx.phile('climb');
+ if (S.picked) ctx.say(`you read ${S.picked} of them on the way past. P.`, 4000);
await ctx.card('ROOT', `${S.passed} of ${S.gates.length} clean · one hop remaining`, 3600);
ctx.next();
})();
@@ -435,6 +515,7 @@ export default {
A.burst(1320, 1980, 0.4, 0.2);
ctx.say('a bit flipped in a row you were never allowed to touch. you did not read it. you shouted next to it.', 6000);
ctx.phile('rowhammer');
+ ctx.phile('xz');
}
}
}
@@ -466,6 +547,7 @@ export default {
await ctx.sleep(5800);
ctx.say('you did nothing. you held up a sign.', 4600);
ctx.phile('log4shell');
+ ctx.phile('y2k');
S.signBack.visible = false;
})();
}
diff --git a/src/levels/coldboot.js b/src/levels/coldboot.js
index b60cfae..b6df55f 100644
--- a/src/levels/coldboot.js
+++ b/src/levels/coldboot.js
@@ -254,6 +254,7 @@ export default {
if (!S.strayed && Math.hypot(body.pos.x, body.pos.z) < 30) {
S.strayed = true;
ctx.phile('ring-buffer');
+ ctx.phile('rfc');
ctx.say('you have left your slot. this has never mattered before.', 3800);
}
diff --git a/src/levels/handshake.js b/src/levels/handshake.js
index 091b580..4f03443 100644
--- a/src/levels/handshake.js
+++ b/src/levels/handshake.js
@@ -137,7 +137,7 @@ const BOSS = {
}
},
win(ctx) { S.b.tone.stop(0.5); nextPhase(ctx, 'the line is open. now you have to say something.'); },
- exit() { S.b.tone?.stop(0.2); },
+ exit() { S.b?.tone?.stop(0.2); },
},
// ── 2. DTMF — every digit is a row tone crossed with a column tone.
@@ -280,7 +280,7 @@ const BOSS = {
}
},
win(ctx) { S.b.tone.stop(); ctx.phile('ansam'); nextPhase(ctx, 'it has stopped asking politely.'); },
- exit() { S.b.tone?.stop(); },
+ exit() { S.b?.tone?.stop(); },
},
// ── 4. V.8bis — THE SCREAM. The modem asking what can you do.
@@ -437,16 +437,18 @@ const BOSS = {
const ORDER = ['dialtone', 'dtmf', 'ans', 'scream', 'carrier'];
async function nextPhase(ctx, line) {
- if (S.switching) return;
+ if (S.switching || S.dead) return;
S.switching = true;
const cur = BOSS[ORDER[S.i]];
if (cur && cur.exit) cur.exit();
for (let i = S.arena.children.length - 1; i >= 0; i--) S.arena.remove(S.arena.children[i]);
if (line) { ctx.say(line, 3200); await ctx.sleep(1900); }
+ if (S.dead) return;
S.i++;
if (S.i >= ORDER.length) { S.switching = false; return; }
const nb = BOSS[ORDER[S.i]];
await ctx.card(nb.name, nb.freqs, 2400);
+ if (S.dead) return;
ctx.say(nb.hint, 3600);
if (S.i > 0) ctx.spendTTL(1);
nb.enter(ctx);
@@ -494,6 +496,7 @@ export default {
ctx.hint('WASDmove');
await ctx.card('THE HANDSHAKE', 'hop 45 · five tones, in order', 3000);
+ if (S.dead) return; // left via the map while the card was up
nextPhase(ctx, null);
},
@@ -533,6 +536,7 @@ export default {
},
async unmount(ctx) {
+ S.dead = true; // any await still in flight bails on resume
const cur = BOSS[ORDER[S.i]];
if (cur && cur.exit) cur.exit();
ctx.link.glitch = 0;
diff --git a/src/levels/shell.js b/src/levels/shell.js
index 25c3885..8192e58 100644
--- a/src/levels/shell.js
+++ b/src/levels/shell.js
@@ -194,6 +194,8 @@ export default {
if (cmd === 'help') {
term.writeln('|08 who news talk minna make uname uptime logout');
+ term.writeln('|08 cat — try /etc/passwd, /etc/shadow, trust.txt, setuid.4, its.txt');
+ term.writeln('|08 man — ping finger whois tcpdump named hosts');
term.writeln('|08 (and it will keep compiling while you do all of those.)');
} else if (cmd === 'who') {
WHO.forEach(([u, tty, at, from]) =>
@@ -202,6 +204,31 @@ export default {
term.writeln('|03 thirty-eight logged in. the board had forty-four users in nine years');
term.writeln('|03 and could hold exactly one of them at a time.');
ctx.phile('multiuser');
+ } else if (cmd === 'man') {
+ const MAN = {
+ ping: ['ping', 'PING(8) — send ICMP ECHO_REQUEST to network hosts. "author: mike muuss."'],
+ finger: ['finger', 'FINGER(1) — user information lookup. reads .plan and .project from the home directory.'],
+ whois: ['whois', 'WHOIS(1) — internet user name directory service. try a surname.'],
+ tcpdump: ['tcpdump', 'TCPDUMP(1) — dump traffic on a network. requires you to be root, and to be honest.'],
+ named: ['dns', 'NAMED(8) — internet domain name server. see also: the file this replaced.'],
+ hosts: ['dns', 'HOSTS(5) — host name data base. historical. see named(8).'],
+ };
+ const d = MAN[arg];
+ if (!d) { term.writeln('|12 No manual entry for ' + (arg || '')); continue; }
+ term.writeln('|07 ' + d[1]);
+ ctx.phile(d[0]);
+ } else if (cmd === 'cat' || cmd === 'more') {
+ const DOCS = {
+ '/etc/passwd': ['passwd', 'root:x9Kj2mQp:0:1:operator:/:/bin/csh\n ...and 400 more, and every one of those hashes is readable by you.'],
+ 'trust.txt': ['trusting-trust', 'Ken Thompson, 1984. "you can\'t trust code that you did not totally create yourself."'],
+ '/etc/shadow': ['passwd', 'No such file or directory. (it does not exist yet. that is the point.)'],
+ 'setuid.4': ['setuid', 'one bit: run as the owner, not the runner. necessary, elegant, and half of everything.'],
+ 'its.txt': ['its-nopass', 'the AI Lab machine had no passwords, on purpose, and it worked for a while.'],
+ };
+ const d = DOCS[arg];
+ if (!d) { term.writeln('|12 ' + (arg || '') + ': No such file or directory.'); continue; }
+ term.writeln('|07 ' + d[1]);
+ ctx.phile(d[0]);
} else if (cmd === 'uname') {
if (S.unamePatched) {
term.writeln(arg === '-a'
diff --git a/src/levels/stack.js b/src/levels/stack.js
index 77bfb40..7f98ae7 100644
--- a/src/levels/stack.js
+++ b/src/levels/stack.js
@@ -437,7 +437,7 @@ export default {
A.burst(660 + S.i * 90, 990 + S.i * 90, 0.28, 0.16);
(async () => {
if (shed) ctx.say(`shed ${shed}`, 2200);
- if (S.i === 0) ctx.phile('osi');
+ if (S.i === 0) { ctx.phile('osi'); ctx.phile('layers'); }
await ctx.sleep(700);
S.i = nxt;
S.spawn = S.body.pos.clone();
diff --git a/src/main.js b/src/main.js
index 0268b9d..9169d2b 100644
--- a/src/main.js
+++ b/src/main.js
@@ -120,11 +120,14 @@ const ctx = {
},
say: HUD.say, speak: HUD.speak, hint: HUD.hint, card: HUD.card, sleep: HUD.sleep,
hush: HUD.hush, flash: HUD.flash, expire: () => expire(),
- phile(id, title, body) {
+ // quiet: collect it without a toast. for places that hand over a lot at once —
+ // the climb passes thirty-four years of signs in under a minute and announcing
+ // each one would be a wall of notifications instead of a level.
+ phile(id, title, body, quiet) {
if (game.philes.has(id)) return false;
game.philes.add(id);
const p = phileLookup(id, title, body);
- HUD.say(`phile ${p.title} [${game.philes.size}/${PHILE_TOTAL}] P to read`, 3000);
+ if (!quiet) HUD.say(`phile ${p.title} [${game.philes.size}/${PHILE_TOTAL}] P to read`, 3000);
A.blip(1800, 0.05, 0.05); setTimeout(() => A.blip(2400, 0.06, 0.05), 70);
phileLog.push(p);
return true;
diff --git a/src/philes.js b/src/philes.js
index a93811f..73d4484 100644
--- a/src/philes.js
+++ b/src/philes.js
@@ -1108,6 +1108,1589 @@ wearing it.
the reply was not empty.`,
},
+
+ // ── the deep cuts: phreaking ───────────────────────────────────────────
+
+ 'esquire': {
+ title: 'Secrets of the Little Blue Box',
+ from: 'Esquire, October 1971',
+ body: `a magazine article in a men's magazine, between the adverts, explaining in
+plain english that the telephone network could be operated by anybody who
+could make the right noise.
+
+it did not include the frequencies. it did not need to. it told several
+hundred thousand people that the thing was possible, and possible is the
+only part that is hard.
+
+two of the people who read it were students in california who started
+building the boxes and selling them out of a dormitory. one of them later
+founded a computer company. so did the other one.`,
+ },
+
+ 'tap': {
+ title: 'TAP',
+ from: 'Youth International Party Line, later TAP, 1971-1984',
+ body: `a newsletter. photocopied, stapled, posted to whoever sent stamps.
+
+it started as a way of not paying the phone company on political grounds
+and quietly turned into the only technical publication in the world that
+would print how any of it actually worked.
+
+circulation peaked somewhere in the thousands. the schematics were often
+wrong. it is the direct ancestor of every text file in this game, and of
+the word "phile" itself.`,
+ },
+
+ 'redbox': {
+ title: 'the red box',
+ from: 'a payphone, any payphone',
+ body: `a payphone told the exchange it had been paid by playing tones down the
+line. a nickel was one beep of 1700 and 2200 hertz together. a dime was
+two. a quarter was five, in a particular rhythm.
+
+it did not check. there was nothing to check with. the coin mechanism and
+the tone generator were the same box, and the exchange a mile away simply
+believed whatever noise arrived.
+
+people built these into pocket tone dialers by changing one crystal.`,
+ },
+
+ 'blackbox': {
+ title: 'the black box',
+ from: 'the other end of the call',
+ body: `billing started when the called phone went off-hook, because that is how
+the exchange knew somebody had answered.
+
+so you put a resistor across your own line that let the ring through but
+never quite told the exchange you had picked up. the caller could hear you.
+you could hear them. the meter never started.
+
+it worked for about fifteen minutes a call before the equipment got
+suspicious, which was long enough, and it cost about a dollar to build.`,
+ },
+
+ 'loop': {
+ title: 'the loop-around',
+ from: 'a pair of numbers that only talk to each other',
+ body: `test circuits. two numbers wired together so a lineman could ring one from
+one end of town and the other from the other and check the pair.
+
+which means two people who both know the pair can talk, free, forever, on a
+circuit that appears on nobody's bill — because it is not a call to
+anywhere, it is a call to a piece of test equipment that has two ends.
+
+whole friendships lived on those. some of them for years.`,
+ },
+
+ 'joybubbles': {
+ title: 'Joybubbles',
+ from: 'born blind, with perfect pitch, 1949',
+ body: `he could whistle 2600 hertz. not approximately - exactly, on demand, cold,
+with no reference tone, which almost nobody alive can do.
+
+he found the network at seven years old by dialling numbers to hear what
+answered, and remembering all of it. he was running free conference calls
+across the country before he was a teenager, because there was nothing else
+to do and he was extremely good at it.
+
+he legally changed his name to Joybubbles in 1991 and spent his later years
+running a phone line where he read children's stories to anybody who rang.
+he never once used any of it to make money.`,
+ },
+
+ 'inward': {
+ title: 'the inward operator',
+ from: 'social engineering, before it had a name',
+ body: `you did not need a blue box if you could sound like the phone company.
+
+there was an operator whose whole job was helping other operators, and she
+was not equipped to doubt you, because in her world the only people who
+knew the internal codes were colleagues. so you learned the codes, and the
+tone of voice, and you asked for what you wanted, and you got it.
+
+no equipment. no frequencies. the largest machine ever built, operated by
+sounding like you belonged in it.`,
+ },
+
+ // ── the deep cuts: the boards ──────────────────────────────────────────
+
+ 'cbbs': {
+ title: 'the first one',
+ from: 'Chicago, February 1978',
+ body: `there was a blizzard. nobody could get to the club meeting, so two members
+built a way to leave messages for each other instead - one wrote the
+software, the other built the hardware, in about two weeks, mostly because
+they were snowed in.
+
+CBBS. one line, 300 baud. they published how to build it, and within a few
+years there were thousands of them.
+
+the entire culture in this game exists because it snowed heavily in
+Illinois in January 1978.`,
+ },
+
+ 'xmodem': {
+ title: 'XMODEM',
+ from: 'Ward Christensen, 1977, given away',
+ body: `128 bytes, a checksum, and a one-byte answer meaning yes or no. send the
+block, wait, send it again if the answer is no.
+
+that is about as simple as a file transfer can be and still survive a line
+that loses things. he wrote it for himself, gave it away, and it became the
+way software moved between human beings for fifteen years.
+
+every warez board, every shareware disk, every text file in this game moved
+128 bytes at a time under a protocol nobody was ever paid for.`,
+ },
+
+ 'nodelist': {
+ title: 'the nodelist',
+ from: 'FidoNet - one text file, updated weekly',
+ body: `every board in the network, its number, its phone number, its operator, its
+hours. published once a week. everybody downloaded it. that was the routing
+table.
+
+there was no server. the network's entire topology was a document that
+people agreed to keep up to date, and it worked for fifteen years across
+tens of thousands of nodes.
+
+it was maintained by volunteers, because somebody had to.`,
+ },
+
+ 'ansi-art': {
+ title: 'the ANSI scene',
+ from: 'sixteen colours and eight block characters',
+ body: `eighty by twenty-five cells, sixteen colours, and half a dozen block glyphs
+of different densities. that is the whole palette.
+
+people made portraits with it. groups formed, released monthly art packs,
+had rivalries, split acrimoniously and reformed under new names - over a
+medium whose maximum resolution was a text screen on a machine that could
+not draw a diagonal line.
+
+almost none of it survives in a form anybody can still view.`,
+ },
+
+ // ── the deep cuts: unix ────────────────────────────────────────────────
+
+ 'passwd': {
+ title: 'why /etc/passwd is world-readable',
+ from: 'a decision from 1973 that everybody inherited',
+ body: `the password file has to be readable by everything, because everything
+needs to turn a user id into a name.
+
+so the passwords were hashed rather than stored - good - and the hashes
+were left in the file everybody can read, which seemed fine, because who
+would sit there guessing.
+
+people sat there guessing. the fix took until 1988, and the fix was to move
+the hashes into a second file called shadow. that is the entire solution:
+put it somewhere else.`,
+ },
+
+ 'trusting-trust': {
+ title: 'Reflections on Trusting Trust',
+ from: 'Ken Thompson, Turing Award lecture, 1984',
+ body: `he explains, calmly, that he could put a back door in the login program.
+
+then that he could put it in the compiler instead, so it inserts the back
+door whenever it compiles login - and delete it from login's source.
+
+then that he could teach the compiler to insert *that* whenever it compiles
+a compiler, and delete it from the compiler's source too. now the back door
+exists in no source code anywhere on earth, and copies itself into every
+future compiler forever.
+
+"you can't trust code that you did not totally create yourself."`,
+ },
+
+ 'setuid': {
+ title: 'setuid',
+ from: 'one bit',
+ body: `a bit on a file that says: when anybody runs this, run it as whoever owns
+it, not as whoever ran it.
+
+it is how an ordinary user changes their own password in a file they are
+not allowed to write. it is necessary and it is elegant.
+
+it is also why a bug in one small program is a bug in the entire machine,
+and roughly half of everything on the way down this stack starts with
+somebody finding a setuid binary that was not careful enough.`,
+ },
+
+ 'its-nopass': {
+ title: 'the machine with no passwords',
+ from: 'the MIT AI Lab',
+ body: `it had no passwords, deliberately. anybody could log in as anybody. you
+could watch what other people were typing and they could watch you.
+
+this was not an oversight and it was not naivety. it was a position: the
+machine belonged to the people using it, and locking each other out of
+parts of it would have made it a worse machine to think with.
+
+it worked, for a while, in a building where everyone knew everyone. every
+single thing after this point in the game is a consequence of that no
+longer being true.`,
+ },
+
+ // ── the deep cuts: the network ─────────────────────────────────────────
+
+ 'first-message': {
+ title: 'LO',
+ from: 'UCLA to SRI, 29 October 1969, 22:30',
+ body: `the first thing ever sent between two computers on the ARPANET was the word
+LOGIN.
+
+it crashed after two characters. so the first message was LO, and then
+they rang each other on the telephone to check what had happened, because
+the telephone worked.
+
+they brought it back up about an hour later and it went through. the log
+entry for the most important thing that happened that decade reads
+"talked to SRI host to host".`,
+ },
+
+ 'flag-day': {
+ title: 'the flag day',
+ from: '1 January 1983',
+ body: `the network changed protocol. NCP off, TCP/IP on, everywhere, on the same
+morning, because there was no way to run both.
+
+about four hundred hosts. they had over a year of warning and it still
+took months to finish, and people wore badges that said "I SURVIVED THE
+TCP/IP TRANSITION".
+
+it is the last time in history the entire internet did anything at once.
+There will not be another one.`,
+ },
+
+ 'rfc': {
+ title: 'Request for Comments',
+ from: 'RFC 1, April 1969',
+ body: `a graduate student had to write up what they had agreed, was terrified of
+seeming to speak for anybody, and so called it a "request for comments" and
+wrote it in a bathroom at three in the morning so as not to wake his
+housemates.
+
+that apologetic hedge became the name of the document series that
+standardises the internet. they are still called that. there are now
+thousands and the tone has never quite recovered from being set by somebody
+trying very hard not to presume.`,
+ },
+
+ 'traceroute': {
+ title: 'how traceroute cheats',
+ from: 'the TTL field, used sideways',
+ body: `nothing in the protocol tells you the path. there is no "list the routers"
+message and there never was.
+
+so: send a packet with a TTL of one. the first router decrements it to
+zero, throws it away, and politely posts you a note saying it did — and the
+note has its address on it. now send one with a TTL of two.
+
+the map in the corner of this game is built out of a stack of dead packets
+and the apologies their killers sent back.`,
+ },
+
+ 'ping-death': {
+ title: 'the ping of death',
+ from: '1996',
+ body: `the maximum size of an IP packet is 65,535 bytes. so nobody checked, because
+it cannot be bigger than the maximum, that is what maximum means.
+
+but a packet can be fragmented, and if the last fragment claims an offset
+near the top, the pieces reassemble into something larger than the maximum,
+into a buffer sized for the maximum.
+
+you could crash almost any machine on earth in 1996 by pinging it with a
+number that was too big.`,
+ },
+
+ // ── the deep cuts: what happened to people ─────────────────────────────
+
+ 'e911': {
+ title: 'the E911 document',
+ from: 'Phrack 24, and everything that followed',
+ body: `a text file describing the administration of the emergency call system. it
+was copied off a telephone company machine and printed in a hacker zine.
+
+the company valued it at seventy-nine thousand dollars, which is what
+turned it into a felony. at trial it emerged you could order the same
+information from them for thirteen dollars.
+
+the case collapsed. by then it had already produced a nationwide raid, a
+publisher raided over a game book, and, directly out of the wreckage, the
+Electronic Frontier Foundation.`,
+ },
+
+ 'sjgames': {
+ title: 'they raided a game company',
+ from: '1 March 1990',
+ body: `the Secret Service took the computers of a company in Austin that made
+tabletop roleplaying games, because one of their employees was on a board
+they were interested in.
+
+among the things seized was the entire manuscript of a game book about
+computer hacking, which the agents appear to have read as a manual.
+
+the company nearly went under. they sued and won. one of the people who
+helped pay the lawyers went on to found the EFF, and the reason there is
+any legal defence for this at all is that somebody took a boardgame
+publisher's hard drives.`,
+ },
+
+ 'l0pht': {
+ title: 'thirty minutes',
+ from: 'a Senate committee, 19 May 1998',
+ body: `seven people testified under their handles, which the committee allowed,
+and told a Senate panel that they could make the internet unusable
+nationwide in about half an hour.
+
+they were not exaggerating and they were not threatening. they had been
+saying it publicly for years and nobody with authority had asked.
+
+they had been doing this out of a shared loft in Boston with second-hand
+equipment. the flaw they were describing took another decade to fix.`,
+ },
+
+ 'mitnick': {
+ title: 'sequence numbers',
+ from: 'Christmas Day, 1994',
+ body: `the trick was that two machines trusted each other by address, and the only
+thing stopping you pretending to be one of them was that you had to guess a
+number.
+
+the number was supposed to be unpredictable. it was not. it went up by a
+fixed amount, so you could measure it, and then you could speak as somebody
+else without ever hearing the replies — talking into a phone you cannot
+listen to, and being believed.
+
+he did it. it took a year to find him, and it made both their names.`,
+ },
+
+ // ── the deep cuts: the machines lie to you ─────────────────────────────
+
+ 'weak-bits': {
+ title: 'weak bits',
+ from: 'copy protection, 1983',
+ body: `they wrote a section of the floppy disk so badly, on purpose, that reading
+it gave a different answer every time.
+
+so the game read that spot twice and compared. a real disk disagreed with
+itself. a copy — made by a drive doing its honest best to write down what
+it read — was perfectly consistent, and therefore fake.
+
+the only way to prove the disk was genuine was to prove it was unreliable.`,
+ },
+
+ 'lenslok': {
+ title: 'Lenslok',
+ from: 'a piece of plastic in the box, 1985',
+ body: `the game showed you a scrambled pattern. you held a prism lens against the
+screen and it resolved into two letters, which you typed in.
+
+it did not work. the lens was cut for one screen size, and televisions
+varied, so thousands of people who had paid for the game simply could not
+read the code and could not play it.
+
+there is a version of this argument still going on, forty years later,
+about whether the copy or the original is the one that works.`,
+ },
+
+ 'nfo': {
+ title: 'the .nfo file',
+ from: 'the scene, in ASCII',
+ body: `every release came with a text file. an enormous ASCII logo, the group's
+name, the date, sometimes install notes, and then a long list of greetings
+to other groups.
+
+nobody was paid. nobody was credited anywhere that mattered. so the file
+was the credit — a signature on a thing they could never publicly admit to
+making, addressed to the only people who would ever see it.
+
+they are some of the most careful typography of the era and they exist
+entirely inside a crime.`,
+ },
+
+ // ── the deep cuts: modern ──────────────────────────────────────────────
+
+ 'heartbleed': {
+ title: 'heartbleed',
+ from: '2014',
+ body: `a keepalive message says: here are some bytes, send them back, and here is
+how many there are.
+
+nobody checked that the number matched the bytes. so you sent one byte and
+said it was sixty-four kilobytes, and the server sent back your byte
+followed by sixty-four kilobytes of whatever happened to be next to it in
+memory - passwords, keys, other people's requests.
+
+two years in the wild. it left no trace in any log, because asking is not
+an error.`,
+ },
+
+ 'xz': {
+ title: 'the 500 millisecond backdoor',
+ from: 'March 2024',
+ body: `somebody spent two years being helpful. real patches, real reviews, real
+politeness, until the exhausted volunteer maintaining a compression library
+gratefully handed them commit access.
+
+then they put a back door into the build system, aimed at ssh.
+
+it was caught because an engineer benchmarking a database noticed his logins
+were taking half a second longer than they should, and would not let it go.
+the most sophisticated supply chain attack anybody has seen was defeated by
+one man being annoyed about 500 milliseconds.`,
+ },
+
+ 'y2k': {
+ title: 'the one that did not happen',
+ from: '1 January 2000',
+ body: `two digits for the year, because storage cost money and 1999 was a long way
+off, and by then somebody else would have fixed it.
+
+they did. hundreds of thousands of people spent several years and an
+enormous amount of money going through everything line by line, and on the
+night almost nothing broke.
+
+for which the reward has been thirty years of people using it as the
+example of a panic about nothing. it is the only large-scale software
+disaster that was successfully prevented, and prevention looks exactly like
+there never having been a problem.`,
+ },
+ // ── batch: the culture, the law, the plagues ───────────────────────────
+
+
+ '2600mag': {
+ title: 'a quarterly, and it costs a quarter to make the call',
+ from: 'subscription card, 1984',
+ body: `we are printing it because the information exists whether or not it is
+printed, and the difference between a magazine and a conspiracy is that a
+magazine has an address on the back.
+
+the phone company knows all of this already. the only people it is being
+kept from are the people paying for it.`,
+ },
+
+ 'mentor': {
+ title: 'somebody wrote it the week he got arrested',
+ from: 'reader letter, Phrack, 1986',
+ body: `it went round every board in the country in about nine days, which
+tells you more than the text does.
+
+he did not say anything nobody had thought. he said that being curious about
+a machine is not the same as wanting to hurt anybody, and that the reason we
+are all here is that this is the one place where nobody asks what you look
+like before deciding whether you are worth talking to.
+
+people print it out and put it on the wall. i think that is a bit much. but
+i understand why they do it and so does everyone who has ever been fifteen
+with a modem.`,
+ },
+
+ 'defcon': {
+ title: 'it started as a leaving party',
+ from: 'flyer, Las Vegas, 1993',
+ body: `a friend was moving away and the network he ran was closing, so the
+plan was a party for the people on it. then he could not come, and about a
+hundred showed up anyway.
+
+nobody organised the good parts. somebody brought a lockpicking kit and
+somebody brought a scanner and the hotel had a payphone in the lobby that
+by sunday could not be used by anyone for anything.`,
+ },
+
+ 'cdc': {
+ title: 'a text file group from a slaughterhouse in Lubbock',
+ from: 'group file 001, 1984',
+ body: `the name is a joke and the numbering is a joke and the ranks are a joke.
+what is not a joke is that we are producing more original writing per month
+than most of the boards that call themselves elite, and all of it is free,
+and none of it is stolen.
+
+anyone can distribute warez. it takes about eleven seconds and a phone line.
+writing something down is harder and that is the whole of the argument.`,
+ },
+
+ 'mod-lod': {
+ title: 'the war',
+ from: 'a log somebody kept, 1990-92',
+ body: `two crews, one out of Texas and one out of New York, and it started as
+an argument about who knew more and ended with people listening to each
+other on the switch.
+
+they were not fighting over money. there was no money. they were fighting
+over whether you were the kind of person who read the manual or the kind of
+person who guessed, and it went on until the federal government read the
+logs and decided both answers were illegal.`,
+ },
+
+ 'sundevil': {
+ title: 'the eighth of may',
+ from: 'a board that did not come back up, 1990',
+ body: `forty-two machines in fourteen cities in one morning. most of the
+people whose machines went out the door were never charged with anything.
+
+the machine is the evidence and the machine is also the board, so seizing it
+does not preserve evidence, it deletes a town. nine years of message base
+sitting in an evidence locker in a box with a number on it, and the users
+are just gone, they have no way to reach each other, that was the board.`,
+ },
+
+ 'cfaa': {
+ title: 'exceeding authorised access',
+ from: 'somebody reading the statute at a kitchen table, 1986',
+ body: `nobody can tell me what the words mean. i have asked a lawyer and he
+asked me what the computer said when i logged in.
+
+if the machine lets you in and then you look at something the owner did not
+want you to look at, is that access without authorisation, or is that a
+badly configured machine. the statute does not say. it was written by people
+describing something they had seen in a film.`,
+ },
+
+ 'pgp': {
+ title: 'they classified it as a munition',
+ from: 'a mailing list, 1993',
+ body: `the export rules put strong cryptography in the same category as
+guided missiles. so the source got printed as a book, because a book is
+speech and speech is protected, and shipped, and typed back in by hand on
+the other side.
+
+they made the argument that mathematics is a weapon and then had to explain
+why a paperback is not.`,
+ },
+
+ 'clipper': {
+ title: 'key escrow',
+ from: 'a comment filed with the FCC, 1993',
+ body: `the proposal is that everyone use strong encryption, and that a copy of
+every key is held by the government in two halves in two agencies, and that
+this is safe because you would need both halves.
+
+what is being asked for is not a backdoor, they are quite clear about that,
+it is a front door with a lock on it and two keyholders. the objection is
+not that the keyholders are untrustworthy. the objection is that a system
+with a spare key is a different system, and there is no way to build one
+that only opens for the right hands.`,
+ },
+
+ 'des': {
+ title: 'fifty-six bits',
+ from: 'a hardware note, 1998',
+ body: `it was standardised at fifty-six and people said at the time that
+fifty-six was chosen because it was strong enough for a company and weak
+enough for a government, and for twenty years that was an accusation you
+could not prove.
+
+we built the machine for a quarter of a million dollars out of custom chips
+that only do one thing. it went through the whole keyspace and found the key
+in fifty-six hours. the point was never the key. the point was to end the
+argument about whether it could be done by publishing the receipt.`,
+ },
+
+ 'munitions': {
+ title: 'the shirt is a munition',
+ from: 'a conference floor, 1995',
+ body: `three lines of perl on the front and a machine-readable barcode of the
+same three lines underneath, and a line of small print explaining that
+wearing this out of the country may require a licence.
+
+it is a very good joke, and like all very good jokes about the law it works
+by taking the law completely seriously.`,
+ },
+
+ 'eternal-september': {
+ title: 'september never ended',
+ from: 'a post nobody replied to, 1993',
+ body: `every year the new students arrive in september, ask everything that
+has already been asked, and by october somebody has explained it to them and
+they are one of us.
+
+this year a commercial service connected its entire membership at once and
+they are not going to stop arriving. there is no october coming. the mistake
+we made was assuming the mechanism was the explaining. the mechanism was
+that there were more of us than of them.`,
+ },
+
+ 'great-renaming': {
+ title: 'they moved everything',
+ from: 'a sysadmin, 1987',
+ body: `net dot everything became seven hierarchies with sensible names and the
+backbone sites got to decide what went in them, which was the actual change,
+the names were incidental.
+
+within a year somebody built a hierarchy where nobody decides. it carries
+the things the backbone would not carry and the recipes, and it is the
+biggest thing on the network. you cannot vote a topic out of existence, you
+can only find out where it went.`,
+ },
+
+ 'nsfnet': {
+ title: 'they switched off the backbone',
+ from: 'an operations note, 30 April 1995',
+ body: `the acceptable use policy said no commercial traffic, and for years the
+argument about that was theological. this morning the backbone it applied to
+was decommissioned and the argument was over, not won.
+
+nothing broke. that is the part i want on the record. the thing was built so
+that no piece of it was the thing, so you can take out the middle of it and
+the traffic simply goes another way and nobody notices, which is either the
+finest engineering of the century or the reason none of us will ever be
+able to explain to anybody what we did.`,
+ },
+
+ 'elk-cloner': {
+ title: 'a fifteen year old and a boot sector',
+ from: 'a school computer room, 1982',
+ body: `it lives in the boot block, it copies itself onto any disk you put in
+after, and on the fiftieth boot it prints a poem about itself and then
+carries on as normal.
+
+it does not delete anything. he wrote it to annoy his friends, which it did,
+for about two years, in one school, before anybody had a word for it.`,
+ },
+
+ 'brain': {
+ title: 'their real address was in it',
+ from: 'a disassembly, 1987',
+ body: `two brothers in Lahore who sell medical software, and the copy
+protection they wrote for it puts their shop name, their street, and three
+phone numbers in the boot sector.
+
+they were not hiding. they thought only people who had pirated the disk
+would ever see it, and they wanted those people to know who to call. people
+did call. for years. from countries they had never heard of, about disks
+they had never touched, because the thing kept going long after it stopped
+being about them.`,
+ },
+
+ 'michelangelo': {
+ title: 'the sixth of march',
+ from: 'a support desk, 1992',
+ body: `we were told five million machines. the number came from one man on
+television and it was repeated for six weeks until it was a fact.
+
+on the day itself we found it on maybe a dozen. i spent a fortnight scanning
+disks for people who had never previously known a disk could be scanned, and
+the software to do it sold extremely well. i am not saying the thing was not
+real. i am saying i now know exactly how afraid a person can be made about
+a date.`,
+ },
+
+ 'iloveyou': {
+ title: 'nobody could charge anybody',
+ from: 'a wire report, May 2000',
+ body: `it arrives from someone who knows you, because it arrived from their
+address book, and the attachment ends in txt if your machine has been
+helpfully hiding the real extension, which by default it has.
+
+they found who wrote it inside a week. then the prosecutors found there was
+no law in the country covering it, and there was nothing to charge him with,
+and he went home. the law arrived two months later and could not be applied
+backwards.`,
+ },
+
+ 'slammer': {
+ title: 'three hundred and seventy-six bytes',
+ from: 'a traffic graph, 25 January 2003',
+ body: `no file. no disk. one UDP packet that fits in one datagram, which means
+no handshake, which means it does not wait to find out whether anyone is
+there, it just goes.
+
+it doubled every eight and a half seconds. it had infected effectively
+everything it could reach in under ten minutes, which is faster than any
+human being on earth could be woken up and told. the machines did not fall
+over because it hurt them. they fell over because it kept talking.`,
+ },
+
+ 'stuxnet': {
+ title: 'it wanted a particular room',
+ from: 'analyst notes, 2010',
+ body: `four unpublished holes in one thing. you do not spend four on a crime.
+you spend four when there is exactly one machine on earth you need and no
+second attempt.
+
+it walks through every computer it can reach and does nothing to any of
+them. it is looking for a controller running a particular drive at a
+particular frequency in a particular count, and if it does not find that, it
+stays quiet. when it did find it, it changed the speed slowly, and fed the
+operators the readings from an hour ago.
+
+it is the first one that was never about the computers.`,
+ },
+
+ 'mirai': {
+ title: 'sixty-one passwords',
+ from: 'a takedown post-mortem, 2016',
+ body: `no exploit. no overflow. a list of sixty-one factory logins, tried
+against everything with a port open, and a very large number of cameras and
+recorders that have a login the owner has never once seen, because the
+owner interacts with the camera through an app.
+
+you cannot patch them. there is no update. the manufacturer does not exist
+anymore, the brand was a sticker. the fix is a landfill.`,
+ },
+
+ 'wannacry': {
+ title: 'somebody registered the domain',
+ from: 'a hospital IT office, May 2017',
+ body: `the hole was found by a government, kept, lost, and published by
+somebody else. a month after the patch shipped it went through everything
+that had not applied it, which was everything with a machine that cannot be
+rebooted during clinic hours.
+
+it was stopped that afternoon because a researcher noticed it checked for a
+nonsense domain before running, assumed it was a sandbox check, and
+registered the domain for about ten dollars. that was the kill switch. it
+was not meant to be one.`,
+ },
+
+ 'hayes': {
+ title: 'AT',
+ from: 'a modem manual, 1981',
+ body: `attention. every command starts with those two letters so the modem can
+work out your speed from the shape of them before it knows anything else.
+
+the hard part was never the commands. the hard part is that the same wire
+carries what you type to the modem and what you type to the far end, so the
+modem needs to know which you meant. three pluses, with a second of silence
+either side. the silence is the actual invention. anything can send three
+pluses. only a person who has stopped typing can send nothing at all first.`,
+ },
+
+ 'coupler': {
+ title: 'why it is a rubber cup and not a plug',
+ from: 'a rental agreement, 1972',
+ body: `it is illegal to attach a foreign device to the network electrically.
+that is the whole reason. the handset sits in two rubber cups and we talk to
+it with sound, through the air, like a person would, because there is no
+rule against making a noise into a telephone.
+
+so the coupler exists because of a sentence in a tariff. every hiss you have
+ever heard on one of these is the sound of a legal argument being routed
+around.`,
+ },
+
+ 'vt100': {
+ title: 'escape, bracket, two, J',
+ from: 'a terminal reference card, 1978',
+ body: `clears the screen. escape bracket H puts the cursor home. it is a
+character stream that occasionally means move rather than print, and the
+escape character is how the stream tells you which.
+
+there is no reason any machine made after this decade should speak it. every
+one of them does. we are all still emulating a piece of glass and metal that
+weighed twenty kilograms because everybody wrote to it and nobody wants to
+be the one who breaks the writing.`,
+ },
+
+ 'uucp': {
+ title: 'bang paths',
+ from: 'an address written on a napkin, 1984',
+ body: `you do not have an address. you have directions. you name every machine
+between here and there and the mail is copied from each one to the next
+overnight over a phone call, and if you get one hop wrong the whole thing
+comes back a week later.
+
+which means to write to someone you must first know where you both are
+relative to the big sites. everybody memorised the same three hub names. the
+network had a shape and you could hold it in your head, and that has never
+been true again since.`,
+ },
+
+ 'finger': {
+ title: 'the plan file',
+ from: 'a machine at a university, 1987',
+ body: `it will tell anyone on the network your name, your terminal, how long
+you have been idle, and then it prints a file out of your home directory
+that you can put anything you like in.
+
+people put jokes in it. people put their thesis progress in it. somebody
+wired the drinks machine to the network so you can finger it and find out
+which column is cold. none of this was designed. the protocol had a hole
+shaped like a person in it and everybody filled it in.`,
+ },
+
+ 'sendmail-debug': {
+ title: 'it was left switched on',
+ from: 'a post-mortem, November 1988',
+ body: `the mailer has a debug mode where the recipient can be a command
+instead of a person. it is there so you can test a machine you are sitting
+in front of. it shipped enabled, in the binary, on everything, for years.
+
+so you could mail a program to a computer and it would run it, and that is
+not an exploit, that is documented behaviour. the hole was not the code. the
+hole was that nobody who compiled it ever asked what it was for.`,
+ },
+
+ 'fork-bomb': {
+ title: 'it is thirteen characters',
+ from: 'somebody who did it by accident, 1990',
+ body: `a function that calls itself twice and backgrounds both. it is not
+clever and it is not an attack, it is just what a machine does when asked to
+do a small reasonable thing an unlimited number of times.
+
+there is no bug. every part of this is the system working. the limit that
+would stop it exists and has always existed and is set, on this machine, to
+a number somebody chose in 1979 for a machine with far fewer users.`,
+ },
+ // ── batch: the scene, the iron, the first convictions ──────────────────
+
+ 'courier': {
+ title: 'zero day means today',
+ from: 'a group file, 1993',
+ body: `it is not about using the thing. half of us do not even install it.
+it is about the gap between the shop opening and it being on the boards, and
+that gap is measured in hours, and somebody is timing it.
+
+the couriers are not crackers. they move it. they have accounts on nine
+boards on three continents and a phone bill that would frighten a small
+business, and their entire reputation rests on being first by nine minutes.`,
+ },
+
+ 'cracktro': {
+ title: 'the bit before the game',
+ from: 'a disk that came from a friend of a friend, 1989',
+ body: `you put the disk in and before anything else there is a scroller, a
+tune, and a logo with more work in it than the game has in its title screen.
+
+it is a signature. the protection is gone and the person who removed it has
+signed the wall on the way out, in a demo that fits in the space where the
+protection used to be, and greeted forty people you will never meet.`,
+ },
+
+ 'keygen-music': {
+ title: 'why does it have a tune',
+ from: 'a message base, 1997',
+ body: `it is a tracker module, thirty kilobytes, four channels, and it loops
+until you close the window.
+
+nobody needs it. it is there because the person who wrote the generator is
+the same kind of person who wrote the intro on the disk, and because leaving
+in silence would be like leaving a room without saying anything.`,
+ },
+
+ 'pipes': {
+ title: 'the vertical bar',
+ from: 'a memo somebody kept for years, 1973',
+ body: `he had been asking for it for a while, on the grounds that if a program
+is a garden hose it ought to be possible to screw one onto another.
+
+the whole thing went in overnight and by morning every command in the system
+had to be rewritten to read from the input and write to the output and shut
+up about it. that is the rule that made everything since possible: do one
+thing, and do not print a banner.`,
+ },
+
+ 'everything-file': {
+ title: 'the disk is a file, the terminal is a file, the memory is a file',
+ from: 'a lecture nobody wrote down, 1975',
+ body: `there is no special call to read a tape. it is open, read, write, close,
+and if the thing on the end of it is a spinning platter or a printer or
+another human being at a keyboard, that is not the program business.
+
+it means a tool written by somebody who never heard of your hardware works
+on your hardware. this is the single most valuable idea in the whole system
+and it is usually explained as a syntax convenience.`,
+ },
+
+ 'devnull': {
+ title: 'the bit bucket',
+ from: 'a manual page, 1974',
+ body: `a device that accepts everything written to it and reports success, and
+returns nothing at all when read.
+
+it is not a deletion. deletion is an operation with consequences. this is a
+place that is always empty and never full, and every operator learns early
+that having somewhere to point the output is the difference between a job
+that runs and a disk that fills up at four in the morning.`,
+ },
+
+ 'editors': {
+ title: 'why the commands are one letter',
+ from: 'an argument that will never end, 1979',
+ body: `it was written over a three hundred baud line. at three hundred baud
+you can watch the screen redraw and you would rather it did not, so you do
+not redraw, and every command is one keystroke because every keystroke is
+paid for in tenths of a second.
+
+people now argue about it as taste. it is not taste. it is the shape of a
+slow line, preserved, and defended by people who have never seen one.`,
+ },
+
+ 'man-page': {
+ title: 'read the section at the bottom',
+ from: 'a note to a new user, 1985',
+ body: `name, synopsis, description, options, files, see also, diagnostics,
+bugs. everybody stops at options.
+
+the bugs section is the honest part of the entire operating system. somebody
+sat down and wrote out, in the documentation, in the shipping product, the
+list of ways their own program is wrong. no industry since has managed it.`,
+ },
+
+ 'hosts-equiv': {
+ title: 'trust by name',
+ from: 'a security review, 1988',
+ body: `the file lists machines. if a login comes from a machine in the file
+claiming to be a user who exists here, it is that user, no password.
+
+the entire model rests on a machine being able to say who it is and be
+believed. the address in the packet is written by the sender. we built a
+continent of trust on a field the untrusted party fills in.`,
+ },
+
+ 'punch-card': {
+ title: 'eighty columns',
+ from: 'a keypunch room, 1968',
+ body: `the card is eighty columns because that is what fits on a card that fits
+in a hand, and the hand and the card are older than any of this.
+
+your terminal is eighty columns. your printer is eighty columns. the file
+you are reading wraps at eighty. long after the last card is punched, every
+screen on earth will still be the width of a piece of cardboard somebody
+sized for a census in the last century.`,
+ },
+
+ 'core-memory': {
+ title: 'it was woven',
+ from: 'a factory floor, 1964',
+ body: `little rings of ferrite on a grid of wires, and every one of them was
+threaded by a person, by hand, under a lens. a machine with sixteen
+kilowords of it contains a hundred and thirty thousand rings and somebody
+put a wire through each.
+
+it remembers with the power off, because the ring is either magnetised one
+way or the other and has no opinion about electricity. when a program dies
+and the system writes out what was in memory, that is what it is writing.
+that is what the word means. it is the actual core.`,
+ },
+
+ 'pdp11': {
+ title: 'the machine the language is shaped like',
+ from: 'a lab, 1972',
+ body: `the reason the language has increment and decrement operators is that
+this machine has an instruction that does exactly that as part of addressing
+memory, and it seemed a shame not to be able to say it.
+
+people will use that language on machines that do not exist yet, on hardware
+with nothing in common with this, and the operators will still be there,
+still shaped like an instruction on a machine in a room in New Jersey.`,
+ },
+
+ 'tape-load': {
+ title: 'the noise is the program',
+ from: 'a bedroom, 1983',
+ body: `four minutes of screeching out of a cassette and stripes running down
+the border, and if somebody opens the door and the plug wobbles you start
+again.
+
+it is not a sound that represents the data. it is the data. you are hearing
+the bytes. if you rewind to the right spot and listen you can hear the
+loading screen arrive before you can see it.`,
+ },
+
+ 'altair': {
+ title: 'you enter the program with switches',
+ from: 'a kit that arrived in a box, 1975',
+ body: `no keyboard, no screen. a row of switches for the bits, a button to
+deposit, and lights. to load anything you set eight switches, deposit, set
+eight more, deposit, and if you lose count you begin again.
+
+the first program most people ran made the lights chase each other. it took
+an evening. everyone who did it says the same thing, which is that it was
+the last time they knew, absolutely, everything that was happening.`,
+ },
+
+ 'homebrew': {
+ title: 'the open letter',
+ from: 'a meeting in a garage in Menlo Park, 1976',
+ body: `the club works by everybody bringing what they have and taking what
+they need, and half the machines in the room exist because somebody
+photocopied somebody else schematic.
+
+then a letter goes round from a young man whose interpreter has been copied
+more than it has been bought, and he asks who can afford to do professional
+work for nothing. both sides of the next fifty years are in that room and
+neither of them is wrong.`,
+ },
+
+ 'first-web': {
+ title: 'do not power down',
+ from: 'a label on a machine at CERN, 1991',
+ body: `it is one workstation under a desk and it is the only one, so somebody
+has written on it by hand, in marker, that this machine is a server and
+please do not switch it off.
+
+that is the whole state of the art. the entire thing is a piece of tape on
+a box in Switzerland and one person walking past who might unplug it to
+charge something.`,
+ },
+
+ 'cookies': {
+ title: 'a protocol with no memory',
+ from: 'an engineering note, 1994',
+ body: `every request arrives as if it were the first one. the protocol was
+designed that way on purpose, because a server that remembers everybody is a
+server that falls over.
+
+so we hand the browser a small opaque thing and ask it to give it back next
+time, and now the shop can have a basket. that is what it was for. a basket.
+i want that written down somewhere before anybody does anything else with
+it.`,
+ },
+
+ 'sqli': {
+ title: 'the quote closes early',
+ from: 'a write-up, 1998',
+ body: `the program builds a sentence out of your answer and hands the sentence
+to the database. so you answer with something that finishes the sentence
+early and starts a new one.
+
+the database is not fooled. the database is doing exactly what it was told,
+by a program that could not tell the difference between what it meant to say
+and what somebody typed into a form. same hole as the sled, same hole as the
+mailer, forever: data walked in and was read as instructions.`,
+ },
+
+ 'xss': {
+ title: 'the page says it, so the browser believes it',
+ from: 'a bug report, 2000',
+ body: `you put a script in the comment box. the site prints your comment on the
+page. now everyone reading the page runs your script, and their browser has
+no reason to doubt it, because it arrived inside the site.
+
+the trust is not in you. the trust is in the address bar, and you are
+writing from inside the address bar.`,
+ },
+
+ 'phish': {
+ title: 'the word comes from a program that fished for accounts',
+ from: 'a warning posted to a service, 1996',
+ body: `nobody breaks anything. somebody sends a message that looks like it came
+from the company, asking you to confirm your billing, and enough people
+confirm.
+
+there is no patch. the vulnerability is that a person can be sent a letter,
+and it has been open since the invention of letters.`,
+ },
+
+ 'salt': {
+ title: 'why two people with the same password get different hashes',
+ from: 'a paper on password security, 1979',
+ body: `without it, you hash the dictionary once and compare against everybody
+at the same time. one pass, the whole file.
+
+so a small random value goes in with the password and is stored in the clear
+beside it. it is not secret. it does not need to be. it just means the work
+you did to attack one account buys you nothing at all against the next one,
+and that is the entire trick.`,
+ },
+
+ 'captain-zap': {
+ title: 'he moved the clocks',
+ from: 'a court report, 1981',
+ body: `he did not steal calls. he went into the billing system and changed what
+time it thought it was, so that the whole country got the cheap overnight
+rate in the middle of the afternoon, and the expensive rate at midnight.
+
+the first felony conviction of its kind. the interesting part is not the
+sentence, it is that the network had no idea what time it was and had to be
+told, and whoever told it was believed.`,
+ },
+
+ 'poulsen': {
+ title: 'caller one hundred and two',
+ from: 'a radio station, Los Angeles, 1990',
+ body: `the station gives a car to the hundred and second caller. so you take
+the station twenty-five lines, hold them all, and let exactly one call
+through, which is yours.
+
+it is not a hack of a radio contest. it is a hack of the exchange, and the
+prize is a Porsche. the FBI spent a year on it and a television programme
+put his face on air, and the phone lines the show gave out for tips went
+down during the broadcast.`,
+ },
+
+ 'the-414s': {
+ title: 'a boy on the cover of a magazine',
+ from: 'Milwaukee, 1983',
+ body: `they named themselves after the area code. they got into a cancer
+hospital and a weapons lab, mostly by trying the password the manual
+suggests and finding that nobody had changed it.
+
+what they broke was not the machines. what they broke was the idea that
+these things were far away and looked after by serious people. congress held
+hearings and screened a film.`,
+ },
+
+ 'wargames': {
+ title: 'that is where the word comes from',
+ from: 'a cinema, 1983',
+ body: `he sets the machine dialling every number in an exchange overnight and
+writes down which ones answer with a carrier. now everybody does it, and
+everybody calls it after the film, and the film called it nothing at all.
+
+for about a year afterwards, every school in the country had somebody in it
+trying the same thing, and about a third of them found something, which
+tells you the film was not exaggerating, it was reporting.`,
+ },
+
+ 'ccc-btx': {
+ title: 'they gave it back at a press conference',
+ from: 'Hamburg, 1984',
+ body: `the videotex system, which the post office says is secure. they take a
+hundred and thirty-five thousand marks out of a bank overnight, one small
+page request at a time, running all night.
+
+then in the morning they hold a press conference, hand every pfennig back,
+and explain the flaw. that is the whole point and it is the reason anybody
+listened. the demonstration is worthless if you keep the money.`,
+ },
+
+ 'payphone': {
+ title: 'the box knows what you put in it by the sound',
+ from: 'a service manual, 1979',
+ body: `the coin falls past a hammer and rings a bell. one ring for the small
+coin, two for the middle, a gong for the big one, and the operator, or later
+the equipment, hears it down the same line as your voice.
+
+there is no sensor reporting to a computer. there is a bell, and a
+microphone, and a great deal of faith.`,
+ },
+
+ 'cocot': {
+ title: 'a payphone that belongs to a petrol station',
+ from: 'a field note, 1986',
+ body: `after the breakup anyone can own the box. so the box is now made as
+cheaply as a box can be made, and it does its own dialling and its own
+billing and its own deciding.
+
+which means the intelligence is out on the pavement instead of in the
+exchange, in a metal case, on a wall, in the dark, owned by somebody whose
+main business is selling petrol. every one of them is a small computer that
+nobody is watching.`,
+ },
+
+ 'wats': {
+ title: 'the extender',
+ from: 'a scan list, 1985',
+ body: `a company has a toll-free number and behind it a code, and if you know
+the code you get an outside line billed to the company.
+
+the codes are four digits. the number is free to call. you can sit there all
+night, and the machine can sit there all night without you, and the only
+thing standing between the two of them is a company that thought four digits
+was a lot.`,
+ },
+
+ 'silver-box': {
+ title: 'the fourth column',
+ from: 'a modification guide, 1980',
+ body: `the keypad is a grid. three columns of buttons and four rows, and the
+grid has four columns. the fourth one exists in the standard, has always
+existed, and has no buttons on any telephone you have ever seen.
+
+the tones are A, B, C and D and they are for the military network, where
+they set the precedence of a call, up to and including the level that hangs
+up on everyone else so a general can talk. the phone in your kitchen speaks
+the same language. it just was not given the keys.`,
+ },
+
+ 'operator': {
+ title: 'she is not a security system',
+ from: 'a training memo somebody was not supposed to keep, 1974',
+ body: `she is measured on calls per hour. she has a supervisor and a light on
+her console and a target, and the fastest way to clear a call is to help.
+
+so a caller who is confident, who uses the right word for the thing, who
+sounds tired and slightly bored, gets what he asks for, because arguing with
+him would take ninety seconds and helping him takes twenty. that is not a
+flaw in her. that is what she was hired for.`,
+ },
+ // ── batch: naming things, the tools, the bill coming due ───────────────
+
+ 'dns': {
+ title: 'before this, everybody downloaded the same file',
+ from: 'a transition note, 1983',
+ body: `there was one text file with every host on the network in it. you
+fetched it from one machine in California, by hand, when you remembered,
+and if somebody added a machine on tuesday you found out on friday.
+
+it worked until it did not, and the thing that killed it was not size, it
+was that two people wanted the same name and there was one file and one
+woman keeping it and no way to say no politely at scale.
+
+so the names got a shape, and the shape means nobody has to hold the whole
+list. you only ever have to know who to ask next.`,
+ },
+
+ 'root-servers': {
+ title: 'why there are thirteen',
+ from: 'an operations mailing list, 1995',
+ body: `not thirteen for luck and not thirteen for redundancy. thirteen because
+the answer listing them has to fit in one datagram of five hundred and
+twelve bytes, and fourteen does not fit.
+
+the top of the naming system for the entire species is the size it is
+because of a number somebody picked for a packet.`,
+ },
+
+ 'whois': {
+ title: 'you could just ask who somebody was',
+ from: 'a directory, 1982',
+ body: `type the command and a name and it tells you the person, their machine,
+their office and their telephone number, because when this was written
+everybody on the network was at an institution and being findable was the
+point.
+
+there are a few thousand people. the whole directory is one file kept by one
+person and if your entry is wrong you write to her and she fixes it.`,
+ },
+
+ 'ping': {
+ title: 'he named it after sonar',
+ from: 'a note attached to the source, December 1983',
+ body: `you send a noise into the dark and you time how long it takes to come
+back, and that is all the information there is: something is out there, and
+it is that far away.
+
+it took an evening to write. it is on every machine that has ever spoken
+this protocol and it will outlive everything else either of us does. the
+whole diagnostic is one question, which is are you there, asked of a machine
+that has no obligation to answer.`,
+ },
+
+ 'tcpdump': {
+ title: 'the wire says what it says',
+ from: 'a debugging session, 1988',
+ body: `the program insists it sent it. the far end insists it never came. one
+of them is wrong and neither of them can tell you which.
+
+so you stop asking the programs. you put the card in the mode where it keeps
+everything, not just the frames addressed to you, and you read what actually
+went past. there is no argument after that. this is the only tool in the
+whole box that cannot be lied to.`,
+ },
+
+ 'nmap': {
+ title: 'the machine answers differently when it says no',
+ from: 'an article, 1997',
+ body: `a closed port and a filtered port are both a refusal, but they are not
+the same refusal, and the difference is in the flags.
+
+so you knock on all of them and write down the shape of every no. and if
+you compare the exact wording of the refusals against a list, the machine
+tells you what it is running without ever intending to say anything.`,
+ },
+
+ 'telnet': {
+ title: 'it is all in the clear',
+ from: 'a capture somebody did not mean to take, 1992',
+ body: `the password goes across as characters. not hashed, not wrapped, not
+obscured. characters, one per packet, because it echoes as you type.
+
+anybody on any machine between here and there sees it. this is not a bug and
+there is no fix planned, because the network was built by people who could
+all see each other and the idea that a stranger might be on it was not an
+oversight, it was not a thought anyone had had.`,
+ },
+
+ 'anon-ftp': {
+ title: 'the password is your email address, as a courtesy',
+ from: 'a login banner, 1990',
+ body: `log in as anonymous. it will ask for a password. put your address in,
+not because it is checked, but because it is polite and because the person
+paying for this line likes to know who is using it.
+
+and people did. for years. nearly everybody typed a real address into a box
+that would have accepted anything at all.`,
+ },
+
+ 'gopher': {
+ title: 'it was better, and then it was licensed',
+ from: 'a comparison written for a department, 1993',
+ body: `menus. clean, fast, hierarchical, no markup, and any librarian can put a
+collection up in an afternoon. against a thing from Switzerland where the
+links go sideways instead of down and every page looks like whatever
+somebody felt like.
+
+then the university announces a licence fee for commercial use, and two
+months later CERN puts the other one in the public domain, and that is the
+whole story. the technically better system asked for money in the same
+quarter the worse one became free.`,
+ },
+
+ 'shellshock': {
+ title: 'the code was twenty-five years old',
+ from: 'an advisory, September 2014',
+ body: `you can put a function in an environment variable. the shell parses it
+when it starts. it does not stop parsing at the end of the function.
+
+so anything that passes a string from a stranger into the environment and
+then runs a shell, which is roughly every web server on earth built before
+about 2005, runs the stranger commands. the line responsible was written in
+1989 and read by thousands of people and never once by somebody asking that
+particular question.`,
+ },
+
+ 'meltdown': {
+ title: 'the guess left a mark',
+ from: 'a paper, January 2018',
+ body: `the processor runs ahead of itself, guessing, and throws the work away
+if the guess was wrong. the results are discarded properly. the timing is
+not.
+
+so the guess it made about memory it was not allowed to read is undone, but
+the cache is a fraction faster afterwards, and a fraction is a measurement.
+the fix costs everybody a few percent forever, on hardware that cannot be
+changed, because the mistake is in the silicon and the silicon is in
+everything.`,
+ },
+
+ 'eternalblue': {
+ title: 'somebody had it for years, and then lost it',
+ from: 'a timeline written afterwards, 2017',
+ body: `a hole in a file sharing protocol from 1983 that everything still
+speaks for compatibility with things nobody runs.
+
+an agency found it and kept it, which is a decision with a case to be made
+for it. then the tool escaped, and was published, and within two months it
+had been used against hospitals. the argument about whether to hoard a hole
+assumes you can hold on to it.`,
+ },
+
+ 'equifax': {
+ title: 'the patch had been out for two months',
+ from: 'a congressional exhibit, 2017',
+ body: `a hundred and forty-seven million people, none of whom were customers,
+none of whom agreed to anything, none of whom can leave.
+
+there was no clever attack. there was a published flaw in a common library,
+a fix available in March, a scanner that was pointed at the wrong subnet,
+and a certificate on the monitoring that had expired ten months earlier so
+nobody saw the traffic going out.`,
+ },
+
+ 'solarwinds': {
+ title: 'they did not break in, they got built in',
+ from: 'an incident report, 2020',
+ body: `nobody attacked the eighteen thousand organisations. somebody attacked
+the machine that compiles the software the eighteen thousand organisations
+install on purpose, signed, from the vendor, through the front door,
+during a maintenance window.
+
+every security model in the building assumes the update is the safe part.`,
+ },
+
+ 'aids-trojan': {
+ title: 'twenty thousand floppies in the post',
+ from: 'a conference mailing list, 1989',
+ body: `it went to people who had attended a health conference. it installs, it
+counts your boots, and at ninety it scrambles the names of your files and
+prints an invoice asking for a hundred and eighty-nine dollars to a box
+number in Panama.
+
+there was no network involved. he posted it. the encryption was weak and
+people got their disks back, but the shape of the thing is complete on the
+first try: take something they cannot replace, and sell it back.`,
+ },
+
+ 'tor': {
+ title: 'you cannot hide in an empty room',
+ from: 'a design discussion, 2004',
+ body: `the routing hides who is talking to whom by wrapping it in layers and
+passing it between volunteers, and no single one of them knows both ends.
+
+but if only spies use it, then using it means you are a spy, and the traffic
+is anonymous while the user is not. so the thing only works if the people
+who need it least use it constantly, which is the strangest requirement any
+security system has ever had.`,
+ },
+
+ 'jargon-file': {
+ title: 'they wrote down their own words',
+ from: 'a lab machine at Stanford, 1975',
+ body: `a dictionary of the slang of about four hundred people in three
+buildings, kept as a file, added to by anyone who thought of one.
+
+it is not a joke document, or it is, but it is also the only surviving
+record of how these people talked to each other before there was any money
+in it, and half the entries are about elegance and the other half are about
+somebody specific being wrong in 1973.`,
+ },
+
+ 'rtfm': {
+ title: 'read the manual',
+ from: 'a reply to a question, any year',
+ body: `it is rude and it is nearly always correct, and the two facts have
+nothing to do with each other.
+
+the person answering has typed the answer forty times. the person asking has
+never asked before. both of these are true and the whole culture of the next
+thirty years is built in the gap between them.`,
+ },
+
+ 'bofh': {
+ title: 'a story about the person who has root',
+ from: 'a newsgroup, 1992',
+ body: `he deletes accounts for tone of voice. he explains outages with words
+the manager will not admit to not knowing. it is a joke.
+
+it is funny because everybody reading it has been on one side of it and
+suspects they have been on the other. the operator has absolute power over
+everything you have ever typed, and the only thing between you and that is
+whether he is having a good week.`,
+ },
+
+ 'the-well': {
+ title: 'you own your own words',
+ from: 'Sausalito, 1985',
+ body: `you pay to be here and you post under your real name and the rule at the
+top is that what you write is yours and the consequences are also yours.
+
+it is small on purpose. people meet in person. it is the opposite argument
+to every board in this file, and it is the only one of them where the
+argument was ever written down as a rule rather than just being how things
+happened to go.`,
+ },
+
+ 'swartz': {
+ title: 'he downloaded papers',
+ from: 'a note written by somebody who knew him, 2013',
+ body: `publicly funded research, behind a paywall, downloaded in bulk from a
+network he was entitled to use, from a wiring closet he was not.
+
+the library did not want to press it. the publisher did not want to press it.
+thirteen felony counts anyway, under a statute written in 1986 by people
+describing something they had seen in a film, carrying more years than the
+statute carries for most things done with hands.
+
+he was twenty-six. the law did not change afterwards.`,
+ },
+
+ 'dmca': {
+ title: 'the lock is protected, not the thing behind it',
+ from: 'a lawyer explaining it badly, 1998',
+ body: `it is not about copying. copying was already covered. this makes it an
+offence to get around the mechanism, and separately an offence to tell
+anybody how.
+
+so a fact about how a device works becomes something you can be sued for
+saying, and the first people it is used against are researchers publishing
+a flaw. you may own the thing. you may not know how it works.`,
+ },
+
+ 'eff': {
+ title: 'somebody had to be on the other side',
+ from: 'a founding statement, 1990',
+ body: `after the raids, it turned out that when they come for a bulletin board
+there is nobody whose job it is to show up.
+
+so a few people with money from software put up the money for lawyers,
+because the actual problem was not that the law was wrong, it was that on
+one side there was a federal agency and on the other side there was a
+nineteen year old and his mother.`,
+ },
+
+ 'bernstein': {
+ title: 'source code is speech',
+ from: 'a filing, 1995',
+ body: `he wanted to publish an encryption algorithm. publishing it counted as
+exporting a weapon, so he needed a licence, so he sued.
+
+the argument the court accepted is the important part: source code is how
+these people explain an idea to one another. restricting who may be told is
+not export control, it is telling a mathematician who he may talk to.`,
+ },
+
+ 'busy-signal': {
+ title: 'sixty per minute',
+ from: 'a switching manual, 1975',
+ body: `the tone is the same two notes as the dial tone, interrupted sixty times
+a minute. reorder, which means the network itself is full, is twice as fast,
+and every operator can tell them apart in half a second without thinking.
+
+one of them means a person is already talking. the other means the country
+is. they sound almost the same on purpose, because in both cases what you
+have to do is hang up and try again.`,
+ },
+
+ 'call-waiting': {
+ title: 'star seven zero',
+ from: 'a note taped to a modem, 1994',
+ body: `if somebody rings while you are online, the exchange politely puts a
+click on the line to let you know, and the click destroys the carrier, and
+forty minutes of download becomes nothing.
+
+so you dial star seven zero first, every time, to switch off the feature you
+are paying extra for, so that the thing you are actually doing will work.
+half the households in the country have this written on a scrap of paper
+next to the phone.`,
+ },
+
+ 'second-line': {
+ title: 'the argument that ends with a second line',
+ from: 'a household, 1996',
+ body: `it costs about fifteen a month and it ends a war. nobody picks up the
+phone mid-transfer, nobody misses a call, and the child stops being the
+reason nobody can ring the house after six.
+
+it is the only technical decision in the history of the family that everyone
+agreed on immediately, and it was made for social reasons entirely.`,
+ },
+
+ 'isdn': {
+ title: 'it was ready twenty years early and priced for a business',
+ from: 'a sales sheet, 1993',
+ body: `sixty-four kilobits, digital end to end, connects in under a second
+because there is no negotiation, no training, no handshake. it just is.
+
+it costs what a company pays and it is sold by people who have never sold to
+a house. the technology was finished and waiting the entire time everybody
+was screaming at each other about baud rates.`,
+ },
+
+ 'v90': {
+ title: 'why the fast direction is only one direction',
+ from: 'a standards summary, 1998',
+ body: `fifty-six down, thirty-three up, and everybody assumes it is a
+limitation of the equipment. it is not.
+
+the network is already digital in the middle. the only analogue part is the
+copper to your house. going down, there is one conversion; going up, there
+are two, and the second one adds noise you cannot subtract. the asymmetry
+is not a business decision. it is the number of times the signal changes
+what kind of thing it is.`,
+ },
+
+ 'no-carrier': {
+ title: 'two words',
+ from: 'the last line of a great many logs',
+ body: `it does not mean an error. it means the tone that was carrying you is no
+longer present, which is the same thing the modem would say if you had
+finished properly and hung up.
+
+there is no field for why. the line is either up or it is not, and when it
+is not, the last thing on your screen is somebody in the middle of a
+sentence, and you will never find out what the rest of it was.`,
+ },
+
+ 'toctou': {
+ title: 'between the check and the use',
+ from: 'a bug report, 1993',
+ body: `the program asks whether you are allowed to write to the file. the
+answer is yes. then it writes to the file.
+
+in between those two moments, which are not the same moment, the file can
+be replaced with a link to something else, and the second half of the
+sentence is now about a different file than the first half was.
+
+nothing in the program is wrong. it is only that it assumed the world holds
+still while it thinks, and the world has other users.`,
+ },
};
// levels can still pass their own title/body inline; this is the shared store
diff --git a/tools/philecheck.mjs b/tools/philecheck.mjs
new file mode 100644
index 0000000..e55bda0
--- /dev/null
+++ b/tools/philecheck.mjs
@@ -0,0 +1,29 @@
+// 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);