haxtory/STAGES.md
type-two cb22239363 The soundtrack is built out of the signal
The last structural promise in the design doc. No level gets a chord any
more. Every bed in the game is derived from a real number belonging to the
era it plays under, and src/score.js says which one and why in a comment
above each line.

  coldboot/stack  1000 Hz, the reference tone every piece of telecoms test
                  gear on earth agrees on. The fortress is tuned to a
                  calibration signal.
  timewait        50 Hz mains and its harmonics. The room's own hum was
                  always the entire score; now the bed agrees with it.
  war             the 1800 Hz V.32 carrier folded down, with dial-up hiss
                  under it, because everybody on EFnet was on a modem.
  smash           0x90 is the NOP. Read as a number it is 144, so the sled
                  has a pitch and the level is tuned to the byte you slide
                  down.
  worm            seven partials, and the seventh one is detuned. One in
                  seven, as a chord.
  cuckoo          45.45 baud — the Model 33 rate, 22 ms a bit. Fifty of
                  them in a basement is what he actually heard all night.
  board           the 1200 Hz V.22bis carrier folded four times, which is
                  the doc's exact phrase: a modem carrier detuned into a
                  pad.
  shell           the 750 Hz terminal bell over Helsinki's 50 Hz mains.
  handshake       silence. The fight is the score; anything underneath is
                  noise competing with the only thing in this game that
                  has to be listened to.
  copper          20 Hz ringing current, 50 Hz mains, and Strowger stepping
                  at ten pulses a second. Three real clocks, and the
                  cathedral is the beat they make against each other.
  bluebox         the six MF tones at once. That is literally what is
                  inside a blue box.
  climb           all of it, layered.
  root            2600 Hz folded six times until it is a low room tone.
                  The number the whole game turns on, finally at rest.

ESTABLISHED gets its own cue rather than a bed: 350 and 440, which is the
dial tone the handshake arena opened on and called the sound a line makes
when nothing is happening — with something finally on it.

Fixed while building: the pulse gate was driven by a bare square
oscillator swinging -1..+1, so its off half was not silence, it was the
same tone phase-inverted. Tremolo where a clock was wanted. DC offset now.

All 14 hops swept clean, and no voices leak between them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 20:17:18 +10:00

21 KiB
Raw Blame History

HANDSHAKE — stage briefs

One page per hop. What's built, what's thin, what I'd add, ranked. Write your direction under DIRECTION and I'll build to it.

Current state: 14 playable hops, ~8,300 lines, all clean.

Since the first pass (you gave me the wheel; these were my calls): the philes are readable, the game saves, MTU costs you distance, ROOT reads your whole save and your FidoNet message comes back on Tuesday, TIME_WAIT is a room with sixteen people in it and one of them ages out mid-sentence, SMASHING THE STACK has ret2libc, THE CLIMB is a graded final exam with one gate per era, THE BLUE BOX has the routing card and the 2111 bridge, and THE COPPER lets you follow one call through seven banks of relays to a wrong number. Items marked DONE below.

Remaining gap is still duration: most hops are 36 minutes against a doc that calls for 520.

Repo: ssh://git@100.71.119.27:222/monster/haxtory.git · run: python3 serve.py 8123

Global rule I'm working to: nothing gets added that doesn't earn its minutes. A longer level is not a better level; a level with a second idea in it is.


1 · COLD BOOT — src/levels/coldboot.js

hop 1 · first person · ~90 s · v.90

In: 512-slot NIC ring buffer, instanced packets breathing in their slots, HEAD and TAIL pointers circling, six lines of script establishing what you are, then the DMA fires and rips the camera out at 1400 units/sec into the title card.

Thin: it's a corridor with narration. You can walk but there's nothing to walk to. The "you have left your slot" beat is the only interaction.

What I'd add, ranked

  1. The tail pointer as a threat you can see coming. It's already circling. Make it actually reach you — a slow, unhurried countdown of the ring, so the DMA feels like something that arrived rather than something scheduled.
  2. One other packet who talks. A neighbour in the next slot who has been transmitted before and come back. Three lines. Sets up FIN at hop 9.
  3. The descriptor you can read. Walk to the slot wall, press E, see your own header laid out — seq number, window size, flags. Establishes the SYN/ACK vocabulary the ending depends on.

