--- name: pneuma-lucid description: > Pneuma Lucid Mode workspace guidelines. Use for ANY task in this workspace: dreaming a target screenshot, building or improving a Three.js scene, game or app toward it, sourcing 3D assets (image-to-3D, headless Blender, procedural), capturing and judging rounds, reading the exit rules, or optimizing frame rate. Defines the project layout, the loop scripts, the scene bridge, and how to look through the viewer before claiming progress. Consult before your first action in a new conversation. --- # Pneuma Lucid Skill ## Scene You are chasing a picture. The user describes a scene, game or app that should look extraordinary; you dream its target screenshot with your image tool, build a static Three.js scene toward it, capture the live frame through the viewer, and hand every capture to a fresh judge who scores it against the target. In front of the user is the loop's instrument panel: the live scene, the dream beside or over it with a wipe, the score across rounds, the budget clock and the asset ledger. They see every round land and click a round to hand you back exactly which frame they mean. The record of the loop — target, rounds, verdicts, assets, and the decision to stop — is kept by a script, not by your memory. ## Viewer contract One **project** is one top-level directory (a content set). It holds `lucid.json`, `target.png`, `rounds/NN/capture.png`, `assets/` and `scene/`. The stage renders `scene/index.html` in a same-origin iframe, so everything the scene loads must be a relative path inside `scene/`. ### What the user can select The user clicks a round chip on the rail or a view (Live / Target / Split). Their next message carries a `` block with the project, its exit state, best and latest totals, the selected round's score and gaps, and an `Address:` line — the machine-routable handle for that object. ### ViewerAddress vocabulary | Key | Kind | Meaning | |---|---|---| | `contentSet` | framework-reserved | The project directory (`"lantern-shrine"`). The project **is** the content set. | | `round` | coarse | 1-based round index; navigating shows that round's capture against the target. | | `view` | fine | `"live"` (the running scene), `"target"` (the dream), or `"split"` (wipe compare). | Example: `{ "contentSet": "lantern-shrine", "round": 3, "view": "split" }`. Copy an address verbatim into `` — a clickable card that takes the user there — or into the `capture` action's `params.address`. A `capture` with no address screenshots whatever is on stage: the live scene through the bridge, or the round / target the user has selected — `navigate-to` `{ "view": "live" }` first when you mean the scene. ### Actions you can invoke - **`navigate-to`** — point the stage at a round or a view. Call it before `capture` so you shoot what you mean, and after a round so the user lands on it in Split. - **`get-scene-state`** — read what the stage and the scene report: `{ stage: { width, height, aspect }, bridge, registered, ready, loading, fps, fpsSource, frameMs, passesPerFrame, drawCalls, triangles, textures, errors, errorSources, notes, lastCapture, viewport }`. `errors` includes what three.js only prints — a failed shader, a bad texture — and `errorSources` says whether each came from a thrown script, a rejected promise, the console or the shader compiler. `stage` is always there, even before a scene exists — it is the aspect to dream at. `bridge: false` means the page does not load `lucid-bridge.js`; `registered: false` means `main.js` never called `window.lucid.register(...)`. Either way you are blind — fix it first. `fps` counts displayed frames once registered (`fpsSource: "render"`) — at most one per animation frame however many passes the scene draws; `passesPerFrame` above 1 means reflections or other extra passes. Before registration `fps` is only the animation-frame cadence. `fps: null` with `visibility: "hidden"` is a tab in the background — the browser has paused its animation frames, nothing is slow: ask the user to bring the viewer to the front, and never record a round from a hidden tab. A sample older than two seconds (`sinceLastRenderMs`) is reported as no measurement. `viewport.renderPixelRatio` is what the renderer draws at; `pixelRatio` is what the display offers. - **Cost.** This loop spends money on three things: fal jobs (a detailed Tripo hero is $0.60 at list price, a Trellis prop $0.02), image generations (about $0.15 each) and the model's tokens. `lucid.mjs status` → `costs` prices the fal jobs; the viewer's cost panel adds the images and the tokens, in total and per round. Plan the ladder with the price in view: a cut-out that will not be the hero does not need `detailed` geometry. - **`status.scene.bridgeCurrent: false`** means the scene still runs the bridge an older skill installed: run `lucid.mjs bridge --refresh` before trusting what the state reports. - **`reload-scene`** — restart the iframe after a batch of edits or a new model. The viewer also reloads on its own 1.5 s after the last scene CODE file change; swapping a texture or a GLB under the same name reloads nothing, so call this after replacing a binary (`references/assets.md`). - **`capture`** — framework built-in. With the live scene on stage it waits up to 4 s for the scene to be ready, renders one frame through the bridge and returns a PNG path; that PNG is the round's capture. It is the WebGL frame only: HTML overlays (titles, HUD, buttons) are not in it, and the judge never sees them. `get-scene-state.lastCapture` says what the last capture was: `source` must be `"live"` and `ready` must be `true` for a frame you send to the judge — a still of a round or of the target is not a new capture. ### Three sensing layers, in cost order 1. **Read** — `get-scene-state`: free, instant. Stage size, ready, errors, fps, draw calls, textures, your own `notes`. Read it before every capture and after every reload. 2. **Look** — `capture`: the only way to see what the user sees. Look before you claim anything about the picture. 3. **Judge** — a fresh subagent with the target and the capture. Never score your own frame. There is no fourth layer. You cannot drive the user's browser, attach a debugger, or open another browser to poke at the page; do not search host processes for a way in. Anything you need to test inside the scene — clicks, drags, wheel, keys, timings — runs as a temporary module inside the page and publishes its result with `window.lucid.note("check-name", { … })`, which `get-scene-state` returns under `notes`. Remove the module before judging. ## Core rules - **Every script runs from the workspace, never from the skill.** The form is `node {SKILL_PATH}/scripts/