new-scene-layer
Use when adding a new layer or per-tile dressing to the 3D scene (a new kind of object drawn from tile data: furniture, a vegetation kind, a structure, a data mark), or when moving one between the run
Files1
View on GitHubSKILL.md.claude/skills/new-scene-layer/SKILL.md
GitHubAdding a scene layer
Decisions behind this: ADR 0024 (tiles and their dressing), ADR 0027
(node materials), ADR 0030 (parts on the ground), ADR 0047 (memory).
Deep reference: the city-walker skill, "Node materials" and "Tiles".
Decide where it is built
- Depends only on baked data (no look control, no time of day, no
device)? Bake it into the fine terrain glTF as a named node in
scripts/bake-tiles.ts; the layer file only gives it a material. - Otherwise build it at runtime in the dressing plugin
(
tile-stream.ts), as a builder of steps run in slices (lib/city/build-steps.ts) if it can take more than a few ms on a phone.
Checklist
- Data: a baked per-tile file (
new-bake-stepskill), declared inlib/city/tile.ts; a missing optional file means "layer off". - Frame: positions in the Y-up frame, never inside the rotated
worldgroup; heights from the site's ground (lib/city/ground.ts). - Material: a node material; a
sceneMaterialwhen it has no per-tile data. Fixed strengths are named constant nodes; anything that moves a shared uniform. Mind the eight vertex buffers and sixteen varyings. - Instancing:
Instances, neverInstancedMesh; chunk into cells so off-screen chunks cull; after writing matrices setneedsUpdateandcomputeBoundingSphere(). - Lifecycle: build in
processTileModel, compile before it shows (PostStack.compile), free indisposeTile. Add its bytes tocalculateBytesUsed; add CPU arrays nothing reads later to the CPU-drop lists. A part built after its tile (on demand, or from a file of its own) is aLatePartadapter (late-parts.ts, besidedressingPartandmuralsPart): the module builds, weighs, compiles, hangs and frees it; choosehangAt—"wait"only for what a level's hand-over waits on (the dressing),"compile"for everything else. - Shadows: casts only if static; call
invalidateShadows()when it lands; sway casts through a rigidcastShadowPositionNode. - Ground: feet and edges from
ground-join.ts, joins tested (JOIN_PARTSorcheckJoins); its doc comment says what happens where the ground beside it is higher and where it is lower. - Night: emissive or additive terms scale by
sceneNight(scene-environment.ts); a part drawn only after dark wearsdrawnAtNighton its scene-wide material; a part whose instances follow the datefollows the environment at birth (followEnvironment) and lets go indisposeDressing. - Styles: tag what a picture style may dress (
userData.styleCrown,userData.styleLampHeads); never read the style. - Asking: if a visitor may click it, a ray target from its data
(
lib/city/ask-items.ts), card lines and a heading inlib/city/inquiry-features.ts, a row in the kind table (lib/city/inquiry.ts), an arm inlineage.tsand a fixture ininquiry-kinds.test.ts(ADR 0042). - Phone: measure its bytes on the phone tier; the total must not grow (offset it elsewhere).
- Tests: a unit test of the node setup and geometry; a scene-census line and an e2e assertion for presence.
- Visual check: a real-GPU plate (
bun run shots) from an oblique angle; snapshots in the PR (pr-snapshots). - Docs:
docs-upkeep(ledger, the codebook indocs/rendering.md, the guide's layer table in both languages).