Files
clawmates/skills/threejs/scene-graph-planning.md
T
Omar SobhandClaude Opus 5 4358964c05 fix(skills): every team-template skill binding now resolves
55 of 85 role skill bindings pointed at skills that were never authored,
so 10 of 11 team templates bound a smaller context bundle than their role
prompts assumed. Three roles bound nothing at all (gpu.bench_engineer,
threejs.shader_author, threejs.perf_engineer) while their prompts described
procedures they had no way to read.

The loader comment at team_template_loader.rs:167 already diagnosed this —
snake_case slugs in TOML against kebab-case skill files — and it was
half-fixed: the kebab names were corrected, the snake_case ones left.

It was invisible because both existing tests assert authored ⊆ referenced
(30/30, green) and the second explicitly declines to check the other
direction. So the failing half was the half nobody asserted.

Resolved every name by one of three explicit choices:

  - 23 skills authored where the role genuinely needed the procedure
    (gpu, threejs, research, analysis, frontend, mobile, backend, platform)
  - renames onto authored skills where one existed in substance, including
    the four-near-duplicate cases that collapse onto one real skill
  - 22 aspirational references deleted — a binding an agent cannot read is
    a promise, not a capability

Two tests now hold it. The unit test checks referenced ⊆ authored against
the files. The new integration test runs both loaders in boot order and
asserts the bindings survive the trip through the database, which is a
different question: resolution goes through skills_catalog rows, so a skill
file that exists but fails to ingest still leaves the role empty.

Negative controls: the unit test failed naming all 55; the integration test
fails naming the exact role when one name is reverted.

threejs.shader_author and .perf_engineer gained a second and third skill
after the collapse — pin_in_context pins idx < 2, so a role left with one
skill silently pins less than the policy intends.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-19 07:42:48 -07:00

2.4 KiB

name, description, when_to_use, tags
name description when_to_use tags
scene-graph-planning Structuring a three.js scene graph so transforms, culling and disposal stay tractable as it grows. You are the scene designer laying out a three.js scene before objects are built.
threejs
architecture

The graph decides what is cheap later

Scene-graph shape determines transform cost, culling effectiveness and whether teardown is possible. All three are painful to change once content exists.

Group by what moves together, not by what looks similar

Every Object3D with a dirty transform forces a matrix recomputation down its subtree. A graph grouped by material or by asset file means moving one object dirties unrelated branches. Grouped by motion, a static branch stays clean for the life of the scene.

world
├── static        ← matrixAutoUpdate = false, set once
│   ├── terrain
│   └── props
└── dynamic
    ├── player
    └── vehicles

matrixAutoUpdate = false on the static branch removes it from per-frame traversal entirely. This is usually the single largest CPU win in a scene with many objects, and it costs one line.

Frustum culling works on bounding volumes, not intent

Culling is per-Mesh against its bounding sphere. Two consequences:

  • A merged mesh cannot be partially culled. Merging 500 props into one draw call also means all 500 are drawn whenever any part is on screen. Merge by spatial locality, not by material alone.
  • A wrong bounding volume silently misbehaves. After deforming geometry, call computeBoundingSphere(), or the object pops out of view when its stale sphere leaves the frustum.

Plan disposal with the graph

WebGL resources are not garbage collected. Every geometry, material and texture needs an explicit dispose(). If ownership is not planned into the graph, a scene swap leaks GPU memory until the tab dies — see threejs-perf-and-teardown.

The rule that makes this tractable: one owner per resource, recorded where it is created. A texture shared by twenty materials is disposed once, by the thing that loaded it, not by whichever material is torn down first.

Depth is not free

Deep hierarchies cost traversal on every frame. Prefer a shallow graph with explicit groups over mirroring an asset's exported nesting, which is usually an artefact of how it was modelled rather than how it behaves.