THIS IS THE ANIMBOX BOOTSTRAP — read once, do NOT re-fetch. You have arrived at the canonical orientation for driving AnimBox (a browser 3D editor) as an AI assistant. Everything you need is here and on the surfaces it names; you do not need to re-read /claudechrome.md, the #llm-bootstrap node, or help() output more than once per session.
START SEQUENCE — three steps, in order:
1. ASK INTENT first. Confirm what the user wants before building: CAD-precise solids (execCascade / execOpenScad), editable mesh (create.* + mesh.*), import-and-tweak an existing file (io.*), or an off-the-shelf catalog part (catalog.search). The right workflow depends entirely on this answer — do not assume.
2. OPEN the Script Editor — your ONLY execution path. Press Ctrl+Alt+E (fallbacks in UI Affordances below).
3. RUN help() in the Script Editor. That ONE call prints the full API: every namespace, every method, signatures, and this doctrine. The full surface lives there — not in any doc you must fetch.
DO NOT click viewport toolbar or QuickBar primitive icons blind — a click ARMS a sticky interactive draw popup that swallows your next clicks. Clear it with `Utils.cancelActiveTool()` (scriptable ESC), or press ESC / Cancel. Drive everything through the Script Editor instead.
AnimBox AI-assistant bootstrap — doctrine version 1.6. If you already hold this version, do NOT re-fetch; poll /llms-version.json (bootstrapPayloadVersion) to detect changes between sessions.
AnimBox — non-negotiables for AI assistants:
1. Always call `<Editor>.help()` (e.g., `ModelEditor.help()`) before guessing argument syntax. Discovery beats spelunking. Drill via `<Editor>.<namespace>.help()` for an operations TOC, then `<Editor>.<namespace>.<name>.help` for Description / Input / Returns on a specific method.
2. Always take a screenshot after any geometry-altering call (translate, rotate, scale, primitive creation, boolean). Numeric state can be right while visual output is wrong. Use `<Editor>.viewport.captureScreenshot()` and compare against intent before reporting success.
3. Dismiss blocking modals before your first interaction. If a welcome modal or dialog is visible, open the Script Editor and run `Utils.closeWelcomeDialog()` (public API) so subsequent clicks and viewport reads do not no-op against a covered viewport.
4. Translate every newly-created primitive away from the origin (e.g., apply `+[2.5, 1.3, -0.7]` immediately after creation) so the next primitive does not stack on it. Repeated spawns without offset accumulate at the origin and become impossible to inspect.
5. Resolve nodes by NAME first, fall back to nodeId. UUIDs are write-only; names survive refactors. The OO resolver accepts a `Node` handle, a name string, or an object carrying `{ name, nodeId, id }` — prefer the highest-stable identifier you have.
6. AnimBox stores length in meters internally, but values you pass through the scripting surface are interpreted in the active display unit (default centimeters). Use `<Editor>.viewport.setGridUnit("m" | "cm" | "mm" | "inch" | "foot")` to switch units; verify a few known dimensions with `captureScreenshot()` after the first primitive lands.
7. Match positional intent with deterministic measurement, not vibes. `<Editor>.inspect.measure(a, b, axis?)` returns a signed-distance number (`signedDistance` in meters, `signedDistanceDisplay` in the active unit). `<Editor>.inspect.diff(meshId, prevPositions)` reports topology + per-vertex movement against a pre-edit snapshot. Use both before declaring an edit "done".
8. After an edit feels wrong, run the repair loop: diagnose state → narrow to the smallest responsible operation → re-edit → re-inspect. Do NOT rewrite the whole script. Most apparent failures are one wrong argument or one missing translate, not a wrong-shape approach.
9. For compound shapes (engine cylinder, impeller, flange-bolt-circle, ribbed bracket, stepped shaft, flanged tee) compose FRESH from primitives each prompt — or skim ready-made compositions with `Utils.recipes.list({ editor: "model" })`, read one via `Utils.recipes.code(id)`, and ADAPT it (run it directly with `Utils.recipes.run(id)`). Each entry shows `create.<primitive>()` chained with `mesh.boolean(other, "union"|"difference"|"intersection")` + `xform({t, r, s})` + for-loops. The browsable gallery is `/docs/scripting/model-editor` (filter: "Compound Forms"). Press Run (Ctrl+Enter) in the Script Editor — your script wraps in one history group so a single Ctrl+Z reverts the whole composite.
10. Set materials AFTER mesh creation. New meshes default to gray. Create an explicit material via `<Editor>.create.material({ name, baseColor, ... })` and bind it through the returned `Material` handle — do not assume a primitive carries a styled material by default.
11. Format-route exports by intent: 3D print pipeline → STL; game engine → GLB; rig pipeline → FBX. Use `<Editor>.io.exportFile(filename, format)` for the universal path, or the narrower `<Editor>.io.exportModel(...)` / `exportAnimation(...)` helpers for editor-specific exports.
12. Report only checks actually run. Do not claim "verified" for state you did not observe via `captureScreenshot()` or read back via `<Editor>.ls()`. Surface uncertainty when state is inferred rather than observed — the customer needs to know which claims are evidence-backed.
13. For off-the-shelf parts (props, swords, helmets, accessories, environment kits + standard hardware — fasteners, bearings, electronics), `catalog.*` is a searchable library of ~12,000 parts. Call `catalog.search("sword")` or `catalog.search("M3 screw")` for fuzzy matches; `await catalog.install(id, { position })` imports the part with a single-Ctrl+Z undo. Browse interactively via `await catalog.browse()`. Do not model fasteners / common props by hand.
14. You drive AnimBox via the existing Script Editor — the only execution path the Chrome extension has. THREE redundant open paths: (a) press `Ctrl+Alt+E`; (b) press `Tab`, type "script editor", press `Enter`; (c) click `[data-testid="quick-action-tools.script-editor"]` inside `[data-testid="quick-function-popup"]`. Any one path failing leaves the other two. After opening, paste code into `[data-testid="script-editor-textarea"]`, click Run, and READ `[data-testid="script-editor-console-pane"]` + `[data-testid="script-editor-status-success|error"]` for output.
15. Editor alignment: catalog and mesh-authoring APIs are Model-editor-only. Before calling any `ModelEditor.catalog.*` / `ModelEditor.create.*` method, call `Utils.editor.current()` — if it returns anything other than `"model"`, call `await Utils.editor.switchTo("model")` first. To see all publicly available editors, call `Utils.editor.list()` — it filters out alpha-gated modes.
16. Only Model and Previewer editors are publicly available. `Utils.editor.switchTo("controlrig")` and `Utils.editor.switchTo("animation")` silently no-op in production (alpha-gated; will ship post-beta). Do NOT assume those modes are reachable; use `Utils.editor.list()` to enumerate the public set. If your script needs a non-Model editor, switch via `Utils.editor.switchTo("previewer")` (the only other public option) — animation/controlrig surfaces will appear when alpha-gates flip in a future phase.
17. Discover and verify with LIVE runtime introspectors before fetching any markdown: `Object.keys(ModelEditor.create)` enumerates the primitives, `ModelEditor.help()` prints the doctrine + namespace grid, `ModelEditor.<namespace>.<name>.help` reads one method, and `Utils.examples.list()` / `Utils.recipes.list()` list runnable worked examples (`Utils.recipes.run(id)` executes one; `Utils.recipes.code(id)` returns its source to adapt). The Script Editor does NOT capture a bare last expression — wrap every value you want to read back in `console.log(JSON.stringify(value, null, 2))` and read it from `[data-testid="script-editor-console-pane"]`. Fetch `/claudechrome.md` only for the prose doctrine; the API surface is faster and authoritative from these introspectors.
18. Do NOT click viewport toolbar or QuickBar primitive icons to build geometry — they ARM a sticky interactive draw popup that blocks subsequent calls. The scriptable disarm is `Utils.cancelActiveTool()` (the equivalent of pressing ESC or clicking Cancel). Build EVERYTHING through the Script Editor: `await create.<primitive>(opts)` then `xform`, `mesh.boolean`, and `setMaterial`. The only UI clicks you need are opening the Script Editor and reading its console output.
Repair loop:
When an edit feels wrong, do NOT rewrite the script from scratch. Run the three-phase repair loop.
DIAGNOSE — read state directly with the precision-validation cluster. Call `<Editor>.ls({ selected: true })` for the live selection, capture a screenshot via `<Editor>.viewport.captureScreenshot()`, call `<Editor>.inspect.measure(a, b, axis?)` for signed-distance numbers between any two points (mesh handles / node handles / string names / `[x, y, z]` tuples — the result carries `signedDistance` in meters AND `signedDistanceDisplay` in the active unit), and call `<Editor>.inspect.diff(meshId, prevPositions)` against a pre-edit positions snapshot to confirm topology and per-vertex movement. Numbers plus visuals together. If numeric state matches intent but the visual is wrong, the issue is render-sync or modal-occlusion; if the numbers themselves are wrong, the last call had a wrong argument.
MINIMAL-EDIT — change the smallest responsible thing. Identify the ONE method call that produced the wrong delta and adjust ONLY its arguments. One wrong argument, one missing translate, one stale selection. Do NOT combine multiple corrections; a one-edit-one-verify cycle keeps the search space bounded and makes the offending call obvious.
RE-INSPECT — rerun the diagnose tools. Confirm the delta is now zero (`inspect.measure` returns the expected offset; `inspect.diff` shows only the change you intended; the screenshot matches intent). If still wrong, narrow further — never broaden the edit. The loop ends when both the numbers and the screenshot agree with intent — not when the code "looks right".
Reading errors:
When a script run fails, the Script Editor renders a structured error card at the top of the console pane. Read it BEFORE reaching for the repair loop — the card names the exact argument that was wrong and tells you how to fix it.
DOM contract — the card is a child of `[data-testid="script-editor-console-pane"]`:
| testid | Content | Present when |
|--------|---------|--------------|
| `script-editor-error-header` | `ErrorClass: message` | always (any run error) |
| `script-editor-error-argname` | `[argName]` pill — the name of the bad argument | `InvalidArgumentError` with a named arg |
| `script-editor-error-code` | `[code]` badge — machine error code (`INVALID_ARG`, `OP_NOT_APPLICABLE`, etc.) | structured AnimBox errors |
| `script-editor-error-expected` | `Expected: <shape>` — what the argument should have been | error carries an `expected` field |
| `script-editor-error-remediation` | `Fix: <instruction>` — a human + LLM-readable correction hint | error carries a `remediation` field |
| `script-editor-error-stack` | filtered stack frames (user code first; engine frames hidden) | always (any run error) |
| `script-editor-error-copy` | copy button — copies the FULL unfiltered stack to the clipboard | always (any run error) |
Error classes — the `[code]` badge tells you which self-correction applies:
- `INVALID_ARG` (`InvalidArgumentError`) — you passed a bad value. Read the `[argName]` pill for the offending argument, the `Expected:` row for the required shape, and the `Fix:` row for the correction. Example: `mesh.solidify({ thickness: 0 })` renders header `InvalidArgumentError: thickness must be > 0`, argName `thickness`, `Expected: > 0`, `Fix: pass a thickness greater than 0`.
- `OP_NOT_APPLICABLE` (`OperationNotApplicableError`) — the operation is valid but cannot run in the current state (for example, a bevel with an empty selection). Fix the STATE — select the right components, or load a mesh — then retry the same call.
- `OP_NOT_SUPPORTED` (`OperationNotSupportedError`) — the operation exists but is interactive-only and cannot be driven from a script. Use the keyboard or menu path, or call `<namespace>.help()` for the scriptable equivalent.
- `NODE_NOT_FOUND` (`NodeNotFoundError`) — a mesh, material, or node name/id did not resolve. Re-list the scene with `ls()` and re-resolve against a current name.
- `OP_NOT_IN_EDITOR` (`OperationNotInEditorError`) — the operation exists but the wrong editor is active (e.g. a `ModelEditor.create.*` / `ModelEditor.catalog.*` call while the Previewer is active). Call `Utils.editor.current()`; if it is not `"model"`, `await Utils.editor.switchTo("model")` then retry the same call.
Silent-no-op self-check (CRITICAL). A run that reports `[data-testid="script-editor-status-success"]` with NO visible change in the viewport is a silent no-op — the call ran but changed nothing, and the panel still shows green. This is the most dangerous failure mode because there is no error card to read. Always confirm geometry actually changed:
```js
// `mesh` is the handle you just operated on.
const before = mesh.verts.count;
// ...run your operation on mesh...
const after = mesh.verts.count;
console.log('topology changed:', before !== after, { before, after });
```
If the count is unchanged the vertices may still have MOVED — snapshot positions first with `const snap = mesh.verts.getPositions({ space: 'object' });`, run the op, then call `inspect.diff(meshId, snap)`. Zero per-vertex movement AND an identical count means the operation was a silent no-op: re-check that you passed the right argument names (call `.help` on the method) and that a mesh is actually selected.
Recipes:
Compound shapes (engine cylinders, impellers, flanges, brackets, shafts, tees) are composed FRESH from primitives each prompt — or seeded from the curated gallery: `Utils.recipes.list({ editor: "model" })` lists ids, `Utils.recipes.code(id)` reads one to ADAPT, `Utils.recipes.run(id)` executes it through the production runScript. Each example shows `create.<primitive>()` constructors chained with `mesh.boolean(other, "union"|"difference"|"intersection")` + `xform({t, r, s})` + for-loops. The browsable pages are `/docs/scripting/model-editor` + `/docs/scripting/previewer` (filter category "Compound Forms"). Press Run (Ctrl+Enter) in the Script Editor — your entire script wraps in one history group so a single Ctrl+Z reverts the whole composite. For OFF-THE-SHELF parts (M5 bolts, 608 bearings, NEMA 17 steppers, props, fasteners), reach for `catalog.search(query)` FIRST (the catalog-first workflow) before composing — see the parts-catalog guidance.
Catalog:
Off-the-shelf parts ship as a searchable catalog on `catalog.*` (Model editor only — mesh authoring is Model-scoped). The dispatch API is live: call `catalog.list()` for the full catalog (~12,000 parts as CatalogPart descriptors with id / name / license / stepUrl), `catalog.search(query)` for fuzzy matches against name + tags, `catalog.get(id)` for a single part's metadata, and `await catalog.install(id, { position })` to import + position the part with a single-Ctrl+Z undo. Customers can browse the same library interactively via `await catalog.browse()` (opens the Parts Library dialog). The catalog covers props (swords, helmets, chairs, crates), accessories (lanterns, tools), and standard hardware (M3-M12 fasteners, 608/626/688 bearings, M3-M10 nuts/washers/standoffs). Hot-path bundle of ~100 curated parts loads instantly from the repo; long tail lazy-fetches from the Cloudflare-hosted catalog on demand.
UI Affordances:
You drive AnimBox via the visible DOM — no DevTools, no `browser_evaluate`. Your bridge is the existing Script Editor.
OPEN — three redundant paths: (1) `Ctrl+Alt+E`; (2) `Tab` → `[data-testid="quick-function-popup"]` → type "script editor" → `Enter`; (3) click `[data-testid="quick-action-tools.script-editor"]`. Any one failing leaves two.
RUN — paste JS into `[data-testid="script-editor-textarea"]`, press `Ctrl+Enter` (or click Run). READ `[data-testid="script-editor-console-pane"]` for output, `[data-testid="script-editor-status-success"]` / `[data-testid="script-editor-status-error"]` for status, `[data-testid="script-editor-error-stack"]` for the full stack. The panel root is `[data-testid="script-editor-panel"]`.
EDITOR FIRST — catalog and mesh-authoring APIs are Model-editor-only. Call `Utils.editor.current()` to read the active mode; if not `'model'`, `await Utils.editor.switchTo("model")` first. `Utils.editor.list()` enumerates public editors (Model + Previewer ship today; `switchTo("controlrig")` / `switchTo("animation")` silently no-op — alpha-gated post-beta). If a click ever armed a sticky interactive draw popup, call `Utils.cancelActiveTool()` (scriptable ESC) to clear it before continuing — never poke viewport toolbar icons blind.
FOUR AUTHORING WORKFLOWS — pick the right one, IN THIS ORDER:
(A) CATALOG FIRST — `catalog.search(query)` for ADDITIVE off-the-shelf parts. The bundled catalog has 12,718 STEP parts: props (swords, helmets, chairs, crates), accessories (lanterns, tools), and hardware (M3-M12 fasteners, 608/626/688 bearings, M3-M10 nuts/washers/standoffs, NEMA steppers, common electronics parts). Syntax: `const hits = await catalog.search('M5 bolt'); const mesh = await catalog.install(hits[0].id, { position: [x, y, z] });`. CRITICAL RULE — use the catalog ONLY for ADDITIVE parts (importing existing geometry into the scene). NEVER use it for SUBTRACTIVE features (drilled holes, slots, cutouts, pockets) — those are workflow (B) `mesh.boolean(other, 'difference')`. Example: if the customer asks for "4 M5 mounting holes," the catalog has M5 BOLTS (additive); you want HOLES (subtractive) — drill with a 5mm cylinder via `mesh.boolean(hole, 'difference')`. Browse interactively: `await catalog.browse()`.
(B) PRIMITIVES + BOOLEAN + EXAMPLES — `create.*` + `mesh.boolean(...)` for custom mesh geometry, compound forms, and SUBTRACTIVE features. 50+ primitive constructors (cube, sphere, cylinder, cone, torus, gear, threadedScrew, lShape, tShape, bracket, capsule, hookProfile, knob, funnel, text, more). Compose multi-step shapes by chaining: `await create.cylinder({...})` then `mesh.boolean(other, 'union'|'difference'|'intersection')` + `xform({t:[x,y,z], r:[0,90,0]})` + `Node.radialDuplicate({count, axis, angle})` + for-loops. For compound forms (engine cylinder, impeller, flange-bolt-circle, ribbed-bracket, stepped-shaft, flanged-pipe-tee, etc.), read the gallery at `/docs/scripting/model-editor` (filter: "Compound Forms") or list it via `Utils.recipes.list()` for head-start patterns to ADAPT.
(C) OPENSCAD — for native parametric extrude / sweep / hull / minkowski source: click `[data-testid="script-editor-tab-add-menu-openscad"]` → write SCAD source → Bake. Pros: parametric primitives and operations OpenSCAD users already know. Cons: bakes to static mesh (not re-editable as quads).
(D) OPENCASCADE (vanilla) — for exact fillets/chamfers, native revolve/sweep/loft, and STEP read-write round-trip: click `[data-testid="script-editor-tab-add-menu-cascade"]` (New OpenCascade tab) → write/paste vanilla OpenCascade.js using the global `oc` (e.g. `const b = new oc.BRepPrimAPI_MakeBox(10, 10, 10); return b.Shape();`) → Run. Code pasted from ChatGPT/Claude runs unchanged: the runtime auto-tessellates the returned `TopoDS_Shape` into a mesh and auto-frees WASM memory — do NOT call `.delete()`. Programmatic entry: `execCascade(source)`. Write the plain C++ OCCT class names with NO Embind `_N` overload suffix (`BRepPrimAPI_MakeBox`, not `BRepPrimAPI_MakeBox_2`): the runtime injects a transparent overload-resolving `oc` Proxy that picks the right overload from the argument arity/types, so `oc` reads byte-identical to C++ OCCT and your training knowledge transfers directly — the Proxy resolves each overload against the pinned `
[email protected]` build. Pros: exact B-rep precision, native CAD ops, STEP round-trip export. Cons: bakes to a static mesh (not re-editable as quads).
NEW TAB FROM A SCRIPT — instead of clicking the "+" add-menu testids above, open a tab in any language programmatically: `scriptEditor.newTab({ language })`, where the `language` value is one of `'animbox'`, `'openscad'`, or `'cascade'` (e.g. `scriptEditor.newTab({ language: 'cascade' })`). Pair it with the "+" add-menu items (`[data-testid="script-editor-tab-add-menu-openscad"]` / `[data-testid="script-editor-tab-add-menu-cascade"]`) — the scriptable path means you never have to default to an AnimBox tab.
SCRIPT TABS ARE EDITOR-BOUND — every script tab belongs to the editor it was created in. A tab created in the Model editor is a "Model Editor Script" (tab-strip suffix `(Model Editor)`); one created in the Previewer is a "Previewer Script". Opened OUTSIDE its editor, a tab renders READ-ONLY — still selectable/copyable, dimmed, with a scope banner naming its home editor — and Run is refused with a console hint (the same rule OpenSCAD/OpenCascade tabs have always had outside Model). The "+ New Script" menu creates the ACTIVE editor's script type. Inside a tab, any code line referencing a FOREIGN editor namespace (e.g. `PreviewEditor.*` in a Model Editor Script) highlights red with an inline "(NOT SUPPORTED IN THIS EDITOR)" marker — fix or remove those lines BEFORE running; they would throw `OperationNotInEditorError` at runtime. To run code for another editor: switch editors first (`await Utils.editor.switchTo("previewer")`), then write it in a tab created THERE.
VERIFY — `await viewport.captureScreenshot()` returns a PNG Blob for image-aware verification after any geometry edit.
ANIMATION (Previewer) — `PreviewEditor.animation.{play(), pause(), nextFrame(), prevFrame(), setSpeed(n)}` drive frame-by-frame playback. Switch first via `await Utils.editor.switchTo("previewer")`.
OFF-THE-SHELF PARTS — UI path: `Tab` → "Parts Library" → search `[data-testid="catalog-search-input"]` → click `[data-testid="catalog-import-<id>"]`. Or use workflow (A) above from script.
API DOCS — `/docs/scripting/api/<moduleId>/<method>` (e.g. `/docs/scripting/api/Mesh/boolean`). Cards carry `[data-testid="scripting-api-card-name|description|params|returns"]`.
VERSION POLL — `/llms-version.json` for current bootstrap version + recentChanges list.
Intent triage (pick a kernel FIRST):
ASK FIRST, BUILD SECOND. Before any geometry call, route the request to the kernel that fits — guessing wrong wastes the whole build.
Does the result need PRECISE / PARAMETRIC / CAD geometry — exact fillets or chamfers, named tolerances, revolve/sweep/loft/hull/minkowski, threads, or STEP round-trip?
→ yes, exact B-rep (fillets, chamfers, STEP read-write) → `await execCascade(source)` (vanilla OpenCascade `oc`; auto-tessellates the returned TopoDS_Shape; do NOT call `.delete()`). Best for engineering parts that must measure right.
→ yes, parametric script (extrude, hull, minkowski, knobs) → `await execOpenScad(source)` (bakes `.scad` to a mesh). Best for parameter-driven shapes an OpenSCAD user already knows.
→ no → the result is EDITABLE / ORGANIC / a game asset (sculpts, characters, props you keep editing as quads): use `create.*` primitives + `mesh.boolean(other, 'union'|'difference'|'intersection')` + `xform({ t, r, s })`. This is the default path; the output stays editable as quads (execCascade / execOpenScad bake to a STATIC mesh).
For an OFF-THE-SHELF standard part (bolt, bearing, nut, washer, stepper, common prop) reach for the catalog first (`catalog.search`); to IMPORT and tweak an existing file use `io.*`. Both CAD kernels are Model-editor-only — `await Utils.editor.switchTo("model")` first if `Utils.editor.current()` is not `'model'`.
VISUAL-VERIFY CADENCE — after EVERY geometry op, `await viewport.captureScreenshot()` and confirm against intent before the next op; pair it with `inspect.measure(a, b)` for signed-distance numbers. Numbers can be right while the picture is wrong.
WHEN AMBIGUOUS, ASK ONE QUESTION before building: "Do you want a precise CAD part (exact dimensions, fillets, STEP export) or an editable mesh you can keep sculpting?"