Go to file
type-two 4e4311b568 [lane E+F] The game remembers you: a save file, a level select and an ending
Audit items 9, 10 and 11 — three features whose backends were finished and whose front ends did
not exist.

DIFFICULTY WAS UNREACHABLE. levels/difficulty.js has carried three fully specified tiers since
round 2 — Endoscopy, Clinical, Terminal, with per-tier assists and a note explaining each — and
every single call site in the codebase took the default. `easy` and `hard` were a table nobody
could select. getLevel now takes the saved difficulty (?diff= still wins, so a test URL stays
absolute), and the title has the switch. Verified live: the world boots at 11 spawns on Terminal
against 8 on Clinical.

THE CAMPAIGN WAS SIX LEVELS WITH ONE WAY IN. Reaching anything but L1 meant hand-editing ?lvl=
in the address bar. The title now lists the campaign, unlocking each segment as the one before
it is filed, showing the medal you hold on each. `LS_` is C's secret prefix, so the appendix
stays a row of question marks until you have earned your way in — naming it in the menu would
have given away the one thing in this game you have to discover.

THE CAMPAIGN HAD NO ENDING. C's anal_verge gate carries `to: null` — the only gate in the game
that does, its own note calling it "the end of the campaign" — and boot's ui:continue handler
opened with `if (!e.to) return`. Finishing GUTS parked you on a medal card forever with no way
out but the address bar, and the card said "any key", the same words it uses when it has nothing
to tell you. Now the clear is filed and counted, the card says you are out, and the title carries
CANAL CLEARED.

New core/save.js is the whole persistence layer: one localStorage key, one flat object, best
time / best score / best medal kept per column (they need not come from the same run) plus an
attempt count. Every entry point is wrapped — Safari private mode throws on setItem, and a save
file must never be able to stop the game booting. Its selfcheck (`node web/js/core/save.js
--selfcheck`, now in qa.sh) covers the best-of comparisons and feeds it corrupt, future-versioned
and malformed saves.

cards.js records rather than boot: this file computes the grade, so routing it through the bus
for boot to re-derive would be two copies of the medal table waiting to disagree.

One bug found by clicking rather than reading: the first cut of the chips had no
pointer-events:auto. #ui is pointer-events:none and it inherits — the file's own header says so
— so the buttons rendered, highlighted nothing, and passed the click through to the canvas,
which on the title starts the run. It looked like a menu and behaved like wallpaper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 22:02:58 +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+F] The game remembers you: a save file, a level select and an ending 2026-07-26 22:02:58 +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.