tosijs-3d-ensemble — project plan

The tosijs-3d ensemble format, and a graphical editor for authoring it.

An ensemble is a reusable, JSON-described arrangement of library meshes with declared capabilities and relationships — a rig, a dome facility, a floating fortress of shields, platforms, lift units, turrets and generators. No code, no engine types: plain data a game loads, a tool authors, and a generator can emit.

SPEC.md is the specification. It is the primary document: format, editor affordances, schema system, test scenarios, and five design questions answered with recommendations. This file is the plan — what to build, in what order, with what, and what "done" means at each step.


Where this sits in the ecosystem

Three concerns, deliberately split (SPEC.md §"Where each piece lives"):

lives in audience
ensemble format (types, defaults, validation) tosijs-3d-ensemble every consumer shipping levels
ensemble instantiator (JSON → scene) tosijs-3d-ensemble every consumer shipping levels
the editor tosijs-3d-ensemble, tree-shaken out of a game's bundle authors only

Revised — see SPEC.md open question 5. These first two used to sit in tosijs-3d. The reason to move them is CADENCE, not layering: a format is only finished once something has generated content with it, and the thing that will shake it out is the editor. Hosting it in the framework makes every format revision a framework release, gated by a framework's compatibility promises, while the format is still learning what it is.

ONE package, not two. Owner: "I don't think two packages is right, just the editor should be thoroughly tree-shakeable if you just want to consume ensembles." The property that mattered — the editor writes exactly what the runtime reads, because it is the same code — is strongest this way: there is no version at which the tool and the runtime can disagree. What a game must not pay for is the EDITOR, and the mechanism that guarantees that is a bundler. src/tree-shaking.test.ts bundles exactly what a game imports and fails if any editor module survives — a stray import is how this rots, and nothing else would notice.

Promotion into tosijs-3d later remains the right end state if the format proves universal; keeping it dependency-free is what keeps that cheap.

The line, from the owner: "The 'prefab' structure and the tool for instantiating it belong in tosijs-3d, the editor is simply a tool for creating those graphically." — later refined to: "it could be a lightweight and separable import from the editor library. Maybe the latter makes more sense." The split between format and editor is unchanged; only its address moved.

A shipped game carries the format and the loader and no editor at all — the common case, and it must not pay for authoring. This project depends on tosijs-3d for both, which means the editor and the game call the same instantiator: "what you author is what you get" holds by construction rather than by discipline.

Dependencies

for
tosijs-3d the scene, and the SVG UI the editor's chrome is built on
tosijs-ui the build/doc system (tosijs-ui/site), and DOM widgets where the editor is not
tosijs-schema schemas as JSON Schema → types + validation; tjs predicates later
tosijs state

Build it the ecosystem's way

This is a first-class tosijs project, not a side tool. Before substantive work, read ../tosijs-coding-practices/README.md (the index) and the practice doc for the task at hand. CLAUDE.md records the non-negotiables; the ones that shape this project most:


Milestones

0a — settle the UI question first — half done

The piece list and a property panel are BUILT in the SVG UI and verified in a browser: selecting a piece through the component's API swaps the property panel and its sliders. The headset half has not been run, and one thing found already argues for running it soon — clicking a list3d row highlights it but does not fire onSelect (see UPSTREAM.md), so selection currently works through the API and not through the pointer.

Original framing, kept because the test is still the test:

Before the scaffold hardens around a widget set, build the piece list and one property panel in tosijs-3d's SVG UI and try them in a headset. That is the cheapest possible test of open question 5, and it probes the SVG UI's weakest area: it has label/slider/toggle/select/button/list, but no FORM layer, and a schema-driven property panel is a form generator.

Done when: you can select a piece from a list and change one number, in VR. If that feels wrong, question 5 answers itself and the scaffold goes to tosijs-ui with nothing lost. Discovering it in milestone 3 costs the panel twice.

0 — scaffold ✅ DONE

1 — the format + registry — mostly done

Importable by a game with no editor. Asserted by src/tree-shaking.test.ts, not by intention.

Done: types, roles-as-presets with per-feature merge, validate returning {severity, code, message, path}, the registry with two-phase bind/link, buildEnsemble/loadEnsemble with dispose, built-in feature registrations, and environment primitives (terrain, water, clouds, ambient, fog) as features with no mesh — see SPEC.md open question 2.

Not done: JSON Schema via tosijs-schema for the format itself (feature schemas are already JSON Schema), nested-ensemble flattening (validate reports it rather than half-doing it), and the manta-recon migration below.

2 — inspect — started

Already in: the backdrop (land/aquatic, pinned to origin), placement, a first cut of fit-to-bounds framing, live non-blocking validation in the panel, and placeholder cubes when a piece's library is absent so the ARRANGEMENT still reads. Still to do: the library palette, camera re-fit as async models load, named angles, the ortho toggle, animation transport, and continuous shadow registration.

Read-only, and useful on its own — this is the testbed the ecosystem has been missing (cf. tosijs-3d#20, which asked for exactly this kind of reference scene).

3 — edit — in progress

4 — test

An ensemble is a puzzle; a static render says nothing about whether it is solvable or fair.

5 — ship it as a component


Deliberate non-goals for v1

Sketched, not scheduled — SPEC.md Part 6

Four capabilities the owner outlined while the SVG UI backlog lands upstream: procedural tile sets, placeable lights with default geometry, points and zones with consumer-defined types, and provinces that reshape terrain. None is built.

Two things they share are worth knowing before any of them starts, because both are format decisions rather than editor ones: each is a rule that produces pieces (so it wants loader expansion, like nested ensembles), and each takes its vocabulary from library annotations rather than code (tosijs-3d#45).

The cheapest is points and zones — the format already holds them, and the whole gap is an editor affordance plus a consumer-supplied list of kinds. The most expensive is provinces, which needs a third build phase: a piece sitting "on the ground" cannot resolve its height until the terrain carve has run.

Explicitly out of scope for v1

Named so they are decisions rather than omissions: undo/redo, multi-select (still out — the manipulator lands on a single selection first), alignment guides, terrain painting, nested ensembles (reserve the shape — allow "ensemble": "name" on a piece and flatten at load — but do not build live instances), and the encounter layer (SPEC.md open question 4: ensemble = what a thing IS, encounter = what it is DOING HERE; build it once there are enough ensembles for the distinction to bite).


Prior art

A working prototype lives in ../manta-recon — the editor grew inside a game and was extracted for the reasons above. Lift from it; do not start clean out of tidiness:

src/prefab.ts (format + validation) · src/prefab-runtime.ts (instantiator) · src/bench-gizmo.ts (GizmoManager binding) · src/bench-view.ts (placement, framing, ortho, shadow casters) · src/zones.ts (zone registry) · src/prefab-editor.ts + static/prefab.html (the bench)

SPEC.md §"What Manta contributes back" lists the traps these encode. They are not trivia — each cost real time, and the two most expensive were assuming a name instead of listing what is there and verifying a mechanism instead of an outcome.