# Run a product game with the DevTune desk

The AI generates into `product/games/<name>/`.  
A human (or agent with monorepo access) serves from the **DevTune monorepo** (parent of `product/`).

---

## Preferred path (one command)

From `product/`:

```powershell
.\start-desk.ps1
# or:
.\start-desk.ps1 -Game horizon-courier -Port 5430
```

Monorepo CLI shape (same jobs; future `npx devtune` maps here):

```bash
node product/bin/devtune.mjs battery product/games/<name>
node product/bin/devtune.mjs doctor
```

**Junctions:** a game-local `medium`/`vendor` junction is **optional**. Serve mounts
`product/medium` and `product/vendor` when the root is `product/games/<name>`.

What it does, in order:

1. Frees the game port and bridge port **7420**
2. Starts the **bridge** (Keep) and waits until it listens
3. **`verify` + `smoke`** — ownership + HTTP pack mounts (physics/movement/camera + Rapier)
4. **`doctor`** — product/medium ↔ substrate sync + bridge WebSocket probe
5. Serves the game with **`--shell`** (desk inject)

Open: **http://127.0.0.1:5430/** (or your `-Port`)

| Key | Action |
|---|---|
| Play | WASD / look as the game defines |
| **C** | Design desk — and back to Play |
| **F** | Freeze (in Design; freezes on enter) |
| **K** | Keep (write source — needs bridge) |
| **Esc** | Clear selection / end freelook (not Play) |
| **RMB** | Freelook (hold) or orbit focused object |
| Full desk contract | `CLAUDE_DESK_HANDOFF.md` |

⚠️ **Do not leave another serve on the same port** (e.g. old substrate on 5430) — wrong app or blue screen while the HUD lies.

---

## Manual path (two terminals)

Only if you need to split processes. Prefer `start-desk.ps1`.

From monorepo root (`devtune/`):

```bash
# Terminal A — Keep
node bridge/bridge-server.js

# Terminal B — gates then host
node product/tools/verify.mjs product/games/<name>
node product/tools/doctor.mjs
node adapters/serve/serve.mjs product/games/<name> --port 5430 --shell
```

`verify.mjs` runs born-owned checks then **smoke** (unless `--no-smoke`).  
Add `--doctor` to append doctor on the same CLI.

---

## Gates only (no browser)

```bash
# from monorepo root — one shot (preferred for agents)
node product/tools/battery.mjs product/games/<name>

# or stepwise
node born/verify.mjs product/games/<name>
node product/tools/smoke.mjs product/games/<name>
node product/tools/doctor.mjs
node --test product/tools/medium-sync.test.mjs product/tools/pd1-live.test.mjs
```

| Gate | Proves |
|---|---|
| verify | Literals, no mega-bind, imports in serve sandbox |
| smoke | `/` + shell inject + medium packs 200 + Rapier |
| doctor | medium dual-truth + bridge WS accepts |

---

## When a gate is red (agent step 6 / human)

| Red | Do **not** | Do |
|---|---|---|
| verify FATAL | “Ship anyway” / hand-edit around | Read the rule + fix; re-run verify |
| smoke pack 404 | Junction-only hacks as the product | Fix serve mounts / import path; re-smoke |
| doctor medium drift | Edit only product/medium forever | Copy substrate pack → product/medium (rapier node path may differ) |
| doctor bridge | Claim Keep works | `npm install` in `bridge/`; re-run bridge |
| Desk “not connected” | Blame the shell first | Confirm smoke inject line + hard-refresh |

Report red output to the human intact — that is the failure paste path.

---

## Rapier URL

Games should pass:

```js
rapierUrl: '/__devtune/shell/vendor/rapier.mjs'
```

The shell host serves that path when `--shell` is on.  
Product also ships `product/vendor/rapier.mjs` as fallback.

---

## Design place (crates / courier)

Packs register `globalThis.__devtunePhysicsPack` / `__devtuneMovementPack`.  
The desk calls `setPose` on gizmo drag so unfreeze/play do not snap meshes back.  
If place snaps back: freeze is on, but pack hook is missing (game never called ensurePhysicsWorld / createMovementPack).
