Go to file
type-two 9482b8a2bf [lane E+B] The pause card stops fighting the player, and a refused pointer lock stops
faking a crash

Audit item 8, plus two bugs the last commit introduced and one it exposed.

REQUESTPOINTERLOCK RETURNS A PROMISE. Both new call sites wrapped it in try/catch, which catches
nothing — the rejection went to window.onunhandledrejection, which the previous commit had just
taught to paint a full-screen FEED LOST. So a browser declining a lock for any of its ordinary
reasons (document not focused; Chrome's ~1s lockout after the player leaves a lock with Escape)
presented to the player as the game crashing. Caught by clicking: "the root document of this
element is not valid for pointer lock" in 15px type over a game running perfectly well
underneath. All three sites — title dismissal, resume, and input.js's canvas click — now swallow
the rejection. A refused lock costs one click, which is what input.js has always been for.

PAUSE ON BLUR HAD NO CARD AND TWO OWNERS. boot.js emitted ui:pause directly, so the loop halted
while cards.js's own `paused` stayed false: the game stopped with nothing on screen to say why,
and the next P press raised a card that then took a SECOND press to leave. Moved to cards.js,
which owns the pause state — setPaused raises the card and emits. It already refuses while the
title or debrief is up, so alt-tabbing away from a card correctly does nothing. Verified: blur
raises PAUSED, one P resumes, exactly one true and one false on the bus.

THE PAUSE CARD WAS NOT MODAL. #ui is pointer-events:none and it inherits, so a click anywhere
but the RESUME button fell through to the canvas — whose mousedown handler re-requests pointer
lock whenever it is not held. Clicking beside the button captured the pointer, hid the cursor,
and left the player paused with nothing to press RESUME with. The dimmer takes pointer-events
now (our own handlers are capture-phase on window, so they still see every click; only the
canvas stops seeing them). The title keeps no dimmer and stays click-through by design.

Pausing also now releases the lock and resuming takes it back. Pausing with P used to keep the
pointer captured — a RESUME button that could not be pointed at — while Escape looked fine only
because the browser drops the lock for you. Same key, two behaviours, no stated reason.

And losing the lock now drops the queued EDGES, not just held keys: the Space you pressed to
reach the RESUME button was still in the queue when the card went, so the run restarted by
spending a boost you did not ask for.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 22:11:29 +10:00
.claude [chore] Drop the throwaway preview ports 2026-07-25 22:52:08 +10:00
docs [lane B+C+F] The Appendix Sovereign: the secret boss, and a reputation you cannot fake 2026-07-25 23:25:36 +10:00
pipeline [lane A] MODELBEAST dresses the molecules: 5 matcaps, and the canal picks space-filling 2026-07-16 21:31:24 +10:00
tools [lane E+F] The game remembers you: a save file, a level select and an ending 2026-07-26 22:02:58 +10:00
web [lane E+B] The pause card stops fighting the player, and a refused pointer lock stops 2026-07-26 22:11:29 +10:00
.gitignore [lane A] The swept room + the acid sea, built — and it corrected me four times 2026-07-16 18:04:16 +10:00
README.md [infra] GUTS is live at partly.party/gutsy — deploy wired, ship-check clean 2026-07-17 23:02:04 +10:00

GUTS — a fantastic voyage through the alimentary canal

Browser flight-combat game (three.js, zero build step). You pilot ENDO-1, a microscopic probe, mouth → rectum through five anatomically-grounded biomes: ride peristalsis down the esophagus, cross the acid sea of the stomach, thread the villi forest of the small intestine, survive the gas swamps of the colon. Fantastic Voyage × Star Fox × Descent.

Run it

cd web && python3 -m http.server 8140
# http://localhost:8140            — the game
# http://localhost:8140/?stub=1&dbg=1  — stub world + debug HUD (works from day 0)

No bundler, no npm, no build. ES modules + import map; three.js r175 vendored in web/vendor/ (never CDN — the game must run offline). Before you finish a session: bash tools/qa.sh.

The one-paragraph tech pitch

The whole canal is a spline. World position is (s, θ, ρ) — distance along the canal, angle and radius in the cross-section. Tube walls are chunked extrusions along parallel-transport frames; peristalsis and breathing are vertex-shader displacement (cheap, squishy, everywhere); collision is an analytic radius test (no physics engine). Caverns (mouth, stomach) switch the same controller to free 6DOF. Look is synthetic scanner — dark endoscope void, SEM-style monochrome walls with FLUX-generated detail textures, emissive-coded enemies. Everything is seeded + deterministic, and every asset is optional at runtime — the game boots and plays asset-free with procedural fallbacks.

Hard laws (all lanes)

  1. Determinism. No Math.random outside js/core/rng.js. Seeded RNG only. qa.sh greps.
  2. Assets are optional. Missing texture/model/audio ⇒ procedural fallback, never a crash.
  3. Eyeball everything. Visual work isn't done until a screenshot is in docs/shots/laneX/.
  4. Perf budget: ≤300 draw calls, 60 fps on M-series / mid laptop. Measure via window.DBG.
  5. Free-first. Local MODELBEAST generation before any paid API; fal.ai is a gated fallback (John's go). See PIPELINE.md.
  6. Git: stage only your exact paths, commit + push each session, never git add ..

Deploy

Live: https://partly.party/gutsy/bash tools/deploy.sh (QA gate → rsync web/ minus dev/docker cp into forum-nginx → verify). No build step; web/ ships as-is.

Two things to know before you touch it, both documented at length in the script header:

  • It's ephemeral. Content lives in the container's own layer (like blobbo/glytch), so a docker compose up --force-recreate or nginx image bump wipes it — re-run the script. The durable fix is a bind mount in forum/docker-compose.yml (what cratewars/roguelike use), which costs a forum-nginx recreate and brief partly.party downtime.
  • A 404 there returns HTTP 200. nginx's catch-all try_files ... /index.html serves the arcade index for any missing file. Verify on content, never on status codes.

Origin: ssh://git@100.71.119.27:222/monster/guts.git.