build notes · 2026-10-23 Escape House 85,196 bytes · sha 4076ac12

Escape House — a Desmos escape room, rebuilt as one offline file

The original is a Desmos activity: rooms, codes, items and flags all living inside a graphing calculator's expression list. This is a rebuild of it as a single 85,196-byte HTML file that opens with no network, no assets and no install — 21 rooms across three levels, six lock mechanics, the original's exact codes, and a three-tier hint system that reads your own save file to know which step you are stuck on.

expressions read
794 in the state
clickable hotspots
55 mapped
shipped file
85,196 bytes, one file
source
1,473 lines / 10 files
rooms
21 9 + 6 + 6
lock mechanics
6 locks · 32 hints
the ask, verbatim

“can you make the game fully follow the original version and add a hit button on the top that helps poeple who are doing the thing and they dont know how to do”

Two requirements, and the second one is a design problem rather than a feature: a hint button that is useful while somebody is inside a puzzle, not only between puzzles.

1 · what the original actually is

The Desmos activity stores its whole game in the expression list. Reading the exported state gives the real structure — not what the screenshots look like, but what the author wired up:

evidencecountwhat it gave me
expressions.list794576 expressions, 175 images, 42 folders, 1 text note
clickableInfo55every hotspot's click action, in Desmos action-list form
folders42the author's own grouping: room art, puzzle art, state machine, item art
rooms (by coordinates)9each room is a fixed viewport + P_{centerScene}; nine centres cluster on a 3×3 grid
uploads.desmos.com175every image is a public URL, so clue art could be downloaded and read

The flags are brace-named, which is why naive grep misses them: the switch is u_{lightsOn}, the trapped door is u_{iceKey}, the dart is u_{sedDart}. Strip the braces and the whole dependency graph falls out of the click actions. Every code in this rebuild came out of that graph — 131 on the kitchen drawer, 962 on the bedroom keypad, 4 5 19 13 15 19 on the bookcase panel (A1Z26 for DESMOS), and 7:30 for the clock.

2 · the chain, as the author wired it

Eleven items, each one opening exactly one thing. Nothing is decorative: every item in the original's inventory has a consumer, and every consumer has a producer.

itemfoundused onopens
Ice cubekitchen fridgehot tapice key
Ice keymade at the sinkliving-room trapdoorbasement landing
Dartbasement store, slice wheelballoonthe rope, then the stairs
Glovesbedroom wallgreenhouse cactusgold key
Gold keycactusglass casemagnifier
Magnifierglass casedesk lampfire (needs the lamp on too)
Screwdriverbedroom nightstandbedroom keypad and office computergreenhouse door · computer panel
Clock handslibrary shelfkitchen clockbike chain (clock face opens)
Bike chaininside the clockliving-room bikenothing — pure payoff
UV lightlibrary bookcase panelwalls in every roomthe codes you cannot see otherwise
Tapekitchen drawer(read directly)the bookcase code

3 · six locks, none of them a text input

lockinteractionwherecode
3-digit dialthree wheels, click to advance each digitkitchen drawer1·3·1
Three-key keypad7-segment style pad, needs the screwdriver to expose itbedroom9·6·2
Six number dialssix wheels running 1–26, because the code is a wordlibrary bookcase4 5 19 13 15 19
Clockfit the hands, then set 7:30 — the face swings openkitchen7:30
Slice wheelfive wedges rotate; every wedge must land on 2, 6 or 9basement dart cabinet—
Node panelfour wires click round a cycle until they line up, then the power buttonoffice computer—

4 · the two mechanics the first pass missed

1

The clock is a container, not a clue

The first rebuild used the clock as a wall code only. In the original the clock takes two inputs — the hands from the library shelf and the time from a UV mark in the basement — and then opens, with the bike chain inside. That chain is the only thing that makes the living-room bike do anything, and the bike is a dead end on purpose: the author put a payoff there with no lock behind it.

2

The computer is three puzzles in a row

Screwdriver to open the panel → four nodes to align → power button → lights-out grid → the disk drive ejects. The first pass collapsed all of that into a single click. Also corrected: the screwdriver in the original opens the computer panel, not the keypad — the keypad's gate is its own flag (u_{keypadOpen}), and the two were being conflated because both are in the bedroom.

