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…
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.
@builtin::systems.particles.curves exposes a ColorSequence.new that
reads these same three shapes, so a colour ramp written for an emitter
spec is the literal this constructor takes. The two differ in what they
do with the times: this one keeps the written times and leaves the
first at 0, last at 1 rule to raise, while the particles reader sorts,
clamps and forces the endpoints. The particles reader also carries an
alpha channel this one drops, and holds 16 stops where this one holds
64, resampling a longer list down to its own width.
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.
Scoped to this part · feeds back into the world's score.