destroyulator/LANES/LANE1-godot-record-ritual.md
Monster Robot Party c03bb85c9f Add parallel-lane execution specs for Opus 4.8
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>
2026-07-14 16:47:47 +10:00

88 lines
5.4 KiB
Markdown

# 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` — FP `CharacterBody3D`: mouse-look, WASD, `camera`, punch/kick
via `MeleeAttack`. 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, `smashed` signal.
- `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, carries `M_Sleeve_Front/Back` mats).
Disc-only + sleeve-only source GLBs exist at `~/Documents/Destroyulater/3D-STORE/clean_glbs/`
(`VinylDisc_v4.glb`, `VinylSleeve_v4.glb`) — copy into `game/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 `_disc` local position along the sleeve's
+X (opening) axis by `amount * sleeve_length`; peek the disc label as it emerges.
- At `_extract >= 1.0`: `release_disc()` → unfreeze `_disc`, detach as free brittle vinyl
`Smashable`, keep the now-empty sleeve as a trashable cardboard body.
- The disc + sleeve both reuse `Smashable`'s shatter/juice path (emit `smashed`).
### 2. `game/scripts/GrabController.gd` — attach under `Player`
- A hold point (`Node3D` child of camera, ~0.5m forward, ~-0.1 down).
- `E` = grab the `Record` under the crosshair (reuse the sphere/ray query pattern from
`MeleeAttack.strike`, but filter for `Record`). Only grabbable within reach (~2m).
- While held: accumulate `InputEventMouseMotion.relative.x` into the held record's
`set_extract()` (tune: ~600px of drag = full pull). Show an on-screen extract meter.
- `LMB` while holding a freed disc = throw (impulse along camera forward, disc is brittle
so it cracks on impact). `G` or `LMB` on the empty sleeve = drop/trash it.
- `Esc` still 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 existing `Main._unhandled_input`.
### 4. Wire into `Main._build_rack`
- Replace the `_glb_piece(RECORD_GLB, "vinyl", …)` record placement with `Record` instances
(sleeve+disc). Keep them stacked on crates; keep `crate.supports.append(record)`.
- Keep `_skin_record()` for the sleeve art; add a small disc-label tint by `disc_genre`.
## Acceptance (must pass before each commit)
```sh
# 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 via `global_transform` each `_physics_process`, OR
use a `Generic6DOFJoint3D`/`PinJoint3D` to the hold point — simpler is freeze+follow.
- Don't let the disc's brittle `body_entered` fire while it's still inside the sleeve
(guard: only arm brittleness after `release_disc()`).
- Keep everything in groups `smashable`/`debris` so the HUD body count + `Juice` keep 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`.