Depth: four levels get a second idea

Working to the rule in STAGES.md — a longer level is not a better level,
a level with a second idea in it is. Nothing here is padding.

COLD BOOT was ninety seconds and it is the first ninety seconds of the
game. Now: your own descriptor is written on the wall of your slot — seq,
window, flags, ack, ttl — so the vocabulary the ending depends on is
something you read standing up rather than something narrated at you. The
packet in the next slot has been transmitted before and came back, which
is not supposed to happen, and it explains that if you ever do get an
answer you will not return here, and wants to be very clear that that is
the good ending. And the tail pointer, which has been circling since the
first frame, now actually has to reach you — the DMA is something that
arrives rather than something the level schedules.

THE CUCKOO'S EGG was a timer. Now it is a decision. ps shows him working
and what it shows changes as he narrows: fifty printers on fifty lines,
then a printer on ttyf3, which is yours, then nothing running except you.
The process table is world-readable, so the hunt is visible to the hunted.
And `clean` edits the accounting files — it costs you 34 seconds of clock
and buys back eleven lines of cover, and the game tells you plainly that
the fanfold in his basement has no editor. Filesystem roughly tripled,
most of it dull on purpose, three files that matter — including sventek's
.rhosts, which is two plus signs.

THE HANDSHAKE bosses escalate now instead of sitting at one difficulty.
Dial tone stops being patient after the first resonance and the rings come
faster. DTMF's beams speed up past the fourth digit because the switch
times out. ANSam's deaf window narrows from 190ms toward 90 every time you
use it — the exploitable thing gets harder to exploit, which is the shape
of the whole game. And failing the scream now makes the carrier channel
wander, so a bad negotiation is a bad line and not only worse graphics.

TIME_WAIT: somebody arrives. At 62 seconds the door opens and a new SYN
comes in with the flag still set, asking whether this is it and whether
somebody is coming to explain, having already worked out that nobody is.
The room does not look up at that either.

81 philes. All 14 hops swept clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-08-02 22:21:16 +10:00
parent 682b7d88fa
commit 36b68d6a76
5 changed files with 293 additions and 22 deletions

View File