DIRECTION:

_


2 · THE STACK — src/levels/stack.js

hops 28 · third-person platformer · ~5 min · v.90

In: seven descending layers, one per OSI layer, each shedding a header shell. NX floors you fall through, rate-limit gates on a real 1.2 s cycle, canaries that scream, ASLR reshuffling the NETWORK layer on every death, CSMA/CD platforms flickering on DATA LINK, and the stateful FIREWALL boss where the solution is to make it send something outbound and arrive as the reply.

Thin: seven platforms per layer generated from one rule, so all seven layers play the same. The hazards are one-note — you meet each idea once.

What I'd add, ranked

  1. Give each layer its own verb. DONE — seven floors, seven jobs:
    • 7 APPLICATION — plain. The only polite floor.
    • 6 PRESENTATIONtranscode. The same bytes in two encodings, RAW and BASE64, and only one set of platforms is real at a time. Q switches representation, in mid-air.
    • 5 SESSIONkeep it alive. A platform you have stood on starts closing the moment you leave it. Standing still is the mistake.
    • 4 TRANSPORTreassemble. They arrived out of order. Only the next platform in sequence is solid, and the sequence has nothing to do with where they are.
    • 3 NETWORKroute. Every platform is a router and every router costs a hop out of a budget of five. Overrun it and the floor ASLR-reshuffles under you.
    • 2 DATA LINKlisten before transmit. Shared media; each platform is busy on its own cycle. Land on a busy one and you both lose, with real exponential backoff — 0.35s, then 0.7, then 1.4, then 2.4.
    • 1 PHYSICALbe a signal. No platforms at all: 46 segments of a travelling wave you ride, attenuating down the wire.
  2. Make the firewall a three-stage fight. It still has one idea.
  3. Vertical shortcuts. Skip a layer, keep the header, and have the copper levels behave differently because you're still wearing TLS.
  4. The canaries should be worth waking. (Note: canaries and NX floors were retired in the verb rebuild — the hazards are now the verbs. Re-add only if a floor needs a second idea.)

DIRECTION:

_


3 · TIME_WAIT — src/levels/timewait.js

hop 9 · walking sim · ~2 min · v.32bis

In: the beige room. Plastic chairs, 50 Hz strip-light hum synthesised from actual 50/100/150 Hz, a wall clock doing a real two-minute lap, six seated half-open connections with three lines each, and FIN by the door with nine.

Thin: it's the shortest emotionally-load-bearing level in the game. Six NPCs is not a room. And FIN's nine lines fire on a single E-mash.

What I'd add, ranked

  1. More people, fewer words each. DONE — sixteen seated, one or two lines apiece, most of it not about you.
  2. Someone ages out mid-sentence. DONE — at 38 s, quietly, and nobody in the room looks up.
  3. The clock should matter. DONE — a full two-minute lap is noticed, and is a phile.
  4. FIN refuses if you rush him. DONE — three real conversations before he'll tell you about the door.
  5. Still open: somebody should arrive while you're in there, not just leave.

DIRECTION:

_


4 · THE WAR — src/levels/war.js

hops 1016 · top-down twin-stick · ~3 min · 9600

In: #2600 on EFnet. Sixteen nicks milling and saying things, the op wearing a crown and chasing you to kick you, ten seconds of telegraphed lag, the netsplit, the ghost crown on the empty side, and the rejoin that makes it real. Sub7 body-snatch on the bots.

Thin: two successful split-rides and it's over. The Sub7 possession has no purpose — you take somebody over and then there's nothing to do with them.

What I'd add, ranked

  1. The trojan arms race. DONE — WinNuke, Sub7, Smurf, DCC send, each patched by the level 70 s after first use, with the countdown on screen. You cannot finish on one tactic.
  2. DCC send. DONE — PAMELA.JPG.EXE. They take it. They always take it, and then they're yours and they body-block the rivals.
  3. Rivals. DONE — a crew turns up after the first rejoin who've read the same text files you have, and they'll take the ghost crown out from under you.
  4. A second channel. #phreak next door, different rules, and the war is over which one the population ends up in.
  5. Give possession more point. A possessed client should go where you can't — through a ban, into +i.

