You are SYN. You want ACK. Root is the only one who can give it to you.
Three load-bearing ideas, all working:
- TTL is the health bar and it only goes down. 64 hops, no healing.
- Bandwidth is resolution. One render target, one shader; at 1200 baud
and below the 3D scene renders as ASCII glyphs.
- Every header you drop is a piece of clothing. Seven wireframe shells,
one per OSI layer, shed on the way down.
Every frequency is the real frequency, synthesised, nothing sampled:
350+440 dial tone, the DTMF matrix, R1 MF pairs at real KP/digit timing,
2600 Hz, ANSam at 2100 reversing phase every 450 ms, Bell 103 FSK.
Thirteen hops, each a different genre over one renderer — FP tutorial,
OSI descent platformer, walking sim, EFnet twin-stick, 2D buffer overflow,
Morris worm RTS, LBL terminal stealth, a whole BBS with a playable LORD,
the five-boss handshake arena, the copper, a playable blue box, the climb,
and an empty room with an ACK in it.
No engine, no build step, one vendored three.js.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
108 lines
5.1 KiB
Markdown
108 lines
5.1 KiB
Markdown
# HANDSHAKE
|
||
|
||
> *a love story in 64 hops*
|
||
|
||
You are **SYN**. You want **ACK**. Root is the only one who can give it to you.
|
||
|
||
Design doc: [DESIGN.md](DESIGN.md).
|
||
|
||
## Run it
|
||
|
||
```bash
|
||
python3 /Users/johnking/Documents/handshake/serve.py 8123
|
||
```
|
||
|
||
Then open `http://localhost:8123/`. Headphones — every frequency in this is the real
|
||
frequency and half the mechanics are inaudible without them.
|
||
|
||
`serve.py` is `http.server` plus `Cache-Control: no-store`, because ES modules cache
|
||
hard and it makes every edit look like it did nothing.
|
||
|
||
## Controls
|
||
|
||
| | |
|
||
|---|---|
|
||
| `WASD` | move |
|
||
| `SPACE` | jump / hold 2600 Hz |
|
||
| mouse | look (click to capture; dragging works too) |
|
||
| `E` | use |
|
||
| `M` | traceroute — the level select, click any hop you've reached |
|
||
| `` ` `` | hop select (dev) |
|
||
| `ESC` | release the mouse |
|
||
|
||
## What's here
|
||
|
||
Thirteen playable hops, each a different genre, one renderer.
|
||
|
||
| id | hop | what it is |
|
||
|---|---|---|
|
||
| `coldboot` | 1 | FP tutorial in a 512-slot NIC ring buffer, then the DMA rips you out |
|
||
| `stack` | 2–8 | 7-floor third-person descent. NX floors, rate gates, canaries, ASLR reshuffles on death, stateful FIREWALL boss |
|
||
| `timewait` | 9 | walking sim. beige room, 50 Hz hum, half-open connections in plastic chairs, FIN by the door |
|
||
| `war` | 10–16 | EFnet twin-stick. netsplit riding, ops crowns, Sub7 body-snatch |
|
||
| `smash` | 17–20 | 2D side-scroll `char buf[64]`. canary, return address, NOP sled, shellcode |
|
||
| `worm` | 21–28 | Morris worm RTS with the 1-in-7 reinfection locked on. you lose by winning |
|
||
| `cuckoo` | 29–34 | LBL 1986 terminal. connection time is visibility. you get caught |
|
||
| `board` | 35–44 | the BBS. busy signal, ANSI painting at live baud, ratios, NUP gate, ANSI bomb, the sysop watching you type, FidoNet, playable LORD |
|
||
| `handshake` | 45–50 | five-boss rhythm arena. dial tone → DTMF → ANSam → V.8bis → carrier |
|
||
| `copper` | 51–58 | FP through duct, load coils, a Strowger cathedral, a lineman's handset |
|
||
| `bluebox` | 59–63 | playable 13-button blue box. 2600, KP/MF/ST, tandem stacking, then SS7 |
|
||
| `climb` | 63→1 | on-rails reverse ascent. Spectre split-timeline, Rowhammer, Log4Shell |
|
||
| `root` | 64 | the room is empty. then the ACK comes back |
|
||
|
||
## The three load-bearing ideas
|
||
|
||
**TTL is the health bar and it only goes down.** 64 hops, no healing, ever. Levels
|
||
aren't hard, they're *expensive*.
|
||
|
||
**Bandwidth is resolution** — [`src/link.js`](src/link.js). One render target and one
|
||
shader. Your baud rate sets the target height; at 1200 and below the scene renders as
|
||
ASCII glyphs. `HS.link.setBaud(0..6)` in the console to see it. The V.8bis fight sets
|
||
this for real.
|
||
|
||
**Every header you drop is a piece of clothing** — `makeSyn()` in
|
||
[`src/kit.js`](src/kit.js). Seven wireframe shells, one per OSI layer, shed as you
|
||
descend.
|
||
|
||
## Audio
|
||
|
||
[`src/audio.js`](src/audio.js) — every tone is oscillators, nothing is sampled, and
|
||
every frequency is the real one: 350+440 dial tone, the DTMF matrix, R1 MF pairs with
|
||
real 100 ms KP / 60 ms digit timing, 2600 Hz, ANSam at 2100 Hz reversing phase every
|
||
450 ms, Bell 103 FSK. About 300 lines and zero megabytes.
|
||
|
||
## Layout
|
||
|
||
```
|
||
index.html shell, all CSS, importmap
|
||
serve.py dev server, no-store
|
||
src/main.js router, TTL, traceroute map, the loop
|
||
src/link.js bandwidth → resolution. the post shader.
|
||
src/audio.js every tone in the game
|
||
src/term.js 80x25 cell terminal, pipe colours, CP437 blocks as geometry
|
||
src/kit.js body/collision, camera rigs, SYN, labels, seeded rng
|
||
src/input.js keys, pointer lock, the ANSI bomb's remap table
|
||
src/hud.js status line, subtitles, title cards, NO CARRIER
|
||
src/levels/*.js one file per hop
|
||
vendor/ three.js r17x, vendored
|
||
```
|
||
|
||
Each level exports `{ id, hop, title, via, baud, mount(ctx), update(dt,t,ctx), unmount(ctx) }`.
|
||
`ctx` hands it a fresh scene, camera and collision world, plus `say/card/hint/phile/
|
||
spendTTL/setBaud/next`. Genre switches are just different camera + controller modules
|
||
over one renderer — that's the whole trick.
|
||
|
||
## Dev handles
|
||
|
||
`HS` on `window`: `HS.goto(id)`, `HS.link.setBaud(n)`, `HS.game`, `HS.philes`,
|
||
`HS.run(n)` — drives `n` frames by hand, because `requestAnimationFrame` is throttled
|
||
to zero in a hidden tab and that makes the game look broken when it's only asleep.
|
||
|
||
## Skipped
|
||
|
||
- **Multiplayer (`CHECKSUM`)** — the Journey-style anonymous pass-by. Needs a server; add when there's something to connect to.
|
||
- **MTU fragmentation** — BBS cargo lowers your MTU and it's on the HUD, but nothing splits you into two packets yet. Add when a level has a corridor narrow enough to need it.
|
||
- **The 200 philes** — the collectible framework is live (`ctx.phile`, `HS.philes`) with ~20 written. The rest is a writing job, not a code job.
|
||
- **Trade Wars** — the second door game. LORD is real and playable; TW2002 is "closed for maintenance since 1994", which is both a joke and true.
|
||
- Levels are one hop's worth of content each, not the full 5–20 minutes the design doc describes. The genres, mechanics and set pieces are all in — the *duration* isn't.
|