@ -5,7 +5,7 @@
import * as THREE from '../../vendor/three.module.js';
import * as A from '../audio.js';
import * as In from '../input.js';
import { Body, Look, World, rng, sprite, damp, clamp } from '../kit.js';
import { Body, Look, World, rng, sprite, panel, glow, damp, clamp } from '../kit.js';
let body, look, ring, packets, head, tail, streaks, S = {};
@ -15,7 +15,9 @@ export default {
async mount(ctx) {
const { scene, camera } = ctx;
S = { t: 0, phase: 'wake', dma: 0, spoke: 0, locked: false };
S = { t: 0, phase: 'wake', dma: 0, spoke: 0, locked: false,
armed: false, readDesc: false, nbSaid: 0, nbCool: 0, tailWarned: false, tailNear: false };
window.__S = S;
scene.background = new THREE.Color(0xfbfdff);
scene.fog = new THREE.Fog(0xfbfdff, 40, 300);
@ -102,10 +104,43 @@ export default {
body.pos.set(0, 0, R - 4);
look = new Look();
look.yaw = Math.PI / 2; // stand in your slot and look along the curve
S.body = body; S.look = look; // every other level keeps these on S; match it
camera.near = 0.05; camera.far = 1200; camera.updateProjectionMatrix();
ctx.hint('<k>CLICK</k>look around');
S.line = 0;
// ── your own descriptor, on the wall of your slot. The whole ending depends on
// the player knowing what a sequence number is, so it is written down where
// they are standing rather than narrated at them.
S.seq = 1 + Math.floor(rand() * 4294967);
const desc =
'DESCRIPTOR nic0.tx[0341]\n\n' +
` seq ${S.seq}\n` +
' window 8192\n' +
' flags SYN\n' +
' ack —\n' +
' ttl 64\n\n' +
' this is all of you.';
S.desc = panel(desc, { color: '#2a4a5e', size: 2.6, align: 'left' });
const A0 = Math.PI / 2; // the angle you wake up at
S.desc.position.set(Math.cos(A0 + 0.055) * (R - 0.9), 1.6, Math.sin(A0 + 0.055) * (R - 0.9));
S.desc.lookAt(0, 1.6, 0);
S.desc.visible = false;
scene.add(S.desc);
S.descAt = new THREE.Vector3(Math.cos(A0 + 0.055) * (R - 3), 0, Math.sin(A0 + 0.055) * (R - 3));
// ── the packet in the next slot. It has been sent before. It came back.
S.neighbour = new THREE.Group();
const nb = new THREE.Mesh(new THREE.IcosahedronGeometry(0.46, 1), glow(0x9fd8ff, 1.2));
S.neighbour.add(nb); S.nbCore = nb;
const nh = new THREE.Mesh(new THREE.IcosahedronGeometry(0.7, 1),
new THREE.MeshBasicMaterial({ color: 0x6fb8e8, wireframe: true, transparent: true, opacity: 0.3 }));
S.neighbour.add(nh); S.nbHalo = nh;
S.nbAt = new THREE.Vector3(Math.cos(A0 - 0.075) * R, 0.9, Math.sin(A0 - 0.075) * R);
S.neighbour.position.copy(S.nbAt);
scene.add(S.neighbour);
S.nbSaid = 0;
// it starts by telling you what you are, because nothing else here will.
S.script = [
'you are a synchronise packet.',
@ -113,11 +148,24 @@ export default {
'<i>are you there</i>',
'nobody has ever answered it.',
'there are five hundred and twelve slots in this ring and you are in one of them.',
'the tail pointer is coming round.',
'the descriptor on the wall of your slot is all of you. <b>E</b> to read it.',
];
S.next = 3.0;
},
// The one in the next slot. It has been transmitted, and it came back, which is
// not supposed to happen, and it is the first hint of where this ends.
NEIGHBOUR: [
'you are new. you have got the flag still set.',
'i had that. SYN, window eight thousand, off you go.',
'i went out. eleven hops. i do not remember most of it.',
'...',
'no. nobody answered. i came back on my own, which is not a thing that is<br>supposed to happen, and nobody has come to ask me about it.',
'they put me back in the ring like a returned parcel.',
'if it does answer — and it will not — you will not come back here.<br>you go into a socket and you stop being a packet at all.',
'that is the good ending. i want to be clear that that is the good one.',
],
update(dt, t, ctx) {
S.t += dt;
@ -135,6 +183,10 @@ export default {
packets.instanceMatrix.needsUpdate = true;
}
if (S.neighbour) {
S.neighbour.position.y = 0.9 + Math.sin(S.t * 0.8) * 0.05;
S.nbHalo.rotation.y += dt * 0.4;
}
const ha = S.t * 0.42, ta = S.t * 0.42 - 1.15;
head.position.set(Math.cos(ha) * S.R, 0, Math.sin(ha) * S.R);
tail.position.set(Math.cos(ta) * S.R, 0, Math.sin(ta) * S.R);
@ -153,7 +205,49 @@ export default {
ctx.say(S.script[S.line], 3600);
S.line++;
S.next = S.t + 3.9;
if (S.line === S.script.length) S.dmaAt = S.t + 4.4;
// the DMA no longer fires on a timer — the tail pointer has to reach you
if (S.line === S.script.length) { S.armed = true; ctx.hint('<k>WASD</k>move<br><k>E</k>read'); }
}
// ── the descriptor
const dd = Math.hypot(body.pos.x - S.descAt.x, body.pos.z - S.descAt.z);
S.desc.visible = dd < 7;
if (dd < 3.4 && !S.readDesc && In.useHit()) {
S.readDesc = true;
A.blip(1800, 0.04, 0.05);
ctx.say(`sequence number ${S.seq}. window eight thousand one hundred and ninety two.<br>one flag set, and it is the one that means <i>asking</i>.`, 6200);
ctx.phile('descriptor');
}
// ── the neighbour
const nd = Math.hypot(body.pos.x - S.nbAt.x, body.pos.z - S.nbAt.z);
S.nbCore.material.color.setHex(nd < 3.2 ? 0xd8f0ff : 0x9fd8ff);
if (nd < 3.2 && !S.nbTalking) ctx.hint('<k>E</k>talk');
if (nd < 3.2 && In.useHit() && S.nbCool <= 0) {
S.nbCool = 0.5;
const L = this.NEIGHBOUR[Math.min(S.nbSaid, this.NEIGHBOUR.length - 1)];
ctx.say(L, 4400, 'SYN');
S.nbSaid++;
A.blip(420 + Math.random() * 90, 0.02, 0.02);
if (S.nbSaid === this.NEIGHBOUR.length) ctx.phile('came-back');
}
S.nbCool = Math.max(0, (S.nbCool || 0) - dt);
// ── the tail pointer. It has been coming round this whole time. Now it
// arrives, and the DMA is something that happened to you rather than
// something the level scheduled.
if (S.armed) {
const dTail = Math.hypot(body.pos.x - tail.position.x, body.pos.z - tail.position.z);
if (!S.tailWarned && dTail < 26) {
S.tailWarned = true;
ctx.say('the tail pointer is coming round.', 4000);
}
if (dTail < 22) {
// it drags the room toward it as it closes
ctx.link.glitch = Math.max(0, (22 - dTail) / 22) * 0.28;
if (!S.tailNear && dTail < 9) { S.tailNear = true; A.thud(60, 0.7, 0.3); ctx.say('it does not slow down for anybody.', 3000); }
}
if (dTail < 3.2 && !S.dmaAt) S.dmaAt = S.t;
}
// walk far enough out and the ring notices you are missing
@ -163,7 +257,7 @@ export default {
ctx.say('you have left your slot. this has never mattered before.', 3800);
}
if (S.dmaAt && S.t > S.dmaAt) {
if (S.dmaAt && S.t >= S.dmaAt) {
S.phase = 'dma'; S.dma = 0;
ctx.hint('');
ctx.hush?.();

View File

@ -13,11 +13,16 @@ import * as In from '../input.js';
import { Term } from '../term.js';
const FS = {
'/': ['usr', 'tmp', 'mnt', 'etc'],
'/usr': ['games', 'lib', 'spool', 'milnet'],
'/usr/milnet': ['sdinet.doc', 'hosts.mil', 'roster.txt'],
'/etc': ['passwd', 'accounts', 'motd'],
'/': ['usr', 'tmp', 'mnt', 'etc', 'dev'],
'/usr': ['games', 'lib', 'spool', 'milnet', 'sventek'],
'/usr/milnet': ['sdinet.doc', 'hosts.mil', 'roster.txt', 'README'],
'/usr/sventek': ['.login', '.rhosts', 'notes.txt', 'thesis.tex'],
'/usr/spool': ['lpd', 'mail'],
'/usr/spool/mail': ['sventek', 'cstoll'],
'/usr/games': ['adventure', 'rogue', 'fortune'],
'/etc': ['passwd', 'accounts', 'motd', 'utmp', 'syslog'],
'/tmp': ['.x', 'core'],
'/dev': ['tty01', 'ttyf3', 'lp0'],
'/mnt': [],
};
const DOCS = {
@ -46,6 +51,33 @@ nine seconds of connect time that nobody paid for.
everybody else who saw this rounded it off. one person did not, and that
is the entire reason any of this was ever found out.` },
'/usr/milnet/hosts.mil': { title: 'hosts.mil', body: 'four hundred and sixty addresses. he printed all of them.' },
'/usr/milnet/README': { title: 'README', body: 'this directory is a mirror. do not edit. — j.s.' },
'/usr/sventek/.rhosts': { title: '.rhosts', body: `+ +
two characters. it means: any host, any user, no password.
somebody typed that to save themselves twenty seconds a day in 1984 and
then left, and the account has been sitting here ever since with the door
wedged open by a doorstop made of two plus signs.` },
'/usr/sventek/notes.txt': { title: 'notes.txt', body: `- ask dave re: tape allocation
- renew parking permit
- back sept 4
(he has been in england since june. these are from before.)` },
'/usr/sventek/thesis.tex': { title: 'thesis.tex', body: '\\documentclass{article} ... four hundred pages about crystal lattices. it is not about you.' },
'/usr/spool/mail/cstoll': { title: 'mail: cstoll', body: `From: dave@lbl
Subject: the 75 cents
nobody cares about the 75 cents, cliff.
(he cared.)` },
'/etc/syslog': { title: 'syslog', body: `Nov 4 02:14:07 lbl login: ROOT LOGIN ttyf3
Nov 4 02:14:09 lbl su: sventek to root
Nov 4 02:19:44 lbl login: ROOT LOGIN ttyf3
it is writing down everything you do, in a file you can read, which you
could edit and he has a printer copy that you cannot.` },
'/dev/tty01': { title: '/dev/tty01', body: 'his terminal. you can read it. that means he can read yours.' },
'/etc/motd': { title: 'motd', body: 'LBL VAX 11/780. be nice to each other.' },
'/usr/milnet/roster.txt': { title: 'roster.txt', body: 'names, ranks, and one phone number in Hannover.' },
};
@ -57,7 +89,9 @@ export default {
baud: 1, noRetrain: true,
async mount(ctx) {
S = { t: 0, lines: 50, read: 0, cwd: '/', gone: false, caught: false, elapsed: 0 };
S = { t: 0, lines: 50, read: 0, cwd: '/', gone: false, caught: false, elapsed: 0,
psLooks: 0, sawPrinter: false, cleaned: false };
window.__S = S;
ctx.scene.background = new THREE.Color(0x000000);
ctx.camera.position.set(0, 0, 5);
@ -107,7 +141,8 @@ export default {
if (!cmd) continue;
if (cmd === 'help') {
term.writeln('|08 ls cd <dir> cat <file> who ps logout');
term.writeln('|08 ls cd <dir> cat <file> who ps clean logout');
term.writeln('|08 clean — edit the accounting files. costs time, buys cover.');
term.writeln('|08 every second you stay logged in he crosses off more lines.');
} else if (cmd === 'ls') {
const here = FS[S.cwd] || [];
@ -137,10 +172,35 @@ export default {
term.writeln('|11 sventek ttyf3 Nov 4 02:14 (you)');
term.writeln('|08 cstoll tty01 Nov 4 02:14 (he has been on since you were)');
} else if (cmd === 'ps') {
term.writeln('|08 1 /etc/init');
term.writeln('|08 214 /usr/lib/lpd ← fifty of these');
term.writeln('|08 907 cat /dev/tty01 > /dev/lp0');
term.writeln('|08 908 sleep 1; beep pager ← this one is taped to a teletype');
S.psLooks = (S.psLooks || 0) + 1;
term.writeln('|08 1 /etc/init');
term.writeln('|08 214 /usr/lib/lpd');
term.writeln('|08 908 sleep 1; beep pager (taped to a teletype)');
// what he is doing changes as he narrows it, and you can watch him do it
if (S.lines > 24) {
term.writeln('|08 907 cat /dev/tty07 > /dev/lp0');
term.writeln('|08 911 cat /dev/tty12 > /dev/lp1');
term.writeln('|03 he is printing every line in the building. all of them at once.');
} else if (S.lines > 6) {
term.writeln('|08 907 cat /dev/tty01 > /dev/lp0');
term.writeln('|14 938 cat /dev/ttyf3 > /dev/lp2');
term.writeln('|12 ttyf3 is yours. he has a printer on your line.');
if (!S.sawPrinter) { S.sawPrinter = true; ctx.phile('printer'); }
} else {
term.writeln('|12 938 cat /dev/ttyf3 > /dev/lp2');
term.writeln('|12 944 /usr/bin/trace ttyf3 --hold');
term.writeln('|12 he has stopped printing anything except you.');
}
} else if (cmd === 'clean' || cmd === 'wipe') {
if (S.cleaned) { term.writeln('|08 already done. it does not un-notice.'); continue; }
S.cleaned = true;
await term.paint('|07 editing /etc/utmp and /etc/syslog ...\n', 700);
S.elapsed += 34; // it costs you a lot of clock
await ctx.sleep(1600);
S.lines = Math.min(50, S.lines + 11); // and buys back a real amount of cover
await term.paint(`|10 your login is gone from the accounting file.\n`, 900);
await term.paint('|08 the paper copy in his basement is not, because paper does not\n have an editor. you bought time, not safety.\n', 1100);
ctx.phile('wipe');
} else if (cmd === 'logout' || cmd === 'exit') {
return this.leave(ctx, term, false);
} else {

View File

@ -72,7 +72,9 @@ const BOSS = {
update(dt, t, ctx) {
const b = S.b; b.t += dt;
b.heads.forEach((h, i) => {
const period = 175 / h.f; // 350 and 440 do not line up often
// phase two: after the first hit it stops being patient
const urgency = 1 + (b.hits >= 1 ? 0.55 : 0) + (b.hits >= 2 ? 0.75 : 0);
const period = (175 / h.f) / urgency; // 350 and 440 do not line up often
h.phase += dt;
h.mesh.scale.setScalar(1 + Math.sin(b.t * (h.f / 100)) * 0.08);
if (h.phase > period) {
@ -86,7 +88,7 @@ const BOSS = {
});
for (let i = b.rings.length - 1; i >= 0; i--) {
const rr = b.rings[i];
rr.r += dt * 9;
rr.r += dt * 9 * (1 + b.hits * 0.45);
rr.m.scale.setScalar(rr.r / 0.2);
rr.m.material.opacity = Math.max(0, 0.9 - rr.r / 26);
if (rr.r > 26) { S.arena.remove(rr.m); rr.m.geometry.dispose(); b.rings.splice(i, 1); }
@ -180,8 +182,11 @@ const BOSS = {
update(dt, t, ctx) {
const b = S.b;
b.rt += dt; b.ct += dt;
if (b.rt > 0.92) { b.rt -= 0.92; b.row = (b.row + 1) % 4; A.burst(A.DTMF_ROW[b.row], null, 0.05, 0.05); }
if (b.ct > 0.61) { b.ct -= 0.61; b.col = (b.col + 1) % 4; A.burst(A.DTMF_COL[b.col], null, 0.05, 0.05); }
// phase two: past halfway the switch stops waiting for you
const rush = b.at >= 4 ? 0.62 : 1;
if (b.rt > 0.92 * rush) { b.rt -= 0.92 * rush; b.row = (b.row + 1) % 4; A.burst(A.DTMF_ROW[b.row], null, 0.05, 0.05); }
if (b.ct > 0.61 * rush) { b.ct -= 0.61 * rush; b.col = (b.col + 1) % 4; A.burst(A.DTMF_COL[b.col], null, 0.05, 0.05); }
if (b.at >= 4 && !b.rushed) { b.rushed = true; ctx.say('<b>the switch is timing out.</b> it will not hold the digits forever.', 3600); }
b.beams.rows.forEach((m, i) => { m.material.opacity = i === b.row ? 0.5 : 0; });
b.beams.cols.forEach((m, i) => { m.material.opacity = i === b.col ? 0.5 : 0; });
@ -237,7 +242,8 @@ const BOSS = {
S.b.tone = A.ansam({ gain: 0.13 });
S.b.tone.onReverse(ph => {
S.b.dir = ph;
S.b.still = 0.19; // the deaf window. this is your opening.
// the deaf window narrows every time you use it — 190 ms down to about 90
S.b.still = Math.max(0.085, 0.19 - S.b.hits * 0.021);
A.relayClick(0.18, 0.6);
});
ctx.say('two thousand one hundred hertz, reversing its phase every four hundred and fifty milliseconds.', 5200);
@ -269,6 +275,7 @@ const BOSS = {
A.burst(2100, 1050, 0.18, 0.2);
S.flash = 0.7;
ctx.say(`<b>echo canceller down</b> ${b.hits}/${b.need}`, 1200);
if (b.hits === 2) ctx.say('it is learning. <b>the deaf window is getting shorter.</b>', 3400);
if (b.hits >= b.need) this.win(ctx);
}
},
@ -390,7 +397,9 @@ const BOSS = {
const b = S.b; b.t += dt;
b.z += dt * 34;
// the channel snakes. one sine is a corridor; two is a river.
const chan = z => Math.sin(z * 0.035) * 9 + Math.sin(z * 0.011 + 2) * 5;
// a line you negotiated badly is a line that wanders more
const w = 1 + (6 - (ctx.game.flags.baudEarned ?? 3)) * 0.22;
const chan = z => (Math.sin(z * 0.035) * 9 + Math.sin(z * 0.011 + 2) * 5) * w;
b.x = clamp(b.x + In.axis().x * dt * 26, -22, 22);
S.p.position.set(b.x, 2, 0);
const off = Math.abs(b.x - chan(b.z));

View File

@ -39,6 +39,17 @@ const SEATED = [
// because everybody in the room has watched it happen a great many times.
const AGE_OUT = 'i was going to say —';
// And somebody arrives, later, through the same door, with the flag still set — which
// is what you looked like when you walked in, and the room knows it.
const ARRIVAL = [
'...is this it?',
'i sent mine about forty seconds ago.',
'they said somebody would come and tell me what happens next.',
'nobody is coming, are they.',
'...',
'how long have you been here.',
];
// He will not talk to somebody who walked the length of the room without stopping.
const FIN_REFUSE = [
'there is a room full of them behind you and you have come straight to the door.',
@ -67,7 +78,8 @@ export default {
async mount(ctx) {
const { scene, camera } = ctx;
S = { t: 0, near: null, talking: null, li: 0, finDone: false, door: null, cool: 0,
listened: 0, lapped: false, doomed: null };
listened: 0, lapped: false, doomed: null, arrived: false, newcomer: null };
window.__S = S;
scene.background = new THREE.Color(0x1a1712);
scene.fog = new THREE.Fog(0x1a1712, 8, 46);
@ -241,6 +253,36 @@ export default {
'they were halfway through a sentence. nobody in the room looked up.');
}
// ── and then somebody new comes through the door, with the flag still set,
// looking exactly like you did, and the room does not look up at that either.
if (!S.arrived && S.t > 62) {
S.arrived = true;
const g = new THREE.Group();
const core = new THREE.Mesh(new THREE.IcosahedronGeometry(0.26, 1),
new THREE.MeshBasicMaterial({ color: 0x9ff5ff }));
g.add(core);
const halo = new THREE.Mesh(new THREE.IcosahedronGeometry(0.42, 1),
new THREE.MeshBasicMaterial({ color: 0x2ad8ff, wireframe: true, transparent: true, opacity: 0.4 }));
g.add(halo);
g.position.set(12.0, 0.92, 0);
ctx.scene.add(g);
S.newcomer = { g, core, halo, x: 2.0, z: 1.0, data: { name: 'SYN', lines: ARRIVAL },
phase: 0, spoken: 0, walking: true };
S.people.push(S.newcomer);
A.blip(520, 0.03, 0.03);
ctx.say('the door opened.', 2600);
}
if (S.newcomer && S.newcomer.walking) {
const n = S.newcomer;
n.g.position.x = damp(n.g.position.x, n.x, 1.1, dt);
n.g.position.z = damp(n.g.position.z, n.z, 1.1, dt);
if (Math.abs(n.g.position.x - n.x) < 0.3) {
n.walking = false;
ctx.say('it still has the flag set. it looks exactly like you did an hour ago.', 5200);
ctx.phile('arrival');
}
}
// ── a full lap of the clock. two minutes is MSL, and the room notices you did it.
if (!S.lapped && S.t > 120) {
S.lapped = true;

View File

@ -23,6 +23,31 @@ export const PHILES = {
actually wrote the tail pointer back. It is always the tail pointer. */`,
},
'descriptor': {
title: 'the descriptor',
from: 'nic0.tx[0341]',
body: `seq, window, flags, ack, ttl.
that is the whole of you. five fields in a ring buffer slot, and one of the
flags is set, and the one that is set is the one that means asking.
everything that happens over the next sixty-four hops is an attempt to get
the fourth field filled in.`,
},
'came-back': {
title: 'the one that came back',
from: 'the next slot along',
body: `it went out. eleven hops. nobody answered, and it came back on its own,
which is not a thing that is supposed to happen, and nobody has ever come
to ask it about it. they put it back in the ring like a returned parcel.
it wanted you to know that if you do get an answer you will not come back
here you go into a socket and you stop being a packet at all.
and it wanted to be very clear that that is the good ending.`,
},
'osi': {
title: 'the seven layers, as built',
from: 'ISO/IEC 7498-1, and what actually happened',
@ -132,6 +157,47 @@ be worse.
the chair is still there. it goes on being a chair.`,
},
'printer': {
title: 'a printer on your line',
from: 'ps, at 02:19',
body: `he started with a printer on every line in the building, because he did not
know which one you were.
now there is exactly one left running and the device it is attached to is
yours.
you can watch him narrow it in ps, which is the joke: the process table is
world-readable, so the hunt is visible to the hunted, and neither of you
can do anything about the other knowing.`,
},
'wipe': {
title: 'what editing the logs actually buys',
from: '/etc/utmp',
body: `you can edit the accounting file. it is a file. it has your login in it and
then it does not.
what you cannot edit is the fifty printers in his basement, because paper
does not have an editor, and the line printer wrote it down as it happened
on a continuous fanfold that nobody can go back and change.
that is the whole reason he used printers. not because they were good
because they were the only medium in the building you could not reach.`,
},
'arrival': {
title: 'somebody new came in',
from: 'the door, at 62 seconds',
body: `it still has the flag set.
it sent forty seconds ago. it is asking whether this is it, and whether
somebody is coming to explain what happens next, and it has already worked
out on its own that nobody is.
it looks exactly like you did when you walked in, and the room does not
look up at that either.`,
},
'msl': {
title: 'a full lap',
from: 'two minutes',