# DevTune V1 shell (desk)

The living Design desk. Plain ES modules; vendored three.js + Rapier.  
**Product path:** embedded into `product/host/shell` via `node product/tools/embed-host.mjs`.

```bash
# Standalone beachhead
npx http-server shell -p 8145 -c-1

# Product (preferred)
cd product && node bin/devtune.mjs serve games/client-demo --port 5430 --shell
```

→ **http://127.0.0.1:5430/**

---

## Keys (2026-08-04)

| Key | Action |
|-----|--------|
| **C** | Design (editor) |
| **P** | Play (smooth ease back) |
| **Esc** | End freelook → close search → clear selection |
| **F** | Freeze (Design; freezes on enter) |
| **K** | Keep |
| **Z** | Frame / focus object |
| **G** | Cycle gizmo Move → Rotate → Scale |
| **X** | Lock body from pick |
| **Tab** | Camera ↔ pawn WASD |
| **/** | Search |
| **?** | Keymap |

**Mouse**

| | |
|--|--|
| LMB | Select / gizmo |
| Shift+LMB | Multi-select |
| **RMB unfocused** | Freelook (pointer lock, hold) |
| **RMB focused** | Orbit focused object |
| MMB | Pan |
| Wheel | Zoom |
| Shift+gizmo drag | Follow object while placing |

**Removed hotkeys:** L, 1, 2, M, B (solid is Object-panel button only).

---

## Modules

| File | Role |
|------|------|
| `js/main.js` | Loop, input, camera, gizmo drag, keep/panic |
| `js/modes.js` | owns() + keyIntent + live binds |
| `js/session.js` | Held / kept / no-save honesty |
| `js/ui.js` | Lens: Object \| Gameplay tabs, header toasts |
| `js/gizmo.js` | Translate / rotate / scale + screen-axis drag |
| `js/rig.js` | Orbit + freelook math |
| `js/physics.js` | Make Solid + kinematic/dynamic mass |
| `js/selection.js` | Multi-select + body-level locks |
| `js/adapters/substrate.js` | Real-game adapter + swarm |
| `js/runtime.mjs` | Inject into served game |

**Full Claude handoff:** [`../product/CLAUDE_DESK_HANDOFF.md`](../product/CLAUDE_DESK_HANDOFF.md)

```bash
node --test shell/test/*.mjs
```

---

## Place / swarm contracts (do not break)

- Gizmo moves **mesh**; mid-drag `syncInstanced({ adopt: false })`; adopt on release.
- Swarm highlight via **instanceColor**; locks per **`id#instance`**.
- Focus pin uses **`focusTargetId`**, not selection (smooth sequential focus).
- Edge-on axis drag: floored screen projection — no head-on jitter.

---

## Make Solid

Object panel → **BODY** → Solid. Optional Float / Mass (session Rapier).  
Game-owned `collide()`: session body does not dual-own Keep of game colliders; pillars may record `dyn*`/`m*` on `pillars.moves`.
