Proof W3 lane T2: effects engine and colour fan (2026-10-11)
- Worktree:
~/trc-lighting/wt-effects, branch w3-effects (from two-screen 562d9a1).
- Dry run:
tools/mac-dryrun.sh 7093 ~/trc-lighting/wt-effects. Port 7093 was used because 7087 (wt-s2proof) and 7088 (T1 wt-setlist) were taken. dryRun=true, dmx.running=false, so every /api/dmx/frame request renders a frame at request time on the real clock. /api/dmx/nodes was never called.
- Build: 0 errors. The only warnings are the existing 9 CA1416 in
CamFrame.cs, none in the effects files.
- Method: a Python sampler read
GET /api/dmx/frame?u=1&from=56&to=144 back to back (31,394 frames in 8.0 s, mean gap 0.3 ms, worst gap 21 ms). It took each sample's time as the midpoint of its request, and read the 12 par dimmers: absolute channel addr+7, addresses 49 to 137. Period and phase come from a least-squares sine fit per fixture, with a zero-crossing count on the first fixture as a check.
- Effect under test:
POST /api/fx {fixtures:[12 pars], order:"map", kind:"sine", beats:2, size:100, spread:50, base:100} at 120 BPM. The map order was 2201, 3101, 2206, 2202, 3102, 2203, 3103, 2204, 3104, 2207, 2205, 3105.
Period at 120 BPM, beats 2 (target 1000 ms within 2 %)
| Measure |
Result |
| Sine-fit period, all 12 fixtures |
1000.0 ms each (min 1000.0, max 1000.0), error 0.0 % |
| Zero crossings, 2201, 7 intervals |
mean 1000.0 ms, min 999.9, max 1000.2 |
| Range on every fixture |
0 to 255 |
Spread 50 (expected lag i x 0.5/12 cycle = 41.67 ms per place)
| Place |
Fixture |
Lag measured |
Expected |
Error |
| 0 |
2201 |
0.0 ms |
0.0 |
0.0 |
| 1 |
3101 |
41.7 |
41.7 |
0.0 |
| 2 |
2206 |
83.3 |
83.3 |
0.0 |
| 3 |
2202 |
125.0 |
125.0 |
0.0 |
| 4 |
3102 |
166.7 |
166.7 |
0.0 |
| 5 |
2203 |
208.3 |
208.3 |
0.0 |
| 6 |
3103 |
250.0 |
250.0 |
0.0 |
| 7 |
2204 |
291.7 |
291.7 |
0.0 |
| 8 |
3104 |
333.3 |
333.3 |
0.0 |
| 9 |
2207 |
375.0 |
375.0 |
0.0 |
| 10 |
2205 |
416.7 |
416.7 |
0.0 |
| 11 |
3105 |
458.3 |
458.3 |
0.0 |
Continuity: BPM change and live edits
Metric: the largest change on any of the 12 dimmers between a sample and the sample 25 ms (one frame) later. It is taken around the moment of the change, and compared with the same metric in steady running on either side. The steady figure measures 18 to 48 counts, against a theoretical 20 at 1 Hz, because the sample times carry up to 21 ms of request jitter. That steady figure is the frame step the change is held to.
| Change |
Steady before |
Across the change |
Steady after |
Result |
| BPM 120 to 90, trial 1 |
34 |
42 |
36 |
within steady range of the run (max 48) |
| BPM 120 to 90, trial 2 |
36 |
21 |
41 |
pass |
| BPM 120 to 90, trial 3 |
48 |
21 |
27 |
pass |
| beats 2 to 1 at 90 BPM, 3 trials |
38 / 38 / 19 |
31 / 31 / 31 |
78 / 51 / 54 (faster wave) |
pass |
| spread 50 to 0 |
18 |
35 |
n/a |
pass |
- After the BPM change the sine-fit period was 1333.3 ms (target 1333.3). After beats 2 to 1 at 90 BPM it was 666.7 ms (target 666.7).
- A spread edit first jumped 237 counts in one frame, because the per-light offsets were applied at once. The edit now glides size, spread and phase over two cycles of the effect. Measured after the fix (across the edit / largest step during the glide / steady before):
- spread 50 to 0: 24 / 35 / 24 and 22 / 45 / 49.
- size 100 to 30: 22 / 38 / 22 and 22 / 27 / 36.
- phase 0 to 180: 26 / 63 / 36 and 28 / 44 / 34.
Grand master 50 halves the peak
|
Peak, all 12 |
Trough, all 12 |
| Master 100 |
255 |
0 |
| Master 50 |
127 |
0 |
The worst ratio error was 0.002 (127/255 = 0.498).
Blackouts and undo
- Kept blackout
{keep:true}: the dimmer maximum over 0.7 s was 0. GET /api/fx still listed the effect, and its cycle moved from 0.157 to 0.858 during the hold, so it ran on. After release the peak was 255 within 1.1 s.
- Hard blackout
POST /api/blackout {}: GET /api/fx returned [] and the dimmer maximum was 0. POST /api/undo undid "blackout" and GET /api/fx returned effect 2 (sine, beats 2, spread 50, 12 fixtures). Every par then swung 0 to 255 again.
- Stop:
POST /api/fx/stop {id} added one undo entry, "effect stop" (depth 5 to 6), leaving 0 effects. Undo brought back effect 2.
- Start:
POST /api/fx {group:4, kind:"colourcycle"} added one undo entry, "effect colourcycle". Undo left only effect 2.
Mood recall drops only the lights it writes
| Look |
Writes dimmer on |
Effect fixtures after the recall |
Expected |
| 763 RED ALARM |
2201 to 2207 |
3101 to 3105 |
3101 to 3105 |
| 771 SLOW PULSE |
2201 to 2207 |
3101 to 3105 |
3101 to 3105 |
Hit above the effect
The effect was a sine on the 12 pars at base 100. The test pressed hit seldip (BLACKOUT HIT) on 2201 and 2202.
- While it was held, 2201 and 2202 read a maximum of 0. 3101 kept swinging 0 to 255.
- After release, 2201 swung 0 to 255 again and the effect was still running.
Colour fan, from h 0 to h 240 (s 100, l 50) across the 12 pars
This is POST /api/colour/fan {fixtures:[12 pars], from:{h:0,s:100,l:50}, to:{h:240,s:100,l:50}}. The pars were at full on groups 3 and 5. The hue is read from the frame's ColorRGB_R/G/B channels.
| Fixture |
Map x |
Asked hue |
Frame RGB |
Frame hue |
| 2201 |
-3.2 |
0 |
255, 0, 0 |
0.0 |
| 3101 |
-3.2 |
21.8 |
255, 93, 0 |
21.9 |
| 2206 |
-2.5 |
43.6 |
255, 186, 0 |
43.8 |
| 2202 |
-1.6 |
65.5 |
232, 255, 0 |
65.4 |
| 3102 |
-1.6 |
87.3 |
139, 255, 0 |
87.3 |
| 2203 |
0 |
109.1 |
46, 255, 0 |
109.2 |
| 3103 |
0 |
130.9 |
0, 255, 46 |
130.8 |
| 2204 |
1.6 |
152.7 |
0, 255, 139 |
152.7 |
| 3104 |
1.6 |
174.5 |
0, 255, 232 |
174.6 |
| 2207 |
2.5 |
196.4 |
0, 186, 255 |
196.2 |
| 2205 |
3.2 |
218.2 |
0, 93, 255 |
218.1 |
| 3105 |
3.2 |
240 |
0, 0, 255 |
240.0 |
- Hue rises monotonically left to right, and map x never decreases. Amber (RY) was 0 on every par, and nothing was skipped.
- The fan was one undo step, labelled "colour fan". Undoing it put 2201 back to 255, 255, 255.
Regression
tools/regress-v2.sh 7093 gave GREEN, 14 passed. The checks were status, pick, aim, stack, mood-solo, hit, blackout, undo, timeline, userscene, /desk.html, /screen2.html, phone and /help/.