Expand description
Star-pattern scene: a Hankin star rosette built from a continuous contact
angle and cached (ADR-0007 generator build model), cheap to animate. Per
frame the scene resolves variant to an angle, reuses the cached rosette
unless the request has moved more than one step, and applies a
rotate/scale/colour/draw-on transform (allocation-free).
§variant is a contact angle, not an index (ADR-0060)
variant maps linearly onto a contact-angle offset rather than flooring into
one of three precomputed rosettes. 0, 1 and 2 land on exactly the
-24 / 0 / +24 degree offsets of those three, so a preset binding integers
draws the rosette it names, while a fractional value is a real rosette in
between and [smoothing] has something to interpolate.
The cache stays, keyed on the built angle with hysteresis: a request more
than STEP_DEG from the built angle rebuilds, anything nearer reuses. That
is what keeps generator work off the hot path (ADR-0007) now that a bound
param can reach it.
The step is measured, not assumed (ADR-0060 leaves the number open). At
0.1 degrees:
- Invisible in motion. The worst case is the sharpest reachable rosette: a 12-fold star at an 11-degree contact angle moves a vertex 11.0 px per degree at 1080p, so one step is 1.14 px there and 0.67 / 0.25 px at the 20 / 55-degree angles the two shipped presets use — under a stroke that is itself several pixels of glow wide.
- Cannot rebuild every frame. The full
variantrange is 48 degrees of contact angle, i.e. 480 steps, so a sweep slower than 8 s at 60 fps rebuilds on a fraction of its frames. Both shipped presets sweep in ~45 s, which is about one rebuild every six frames. - And a rebuild fits the frame anyway. Measured at the loader’s maximum
order (
n = 12, so2n = 24segments): 0.34 us, 0.002% of a 16.7 ms frame. A hypothetical rosette filled to the floor tier’s whole 20 000-segment cap (n = 10 000, unreachable from the preset surface, whose tilings stop at 12) costs 282 us — 1.7% of a frame — so even the ceiling this scene cannot reach is inside budget.
§The colour axis: radius from the rosette centre (ADR-0059)
This scene honours [palette] / [palette_b] / palette_mix / hue_spread
/ saturation through the shared ColorRamp, on a normalized radius
axis: a Hankin rosette is rotationally symmetric about the frame centre, so
radius is the only ordering the construction itself supplies.
On the bare rosette that ramp is identically flat — measured, not
estimated. The rosette is 2n congruent segments: each runs from a
contact point on the unit circle to a petal tip at radius
sin(a) / sin(pi/n + a), and every one is a rotation or reflection of every
other about a centre that normalize_fit leaves at the origin (every tiling
order the loader accepts — 4, 6, 8, 12 — is even, so the bounding box is
centred). Each segment’s radial interval is therefore the same interval, and
one colour per segment has nothing to distinguish. Across both shipped presets
and all three of their variants the spread of segment radii is 1.2e-7,
which is f32 noise and not a range.
The figure’s radial extent is a different quantity, and it is the “hollow
ring” of design-backlog 0007: at star_rosette’s 12-fold / 20-degree rosette
the strokes live between radius 0.54 and 0.90, so the inner 60% of the
disc is empty, and star_lantern’s 55-degree variant empties 87%. That is
the interior question, not something a colour axis can answer.
hue_spread is therefore a no-op on a rings-less preset, stated here and
in presets/README.md rather than shipped as a lever that quietly does
nothing. What such a preset does gain is [palette] itself.
§The interior: rings of motifs (ADR-0079)
[generator] rings is an optional roster of concentric rings — { motif, count, radius, scale, phase } each — drawn through the same LineRenderer
alongside, or instead of, the interlace. It answers design-backlog 0007’s
hollow-ring half, and it is placement rather than construction: copy i of
a ring of k sits at 2*pi*i/k + phase, scaled by scale, at distance
radius, in the same fit-normalized world the rosette lands in (the rosette
spans +/- 0.9, so a radius near 0.9 sits on its rim and anything smaller
is genuinely interior).
Two consequences worth stating where they can be read:
- With
ringsabsent nothing here runs at all, and the scene draws the Hankin path segment for segment — the rings live in their own buffer and the combined one is never even allocated. - With
ringspresent the radial colour axis stops being degenerate. The ramp is computed over the combined figure, which really does span radii, sohue_spreadbecomes a live lever on exactly the presets that have an interior to spread across.
The motif roster is closed (Motif): a look outside it routes back
through architect + dev rather than being added on request (ADR-0079).
§The rings move: three levers, and why two of them are radial
The roster stays structural; what moves is a RingMotion applied to it.
ring_phase turns alternate rings in opposite directions, ring_spread
multiplies every radius about the centre, and ring_scale multiplies every
motif’s size. All three default to RingMotion::STATIC, the exact identity
(+ 0, * 1, * 1), so a preset that binds none of them draws the static
ornament bit for bit.
The radial pair is not a garnish, and this is the one design note worth
reading before authoring a mandala. core/tests/animation.rs captures at
96x96 and diffs whole frames, and a ring mandala is more rotationally
symmetric than the bare rosette design-backlog 0009 measured — an 18- and
24-fold figure turned by any angle lands almost on top of itself, so spin
alone reads as frozen to that gate and, at a distance, to the eye.
ring_spread and ring_scale change what the figure is at each radius
rather than where it sits, so they move pixels. A shipped mandala carries its
animation on those and spends ring_phase on the counter-rotation, which is
the ornamental gesture rather than the liveness.
Like the rosette, the ornament is rebuilt under hysteresis: a motion
further than one step (RING_PHASE_STEP and friends) from what is held
rebuilds, anything nearer reuses. A preset binding none of the three never
rebuilds after configure — but one that animates a lever re-places its
ornament on most frames, which is affordable rather than free. See
RING_PHASE_STEP for the measurement.
Structs§
- Ring
Spec - One concentric ring of repeated motifs: the validated form of one entry in the
[generator] ringsarray (ADR-0079). - Star
Pattern Scene - A generator scene drawing a Hankin star pattern.
Enums§
- Motif
- The closed, curated motif roster (ADR-0079): the shapes a
[generator] ringsentry may repeat around a ring.
Constants§
- DEFAULT_
RING_ SCALE - The
scalea ring takes when it declares none — a motif a quarter the size of the fit-normalized figure, which is legible at every ring count in the roster. - MAX_
RING_ COUNT - The largest
countone ring may declare, enforced at load. - MIN_
SCALLOP_ LOBES - The fewest lobes a
Motif::Scallopboundary is built with. - PARAMS
- Parameter vocabulary — see
fragment_field::PARAMS. Keep in sync withset_parambelow.