DIRECTION:

_


5 · SMASHING THE STACK — src/levels/smash.js

hops 1720 · 2D side-scroll · ~3 min · 9600

In: one char buf[64] you walk right along, the canary value on a ledge, the canary bird at the boundary, four return-address bytes you write little-endian by standing on them in order, a 40-tile NOP sled you slide down, shellcode at the end, and Aleph One chain-smoking at the start.

Thin: ret2libc is described in the design doc and isn't in. The level is one straight line — no reason to ever go back.

What I'd add, ranked

  1. ret2libc as a real second route. DONE — NX lands the instant you reach the shellcode; libc appears; four gadgets in order, four real decoys, and the chain lays the bridge behind you.
  2. Make the address matter. Any four bytes work because they're prewritten. Let the player choose, including wrongly.
  3. A second buffer. Off-by-one into the low byte of the saved pointer. Harder, better.
  4. Aleph One should say more than one line. He's the mentor and he's got four words.

DIRECTION:

_


6 · THE WORM — src/levels/worm.js

hops 2128 · RTS · ~3 min · 9600

In: 60 ARPAnet hosts on a graph with real names, three vectors (fingerd overflow, sendmail DEBUG, rsh trust) with different reach and rate, load accumulating per host, hosts dying at load 7, links dying with them, and the 1-in-7 reinfection locked on and displayed and unchangeable.

Thin: you pick a vector and watch. The only input is 1/2/3.

What I'd add, ranked

  1. Let the player try to stop it. DONE — an antidote, on K, three seeds. It travels only over live links, one hop per carrier per tick, while you are killing links. It cures fast and then stops dead with its component severed while the death count climbs past it. In testing: 4 → 12 → 18 → 19, stall, dead 20 → 30. The cure works perfectly on everything it can reach. It cannot reach the rest, because you already took the road.
  2. Named hosts that fall over. DONE — the first six are named as they go.
  3. The phone calls. DONE — six, and none of them can send mail to warn anybody, because the broken thing is the thing you'd have used.
  4. Pacing note: attempt rate now scales with the infection (every infected host runs a copy), which is both correct and the only way it reached its own milestones inside a human lifetime. First deaths ~22 s, endgame ~60 s.
  5. A win condition that is worse. Spread to everything before anything dies. Should be possible and should require restraint the worm didn't have.

DIRECTION:

_


7 · THE CUCKOO'S EGG — src/levels/cuckoo.js

hops 2934 · terminal stealth · ~3 min · 1200

In: LBL VAX 11/780, logged in as sventek. A filesystem with six real documents, ls/cd/cat/who/ps, and a "lines still being watched" bar that decays on real elapsed connect time. The SDINET bait file costs you a third of your margin for reading it. You get caught; the only variable is what you got first.

Thin: six files and five commands. The tension is a timer, not a decision.

What I'd add, ranked

  1. Make him visible in ps. He's already in there as a joke. Let the player actually watch his processes change — cat /dev/tty01 > /dev/lp0 appearing on your line is the moment the level turns.
  2. Choices that trade time for cover. Clearing logs costs 40 seconds and buys you 10 lines. Reading a file costs time and gains a phile. Right now everything is free except reading.
  3. More filesystem. The pleasure is poking around. Twenty files, most of them boring on purpose, three that matter.
  4. The Hannover number. It's mentioned in roster.txt and goes nowhere. It should go somewhere — even if where it goes is the blue box level, forty hops later.

DIRECTION:

_


8 · THE BOARD — src/levels/board.js

hops 3544 · terminal · ~8 min · whatever you earned

In: the heart, and the most finished level. Busy signal on the first dial, ANSI logo painting at baud/10 characters a second, six message-base messages, five files with a real 1:3 ratio, the ANSI bomb that actually remaps your movement keys, the sysop breaking into chat and telling you he can see you typing, the NUP gate, the elite sub-board that tells you how to leave, FidoNet's overnight run, and a playable LORD in the corner.

Thin: LORD is one fight. Trade Wars is a joke that's closed. The message base is read-only.

