The §79 node-type pair for §50's shape family — the nine classes R-23
shipped on 2026-08-09 (Circle, Ellipse, Rectangle, RegularPolygon,
Polygon, Star, Sector, Ring, PathShape).
Separated from registerRenderSerializers for
registerPhysicsSerializers's reason and no other: nine classes and
the §51 path parser behind PathShape are a real bundle unit, and a 3D or
UI application that saves scenes should not carry the 2D vector stack to do
it (§91). registerSceneNodeTypes calls this, so an application that
wants everything still makes one call.
What a shape document carries
A material key (see the module header — a shape points at a material it
does not own), the two ordering fields, §49's two shadow flags, the
flattening tolerance, §58's fill and stroke, and the shape's own
parameters. There is deliberately no geometry key: a shape derives its
fill and stroke from those parameters and owns the result (§83), exactly as a
Sprite derives its quad, so the payload rebuilds it exactly and a document
that named one would be naming a resource the application never created.
A paint-derived shape (the §58 object tier, 2026-08-29) writes no
material key, for the geometry key's exact reason: its material is
derived from the paints the payload carries whole, and the reader
re-derives it. The one resource inside a paint — a pattern's texture —
travels as a logical key against SceneNodeTypeOptions.textures.
§58 is additive in both directions (R-16, 2026-08-09; object tier 2026-08-29)
fill and stroke are written only when they differ from the class's own
default — "inherit"/null for the nine closed primitives, "none" for
the three stroke-only ones. So a shape that names no paint writes the
byte-identical document R-23 wrote, and a document written before §58
existed restores a fill-only shape with no field missing. A paint this
build cannot draw — an unknown kind (§58's conic, or a later build's), a
malformed object paint, or any object paint in a build that never called
registerShapePaints() — is dropped rather than refused wherever a
material key exists to fall back to: the shape reloads in that material's
colour, visible and recoverable, where refusing the node would lose the
artwork entirely. A paint-derived document has no such fallback, so there
the drop becomes a loud FourError naming the paint and the registration
call — inventing a material the document never named is the substituted
triangle again. A paint the material cannot draw at all is a different
matter and is refused, the way a Sprite whose material key resolves to
a lit one is.
Two reading rules, and where the line between them is
§96's rule is that data from outside is filtered rather than trusted, and the
A-12/R-18 precedent is that a corrupted field restores its default rather than
taking the whole scene down. That applies to every field the class itself
defaults — tolerance, radius, width, height, rotation, a
rectangle's corner radius, the ordering and shadow fields.
The other rule covers the parameters a shape's constructor makes
required, because they are the shape: a regular polygon's side count, a
star's two radii, a sector's angles, a ring's hole, a polygon's points, a
path's commands. A document that does not carry one of those, carries one the
class would refuse, or carries a pair that contradicts itself, is refused
loudly with a FourError — the same answer registerRenderSerializers
gives a missing material key, and for the same reason: the alternative is to
invent a shape the document never described and hand it back as if it had.
A substituted triangle looks like a bug in the author's data; a refusal names
the node, the type, and the field.
Arcs, and the one number that is not bit-exact
§51's arc command stores a signed sweep; §51's builder — the only parse
that preserves the well-formedness every reader assumes, which is why Path
has no fromCommands — takes a start angle and an end angle. So an arc
is written as [startAngle, endAngle] and read back through ellipse(…),
which recomputes the sweep. The recomputed sweep can differ from the original
by a few ulps when the start angle is large beside the sweep (measured: up to
1.8e-15 rad over 200 000 samples), because fl(fl(s + d) − s) is not d in
general and no choice of end angle can make it one.
Writing the end angle rather than the sweep is what keeps the document
exact anyway: fl(s + arcSweep(s, e)) is e for every pair measured
(0 mismatches in 500 000), so a document round-trips byte for byte even
though the path's stored sweep may move in its last bit. Whole turns and zero
sweeps are exact in both, since arcSweep returns ±2π and 0 by early exit.
Nothing else in the family is affected: the eight parametric shapes store
their own parameters and restore them bit for bit.
Matched by exact class identity, like every other pair here.
The §79 node-type pair for §50's shape family — the nine classes
R-23shipped on 2026-08-09 (Circle,Ellipse,Rectangle,RegularPolygon,Polygon,Star,Sector,Ring,PathShape).Separated from registerRenderSerializers for registerPhysicsSerializers's reason and no other: nine classes and the §51 path parser behind
PathShapeare a real bundle unit, and a 3D or UI application that saves scenes should not carry the 2D vector stack to do it (§91). registerSceneNodeTypes calls this, so an application that wants everything still makes one call.What a shape document carries
A material key (see the module header — a shape points at a material it does not own), the two ordering fields, §49's two shadow flags, the flattening
tolerance, §58'sfillandstroke, and the shape's own parameters. There is deliberately no geometry key: a shape derives its fill and stroke from those parameters and owns the result (§83), exactly as aSpritederives its quad, so the payload rebuilds it exactly and a document that named one would be naming a resource the application never created.A paint-derived shape (the §58 object tier, 2026-08-29) writes no material key, for the geometry key's exact reason: its material is derived from the paints the payload carries whole, and the reader re-derives it. The one resource inside a paint — a pattern's texture — travels as a logical key against SceneNodeTypeOptions.textures.
§58 is additive in both directions (
R-16, 2026-08-09; object tier 2026-08-29)fillandstrokeare written only when they differ from the class's own default —"inherit"/nullfor the nine closed primitives,"none"for the three stroke-only ones. So a shape that names no paint writes the byte-identical documentR-23wrote, and a document written before §58 existed restores a fill-only shape with no field missing. A paint this build cannot draw — an unknown kind (§58's conic, or a later build's), a malformed object paint, or any object paint in a build that never calledregisterShapePaints()— is dropped rather than refused wherever a material key exists to fall back to: the shape reloads in that material's colour, visible and recoverable, where refusing the node would lose the artwork entirely. A paint-derived document has no such fallback, so there the drop becomes a loudFourErrornaming the paint and the registration call — inventing a material the document never named is the substituted triangle again. A paint the material cannot draw at all is a different matter and is refused, the way aSpritewhose material key resolves to a lit one is.Two reading rules, and where the line between them is
§96's rule is that data from outside is filtered rather than trusted, and the A-12/R-18 precedent is that a corrupted field restores its default rather than taking the whole scene down. That applies to every field the class itself defaults —
tolerance,radius,width,height,rotation, a rectangle's corner radius, the ordering and shadow fields.The other rule covers the parameters a shape's constructor makes required, because they are the shape: a regular polygon's side count, a star's two radii, a sector's angles, a ring's hole, a polygon's points, a path's commands. A document that does not carry one of those, carries one the class would refuse, or carries a pair that contradicts itself, is refused loudly with a
FourError— the same answer registerRenderSerializers gives a missing material key, and for the same reason: the alternative is to invent a shape the document never described and hand it back as if it had. A substituted triangle looks like a bug in the author's data; a refusal names the node, the type, and the field.Arcs, and the one number that is not bit-exact
§51's arc command stores a signed sweep; §51's builder — the only parse that preserves the well-formedness every reader assumes, which is why
Pathhas nofromCommands— takes a start angle and an end angle. So an arc is written as[startAngle, endAngle]and read back throughellipse(…), which recomputes the sweep. The recomputed sweep can differ from the original by a few ulps when the start angle is large beside the sweep (measured: up to 1.8e-15 rad over 200 000 samples), becausefl(fl(s + d) − s)is notdin general and no choice of end angle can make it one.Writing the end angle rather than the sweep is what keeps the document exact anyway:
fl(s + arcSweep(s, e))isefor every pair measured (0 mismatches in 500 000), so a document round-trips byte for byte even though the path's stored sweep may move in its last bit. Whole turns and zero sweeps are exact in both, sincearcSweepreturns ±2π and 0 by early exit. Nothing else in the family is affected: the eight parametric shapes store their own parameters and restore them bit for bit.Matched by exact class identity, like every other pair here.