4 · Flow
Everything that happens over time in RFX is a script, or a child of one. The whole control model is one dropdown on each child.
The dropdown
Section titled “The dropdown”Every step you add has a flow box on its row. It answers two questions
that have nothing to do with each other — does my next sibling wait for me,
and does my parent wait for me — and it packs both into one word:
flow |
Gates the next sibling? | Parent waits for it? | Use it for |
|---|---|---|---|
serial (default) |
✅ | ✅ | a step — one thing after another |
parallel |
❌ | ✅ | a layer — run together, all awaited |
detached |
❌ | ❌ | fire and forget — a background sound or loop |
That is the entire vocabulary. There is no container “mode”: a container is always serial-by-default, and its children decide.
1 · A look is layers
Section titled “1 · A look is layers”Open the idle script from part 2 and add a second child: + effect ▸
spark, targeting @barrel, colour #ffc08a, sec 0.6, loop ticked.
Both children now loop, so neither gates the other: the blink and the random sparks run at once on the same eight pixels, the spark painting over the blink. Add a third and you have three layers. You configured no scheduler.
2 · A sequence is steps
Section titled “2 · A sequence is steps”Make a new script called shot. Add four children — this is the card the whole
part is about:
| # | Add with | Verb | Set | flow |
|---|---|---|---|---|
| 1 | + effect |
tone |
720, wave square, seconds 0.09, volume 0.35 |
detached |
| 2 | + action |
set |
@barrel, color #ffffff, sec 0.04 |
serial |
| 3 | + effect |
comet |
@barrel, #ff7a00, lap 0.2, tail 5 |
serial |
| 4 | + action |
wait |
0.07 |
serial |
Then on the comet, open + timing / condition and add stop after —
0.2 s.
Read the card downward:
- The tone is detached, so the muzzle flash on the next row starts
immediately instead of waiting 90 ms for a sound to finish. This is the most
common use of
detachedthere is. - The flash and the comet are serial steps — the comet does not start until the white flash has held for its 0.04 s.
- “stop after” gives the comet a lifetime. A comet with no lifetime and no
loop would run one lap and stop; with one, it is a step with a definite end,
which is what lets the next child know when to start. (In the file this is
the uniform
formodifier.) - The
waitis recovery time — it gates nothing but the end of the script, which is exactly what stops you machine-gunning the trigger.
When the last serial child finishes, the script is done and shot.done fires.
3 · A burst is a nested container
Section titled “3 · A burst is a nested container”Make another script, burst. Drag a group block from the palette’s
Flow section into its list — a group is a step that contains steps.
Put a copy of the four children inside it, then tick the group’s loop box
and type 3 in the count field beside it.
4 · Tap or hold, from one button
Section titled “4 · Tap or hold, from one button”On the Ready state, change the when event dropdown from
trigger.pressed to trigger.tapped → Firing, then add a second handler:
+ when event ▸ trigger.held → Bursting.
Add the Bursting state with run ▸ burst on enter, and
burst.done → Ready.
Both events come from the same physical trigger, and the states pick which one they want. Nothing timed the press; the input did that.
The rule that makes this worth it
Section titled “The rule that makes this worth it”Time, speed and intensity flow downhill. Put 0.5 in the shot script’s
speed box and every child in it — including the tone’s pitch — halves with
it. Drag the Live tab’s master speed slider and the whole show slows, audio and
all.
That one property is why the engine has scripts instead of a list of timers, and it is the best debugging tool in the Studio: a 40 ms muzzle flash you cannot see at 1.0 is perfectly legible at 0.2.
What the editor wrote
Section titled “What the editor wrote”{
// ===========================================================
// BLASTER · STEP 4 — Flow
// Every child answers two questions: does the next sibling
// wait for me, and does my parent wait for me? `flow` is the
// one word that answers both.
//
// serial (default) next waits · parent waits -> a STEP
// parallel next runs · parent waits -> a LAYER
// detached next runs · parent doesn't -> FORGET IT
// ===========================================================
"schemaVersion": 1,
"name": "Blaster 04 - Flow",
"board": "t-display-s3",
"devices": {
"barrel": { "kind": "ledstrip", "count": 8, "pin": 16, "order": "GRB", "brightness": 160 }
},
"inputs": {
"trigger": { "label": "TRIGGER", "color": "#ff3b30", "pin": 14, "activeLow": true, "pull": "up" }
},
"scripts": {
// TWO LAYERS. Both children loop, so both run at once and
// neither gates the other — a looping child is detached
// automatically, because nothing can wait for forever.
"idle": {
"children": [
{ "blink": "@barrel", "color": "#ff6a00", "off": "#140400", "sec": 1.4, "loop": true },
{ "spark": "@barrel", "color": "#ffc08a", "sec": 0.6, "loop": true }
]
},
// ONE ROUND. Four children, three kinds of flow:
// the tone is DETACHED, so the muzzle flash does not wait
// for the sound to finish;
// the flash and the ejecting comet are SERIAL steps;
// `for` gives the comet a lifetime, so it is a step with
// an end rather than a look that never stops.
"shot": {
"children": [
{ "tone": 720, "wave": "square", "seconds": 0.09, "volume": 0.35, "flow": "detached" },
{ "set": "@barrel", "color": "#ffffff", "sec": 0.04 },
{ "comet": "@barrel", "color": "#ff7a00", "lapSec": 0.2, "tail": 5, "for": 0.2 },
{ "wait": 0.07 }
]
},
// THREE ROUNDS. A nested container with `loop: 3` runs the
// shot three times; the parent waits for the whole burst,
// then `burst.done` fires.
"burst": {
"children": [
{ "children": [
{ "tone": 720, "wave": "square", "seconds": 0.09, "volume": 0.35, "flow": "detached" },
{ "set": "@barrel", "color": "#ffffff", "sec": 0.04 },
{ "comet": "@barrel", "color": "#ff7a00", "lapSec": 0.2, "tail": 5, "for": 0.2 },
{ "wait": 0.07 }
], "loop": 3 }
]
}
},
"states": {
"initial": "Ready",
"Ready": {
"enter": [ { "start": "idle" } ],
"exit": [ { "stop": "idle" } ],
"on": {
"trigger.tapped": "Firing", // a quick press -> one round
"trigger.held": "Bursting" // hold it past 400 ms -> three
}
},
"Firing": { "enter": [ { "run": "shot" } ], "on": { "shot.done": "Ready" } },
"Bursting": { "enter": [ { "run": "burst" } ], "on": { "burst.done": "Ready" } }
}
}
The file is a fair mirror of the cards: "flow": "detached" is the dropdown,
"loop": 3 is the checkbox and its count, and "for": 0.2 is the stop after
row. serial never appears, because it is the default — the editor writes the
smallest file that means what you clicked.
Next: 5 · Heat — stop hard-coding what the prop looks like.