Plan: Hack 1982 JS Port — /hack within Mazes of Menace
Context
Jay Fenlason’s original 1982 Hack is the ancestor of NetHack: ~6,200 lines of C across 10 files, no roles, no Amulet of Yendor, no quest — just “escape the dungeon.” We port it to the browser using the same methodology as the NetHack port: one JS file per C file, C field names preserved, async game loop, session-based parity testing, and a patched C harness for reference session capture.
The result lives at mazesofmenace.net/hack — the 1982 prototype next to its 40-year descendant.
For the full story of how four teenagers at Lincoln-Sudbury Regional High School wrote the game that became NetHack, see HISTORY.md.
Directory Structure
hack/
├── index.html Browser entry point
├── PLAN.md This document
├── CODEMATCH.md C↔JS function ledger (every function tracked)
├── DESIGN.md Architecture, state structure, decisions
├── LORE.md Hard-won porting lessons
├── hack-c/
│ ├── upstream/ Verbatim fenlason-hack source (read-only reference)
│ └── patched/ Modified for modern build + seed control + session capture
│ ├── Makefile
│ ├── hack_harness.c Harness: input injection, screen capture, JSON output
│ ├── rng_log.c RNG instrumentation
│ ├── run_session.py Run patched hack with seed+keystrokes → session JSON
│ ├── rerecord.py Re-run sessions from regen metadata
│ └── *.c / *.h Patched source files
├── test/
│ ├── sessions/ Reference JSON sessions captured from C
│ ├── replay_test.mjs Session replay comparator (JS vs C)
│ └── pes_report.mjs Parity Evidence Summary table
└── js/
├── const.js Constants from hack.h / hackfoo.h (leaf)
├── data.js Monster table, item arrays, strings (leaf)
├── gstate.js Global state holder
├── game.js GameState class, map cell factory
├── rng.js rnd.c → rn1/rn2/rnd/d (seeded, logged)
├── mklev.js mklev.c → level generator
├── lev.js hack.lev.c → savelev/getlev/mkobj
├── pri.js hack.pri.c → display/message functions
├── mon.js hack.mon.c → monster AI, combat, makemon
├── do.js hack.do.c → rhack command dispatcher
├── do1.js hack.do1.c → buzz/bhit/dosearch/dosave/ringoff
├── hack.js hack.c → domove/parse/doname/tele/unsee
├── main.js hack.main.c → game loop, shufl, losestr, glo
├── display.js Browser display (80×22 terminal)
├── input.js Async keyboard input queue
└── browser_main.js Browser adapter: wires display + input → main loop
Phases and Gates
Phase 0 — C Reference Harness
Goal: Produce reproducible JSON sessions from the original C code.
Gate: python3 run_session.py --seed 42 --keys ":hhhljj.ss" --out seed42.json produces a
valid, deterministic JSON session. Running twice with same args produces identical output.
Phase 1 — Browser Shell & Display
Goal: 80×22 terminal in the browser; keyboard input working.
Gate: Open hack/index.html in browser; see blank 80×22 terminal; pressing keys shows them
echoed. const.js and data.js import without errors; all 56 monsters accessible.
Phase 2 — Level Generation
Goal: JS mklev.js produces levels matching C reference output for given seeds.
Gate: 5 reference sessions all produce matching level layouts (typgrid match).
Phase 3 — Core Game Engine
Goal: All 9 C files ported; game is playable start to death. Gate: Can start a new game, move around, fight monsters, pick up gold, descend stairs, die.
Phase 4 — Session Parity
Goal: JS sessions match C reference sessions step-by-step. Gate: 10 reference sessions all replay with 100% screen match and ≥95% RNG match.
Phase 5 — Polish & Deploy
Goal: Full game experience; live at /hack.
Gate: A full game session from start to death/escape is satisfying.