Skip to main content

Module star

Module star 

Source
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 variant range 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, so 2n = 24 segments): 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 rings absent 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 rings present the radial colour axis stops being degenerate. The ramp is computed over the combined figure, which really does span radii, so hue_spread becomes 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§

RingSpec
One concentric ring of repeated motifs: the validated form of one entry in the [generator] rings array (ADR-0079).
StarPatternScene
A generator scene drawing a Hankin star pattern.

Enums§

Motif
The closed, curated motif roster (ADR-0079): the shapes a [generator] rings entry may repeat around a ring.

Constants§

DEFAULT_RING_SCALE
The scale a 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 count one ring may declare, enforced at load.
MIN_SCALLOP_LOBES
The fewest lobes a Motif::Scallop boundary is built with.
PARAMS
Parameter vocabulary — see fragment_field::PARAMS. Keep in sync with set_param below.