5 · Heat
So far every colour in the blaster has been a fixed swatch. This part replaces them with one number and one expression, and the prop stops looking scripted.
1 · Two numbers
Section titled “1 · Two numbers”Open DEFINES ▸ Variables. Type heat into + new var, then
ambient. Leave both at 0.
Effects read them, actions write them. Three more are built in and never
declared: time (seconds since start), audioLevel (0–1, live loudness) and
unitId (this board’s own number).
2 · Anywhere a number goes, an expression goes
Section titled “2 · Anywhere a number goes, an expression goes”This is the part that is easy to miss in a graphical editor: every numeric and colour box on every card also accepts an expression. Type one instead of a value and it is re-evaluated every frame.
Change the idle script’s blink to a pattern (use the verb dropdown on the
row — it keeps the step’s flow, loop and notes, and swaps only the verb’s own
parameters), and put this in its colour box:
hsv(0.09 - heat*0.09, 1, 0.16 + 0.55*heat + 0.10*sin(time*3 + i*0.7))One line, re-evaluated for every pixel, every frame:
hsv(h, s, v)takes 0–1 arguments and returns a colour. Hue0.09is orange;0.09 - heat*0.09slides it to0.00, pure red, as heat reaches 1.- The value channel climbs with
heat, so a hot barrel is a bright barrel. sin(time*3 + i*0.7)adds a shimmer offset per pixel —patternhands the expressioni(the pixel index) andcount(how many there are).
3 · Making the number move
Section titled “3 · Making the number move”Four verbs animate a var, all in the palette’s Actions group. Picking the right one matters more than it looks:
| Verb | What it does | Ends? |
|---|---|---|
set |
assign it, now | instantly |
ramp |
move toward to at rate units/sec |
when it arrives |
tween |
move to to over sec, along an ease |
when the time is up |
follow |
glide continuously toward another var at rate |
never |
Firing uses set, because a shot is an event. On the Firing state’s
on enter, add set and target heat — then type an expression into its
value box rather than a number:
min(heat + 0.17, 1)Cooling uses follow, because cooling is a standing fact about the prop
rather than an event. Add a follow step to the top of the idle script’s
list: follow heat, target ambient, rate 0.22.
4 · Guards: a handler that can refuse
Section titled “4 · Guards: a handler that can refuse”Every when row has an if box at its right-hand end, next to
then go →. Put heat < 1 in the one on trigger.tapped.
if is an expression evaluated at the moment the event arrives. If it is false
the handler does not fire at all — and because the Studio works out which
buttons are live from which handlers can fire, the trigger greys itself out
when the barrel is cooked. You configured no enable/disable logic.
5 · Rules: a guard that watches every state
Section titled “5 · Rules: a guard that watches every state”Open BEHAVIORS ▸ Rules ▸ + new rule.
Put heat >= 1 in the when box, leave do empty, and set
then go → to Overheated.
A rule is a condition watched in every state, firing once on the rising edge. This is the overheat cut-out, and it is one card instead of an identical handler copied into Ready, Firing and every state you add later.
6 · The vent
Section titled “6 · The vent”Add the Overheated state, and a vent script for it to run:
| Step | Verb | Set | flow |
|---|---|---|---|
| 1 | play |
powerdown.wav, vol 0.6 |
detached |
| 2 | ramp |
heat → 0 @ 0.5 |
detached |
| 3 | blink |
@barrel, #4d8cff, sec 0.3, off #02060f, stop after 2.2 |
serial |
The blue blink is the only serial child, which makes it the thing that decides how long venting takes — change its stop after and the state comes back sooner, with nothing else to adjust.
What the editor wrote
Section titled “What the editor wrote”{
// ===========================================================
// BLASTER · STEP 5 — Heat
// Stop hard-coding what the prop looks like. Track ONE
// number, point the colours at it, and let the number move.
// ===========================================================
"schemaVersion": 1,
"name": "Blaster 05 - Heat",
"board": "t-display-s3",
// A var is a named number with a starting value. That is all.
// heat 0 = cold, 1 = cooked
// ambient the temperature heat always slides back toward.
// It is a var and not the literal 0 because `follow`
// chases a MOVING target — "room temperature" is
// just a target that happens never to move. Make it
// move later and the prop gets a cooling fan.
"vars": { "heat": 0, "ambient": 0 },
"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": {
"idle": {
"children": [
// Always cooling, while we are idle. `follow` glides one
// number toward another forever at a fixed rate; it never
// finishes, so it detaches itself and just runs.
{ "follow": "heat", "target": "ambient", "rate": 0.22 },
// The glow is now ONE EXPRESSION, re-evaluated per pixel,
// every frame:
// hue 0.09 (orange) slides to 0.00 (red) as heat rises
// value climbs with heat, plus a slow per-pixel shimmer
// `pattern` hands the expression `i` (pixel index) and
// `count`; every expression can also read `time` and any var.
{ "pattern": "@barrel", "loop": true,
"color": "hsv(0.09 - heat*0.09, 1, 0.16 + 0.55*heat + 0.10*sin(time*3 + i*0.7))" }
]
},
"shot": {
"children": [
{ "tone": 720, "wave": "square", "seconds": 0.09, "volume": 0.35, "flow": "detached" },
{ "set": "@barrel", "color": "#ffffff", "sec": 0.04 },
{ "comet": "@barrel", "color": "hsv(0.09 - heat*0.09, 0.9, 1)", "lapSec": 0.2, "tail": 5, "for": 0.2 },
{ "wait": 0.07 }
]
},
// The cost of firing too much. The blue vent blink runs for
// 2.2 s and is the only SERIAL child, so it is what decides
// how long the whole script takes; the sound and the forced
// cool-down ride alongside it.
"vent": {
"children": [
{ "play": "powerdown.wav", "volume": 0.6, "flow": "detached" },
{ "ramp": "heat", "to": 0, "rate": 0.5, "flow": "detached" },
{ "blink": "@barrel", "color": "#4d8cff", "off": "#02060f", "sec": 0.3, "for": 2.2 }
]
}
},
// A RULE watches an expression in EVERY state and fires once,
// on the rising edge. This is the overheat cut-out, and it does
// not have to be repeated in each state that can reach it.
"rules": [
{ "when": "heat >= 1", "to": "Overheated" }
],
"states": {
"initial": "Ready",
"Ready": {
"enter": [ { "start": "idle" } ],
"exit": [ { "stop": "idle" } ],
// `when` guards a handler: at full heat the trigger does
// nothing at all, and the button greys out by itself.
"on": { "trigger.tapped": { "to": "Firing", "when": "heat < 1" } }
},
"Firing": {
"enter": [ { "set": "heat", "to": "min(heat + 0.17, 1)" }, { "run": "shot" } ],
"on": { "shot.done": "Ready" }
},
"Overheated": {
"enter": [ { "run": "vent" } ],
"on": { "vent.done": "Ready" }
}
}
}
Next: 6 · The HUD — put a reticle on a screen.