The §79 node-type pair for the five classes a drawn scene is made of
(2026-08-07, the A-16 remainder): §49's Renderable, §55's Sprite, §47's
PerspectiveCamera and OrthographicCamera, and §68's DirectionalLight.
Renderable — a geometry key, a material key (see the module
header), renderLayer, renderOrder, castShadow/receiveShadow (§69,
R-18), frustumCulled (§87, R-8) and clip (§67, R-23, 2026-08-28).
§49's depthMode is still not
written because the class does not have it yet; it joins the payload with
the feature. (This bullet claimed all four were unwritten until
2026-08-09 — it predated R-18.)
Sprite — a material key, width, height, anchor,
renderLayer, renderOrder, the same four drawable flags, and no
geometry key at all: a sprite
derives its quad from the anchor and the size and owns it (§55), so the
payload above rebuilds it exactly. Its dispose() state is not written —
a disposed sprite is a released resource, not authored scene state.
the cameras — their projection parameters only. The three matrices are
derived: updateProjectionMatrix rebuilds the projection from these
numbers, and the view matrix from the node transform §79 already carries.
The depthRange that projection was last built with is deliberately absent
— it belongs to the renderer, not the camera (§47), so a document does not
pin a scene to the backend that saved it.
DirectionalLight — color and intensity. Its direction is its
node's −Z axis (§68), which is the transform, not a payload.
PointLight — color, intensity, and range. Its position is
the node transform §79 already carries, so it is not a payload either
(R-17, 2026-08-09).
SpotLight — the point light's three, plus innerConeAngle and
outerConeAngle in radians (§7a). Its axis is the node's −Z, like a
directional light's: transform, not payload.
A restored Renderable accepts whatever material its key resolves to;
only a Sprite insists on a "sprite" one. The asymmetry is the classes'
own: Renderable<M extends Material> is generic so that a consumer's own
material kind can be drawn through it, and refusing anything but
"unlit"/"lit" on the way in would make a legitimate
Renderable<GlowMaterial> savable and unloadable — while a Sprite takes a
SpriteMaterial and samples its texture, so any other kind is a run-time
type error that draws nothing.
Why one pair and not three
The cameras and the light live in @fourjs/scene and the two drawables in
@fourjs/render, so a package-shaped split is available and is not taken: the
split that pays is @fourjs/physics versus the drawing tier (a headless
simulation saves scenes without bundling any of this — see
registerPhysicsSerializers), and within the drawing tier a scene
with a camera and a light is a scene that gets drawn. Splitting further would
hand callers a choice with no bundle behind it.
Neither camera nor the light touches the resource options, so a caller with
no catalogs still gets all three; only a Renderable or Sprite reaching
the writer without one is refused (§85).
Matched by exact class identity, like every other pair here: a subclass of
Renderable is not a Renderable for this purpose, and writing it out as one
would silently reload it as a plain renderable.
The §79 node-type pair for the five classes a drawn scene is made of (2026-08-07, the A-16 remainder): §49's
Renderable, §55'sSprite, §47'sPerspectiveCameraandOrthographicCamera, and §68'sDirectionalLight.What each one carries
Renderable— ageometrykey, amaterialkey (see the module header),renderLayer,renderOrder,castShadow/receiveShadow(§69, R-18),frustumCulled(§87, R-8) andclip(§67, R-23, 2026-08-28). §49'sdepthModeis still not written because the class does not have it yet; it joins the payload with the feature. (This bullet claimed all four were unwritten until 2026-08-09 — it predated R-18.)Sprite— amaterialkey,width,height,anchor,renderLayer,renderOrder, the same four drawable flags, and no geometry key at all: a sprite derives its quad from the anchor and the size and owns it (§55), so the payload above rebuilds it exactly. Itsdispose()state is not written — a disposed sprite is a released resource, not authored scene state.updateProjectionMatrixrebuilds the projection from these numbers, and the view matrix from the node transform §79 already carries. ThedepthRangethat projection was last built with is deliberately absent — it belongs to the renderer, not the camera (§47), so a document does not pin a scene to the backend that saved it.DirectionalLight—colorandintensity. Its direction is its node's −Z axis (§68), which is the transform, not a payload.PointLight—color,intensity, andrange. Its position is the node transform §79 already carries, so it is not a payload either (R-17, 2026-08-09).SpotLight— the point light's three, plusinnerConeAngleandouterConeAnglein radians (§7a). Its axis is the node's −Z, like a directional light's: transform, not payload.A restored
Renderableaccepts whatever material its key resolves to; only aSpriteinsists on a"sprite"one. The asymmetry is the classes' own:Renderable<M extends Material>is generic so that a consumer's own material kind can be drawn through it, and refusing anything but"unlit"/"lit"on the way in would make a legitimateRenderable<GlowMaterial>savable and unloadable — while aSpritetakes aSpriteMaterialand samples its texture, so any other kind is a run-time type error that draws nothing.Why one pair and not three
The cameras and the light live in
@fourjs/sceneand the two drawables in@fourjs/render, so a package-shaped split is available and is not taken: the split that pays is@fourjs/physicsversus the drawing tier (a headless simulation saves scenes without bundling any of this — see registerPhysicsSerializers), and within the drawing tier a scene with a camera and a light is a scene that gets drawn. Splitting further would hand callers a choice with no bundle behind it.Neither camera nor the light touches the resource options, so a caller with no catalogs still gets all three; only a
RenderableorSpritereaching the writer without one is refused (§85).Matched by exact class identity, like every other pair here: a subclass of
Renderableis not aRenderablefor this purpose, and writing it out as one would silently reload it as a plain renderable.