What I'd add, ranked

  1. Come back tomorrow, properly. DONEthree days. Day 1 as before. You post to FidoNet on logoff, watch the 04:00 run dial five nodes across the world while nobody is awake, and come back to "you were last on yesterday, calls: 2, you are user #44 of 44."
  2. Let the player post and be replied to. DONE — day 2's first message is FIDONET quoting what you actually typed, with an answer. Four days, three boards, a long distance call somebody else paid for.
  3. Leeching consequences. DONE — over-take on day 1 and day 2 opens with your ratio adjusted to 1:1. He said he would.
  4. The board should close. DONE, and it's the level's payload now — day 3 is LAST CALL. Not raided, not busted: the second line was never coming, the long distance is forty dollars a month, and a third of them have shell accounts and aren't calling any more and he doesn't blame them because it's better. Nine years, forty-four users, six real names, never met one of them. Ratio off, elite board open, NUP given away because it does not matter now. "do not say anything sad in the message base. i will only read it." They all ignore him.
  5. Finish Trade Wars. DONE — ten sectors, seven ports with the real class strings, 24 turns, and the Ferrengi in sector nine.
  6. More messages per day. Five or six each is still thin for a board.

DIRECTION:

_


8b · THE SHELL — src/levels/shell.js

hops 4344 · terminal · ~6 min · whatever you earned

Why it exists: day three of THE BOARD already names the thing that kills it — "about a third of you have got shell accounts now and are not calling any more, and i do not blame you, because it is better, it is genuinely better." The player never got to see it. Now they do, immediately afterwards, and the board's ghost is still warm.

In: kruuna.helsinki.fi, 1991. It connects instantly — no dial tone, no ring, no busy — and that silence after eight minutes of the board is the whole opening beat. who shows eight of the thirty-eight people logged in at this second, against a board that had forty-four users in nine years and could hold one at a time. news is the 25 August 1991 comp.os.minix post, the Freax/linux directory story, and the January 1992 GPL relicence. talk puts every character on screen as it is typed, because that is what talk did. And make compiles a kernel in the top four lines of the screen while you carry on using the shell underneath it — two things at once, which the board could never do, demonstrated rather than stated.

Thin: it is mostly reading. One real interaction (the compile) and one scripted one (the talk).

What I'd add, ranked

  1. Let the player be talked to unprompted. Somebody opens a talk request while you are mid-command. That is the actual texture of a shared machine.
  2. finger and .plan files. The whole social layer of a Unix box was people leaving notes in a file anyone could read.
  3. Quota. 4MB, enforced. Try to keep something and have to delete something else.
  4. The Tanenbaum debate. "LINUX is obsolete", comp.os.minix, January 1992 — a second news thread, and it is funnier and sadder than the first.

DIRECTION:

_


9 · THE HANDSHAKE — src/levels/handshake.js

hops 4550 · rhythm boss rush · ~6 min · sets your baud

In: the showpiece. Five bosses, every one a real frequency doing the real thing. Dial tone as two-circle interference you chase the intersection of; DTMF as a 4×4 grid where you attack at the crossing of a row and column tone; ANSam at 2100 Hz flipping its attack direction on its own 450 ms phase reversals; V.8bis as bullet hell where clean dodges declare capabilities and your score becomes your baud rate for the next two hours; then the carrier ride. Ends on a real CONNECT string.

Thin: the bosses don't escalate into each other. Each is a self-contained idea that stops when it's beaten.

What I'd add, ranked

  1. A second phase for each. Dial tone should eventually stop being patient. ANSam should shorten its deaf window. Right now every boss is at full difficulty from the first second and stays there.
  2. Let the player fail upward. Failing the scream currently just means worse graphics. It should also mean the carrier ride is harder — a bad negotiation is a bad line.
  3. Retrain. Real modems renegotiated mid-call when the line degraded. A retrain event later in the game that drops you back into a 30-second scream fight, at whatever hop you happen to be on, is the best possible callback.
  4. The DTMF number should be dialable wrong on purpose. Dial something that isn't GET-ROOT and see where it goes.

DIRECTION:

_


10 · THE COPPER — src/levels/copper.js

hops 5158 · first person · ~4 min · whatever you earned

In: 300 m of sodium-lit duct with cable bundles, three load coils, a lineman's handset you clip onto a pair to listen to a stranger's phone call, and a central office with 4,200 instanced Strowger switches stepping in a wave, plus the Almon Strowger plaque.

