Placing a piece's body

The instantiator gives a piece its body; features then decorate it. This is the only module that knows how a mesh reaches a tosijs-3d scene, which is why buildEnsemble takes it as an option — a test swaps it for a stub.

destroyable is a DECORATOR, and now it really is one

Most of what an ensemble describes — a lamp post, a rock, a ground plane, a whole standard demo scene — can never be shot, and giving it hit points is not a harmless default: terrain that quietly accumulates damage and vanishes at 100 000 is a worse outcome than terrain that was never a combatant.

For a while that forced an awkward shape. b3d-destroyable is the only element that instantiates a library mesh BY NAME, and it had no way out of the combat system, so either everything was a combatant or destruction had to CREATE the body rather than decorate one. We placed plain pieces as raw library nodes to dodge it, and destroyable had to register body: true.

tosijs-3d 0.7.2 added destroyable="off" (tosijs-3d#39, our ask), so that is over. Every mesh piece is placed the same way and destroyable is a genuine decorator: it contributes hit points to a body that already exists rather than being the reason the body exists.

Rotation and scale go on the NODE; only position survives the element

b3d-destroyable's size is the placeholder cube's edge length and is ignored when library is set — so for every piece with a real mesh, writing it does nothing. Worse, the element forwards only {x, y, z, canonical} to library.instantiate, so rx/ry/rz never reach the instance either.

Both measured before trusting anything: rendered width stayed 5.273 at scale 1, 2 and 4 alike, and an authored rot: [0, 45, 0] left the footprint at 3.63 × 3.63 — identical to no rotation. piece.scale and piece.rot were documented fields that moved nothing, which is the worst kind of control there is. Position is the exception and always worked, because the element rewrites mesh.position from x/y/z every frame.

The element's own mesh — the library instance's root TransformNode — does scale, and the element does not rewrite its scaling the way it rewrites position. So scale is applied there, which also makes per-axis scale free. Filed as tosijs-3d#47: a scale attribute belongs on the element, and then this goes away.

The node does not exist yet when the element is appended. b3d-destroyable instantiates inside lib.ready.then(...), so element.mesh is null for at least a microtask after appendChild. Applying scale once, immediately, would silently do nothing — the same failure as size, one layer down. whenMeshed below is the retry, bounded so a piece that never instantiates cannot leave an observer running forever.

Nothing to instantiate from? Draw a box.

Without a library — or with a mesh name the library does not have — the piece becomes a b3d-box at the same spot. The ARRANGEMENT is most of what an author is judging, so cubes in the right places beat an empty scene.