LANES/PLAN.md — verdict, GODVERSE pipeline architecture, shared contracts, 3GOD security note LANES/LANE1-*.md — Godot: grab/slide/smash record ritual + find-the-misfiled-disc mode (owns game/) LANES/LANE2-*.md — meshgod cell-fracture stage + Destroyulator prop batch -> 3GOD (owns ~/meshgod) LANES/LANE3-*.md — thriftgod-based web smash toy, Rapier physics, 3GOD props (owns web/) LANES/KICKOFF.md — per-lane opening prompts + collision-safety rules Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5.4 KiB
LANE 1 — Godot: the record-destruction ritual + "find the misfiled disc" mode
Owns: game/ only. Engine: Godot 4.7 (Jolt). For: an Opus 4.8 session.
Runs on: M3 Ultra, /Applications/Godot.app.
Objective
Turn records from one-hit props into the game's signature dexterity ritual:
look at a record → Grab (E) to lift the sleeve out of the crate → drag mouse left↔right to slide the black disc out of the sleeve → the freed disc is a throwable brittle object (LMB throw / smash), and the empty sleeve is separately trashable.
Then a first game mode on top of it: "Find the Misfiled Disc" — one disc is in the wrong sleeve; find it before the timer, and the frantic search leaves a mess (the point).
What already exists (read these first)
game/scripts/Player.gd— FPCharacterBody3D: mouse-look, WASD,camera, punch/kick viaMeleeAttack. Add the grab controller here (or a child node it owns).game/scripts/Smashable.gd—class_name Smashable extends RigidBody3D;kind,PROFILES,smash(impulse),shatter(),supports[]/release()cascade,smashedsignal.game/scripts/Main.gd— builds the store (_glb_piece()auto-fits colliders, drops to floor),_skin_record()UV-tiles sleeve art, HUD,B=rain,R=reset._on_smashed→Juice.game/scripts/Juice.gd— hitstop/shake/particles/procedural audio,impact(kind,pos,shards).- Assets:
game/assets/store/record.glb(sleeve, carriesM_Sleeve_Front/Backmats). Disc-only + sleeve-only source GLBs exist at~/Documents/Destroyulater/3D-STORE/clean_glbs/(VinylDisc_v4.glb,VinylSleeve_v4.glb) — copy intogame/assets/store/if you split them.
Deliverables
1. game/scripts/Record.gd — class_name Record extends RigidBody3D
A record = a cardboard sleeve (this body, kind="cardboard" Smashable-like) that
contains a hidden vinyl disc. State machine: IN_CRATE → HELD → EXTRACTING → DISC_FREE.
- Exports/fields:
sleeve_genre: String,disc_genre: String(usually equal; the mode sets one mismatch),_extract: float(0→1 pull progress),_disc: RigidBody3D(the disc, starts frozen inside the sleeve at local offset). grab(hold_point: Node3D): freeze, reparent-follow the player hold point (kinematic).set_extract(amount: float): clamp 0..1, lerp_disclocal position along the sleeve's +X (opening) axis byamount * sleeve_length; peek the disc label as it emerges.- At
_extract >= 1.0:release_disc()→ unfreeze_disc, detach as free brittle vinylSmashable, keep the now-empty sleeve as a trashable cardboard body. - The disc + sleeve both reuse
Smashable's shatter/juice path (emitsmashed).
2. game/scripts/GrabController.gd — attach under Player
- A hold point (
Node3Dchild of camera, ~0.5m forward, ~-0.1 down). E= grab theRecordunder the crosshair (reuse the sphere/ray query pattern fromMeleeAttack.strike, but filter forRecord). Only grabbable within reach (~2m).- While held: accumulate
InputEventMouseMotion.relative.xinto the held record'sset_extract()(tune: ~600px of drag = full pull). Show an on-screen extract meter. LMBwhile holding a freed disc = throw (impulse along camera forward, disc is brittle so it cracks on impact).GorLMBon the empty sleeve = drop/trash it.Escstill releases mouse (don't break existing Player input).
3. game/scripts/GameMode.gd — class_name GameMode extends Node, owned by Main
- FreePlay (default) and FindMisfiled.
- FindMisfiled: on start, pick N records, set exactly ONE to
disc_genre != sleeve_genre(e.g. a techno disc in a jazz sleeve). Start a 60s timer (HUD). Win = the player extracts the misfiled disc and presses F (report) while holding it. Track "records disturbed" and "mess score" (bodies knocked / smashed via_on_smashed). Show result on win/lose. - Key
1= FreePlay,2= start FindMisfiled, on the existingMain._unhandled_input.
4. Wire into Main._build_rack
- Replace the
_glb_piece(RECORD_GLB, "vinyl", …)record placement withRecordinstances (sleeve+disc). Keep them stacked on crates; keepcrate.supports.append(record). - Keep
_skin_record()for the sleeve art; add a small disc-label tint bydisc_genre.
Acceptance (must pass before each commit)
# zero script errors across 180 frames (2 "material is null" lines are benign headless noise)
/Applications/Godot.app/Contents/MacOS/Godot --headless --path game --quit-after 180 2>&1 \
| grep -E 'SCRIPT ERROR:|Parse Error' | grep -v 'material.*null' # must be empty
Plus a print() on mode start confirming the misfiled record was planted (genre A in genre B).
Then eyeball in-editor: grab a record, drag to pull the disc, throw it, trash the sleeve.
Gotchas
- Held bodies: set
freeze=true+ move viaglobal_transformeach_physics_process, OR use aGeneric6DOFJoint3D/PinJoint3Dto the hold point — simpler is freeze+follow. - Don't let the disc's brittle
body_enteredfire while it's still inside the sleeve (guard: only arm brittleness afterrelease_disc()). - Keep everything in groups
smashable/debrisso the HUD body count +Juicekeep working.
Definition of done
Grab/lift/slide/smash works end-to-end; FindMisfiled mode is playable start→win/lose with a
timer and mess score; headless smoke test clean; committed + pushed to origin main.