destroyulator/game/README.md
Monster Robot Party 61757d24bc LANE6: rigged FPS viewmodel, weapon loadout + material matrix, 4 HUD styles
Hands/POV
- Cut a real first-person arms rig out of the GODVERSE modular character kit
  (tools/gen_fps_arms.py): ch01 hands + per-side sleeves on the full 65-bone
  mixamorig skeleton, so all 20 finger bones per hand are poseable at runtime.
  Textures shrunk to 1k; 4.2 MB.
- ViewModel.gd instances that rig ONCE PER HAND and places each instance so its
  own hand bone lands on the grip — no IK. Grip orientation is measured off the
  rig at load (pinky->index knuckle = bore axis, elbow->hand = forearm dir), so
  it survives re-tuning grip_rest_rot instead of needing new euler angles.
- Motion layers: look-sway with spring-back, walk bob scaled by speed and weapon
  heft, idle breathe, landing dip, weapon lower/raise on swap.
- Sleeve material overridden (donor asset is a fantasy leather bracer); hand
  material forced non-metallic (its spec/gloss maps rendered skin as bronze).

Weapons
- Weapon.gd replaces MeleeAttack: 6 weapons, 4 swing archetypes, and the
  weapon-vs-material matrix from the founding chat.
- Smashable moves from binary hits_to_break to hp/toughness, giving three
  outcomes: break, dent, or futile (dead clank, no score, HUD nudge). The box
  cutter genuinely shreds cardboard and genuinely cannot hurt a filing cabinet.
- The hit lands at Weapon.contact THROUGH the swing, not on the click — that
  delay is most of why the sledge feels different from the cutter.
- Slots on 1-6 / wheel / Q; game modes moved to M, HUD cycle to H.

HUD
- Hud.gd: Arcade, Minimal, Work Order (a corporate destruction docket that fills
  in line items) and Dev, over one shared data feed. Scoring + combo multipliers.

Dev harness (not shipped)
- macOS screen-recording perms aren't available to the CLI, so the game records
  itself: dev/demo.tscn + DemoDriver.gd drive a scripted tour for --write-movie,
  and dev/probe_*.gd print rig/scale/placement numbers.

Fix: tools/gen_viewmodel.py box() scaled by size/2 on top of primitive_cube_add's
already-unit side length, halving every box — which detached the bat's blade.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 16:34:00 +10:00

110 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Destroyulator — the game
Mac-first (Apple Silicon / Metal), **Godot 4.7**, **Jolt** physics.
## Run it
**Editor:** open `/Applications/Godot.app`, import this folder (`game/`), press **F5** / ▶.
**CLI:**
```sh
/Applications/Godot.app/Contents/MacOS/Godot --path /Users/m3ultra/Documents/Destroyulater/game
```
## Controls
| Input | Does |
|---|---|
| **WASD / Space** | move / jump |
| **mouse** | look (Esc releases the cursor, click recaptures) |
| **LMB** | swing the equipped weapon |
| **16 · wheel · Q** | pick a weapon · cycle · swap to previous |
| **E · drag ←→ · LMB · G** | grab a record · slide the disc out · throw it · drop |
| **H** | cycle HUD style (Arcade / Minimal / Work Order / Dev) |
| **M** | toggle game mode (FreePlay ⟷ Find-the-Misfiled-Disc) |
| **F** | report the disc in hand (Find-the-Misfiled-Disc) |
| **B · R** | rain 500 bodies (stress gate) · reset the store |
## The weapon-vs-material matrix
This is the decision the game is built on. A swing deals `Weapon.power × Weapon.vs[material]`
against that material's `hp`, and there are three outcomes: it **breaks**, it **dents**
(knockback + a chip of damage), or it's **futile** — a dead clank, no score, no combo,
and a HUD nudge to switch tools.
| | cardboard | paper | vinyl | glass | wood | steel |
|---|---|---|---|---|---|---|
| **Bare Hands** | ok | good | ok | ok | poor | useless |
| **Box Cutter** | **shreds** | **shreds** | good | poor | useless | useless |
| **Cricket Bat** | good | poor | **great** | **great** | ok | poor |
| **Crowbar** | ok | poor | ok | good | good | **great** |
| **Sledgehammer** | ok | poor | ok | **great** | **great** | **great** |
| **Fire Extinguisher** | ok | poor | ok | **great** | good | good |
Tuned in one place: `scripts/Weapon.gd` (`vs` tables) and `scripts/Smashable.gd`
(`PROFILES[kind].hp`). The office printer is the level boss — steel at `toughness_scale
= 5.0`, so it's a real fight with anything but the sledge.
Weapons also differ in **when** they connect, not just how hard: the hit lands at
`Weapon.contact` through the swing animation, so the sledgehammer commits late and heavy
while the box cutter is near-instant.
## The viewmodel
`scripts/ViewModel.gd`. Real rigged hands (mixamorig, all 20 finger bones per hand)
holding real weapon meshes.
The arms rig is **instanced once per hand** and each instance is *placed* so its own hand
bone lands on the grip — no IK solver. That works because in first person you only ever
see forearm and hand. Placement solves two aims at once: the fist's bore lines up with
the weapon shaft, and the forearm runs back toward where that shoulder would be. Both
directions are **measured off the rig** at load (pinky-knuckle → index-knuckle for the
bore, elbow → hand for the forearm), so the grip survives re-tuning `grip_rest_rot`
instead of needing a fresh set of hand-authored euler angles.
Motion is layered on one node: look-sway with spring-back, walk bob scaled by speed and
weapon heft, idle breathing, landing dip, and a keyframed swing arc per archetype
(`overhead` / `horizontal` / `jab` / `thrust`).
## Assets
| Path | Source |
|---|---|
| `assets/viewmodel/fps_arms.glb` | `tools/gen_fps_arms.py` — cut from the GODVERSE modular character kit |
| `assets/viewmodel/{bat,sledge,crowbar,cutter,extinguisher}.glb` | `tools/gen_viewmodel.py` — procedural, grip at origin, shaft +Y |
| `assets/store/*.glb` + `*.fractured.glb` | Lane 2 prop batch; the fractured sibling drives real chunk destruction |
| `assets/art/sleeves_*.jpg` | 3×3 sheets of generated cover art, windowed per record via uv1 offset |
Regenerate either set with:
```sh
/Applications/Blender.app/Contents/MacOS/Blender --background --python tools/gen_viewmodel.py -- --render
```
## Dev harness
`dev/` is not shipped. Because macOS screen-recording permission isn't available to the
CLI, the game records **itself**:
```sh
Godot --path game --resolution 1280x720 --write-movie /tmp/cap.avi --quit-after 2250 dev/demo.tscn
```
`dev/DemoDriver.gd` drives the player through a scripted tour (every weapon, the material
matrix, the boss, all four HUDs) so a change can be eyeballed frame by frame without a
human at the keyboard. `dev/probe_*.gd` print rig/scale/placement numbers — renders are
too coarse to tell a 5 cm error from a 50 cm one.
## Smoke test before committing
```sh
/Applications/Godot.app/Contents/MacOS/Godot --headless --path game --quit-after 300
```
Zero script errors required. After adding any `class_name` script, run
`Godot --path game --editor --quit` first — headless Godot won't rescan a stale class cache.
## What's still missing
The store has no **room**: props sit on a grey plane in a black void, and several topple
on spawn before you touch them. That's the biggest remaining gap — see the top-level README.