# 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 | | **1–6 · wheel · Q** | pick a weapon · cycle · swap to previous | | **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) | | **E** | work at the station you're standing at (locks you in place); otherwise grab a record | | **drag ←→ · LMB · G** | with a record held: slide the disc out · throw it · drop | | **digits · arrows · ENTER** | while working: type · move cell · commit | | **B · R** | rain 500 bodies (stress gate) · reset the office | ## CUBICLE HELL The mode the game is about. See [`../LANES/LANE7-cubicle-hell.md`](../LANES/LANE7-cubicle-hell.md) for the full design. `Rage.gd` is the tilt meter: work provokes it, clean work drains it slightly, and at 100% you SNAP into a forced destruction window that **stalls if you stop destroying**. `RageOverlay.gd` is the blood vessel — a vignette that closes in, procedural veins that creep further into frame, and a heartbeat that goes 62 → 172 BPM. All of it generated in code; no textures, no shader files, no audio assets. `Tasks.gd` is the job. The spreadsheet's selected cell **silently drifts one cell** partway through entry, so the figure lands in the wrong place and you start again. The photocopier jams and wants the right tray out of `1, 2, 2A, 3, 3B`. `CubicleHell.gd` scores it as a payslip: **wages − damages**. Rage drains a flat amount per object but each object bills at its own value, so the meltdown decision is *smash a lot of cheap things fast* versus *one expensive swing*. ## 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. ## The level `scripts/Office.gd` builds LEVEL 01: an open-plan office after the Dunder Mifflin floor plan — bullpen of facing desk pairs behind cubicle partitions, reception, a glass-walled manager's office, conference room, break room with vending machines, a copier alcove for the printer boss, and a warehouse roller door. Drop ceiling, fluorescent troffers (which are the actual light sources), magnolia walls, grey carpet, a window wall with blinds. Office owns the **shell and static fittings**; `Main._populate()` owns everything **smashable**, and asks Office where things go (`desk_spots()`, `chair_spots()`, `copier_spot()`). Walls, desks and counters are boxes built in code because they are boxes; chairs, vending machines, the microwave and the plant come from `tools/gen_office_props.py`, because a box reads as obviously wrong for those. ### The spawn guard Placing ~40 props by formula means something will eventually spawn a few centimetres inside something else, and Jolt's depenetration is not gentle — a chair overlapping a desk left the building at 500 m/s on frame one, knocking over the furniture on its way. `Main._physics_process()` speed-limits dynamic bodies for the first 0.75 s: enough for a real overlap to push apart calmly, far too little to launch anything. Every offender is named once in a warning with **the position it was placed at**, so the underlying overlap stays visible and fixable rather than silently papered over. Verify a level is genuinely at rest with: ```bash /Applications/Godot.app/Contents/MacOS/Godot --headless --path game --quit-after 300 dev/demo.tscn ``` The demo's first act touches nothing and prints total body speed each second. It must read `0.00 m/s`, and no `[spawn]` warnings should appear.