destroyulator/LANES/LANE3-web-smash-toy.md
2026-07-14 18:13:48 +10:00

4.6 KiB

LANE 3 — Web smash toy: thriftgod-based FP demo, 3GOD props, Rapier physics

Owns: web/ (new dir in the destroyulator monorepo). For: an Opus 4.8 session. Runs on: M3 Ultra (serve locally); deploy target later is the digalot.fyi VPS.

Objective

A shareable, instant-link web toy — not a port of the Godot game, a different product in the same universe. Walk around a room first-person, grab records, and smash the place, in the browser. Built on thriftgod's proven FP-interior code, physics via Rapier (WASM), props pulled live from the 3GOD depot. This is the streamer-bait / "try it in one click" front door.

What already exists (study, then fork — DON'T edit thriftgod)

  • ~/Documents/thriftgod (local) — LIVE Three.js first-person game (digalot.fyi). Copy the patterns, don't modify it. Reusable pieces:
    • web/index.html (~129k) — pointer-lock WASD FP controller, seeded procedural interior, 3D pickup/inspect interaction, GLTFLoader + DRACOLoader wiring (~line 1233, fixture kind 'glb'). This is your movement + loading foundation.
    • web/dig.js, web/cards.js — interaction + item UI patterns.
    • Three.js r0.175 via unpkg importmap, no build step (keep this — zero-tooling is a feature).
  • ~/Documents/thriftgod/web/dig.js — procedural Web Audio "fwip/thunk"; reuse the synth approach for smash SFX (mirror Godot's Juice.gd material voices).
  • 3GOD depot: https://digalot.fyi/3god, GET /api/list → shelf JSON; GLBs by slug. Read-only.

Deliverables (all under web/)

1. web/index.html + web/main.js — the app

  • Fork thriftgod's pointer-lock FP controller + a simple room (floor, walls, a couple of racks).
  • Load props from 3GOD by slug via GLTFLoader (start with record, crate, milk-crate-stack, vintage-cash-register, and the Lane-2 printer once it lands). Fallback to a local web/assets/ copy if the depot is unreachable, so the demo never hard-fails offline.

2. web/physics.js — Rapier (WASM) destruction

  • Use @dimforge/rapier3d-compat (or -simd) via CDN/ESM. One rigid body per smashable prop, colliders auto-sized from the GLB bounding box (mirror Godot's _body_local_aabb approach).
  • Support-graph cascade — port the Godot idea: frozen (fixed) bodies hold up dynamic ones; destroying a support switches its dependents to dynamic. Same "knock the legs, it pancakes" feel.
  • Pre-fractured swap: when a prop has a .fractured.glb (Lane 2 contract #2), on smash hide the intact mesh and spawn each chunk_* as a short-lived dynamic body inheriting velocity. Brittle kinds (vinyl/glass) also shatter on hard impact. Pool + despawn debris (JS GC is the ceiling — cap active bodies, sleep/remove settled ones).

3. web/juice.js — feel layer

  • Screenshake (camera trauma), a brief hitstop (scale the physics step / freeze a few frames), per-material particle bursts (Points or instanced quads), and procedural Web Audio smash SFX per material (wood crunch, cardboard rip, vinyl crack+warble, glass tinkle, steel ring) — same recipe as Godot Juice.gd::_synth. Combo counter in the DOM HUD.

4. Record ritual (lite) + share hook

  • A simplified version of Lane 1's ritual: click a record to pull it (drag to slide the disc out), then throw/smash. Keep it snappy for web.
  • A "share your mess" button: screenshot the canvas (toDataURL) + a smash-count caption.

Serve + acceptance

cd web && python3 -m http.server 8099    # then open http://localhost:8099
  • Loads with no console errors; FP movement + pointer lock work; at least one 3GOD prop loads and smashes with particles + sound; holds ~60fps with a roomful of props on the M3 (test in Chrome AND Safari — Safari is the weakest WASM/WebGPU target).
  • Cross-origin note: fetching GLBs from digalot.fyi needs CORS on 3GOD; if blocked, mirror the handful of props into web/assets/ and load locally for now (flag it for the depot owner).

Gotchas

  • Don't import a bundler unless necessary — thriftgod's no-build importmap approach is the norm here.
  • Rapier is WASM: await RAPIER.init() before creating the world; keep physics off the render thread if it janks (Web Worker + SharedArrayBuffer needs COOP/COEP headers — only if needed).
  • The 3GOD SSRF/open-mode issue (see PLAN.md) is a WRITE-side problem; this lane only reads, so it's unaffected — but don't add any write calls to the depot from the web client.

Definition of done

A playable web/ smash toy: walk, grab a record, smash props (fractured where available) with juice, in Chrome + Safari, props from 3GOD (or local mirror). Committed + pushed to origin main under web/. Post a localhost screenshot/gif.