Builtin Library
README.md
colorSequence
Roblox-shaped RGB keyframe-curve value type. A ColorSequence is an
immutable curve of up to 64 keypoints, each carrying
(time, value, envelope) where value is a {r, g, b} triple and
envelope is a per-channel random range half-width. The per-channel
envelope is a notable improvement over Roblox, where ColorSequence
ships with no envelope at all — a long-standing community wishlist
item.
Exposed as the ColorSequence Luau global via --!global. Authored
scripts call ColorSequence.new(...) directly without a require.
The Rust FFI lives at __sequences.* (registered by
crates/zero_scripting/src/ffi/bindings/curves.rs); this module is
the typed Luau wrapper.
Exports
-
ColorSequence.new(...) -> ColorSequenceObj:ColorSequence.new({r, g, b})— constant color (accepts a 3-array or{r=, g=, b=}record).ColorSequence.new(c0, c1)— two-point lerp fromc0toc1.ColorSequence.new({ keypoint, ... })— explicit keypoints.envelopemay be omitted (={0,0,0}), a single number (broadcast to all channels), or a 3-array. First key must anchor attime = 0, last attime = 1. NaN / Inf rejected.
-
ColorSequence.deserialize(payload) -> ColorSequenceObj— rebuild from a{ kind = "ColorSequence", keypoints = {...} }payload produced by:serialize().
Keypoint shapes
An entry of a keypoint list takes any of three written shapes, and the shapes mix within one list:
| Written | Read as |
|---|---|
{ time = 0.5, value = {1,0,0}, envelope = 0.05 } | the named record |
{ 0.5, {1,0,0}, 0.05 } | time, then colour, then envelope |
{ 1, 0, 0 } | a bare colour, timed by its place in the list |
A bare colour takes its time from its place: the entries spread evenly
across [0, 1], so { {1,0.85,0.35}, {1,0.35,0.05} } is a ramp from
the first colour at t = 0 to the second at t = 1, and a list of one
colour holds that colour across the whole domain.
Methods (called via :)
seq:evaluate(t) -> (r, g, b)— deterministic linear interpolation att(multiret).tclamps to[0, 1]; NaN coerces to0.seq:sample(t) -> (r, g, b)—evaluate(t)plus per-channel(math.random() − 0.5)·2·envelope(t)jitter. Per-VMmath.randomseedcontrols jitter reproducibility.seq:keypoints() -> { { time, value = {r,g,b}, envelope = {r,g,b} } }— array snapshot, sorted ascending by time.seq:duration() -> number— always1.0for a well-formed sequence.seq:serialize() -> { kind, keypoints }— scene-save payload.seq:destroy()— drop the FFI handle. Luau removed__gcon tables, so eager cleanup is the caller's responsibility for per-frame-rebuild patterns; otherwise the entry dies with the VM.
Substrate
Backed by zero_curves::Channel (Linear interp). Each sequence
builds the value and envelope channels once at construct time and
reaches into them on every sample — zero per-call allocation, safe
for hot particle loops. Validation errors name the specific rule
("first keypoint must anchor at time = 0",
"too many keypoints (max 64)", …).
Foundation for VFX property-over-lifetime: declarative particles (#2572), GPU emitter (#873), beams, trails. Issue: #2673.