Thin: it's a corridor with three exhibits. The cathedral is the best-looking thing in the game and you walk through it in forty seconds.

What I'd add, ranked

  1. Follow one call. DONE — start it at the MDF and follow it down the aisles through seven banks of relays. Each digit is counted out at ten pulses a second, because that is how a step-by-step switch dials. It connects. It is a wrong number.
  2. Make the cathedral navigable. You now walk the aisles, but it should be somewhere you can get lost.
  3. More to listen to. One phone call on the lineman's handset. There should be six, and one should be a modem, and one should be somebody who hears you.
  4. Weather. The doc says it's the first level with weather. There's no water in it.

DIRECTION:

_


11 · THE BLUE BOX — src/levels/bluebox.js

hops 5963 · music/puzzle · ~5 min · whatever you earned

In: the gut punch. A playable thirteen-button blue box with correct R1 MF pairs and real 100 ms KP / 60 ms digit timing. Hold space for 2600 Hz to seize the trunk, then KP + digits + ST to route, four times, stacking tandems. Then the call comes back and rings the payphone six feet behind you and you hear your own signalling a quarter-second late. Then SS7 arrives and it stops working and Joybubbles keeps whistling anyway.

Thin: the routing codes are dictated. You type what you're told.

What I'd add, ranked

  1. Let the player route. DONE — the routing card is on the wall, the objective names a destination, and Joybubbles refuses to give you the digits.
  2. The 2111 conference. DONE — dialable from anywhere, forever, by anyone who knows the number. Six voices, no faces, loops indefinitely, and holding 2600 drops the bridge. After SS7 there is nothing on it.
  3. Joybubbles should teach by ear only. He half does. Take the frequencies off the readout during his lessons.
  4. Free-play mode after SS7. You keep the box. Let the player still press the buttons.

DIRECTION:

_


12 · THE CLIMB — src/levels/climb.js

hops 63→1 · on rails · ~2 min · whatever you earned

In: an accelerating flight up through eight era palettes in reverse, with the three modern toys: Spectre as a split timeline where you carry a token out of the branch that gets discarded, Rowhammer as a wall you bang on 24 times until a bit flips next door, and Log4Shell as a sign you hold up.

Thin: two minutes for what the design doc calls the best eleven minutes in the game. The eras flash past as colour changes with no content in them.

What I'd add, ranked

  1. One mechanic callback per era. DONE — seven gates: seize, wiper, 450 ms reversal, ANSI bomb, worm, netsplit, NX. Graded, not lethal (you arrive at TTL 2 by design, so nothing up here spends TTL).
  2. Everything on fire. DONE — embers, thickening as you climb.
  3. Log4Shell's beat of silence. DONE — the world stops, the fortress walks over and reads it.
  4. The Spectre section should be a real split screen. Two viewports, two timelines. Scissor test and a second camera.

DIRECTION:

_


13 · ROOT — src/levels/root.js

hop 64 · ~3 min · v.90

In: an empty white room with uid 0 in the middle distance and nobody in it. Four beats of narration at your own pace, then the ACK arrives from 220 units away, ESTABLISHED, a sixteen-second countdown, then FIN / FIN-ACK / ACK / CLOSED, then TIME_WAIT with FIN saying told you, then it loops to hop 1.

Thin: the loop is instant. And the ending doesn't know what you did.

What I'd add, ranked

  1. The ending should read your save. DONE — philes, expiries, the baud you negotiated and lived with, the climb grade, whether somebody vouched for you, whether you hung up without saying goodbye.
  2. Your FidoNet message comes back. DONE — Tuesday. Somebody replied, because somebody always did.
  3. The loop keeps the philes. DONE (flags.looped counts rounds) — but hop 1 is not yet different the second time.
  4. CHECKSUM. The anonymous pass-by. Needs a server. Last.

DIRECTION:

_


Cross-cutting, if you'd rather direct by system than by stage

The philes 75 of 200 written. All in src/philes.js, all readable with P. Pure writing.
Audio DONEsrc/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 DONEsrc/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.
The 64 hops 13 levels covering 64 hop numbers. Either fill in more levels or make the existing ones properly own their ranges.