5 · the hint system

The request was a button for people “doing the thing and they don't know how to do”. A single “here's the answer” button fails that: it either spoils the puzzle or says nothing useful. So the hint is a ladder — three rungs per puzzle beat, and it knows which beats you have already passed.

Rung 1 · direction

No answer, no object, no room unless you are lost. “Something in the kitchen is cold enough to lock a key.” Enough to restart a stalled player.

Rung 2 · the move

“Open the fridge and take the ice cube, then select it and click the hot tap.” The action, in order, still without handing over a code.

Rung 3 · the answer

The code itself, plus the room it belongs to. Only on the third ask for the same beat — a player who re-asks twice has stopped enjoying being stuck.

Where it lives

Header button and the H key; plus a 💡 Stuck? button inside every lock panel, so the hint is reachable from behind the modal backdrop — the exact moment it is needed.

How a hint is chosen

// one beat = one line of puzzle progress
HINTS[level] = [ { id, room, done(G), need(G), t:[rung1, rung2, rung3] } … ]

resolveStep():
  1. if a lock panel is open        → the beat that panel belongs to   (context wins)
  2. else first beat where          !done(G)  &&  need(G)              (never a step you cannot reach)
  3. else                           → "everything here is done, head for the exit"

escalation:  new beat → rung 1 · same beat again → rung 2 · again → rung 3 (capped)
idle:        55 s with no click → the button itself starts pulsing

Three properties that matter more than the wording:

32 beats across the three levels — 18 in the house, 7 in the lab, 7 in the vault. The counter (9 / 18) and the dots are the player's own progress bar; they are not decoration, they are the reason the ladder feels like help rather than a walkthrough.

The drawer lock open, with the hint card showing the drawer beat over the dim backdrop
Inside a lock: the hint card floats above the modal backdrop (z-index 25, between the lock layer and the menu) and already points at the drawer beat, not at the first unsolved thing in the list.
The game at 390 px wide with the hint card open
390 px: the room becomes a fixed-aspect illustration band instead of stretching its props, the header stays on one line, and the hint card is full-width and dismissible.

6 · where this is not the original

Read this before comparing the two side by side.

7 · what broke, and what it taught

symptomcausefix
UV marks never appearedthe rebuilt UV item was missing its uv:true flag, so selecting it never set the lighting stateflag restored on the item definition
Slice wheel could not be solved by keyboard or automationSVGElement has no .click() — the test harness threw, the real mouse never didtests dispatch a real MouseEvent; game code unchanged
Node puzzle rendered one nodea stray </svg> inside the per-node loop closed the SVG after the first rectclose the SVG after the loop
H key did nothing with a lock openthe key handler bailed whenever a modal existed — the one moment the hint is wantedonly arrow keys are blocked by modals
Hint always showed the earliest unsolved steppaintHint() re-resolved the step and ignored the context set by the open panelone resolveStep() shared by both paths
Props squashed on a phonepercentage boxes against a tall viewport; “fixing” it with preserveAspectRatio broke the desktop composition insteadfixed-aspect room band under 560 px, art untouched
Keyboard room arrows deadhandler queried nav.arrow; the buttons are .arrow[data-d]selector corrected
Drawer and poster overlappedboth placed by eye at 62 % and 84 %re-laid the kitchen wall: fridge 5–19 %, counter 28–64 %, drawer 66–78 %, clock 82–92 %

8 · evidence

artifactbytessha256note
playable copy85,1964076ac12e04f0e82…/demos/escape-house/ — served byte-identical
mirror85,1964076ac12e04f0e82…/demos/escape-house-mirror/ — same hash as the file on the Desktop
source85,545—10 files, 1,473 lines, built by python3 build.py
verification——automated playthrough of level 1 in headless Chrome: all 18 hint beats resolved, level completes; levels 2–3 hint ladders driven live

Both copies are excluded from the site's skin injection (SKIP_PREFIX in theme-inject.py), so what the server sends is the file itself — no injected switcher floating over the inventory tray, no rewritten markup.

▶ Play it Full mirror Build spec

9 · what's next