Scenario Tests¶
Auto-generated from test/scenarios/{core,light}/scenario_*.json by moondeck/docs/generate_test_docs.py. Do not edit by hand โ update the JSON file's top-level fields and per-step description / bounds / contract / observed instead, then regenerate.
Scenario tests are the integration tier in the test strategy: each one is a JSON script that drives the full pipeline (desktop or live ESP32) and captures tick / heap per step against per-target contracts. Run them with moondeck/scenario/run_scenario.py (desktop) or moondeck/scenario/run_live_scenario.py (live device). See testing.md ยง Performance contracts for the contract semantics.
AudioService¶
scenario_Audio_mutation¶
test/scenarios/light/scenario_Audio_mutation.json โ Add / configure / remove the AudioService (a Service module) and an audio-reactive effect while the render pipeline runs, proving the robustness rule for the audio producer/consumer pair. AudioService is a Service (it sits beside the pipeline, publishing an AudioFrame), and the audio effects read it through the static AudioService::latestFrame() accessor, NOT a boot-time pointer โ so add/remove can happen in any order at runtime. The checks assert the pipeline keeps RENDERING (buffer non-null, fps measurable) through each mutation: adding the mic, setting its three pins one at a time (the install fan-out's exact add-then-configure sequence โ the web installer / MoonDeck / OTA picker add the AudioService then POST wsPin/sdPin/sckPin as separate control writes from a catalog entry's modules+controls), adding a consumer effect, and crucially REMOVING the mic while a consumer is still live (the consumer must fall back to a silent frame, never deref a dangling pointer โ the bug the boot-loop fix and the unit lifecycle tests pin, here proven end-to-end through the Scheduler). The per-pin sequence proves the self-correcting partial-fill: the first two writes leave a pin unset so AudioService's guard makes the rebuild a no-op, the third completes the set โ the pipeline never stalls through any of it. On the host the mic is inert (hasI2sMic false), so this exercises the wiring/lifecycle, not real capture; capture is proven on hardware. Grid is 64x64 so the tick stays above the host microsecond clock at every step.
Mode: mutate ยท Also touches: SystemModule, Services, Layouts, GridLayout, Layer, RainbowEffect, AudioVolumeEffect, AudioSpectrumEffect, Drivers, PreviewDriver
measure-pipeline-only (measure) ๐¶
Baseline: the render pipeline runs with no audio module present.
Bounds: - FPS โฅ 1 (absolute)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 4,425-125,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 20,000-27,778 | โ / unlimited | โ / unlimited |
esp32 |
โ / 31,250-33,333 | โ / 129KB-130KB | โ / 108KB |
desktop-macos: observed 2026-06-12 โ 2026-07-15desktop-windows: observed 2026-07-08esp32: observed 2026-07-22
measure-audio-added (measure) ๐¶
Pipeline still renders with the (idle, unconfigured) mic added.
Setup (preceding non-measured steps):
- add-audio-module (add_module) โ Add the AudioService under the Services container (where the user adds a capability module). Pins default unset, so it stays idle; the pipeline must keep rendering.
Bounds: - FPS โฅ 1 (absolute)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 3,717-125,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 18,868-27,778 | โ / unlimited | โ / unlimited |
esp32 |
โ / 27,027-28,571 | โ / 124KB-125KB | โ / 108KB |
desktop-macos: observed 2026-06-12 โ 2026-07-05desktop-windows: observed 2026-07-08esp32: observed 2026-07-22
measure-pins-configured (measure) ๐¶
All three mic pins set via the sequential install-fan-out order: pipeline still renders through the full add-then-configure flow a catalog inject performs (add AudioService, then wsPin/sdPin/sckPin one at a time).
Setup (preceding non-measured steps):
- configure-ws-pin (set_control) โ Set the first mic pin (wsPin). This mirrors the install fan-out: a catalog entry's Audio pins arrive as separate /api/control writes, one per pin. After this single write wsPin is set but sdPin/sckPin are still 0, so AudioService's unset-pin guard short-circuits before audioMicInit (no I2S init attempted) and the rebuild is a cheap no-op. The pipeline must keep rendering through it.
- configure-sd-pin (set_control) โ Second pin (sdPin). Still one pin unset (sckPin=0), so still guarded โ the second cheap no-op rebuild. Proves the partial-pin-fill sequence the install injection produces never disturbs the running pipeline.
- configure-sck-pin (set_control) โ Third and final pin (sckPin). Now all three are set, so the guard passes and a real mic init is attempted. On host hasI2sMic is false so the mic stays inert regardless, but this is the write that completes the install-injected pin set; the prepareTree rebuild must not disturb the running pipeline.
Bounds: - FPS โฅ 1 (absolute)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 11,364-125,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 20,000-27,778 | โ / unlimited | โ / unlimited |
esp32 |
โ / 22,222-31,250 | โ / 128KB | โ / 108KB |
desktop-macos: observed 2026-06-13 โ 2026-07-10desktop-windows: observed 2026-07-08esp32: observed 2026-07-22
measure-consumer-live (measure) ๐¶
Pipeline renders with the producer + consumer both wired.
Setup (preceding non-measured steps):
- add-audio-consumer (add_module) โ Add an AudioVolumeEffect consumer under the Layer. It reads the mic via the static accessor; with the mic present it gets the live (silent, on host) frame.
Bounds: - FPS โฅ 1 (absolute)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 9,009-125,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 17,241-25,000 | โ / unlimited | โ / unlimited |
esp32 |
โ / 27,778-30,303 | โ / 113KB-126KB | โ / 108KB |
desktop-macos: observed 2026-06-12 โ 2026-07-10desktop-windows: observed 2026-07-08esp32: observed 2026-07-22
measure-after-mic-removed (measure) ๐¶
Mic gone, consumer remains: pipeline keeps rendering on silent audio (buffer non-null, fps measurable). No crash from the orphaned consumer.
Setup (preceding non-measured steps):
- remove-audio-module (remove_module) โ Remove the mic while the consumer is STILL live. The consumer must fall back to AudioService::latestFrame()'s static silence โ no dangling pointer, no crash. This is the robustness rule's hardest case for this pair.
Bounds: - FPS โฅ 1 (absolute)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 7,519-125,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 17,241-25,000 | โ / unlimited | โ / unlimited |
esp32 |
โ / 29,412-30,303 | โ / 131KB | โ / 108KB |
desktop-macos: observed 2026-06-12 โ 2026-07-15desktop-windows: observed 2026-07-08esp32: observed 2026-07-22
measure-back-to-baseline (measure) ๐¶
Both audio modules gone: back to the pipeline-only baseline, still rendering.
Setup (preceding non-measured steps):
- remove-audio-consumer (remove_module) โ Remove the orphaned consumer too โ clean teardown, pipeline still live.
Bounds: - FPS โฅ 1 (absolute)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 1,198-125,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 14,286-28,571 | โ / unlimited | โ / unlimited |
esp32 |
โ / 31,250-32,258 | โ / 130KB | โ / 108KB |
desktop-macos: observed 2026-06-12 โ 2026-07-15desktop-windows: observed 2026-07-08esp32: observed 2026-07-22
DevicesModule¶
scenario_DevicesModule_scan¶
test/scenarios/core/scenario_DevicesModule_scan.json โ Trigger the device-discovery sweep repeatedly on a running device and confirm the render loop survives every sweep. Pins the robustness principle for DevicesModule: pressing the scan button (a Button control on the Devices submodule of Network) re-runs the subnet sweep, whose HTTP probes block the render task up to the probe timeout per tick. No press, sweep state, or completion may crash or wedge the tick. Runs live only โ discovery needs a real LAN to probe and the module only exists on a connected device, so the in-process desktop runner SKIPs it; on a device it presses the button over HTTP. The bound checks FPS stays within range across repeated sweeps (a sweep is a transient cost, not a permanent degradation), proving the scan never leaves the render loop in a wedged state. (Background: the sweep is boot-only + manual precisely because the blocking probe must not run continuously on the render task โ see DevicesModule.md.)
Mode: mutate ยท live-only (skipped in-process)
scan-1 (set_control) ๐¶
First manual sweep. Baseline: the device renders while a discovery sweep runs.
scan-2 (set_control) ๐¶
Re-trigger the sweep while the previous one's state is still settling โ confirms a re-press mid-cycle doesn't wedge the loop.
scan-3 (set_control) ๐¶
Third sweep, bounded: FPS must stay within 20% of the first (a sweep is a transient cost; repeated scans must not permanently degrade the render loop).
Bounds: - FPS โฅ 80% of baseline
GridBlacksLayout¶
scenario_GridBlacks_blackpixel¶
test/scenarios/light/scenario_GridBlacks_blackpixel.json โ Toggle a mid-strand dark-gap (black column) run on a GridBlacks layout live and verify the full Layouts->Layer->Drivers pipeline keeps rendering. A gap is a physical wire slot held black: it forces the layer off the dense identity fast path onto the folded LUT (which drops the gap slots), while the SpiralEffect fill keeps the lit cells non-zero. Steps: build a 16x16 grid with a moving fill (dense, identity path), then set blackCount live (gap engaged, folded LUT, still renders non-zero because lit cells remain), then clear blackCount back (returns to the identity path). A crash, a wedge, or an all-black buffer on the gapped step would fail the measure. The exact 'gap cells carry no color' correctness is pinned by unit_GridBlacksLayout / unit_Layer_sparse_mapping; this is the live wired-pipeline gate.
Mode: mutate ยท Also touches: Layer, SpiralEffect
dense-no-gap (set_control) ๐¶
Baseline: a plain 16x16 moving fill, no gap. Dense identity fast path (no LUT). Buffer is fully non-zero.
Setup (preceding non-measured steps):
- canvas-clear-layers (clear_children) โ Self-canvas: clear+rebuild the pipeline this scenario assumes, so it runs from any device state.
- canvas-clear-layouts (clear_children)
- canvas-clear-drivers (clear_children)
- canvas-grid (add_module)
- canvas-layer (add_module)
- canvas-effect (add_module) โ A moving fill: every lit cell is a known non-zero color, so an all-black buffer would mean the gap wrongly blanked the whole grid.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 500,000-1,000,000 | โ / unlimited | โ / unlimited |
desktop-macos: observed 2026-07-22
gap-engaged (set_control) ๐¶
Set a dark column run live (blackStart 6, blackCount 4 -> columns 6..9 dark) as the FINAL state. Forces the folded LUT (drops the 4x16 gap slots). The pipeline must keep rendering: the lit cells of the moving fill stay non-zero (buffer non-zero holds), the physical light count is unchanged at 256 (gaps are real wire slots the driver still clocks), no crash. Ending on the gapped state means the buffer-non-zero check asserts the gap did NOT blank the grid.
Setup (preceding non-measured steps):
- gap-start (set_control) โ Position the dark run at column 6 (before engaging it). Still no gap until blackCount is set.
- gap-cleared-again (set_control) โ Clear the run back to 0 (from the earlier gap-start position set): confirms the toggle is reversible with no reboot before the gap is engaged for the final measured state. Returns to the dense identity fast path.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 333,333-1,000,000 | โ / unlimited | โ / unlimited |
desktop-macos: observed 2026-07-22
GridLayout¶
scenario_GridLayout_resize¶
test/scenarios/light/scenario_GridLayout_resize.json โ Resize the grid while the pipeline is running and verify it reallocates cleanly under memory pressure. Lowers to 128x64 (release memory), increases to 128x128 (heaviest config: mirror + LUT). Each measured step captures tick/FPS/heap so the runner reports the degrade behaviour.
Mode: mutate ยท Also touches: MultiplyModifier, Layer
size-128x128 (set_control) ๐¶
Set grid height to 128 (alongside default width 128). Measures the heaviest config as the baseline for the next two steps.
Setup (preceding non-measured steps):
- canvas-clear-layers (clear_children) โ Self-canvas: clear+rebuild the pipeline this scenario assumes, so it runs from any device state (the perf scenarios' pattern). Pre-wired apparatus (Preview/Board) survives clear_children. On the in-process runner the fixture already built the tree; clearing then rebuilding is harmless there and makes the live run order-independent.
- canvas-clear-layouts (clear_children)
- canvas-clear-drivers (clear_children)
- canvas-grid (add_module)
- canvas-layer (add_module)
- canvas-noise (add_module)
- canvas-mirror (add_module)
- canvas-artnet (add_module)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โฅ 8,333 / 797-10,526 | unlimited / unlimited | โ / unlimited |
desktop-windows |
โ / 805-4,566 | โ / unlimited | โ / unlimited |
esp32 |
โ / 4.5-640 | โ / 83KB-99KB | โ / 48KB-84KB |
esp32-eth |
โ / 10.7-10.8 | โ / 132KB | โ / 48KB-52KB |
esp32-eth-wifi |
โฅ 10.0 / 12.4 | โฅ 103KB / 93KB | โ / 48KB |
esp32p4-eth |
โ / 739-880 | โ / 33206KB-33218KB | โ / 376KB |
esp32s3-n16r8 |
โ / 106-217 | โ / 8315KB-8321KB | โ / 104KB-108KB |
desktop-macos: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02 โ 2026-07-10desktop-windows: observed 2026-06-07 โ 2026-07-08esp32: observed 2026-06-02 โ 2026-07-22esp32-eth: observed 2026-06-02esp32-eth-wifi: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02esp32p4-eth: observed 2026-06-17 โ 2026-06-22esp32s3-n16r8: observed 2026-06-22
shrink-to-128x64 (set_control) ๐¶
Shrink to 128x64. Measured: tick/heap captured so the runner reports the realloc behaviour against each target's contract. (The old relative-to-baseline FPS bound was removed โ it compared against the runner's idle pre-scenario baseline, not the prior render step, so it false-failed on fast boards like the P4 where idle FPS dwarfs render FPS.)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โฅ 16,667 / 2,801-21,739 | unlimited / unlimited | โ / unlimited |
desktop-windows |
โ / 6,098-10,638 | โ / unlimited | โ / unlimited |
esp32 |
โ / 11.1-1,529 | โ / 63KB-105KB | โ / 17KB-84KB |
esp32-eth |
โ / 26.4-26.5 | โ / 114KB | โ / 48KB |
esp32-eth-wifi |
โฅ 22.2 / 31.8 | โฅ 83KB / 75KB | โ / 24KB |
esp32p4-eth |
โ / 1,527-1,739 | โ / 33214KB-33226KB | โ / 376KB |
esp32s3-n16r8 |
โ / 415-787 | โ / 8324KB-8331KB | โ / 100KB-112KB |
desktop-macos: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02 โ 2026-07-10desktop-windows: observed 2026-06-07 โ 2026-07-08esp32: observed 2026-06-02 โ 2026-07-22esp32-eth: observed 2026-06-02esp32-eth-wifi: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02esp32p4-eth: observed 2026-06-17 โ 2026-06-22esp32s3-n16r8: observed 2026-06-22
grow-to-128x128 (set_control) ๐¶
Grow back to 128x128. Measured: confirms the heap can return to the heavy baseline after a shrink.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โฅ 8,333 / 1,490-10,204 | unlimited / unlimited | โ / unlimited |
desktop-windows |
โ / 393-4,608 | โ / unlimited | โ / unlimited |
esp32 |
โ / 4.0-839 | โ / 83KB-100KB | โ / 52KB-84KB |
esp32-eth |
โ / 10.4 | โ / 132KB | โ / 48KB |
esp32-eth-wifi |
โฅ 10.0 / 12.2 | โฅ 103KB / 93KB | โ / 52KB |
esp32p4-eth |
โ / 762-875 | โ / 33206KB-33218KB | โ / 376KB |
esp32s3-n16r8 |
โ / 132-251 | โ / 8312KB-8322KB | โ / 100KB-112KB |
desktop-macos: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02 โ 2026-07-10desktop-windows: observed 2026-06-07 โ 2026-07-08esp32: observed 2026-06-02 โ 2026-07-22esp32-eth: observed 2026-06-02esp32-eth-wifi: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02esp32p4-eth: observed 2026-06-17 โ 2026-06-22esp32s3-n16r8: observed 2026-06-22
Layer¶
scenario_Layer_base_pipeline¶
test/scenarios/light/scenario_Layer_base_pipeline.json โ Core pipeline: build LayoutsโGridโLayerโRainbowEffectโDriversโNetworkSendDriver from scratch and verify each module wires correctly. Drives the bounded FPS check at the end so a render-path regression is caught.
Mode: construct ยท Also touches: GridLayout, RainbowEffect, Drivers, NetworkSendDriver
add-artnet (add_module) ๐¶
Add NetworkSendDriver and run the bounded FPS measurement (expected to stay at >=80% of the rated FPS for the 128x128 grid this scenario builds; min_pct needs a live baseline, so it gates only on hardware and is skipped with a WARN in the desktop runner).
Setup (preceding non-measured steps):
- add-layout-group (add_module) โ Create the top-level Layouts container.
- add-grid (add_module) โ Add a 128x128 GridLayout child to Layouts. Set explicitly (the module default is 16x16x1) so the tick is above the host's microsecond clock resolution โ a 16x16 grid renders in <1us on desktop, flooring tick to 0.
- add-layer (add_module) โ Add a top-level Layer wired to the Layouts container, RGB (3 channels per light).
- add-rainbow (add_module) โ Add RainbowEffect as the Layer's only effect.
- add-driver-group (add_module) โ Add a top-level Drivers container wired to the Layer's output buffer.
Bounds: - FPS โฅ 80% of baseline - FPS ร lights โฅ 294,912
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โฅ 20,000 / 2,924-โ | unlimited / unlimited | โ / unlimited |
desktop-windows |
โ / 1,068-8,475 | โ / unlimited | โ / unlimited |
desktop-macos: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02 โ 2026-07-10desktop-windows: observed 2026-06-07 โ 2026-07-08
scenario_Layer_memory_1to1¶
test/scenarios/light/scenario_Layer_memory_1to1.json โ Verify that an unshuffled 1:1 mapping (no modifier) uses no LUT and no driver buffer. Catches a regression where Layer would allocate a passthrough LUT for the identity case.
Mode: construct ยท Also touches: MappingLUT, BlendMap
add-artnet (add_module) ๐¶
Add NetworkSendDriver and run the bounded FPS measurement on the no-LUT path.
Setup (preceding non-measured steps):
- add-layout-group (add_module) โ Create the top-level Layouts container.
- add-grid (add_module) โ Add a 16x16 GridLayout.
- add-layer (add_module) โ Add a Layer wired to Layouts (RGB).
- add-rainbow (add_module) โ Add RainbowEffect as the Layer's effect.
- add-driver-group (add_module) โ Add a Drivers container wired to the Layer.
Bounds: - FPS โฅ 80% of baseline
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โฅ 20,000 / 12,500-โ | unlimited / unlimited | โ / unlimited |
desktop-windows |
โ / 142,857-1,000,000 | โ / unlimited | โ / unlimited |
desktop-macos: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02 โ 2026-06-05desktop-windows: observed 2026-06-07 โ 2026-07-08
scenario_modifier_chain¶
test/scenarios/light/scenario_modifier_chain.json โ Stack TWO modifiers on one Layer (Region then Multiply) and verify the chain composes live end-to-end โ the capability the old single-modifier engine couldn't do. Prepares its own canvas: Layout(Grid 32x32) + Layer + NoiseEffect + Region(0..50) + Multiply(2x), measures the composite, then adds a third (Checkerboard mask) and measures again, then removes the middle modifier and measures โ exercising add/remove on a multi-modifier chain. A broken fold (null buffer, wrong light count, crash on a disabled/removed stage) shows up as a failed measure. The fold composition + order semantics are pinned by unit_Layer_modifier_chain; this is the live end-to-end gate.
Mode: mutate ยท Also touches: RegionModifier, MultiplyModifier, CheckerboardModifier, RotateModifier, NoiseEffect, Layouts, GridLayout, Drivers, NetworkSendDriver
add-mask (add_module) ๐¶
Add a third modifier (Checkerboard mask) on top of the chain โ a 3-deep fold. Measure that the deeper chain still renders.
Setup (preceding non-measured steps):
- region-then-multiply (measure) โ Two stacked modifiers: Region(top-left quarter) then Multiply(2x mirror) compose into one mapping. Measure the live composite.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 7,519-200,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 50,000-83,333 | โ / unlimited | โ / unlimited |
desktop-macos: observed 2026-06-26 โ 2026-07-15desktop-windows: observed 2026-07-08
remove-middle (remove_module) ๐¶
Remove the middle modifier (Multiply) โ the chain re-folds with Region then Checkerboard, no stale state. Measure.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 6,536-55,556 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 17,241-23,810 | โ / unlimited | โ / unlimited |
desktop-macos: observed 2026-06-26 โ 2026-07-01desktop-windows: observed 2026-07-08
add-live-rotate (add_module) ๐¶
Add a DYNAMIC Rotate on top of the static chain โ its modifyLive runs the per-frame remap pass over the composed buffer. Verifies a static chain + a live modifier coexist (the buffer is remapped each frame on top of the baked Region/Checkerboard mapping) without a crash or null buffer.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 1,887-31,250 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 7,246-14,493 | โ / unlimited | โ / unlimited |
desktop-macos: observed 2026-06-26 โ 2026-07-15desktop-windows: observed 2026-07-08
scenario_modifier_swap¶
test/scenarios/light/scenario_modifier_swap.json โ Swap the Layer's modifier between Multiply and Checkerboard and verify the pipeline stays live across each replace. Prepares its own canvas (clear + rebuild) so it runs from any device state: one Layout(Grid 32x32) + one Layer + one effect + one modifier, then replace_module cycles the modifier MOD slot Multiply -> Checkerboard -> Multiply, measuring after each so a broken swap (null buffer / wrong light count) shows up. Exercises the modifier-replace path the UI's drag-replace uses.
Mode: mutate ยท Also touches: MultiplyModifier, CheckerboardModifier, NoiseEffect, Layouts, GridLayout, Drivers, NetworkSendDriver, PreviewDriver
multiply-1 (measure) ๐¶
Multiply modifier active โ pipeline live, LUT folds the grid.
Setup (preceding non-measured steps):
- shrink-w (set_control)
- shrink-h (set_control)
- clear-layers (clear_children)
- clear-layouts (clear_children)
- build-grid (add_module)
- build-layer (add_module)
- build-fx (add_module)
- build-mod (add_module)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 23,256-166,667 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 50,000-71,429 | โ / unlimited | โ / unlimited |
esp32 |
โ / 1,783-4,292 | โ / 119KB-145KB | โ / 108KB |
esp32-eth |
โ / 1,580-7,752 | โ / 172KB-225KB | โ / 76KB-108KB |
esp32p4-eth |
โ / 5,587-6,061 | โ / 33243KB | โ / 376KB |
esp32s3-n16r8 |
โ / 1,773-2,571 | โ / 8350KB | โ / 92KB |
desktop-macos: observed 2026-06-07 โ 2026-07-15desktop-windows: observed 2026-07-08esp32: observed 2026-06-25 โ 2026-07-22esp32-eth: observed 2026-06-07 โ 2026-06-08esp32p4-eth: observed 2026-06-25esp32s3-n16r8: observed 2026-06-25
checkerboard (measure) ๐¶
Checkerboard modifier active โ masks half the lights; pipeline stays live (driver buffer non-null).
Setup (preceding non-measured steps):
- swap-to-checker (replace_module)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 4,184-58,824 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 16,393-24,390 | โ / unlimited | โ / unlimited |
esp32 |
โ / 892-2,041 | โ / 119KB-145KB | โ / 108KB |
esp32-eth |
โ / 769-990 | โ / 170KB-225KB | โ / 76KB-108KB |
esp32p4-eth |
โ / 2,747-2,762 | โ / 33242KB | โ / 376KB |
esp32s3-n16r8 |
โ / 924-943 | โ / 8349KB | โ / 92KB |
desktop-macos: observed 2026-06-07 โ 2026-07-01desktop-windows: observed 2026-07-08esp32: observed 2026-06-25 โ 2026-07-22esp32-eth: observed 2026-06-07 โ 2026-06-08esp32p4-eth: observed 2026-06-25esp32s3-n16r8: observed 2026-06-25
multiply-2 (measure) ๐¶
Back to Multiply โ replace round-trips cleanly, pipeline live again.
Setup (preceding non-measured steps):
- swap-to-multiply (replace_module)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 10,101-166,667 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 45,455-76,923 | โ / unlimited | โ / unlimited |
esp32 |
โ / 2,079-4,348 | โ / 117KB-145KB | โ / 108KB |
esp32-eth |
โ / 1,587-2,278 | โ / 169KB-225KB | โ / 76KB-108KB |
esp32p4-eth |
โ / 6,329-6,410 | โ / 33243KB | โ / 376KB |
esp32s3-n16r8 |
โ / 2,146-2,604 | โ / 8349KB-8350KB | โ / 92KB |
desktop-macos: observed 2026-06-07 โ 2026-07-01desktop-windows: observed 2026-07-08esp32: observed 2026-06-25 โ 2026-07-22esp32-eth: observed 2026-06-07 โ 2026-06-08esp32p4-eth: observed 2026-06-25esp32s3-n16r8: observed 2026-06-25
scenario_perf_full¶
test/scenarios/light/scenario_perf_full.json โ Comprehensive incremental performance check (the SLOW, on-device companion to scenario_perf_light). Mutate mode + canvas-preparing: clear_children whatever the device already had (pre-wired apparatus like PreviewDriver/Board survives โ clear_children only drops user-editable children), rebuild a known minimal tree, then add one subsystem at a time โ audio, device discovery, a modifier, then EVERY output driver this board has (each optional + capped to 64 output LEDs so its per-frame cost is comparable, not its transmit-all-16K time), then a network driver โ measuring the tick/heap delta after each so each subsystem's cost is isolated. Then sweep the grid 16ยฒโ32ยฒโ64ยฒโ128ยฒ (16K) for both a LIGHT effect (Spiral) and a HEAVY one (Noise) to bracket the compute range across sizes. LED drivers are platform-gated (RMT on classic/S3, LCD on S3, Parlio on P4; none on desktop) so each driver step is optional:true and skipped where absent โ the all-drivers comparison is assembled across boards (S3 gives RMT vs LCD, P4 gives RMT vs Parlio). Subsumes the old scenario_Layer_buildup (incremental module cost), scenario_GridLayout_grid_sizes (grid sweep), and scenario_AllEffects_grid_sizes (per-effect size sweep, here reduced to a light/heavy bracket). Runs minutes on a device; not a per-commit gate.
Mode: mutate ยท Also touches: Layouts, GridLayout, Drivers, PreviewDriver, NetworkSendDriver, RmtLedDriver, MultiPinLedDriver, ParlioLedDriver, MultiplyModifier, SpiralEffect, NoiseEffect
measure-minimal (measure) ๐¶
Bare minimum at 16ยฒ: Grid + Layer + Spiral, no output driver, audio/discovery still on as the device ships. The floor for the subsystem-cost diffs below.
Setup (preceding non-measured steps):
- clear-layers (clear_children) โ Start clean: drop whatever effects/modifiers/layouts/drivers the device had (pre-wired Preview survives).
- clear-layouts (clear_children)
- clear-drivers (clear_children)
- build-grid (add_module)
- build-layer (add_module)
- build-fx (add_module)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 40,000-โ | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 200,000-333,333 | โ / unlimited | โ / unlimited |
esp32 |
โ / 2,874-8,929 | โ / 119KB-147KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 14,925-17,544 | โ / 33226KB-33245KB | โ / 376KB |
esp32s3-n16r8 |
โ / 5,376-9,009 | โ / 8340KB-8352KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-04desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-no-audio (measure) ๐¶
Setup (preceding non-measured steps):
- disable-audio (set_control) โ Disable the mic (stops I2S sampling in tick()). Diff vs measure-minimal = the audio subsystem's per-tick cost (device only; optional). Targets it by its runtime name 'Audio' (displayNameFor strips the 'Service' suffix).
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 58,824-โ | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 200,000-333,333 | โ / unlimited | โ / unlimited |
esp32 |
โ / 3,610-9,901 | โ / 120KB-147KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 18,182-18,868 | โ / 33228KB-33245KB | โ / 376KB |
esp32s3-n16r8 |
โ / 8,065-9,901 | โ / 8338KB-8352KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-04desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-quiet (measure) ๐¶
Quiet baseline: render-only, audio + discovery off. The cleanest render floor; the per-driver costs below diff against this.
Setup (preceding non-measured steps):
- disable-devices (set_control) โ Disable the Devices module (stops the blocking HTTP discovery sweep in tick1s()). Diff = the discovery cost (device only; optional).
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 50,000-โ | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 250,000-333,333 | โ / unlimited | โ / unlimited |
esp32 |
โ / 3,257-9,901 | โ / 120KB-146KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 17,544-18,519 | โ / 33226KB-33245KB | โ / 376KB |
esp32s3-n16r8 |
โ / 7,752-9,901 | โ / 8337KB-8352KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-04desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-modifier (measure) ๐¶
Setup (preceding non-measured steps):
- add-modifier (add_module) โ +MultiplyModifier: allocates the mapping LUT. Diff = modifier + LUT cost.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 100,000-โ | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 1,000,000 | โ / unlimited | โ / unlimited |
esp32 |
โ / 2,786-4,065 | โ / 120KB-145KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 8,772-10,638 | โ / 33224KB-33243KB | โ / 376KB |
esp32s3-n16r8 |
โ / 3,413-4,237 | โ / 8336KB-8350KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-04desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-preview (measure) ๐¶
Setup (preceding non-measured steps):
- remove-modifier (remove_module) โ Drop the modifier so the driver-cost measurements below are on the plain 1:1 pipeline (drivers, not the LUT, are what we compare here).
- add-preview (add_module) โ +PreviewDriver. Optional: on a device the pre-wired Preview survives clear_children so it's already present (the add is skipped); on the in-process desktop runner there's no apparatus, so this adds it. Either way the next measure includes Preview.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 66,667-โ | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 200,000-333,333 | โ / unlimited | โ / unlimited |
esp32 |
โ / 2,740-9,524 | โ / 120KB-147KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 15,873-18,182 | โ / 33228KB-33245KB | โ / 376KB |
esp32s3-n16r8 |
โ / 8,065-9,434 | โ / 8335KB-8352KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-15desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-network (measure) ๐¶
Setup (preceding non-measured steps):
- add-network-driver (add_module) โ +NetworkSendDriver (ArtNet/DDP โ works on every platform). Diff = the network output cost (capped by the 16ยฒ grid here).
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 76,923-โ | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 111,111-333,333 | โ / unlimited | โ / unlimited |
esp32 |
โ / 2,513-7,194 | โ / 117KB-145KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 14,493-17,544 | โ / 33226KB-33244KB | โ / 376KB |
esp32s3-n16r8 |
โ / 6,452-8,065 | โ / 8334KB-8351KB | โ / 84KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-15desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-26
measure-rmt (measure) ๐¶
Setup (preceding non-measured steps):
- remove-network-driver (remove_module)
- add-rmt-driver (add_module) โ +RmtLedDriver capped to 64 output LEDs (one pin, ledsPerPin=64). Optional โ classic + S3. Diff = the RMT per-frame cost at a fixed 64-LED output.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 32,258-โ | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 250,000-333,333 | โ / unlimited | โ / unlimited |
esp32 |
โ / 3,226-9,174 | โ / 91KB-122KB | โ / 76KB-108KB |
esp32p4-eth |
โ / 15,873-17,857 | โ / 33200KB-33221KB | โ / 376KB |
esp32s3-n16r8 |
โ / 7,194-9,346 | โ / 8307KB-8328KB | โ / 84KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-01desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-26
measure-i80 (measure) ๐¶
The i80 per-frame cost. Only meaningful where add-i80-driver's pins are valid (S3; desktop is inert but exercises the code path) โ the classic/P4 blocks were removed: they recorded a driver that had failed to init, so the tiny tick was the bail-out path, not an encode.
Setup (preceding non-measured steps):
- remove-rmt-driver (remove_module)
- add-i80-driver (add_module) โ +MultiPinLedDriver capped to 64 LEDs on lane 0 (i80 needs all 8 data pins; unused lanes get 0 LEDs). S3 ONLY โ the pins below are the S3's clean set. MultiPinLedDriver also registers on the classic ESP32 (I2S backend) and the P4 (LCD_CAM), but NO 8-pin set is safe on all three (only GPIO 21 is common: the classic's 6-11 are the SPI flash bus, the P4 exposes a different range), so one shared step cannot measure them. On a non-S3 target the driver fails to init and the measure below is meaningless โ the observed blocks are therefore S3/desktop only. Diff = the i80 per-frame cost.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 76,923-โ | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 200,000-333,333 | โ / unlimited | โ / unlimited |
esp32 |
โ / 3,215-3,226 | โ / 116KB-118KB | โ / 100KB-108KB |
esp32s3-n16r8 |
โ / 7,042-9,259 | โ / 8333KB-8352KB | โ / 88KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-01desktop-windows: observed 2026-07-08esp32: observed 2026-07-22esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-parlio (measure) ๐¶
Setup (preceding non-measured steps):
- remove-i80-driver (remove_module)
- add-parlio-driver (add_module) โ +ParlioLedDriver capped to 64 LEDs on lane 0. Optional โ P4 only. Diff = the Parlio per-frame cost.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 90,909-โ | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 83,333-333,333 | โ / unlimited | โ / unlimited |
esp32 |
โ / 3,195-9,901 | โ / 119KB-147KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 15,873-17,857 | โ / 33225KB-33245KB | โ / 376KB |
esp32s3-n16r8 |
โ / 7,692-9,434 | โ / 8338KB-8352KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-01desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-light-16 (measure) ๐¶
Setup (preceding non-measured steps):
- remove-parlio-driver (remove_module)
- add-preview-for-sweep (add_module) โ Re-add PreviewDriver as the output for the grid sweep (the per-driver adds above each removed their driver; Preview is the cheap, every-board output for a pure-render size curve).
- light-16-w (set_control) โ Grid sweep, LIGHT effect (Spiral is already FX).
- light-16-h (set_control)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 100,000-โ | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 200,000-333,333 | โ / unlimited | โ / unlimited |
esp32 |
โ / 3,356-9,804 | โ / 115KB-147KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 15,385-18,868 | โ / 33226KB-33245KB | โ / 376KB |
esp32s3-n16r8 |
โ / 8,403-9,901 | โ / 8336KB-8352KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-01desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-light-32 (measure) ๐¶
Setup (preceding non-measured steps):
- light-32-w (set_control)
- light-32-h (set_control)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 31,250-1,000,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 7,874-83,333 | โ / unlimited | โ / unlimited |
esp32 |
โ / 1,418-3,367 | โ / 114KB-144KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 7,246-7,576 | โ / 33225KB-33243KB | โ / 376KB |
esp32s3-n16r8 |
โ / 3,049-3,597 | โ / 8331KB-8350KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-10desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-light-64 (measure) ๐¶
Setup (preceding non-measured steps):
- light-64-w (set_control)
- light-64-h (set_control)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 8,000-333,333 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 11,494-20,408 | โ / unlimited | โ / unlimited |
esp32 |
โ / 356-928 | โ / 97KB-135KB | โ / 84KB-108KB |
esp32p4-eth |
โ / 2,008-2,232 | โ / 33218KB-33234KB | โ / 376KB |
esp32s3-n16r8 |
โ / 894-1,011 | โ / 8312KB-8341KB | โ / 88KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-10desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-light-128 (measure) ๐¶
Setup (preceding non-measured steps):
- light-128-w (set_control)
- light-128-h (set_control)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 509-62,500 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 3,623-5,102 | โ / unlimited | โ / unlimited |
esp32 |
โ / 224-441 | โ / 60KB-99KB | โ / 50KB-62KB |
esp32p4-eth |
โ / 515-573 | โ / 33182KB-33198KB | โ / 376KB |
esp32s3-n16r8 |
โ / 114-134 | โ / 8291KB-8305KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-16desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-heavy-16 (measure) ๐¶
Setup (preceding non-measured steps):
- swap-heavy (replace_module) โ Swap to the HEAVY effect (Noise) and repeat the sweep โ the upper bracket of per-pixel compute.
- heavy-16-w (set_control)
- heavy-16-h (set_control)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 16,949-333,333 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 76,923-111,111 | โ / unlimited | โ / unlimited |
esp32 |
โ / 990-2,703 | โ / 120KB-147KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 2,865-3,367 | โ / 33229KB-33245KB | โ / 376KB |
esp32s3-n16r8 |
โ / 1,100-1,361 | โ / 8342KB-8352KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-16desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-26
measure-heavy-32 (measure) ๐¶
Setup (preceding non-measured steps):
- heavy-32-w (set_control)
- heavy-32-h (set_control)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 10,204-71,429 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 19,231-27,778 | โ / unlimited | โ / unlimited |
esp32 |
โ / 306-781 | โ / 113KB-144KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 799-898 | โ / 33227KB-33243KB | โ / 376KB |
esp32s3-n16r8 |
โ / 290-356 | โ / 8339KB-8350KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-10desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-heavy-64 (measure) ๐¶
Setup (preceding non-measured steps):
- heavy-64-w (set_control)
- heavy-64-h (set_control)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 1,984-16,129 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 3,846-6,897 | โ / unlimited | โ / unlimited |
esp32 |
โ / 73.8-204 | โ / 90KB-135KB | โ / 80KB-108KB |
esp32p4-eth |
โ / 196-229 | โ / 33218KB-33234KB | โ / 376KB |
esp32s3-n16r8 |
โ / 85.2-90.3 | โ / 8330KB-8341KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-22desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-heavy-128 (measure) ๐¶
Setup (preceding non-measured steps):
- heavy-128-w (set_control)
- heavy-128-h (set_control)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 111-3,690 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 1,175-1,689 | โ / unlimited | โ / unlimited |
esp32 |
โ / 16.0-137 | โ / 60KB-99KB | โ / 50KB-62KB |
esp32p4-eth |
โ / 53.7-57.4 | โ / 33182KB-33198KB | โ / 376KB |
esp32s3-n16r8 |
โ / 19.2-20.8 | โ / 8293KB-8305KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-13desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-mod-16 (measure) ๐¶
Setup (preceding non-measured steps):
- add-modifier-for-sweep (add_module) โ Re-add the MultiplyModifier on the HEAVY effect and sweep grid sizes โ the diff vs the matching measure-heavy-N step (no modifier) is the modifier's PER-FRAME cost at each grid, answering whether the ~180ยตs seen at 16ยฒ scales with pixel count or is fixed overhead.
- mod-16-w (set_control)
- mod-16-h (set_control)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 125,000-1,000,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 200,000-500,000 | โ / unlimited | โ / unlimited |
esp32 |
โ / 2,020-4,049 | โ / 116KB-145KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 5,263-6,494 | โ / 33224KB-33243KB | โ / 376KB |
esp32s3-n16r8 |
โ / 2,193-2,618 | โ / 8340KB-8350KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-10desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-mod-32 (measure) ๐¶
Setup (preceding non-measured steps):
- mod-32-w (set_control)
- mod-32-h (set_control)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 5,882-333,333 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 62,500-111,111 | โ / unlimited | โ / unlimited |
esp32 |
โ / 547-1,534 | โ / 101KB-140KB | โ / 96KB-108KB |
esp32p4-eth |
โ / 1,631-1,876 | โ / 33218KB-33237KB | โ / 376KB |
esp32s3-n16r8 |
โ / 600-710 | โ / 8329KB-8344KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-06-25desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-26
measure-mod-64 (measure) ๐¶
Setup (preceding non-measured steps):
- mod-64-w (set_control)
- mod-64-h (set_control)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 4,184-71,429 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 18,182-27,778 | โ / unlimited | โ / unlimited |
esp32 |
โ / 144-416 | โ / 95KB-122KB | โ / 72KB-100KB |
esp32p4-eth |
โ / 438-486 | โ / 33194KB-33210KB | โ / 376KB |
esp32s3-n16r8 |
โ / 119-162 | โ / 8307KB-8317KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-04desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-mod-128 (measure) ๐¶
Setup (preceding non-measured steps):
- mod-128-w (set_control)
- mod-128-h (set_control)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 1,300-16,393 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 990-6,897 | โ / unlimited | โ / unlimited |
esp32 |
โ / 29.8-191 | โ / 36KB-63KB | โ / 24KB-48KB |
esp32p4-eth |
โ / 86.3-102 | โ / 33089KB-33105KB | โ / 376KB |
esp32s3-n16r8 |
โ / 16.8-35.6 | โ / 8202KB-8212KB | โ / 92KB-112KB |
desktop-macos: observed 2026-06-17 โ 2026-07-04desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
scenario_perf_light¶
test/scenarios/light/scenario_perf_light.json โ Fast incremental performance check: start from the bare minimum render pipeline and add one thing at a time, measuring the tick/heap delta each step, so a regression shows up as a per-step jump. The LIGHT companion to scenario_perf_full โ it stays small (โค64ยฒ) and driver-free so it runs in seconds. Mutate mode + canvas-preparing: the steps clear_children whatever Layouts/Layers/Drivers the device already had (the pre-wired apparatus like PreviewDriver/Board survives โ clear_children only drops user-editable children) and rebuild a known tree, so it runs from any starting state and always measures the same minimal pipeline. Order: (1) minimal = Grid(16ยฒ)+Layer+a LIGHT effect (Spiral, a light effect), no modifier/driver/audio/discovery; (2) +MultiplyModifier (adds the mapping LUT โ the heavy memory path); (3) +PreviewDriver; (4) swap to a HEAVY effect (Noise) to bracket the compute range; (5) grid 16ยฒโ32ยฒโ64ยฒ to show the size scaling. Full 128ยฒ/16K sweep, real LED/network drivers, audio+discovery cost: see scenario_perf_full.
Mode: mutate ยท Also touches: Layouts, GridLayout, Drivers, PreviewDriver, SpiralEffect, NoiseEffect, MultiplyModifier
measure-minimal (measure) ๐¶
Bare minimum: Grid(16ยฒ) + Layer + Spiral (light effect). No modifier, no driver. The render floor everything else is measured against.
Setup (preceding non-measured steps):
- disable-audio (set_control) โ Quiet I2S sampling so it can't pollute the tick (optional โ device only). Targets the mic by its runtime name 'Audio' (displayNameFor strips the 'Service' suffix; the catalog adds it with id 'Audio').
- disable-devices (set_control) โ Stop the blocking HTTP discovery sweep (optional โ device only).
- clear-layers (clear_children) โ Drop whatever effects/modifiers the device had โ start clean.
- clear-layouts (clear_children)
- clear-drivers (clear_children) โ Drop any output driver; the pre-wired PreviewDriver is non-deletable and survives.
- build-grid (add_module)
- build-layer (add_module)
- build-fx (add_module)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 17,241-โ | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 166,667-333,333 | โ / unlimited | โ / unlimited |
esp32 |
โ / 2,667-8,850 | โ / 119KB-147KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 13,699-18,519 | โ / 33228KB-33246KB | โ / 376KB |
esp32s3-n16r8 |
โ / 5,814-8,850 | โ / 8316KB-8347KB | โ / 80KB-104KB |
desktop-macos: observed 2026-06-17 โ 2026-07-09desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-with-modifier (measure) ๐¶
Cost of the modifier + LUT over the minimal pipeline. Heap delta vs measure-minimal is the LUT allocation.
Setup (preceding non-measured steps):
- add-modifier (add_module) โ +MultiplyModifier: allocates the mapping LUT (the heavy memory path vs the 1:1 no-LUT case).
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 55,556-โ | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 500,000-1,000,000 | โ / unlimited | โ / unlimited |
esp32 |
โ / 3,077-9,709 | โ / 115KB-147KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 8,621-10,309 | โ / 33226KB-33243KB | โ / 376KB |
esp32s3-n16r8 |
โ / 3,195-4,032 | โ / 8330KB-8345KB | โ / 92KB-100KB |
desktop-macos: observed 2026-06-17 โ 2026-07-09desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-with-preview (measure) ๐¶
PreviewDriver is the pre-wired apparatus โ it survives clear_children and is already attached, so the measures above already include it (no add step needed; adding a second Preview is rejected). This is a stable repeat of the effect+modifier config for run-to-run variance.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 142,857-โ | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 1,000,000 | โ / unlimited | โ / unlimited |
esp32 |
โ / 3,067-9,804 | โ / 117KB-146KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 10,417-10,753 | โ / 33226KB-33243KB | โ / 376KB |
esp32s3-n16r8 |
โ / 3,802-4,274 | โ / 8330KB-8345KB | โ / 84KB-100KB |
desktop-macos: observed 2026-06-17 โ 2026-07-09desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-heavy-16 (measure) ๐¶
Setup (preceding non-measured steps):
- swap-heavy-fx (replace_module) โ Swap the light effect for a HEAVY one (Noise โ simplex per pixel) to bracket the compute range at the same 16ยฒ.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 90,909-1,000,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 333,333-500,000 | โ / unlimited | โ / unlimited |
esp32 |
โ / 1,142-4,184 | โ / 117KB-146KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 5,556-6,494 | โ / 33224KB-33243KB | โ / 376KB |
esp32s3-n16r8 |
โ / 2,299-2,506 | โ / 8332KB-8342KB | โ / 88KB-100KB |
desktop-macos: observed 2026-06-17 โ 2026-07-16desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-heavy-32 (measure) ๐¶
Setup (preceding non-measured steps):
- grid-32-w (set_control)
- grid-32-h (set_control)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 26,316-333,333 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 62,500-111,111 | โ / unlimited | โ / unlimited |
esp32 |
โ / 265-1,613 | โ / 112KB-144KB | โ / 100KB-108KB |
esp32p4-eth |
โ / 1,603-1,880 | โ / 33221KB-33237KB | โ / 376KB |
esp32s3-n16r8 |
โ / 562-715 | โ / 8328KB-8333KB | โ / 84KB-104KB |
desktop-macos: observed 2026-06-17 โ 2026-07-01desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
measure-heavy-64 (measure) ๐¶
Setup (preceding non-measured steps):
- grid-64-w (set_control)
- grid-64-h (set_control)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 10,989-71,429 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 21,739-28,571 | โ / unlimited | โ / unlimited |
esp32 |
โ / 77.1-459 | โ / 94KB-135KB | โ / 72KB-108KB |
esp32p4-eth |
โ / 411-491 | โ / 33195KB-33210KB | โ / 376KB |
esp32s3-n16r8 |
โ / 129-162 | โ / 8302KB-8317KB | โ / 92KB-108KB |
desktop-macos: observed 2026-06-17 โ 2026-07-10desktop-windows: observed 2026-07-08esp32: observed 2026-06-17 โ 2026-07-22esp32p4-eth: observed 2026-06-17 โ 2026-06-25esp32s3-n16r8: observed 2026-06-17 โ 2026-06-25
Layers¶
scenario_Layers_composition¶
test/scenarios/light/scenario_Layers_composition.json โ Multi-layer composition end-to-end: LayoutsโGrid, TWO Layers under one Layers container (bottom Spiral, top Rainbow), DriversโNetworkSendDriver. Proves the Drivers composite loop builds, allocates its output buffer, blends both enabled layers and feeds the result to the driver without crashing, and gates the bounded FPS so the N-pass composite cost is tracked. The exact alpha/additive blend math and the disable-drops-to-single-layer path are pinned by the unit tests (unit_BlendMap, unit_Layers_container); construct-mode set_control can't apply controls (built post-scheduler), so this scenario uses each Layer's default blend (alpha, full opacity) and asserts wired liveness + tick, not per-byte blend output.
Mode: construct ยท Also touches: Layer, GridLayout, RainbowEffect, SpiralEffect, Drivers, NetworkSendDriver
add-artnet (add_module) ๐¶
Add NetworkSendDriver and run the bounded FPS measurement over the two-layer composite (min_pct gates on hardware; skipped with a WARN in the desktop runner).
Setup (preceding non-measured steps):
- add-layout-group (add_module) โ Top-level Layouts container.
- add-grid (add_module) โ 128x128 GridLayout under Layouts (above host clock resolution so the composite tick is measurable).
- add-layers-group (add_module) โ Top-level Layers container โ the multi-layer composition host.
- add-bottom-layer (add_module) โ Bottom Layer (composited first โ clears + overwrites the output buffer). RGB.
- add-bottom-effect (add_module) โ A Spiral base as the bottom layer's effect.
- add-top-layer (add_module) โ Top Layer (composited second โ blends onto the bottom with its default blend). RGB.
- add-top-effect (add_module) โ Rainbow as the top layer's effect โ composited over the Spiral base.
- add-driver-group (add_module) โ Top-level Drivers container wired to the Layers container (composites all enabled layers into its output buffer).
Bounds: - FPS โฅ 80% of baseline - FPS ร lights โฅ 294,912
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 1,100-19,231 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 267-1,821 | โ / unlimited | โ / unlimited |
desktop-macos: observed 2026-06-25 โ 2026-07-10desktop-windows: observed 2026-07-08
Layouts¶
scenario_Layouts_mutation¶
test/scenarios/light/scenario_Layouts_mutation.json โ Tree mutation on the Layouts container while the pipeline runs: add a second layout (multiple layouts under one Layouts), replace a layout with a different type, and remove a layout. The check is that each mutation leaves the pipeline RENDERING โ Layer + Drivers re-wire via prepareTree and the buffer stays non-null and non-zero. Mirrors the HTTP add/replace/delete handlers; exercises the runner's add_module / replace_module / remove_module ops. NOTE: the Layer renders a dense bounding-box buffer sized by the layouts' coordinate EXTENT, not the summed light count โ layouts that overlap in coordinate space share voxels (two 64x64 grids both occupy x,y in 0..63). There are no per-layout coordinate offsets, so multiple layouts share the same coordinate box; these steps assert liveness, not buffer-size arithmetic. Grids are 64x64 so the tick stays above the host's microsecond clock at every step.
Mode: mutate ยท Also touches: GridLayout, SphereLayout, Layer, RainbowEffect, Drivers, NetworkSendDriver
measure-one-layout (measure) ๐¶
Baseline: a single 64x64 grid layout drives the pipeline.
Bounds: - FPS โฅ 1 (absolute)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 11,905-125,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 16,667-37,037 | โ / unlimited | โ / unlimited |
esp32 |
โ / 31,250-32,258 | โ / 127KB-129KB | โ / 108KB |
esp32-eth |
โ / 41,667 | โ / 224KB | โ / 108KB |
desktop-macos: observed 2026-06-05 โ 2026-07-15desktop-windows: observed 2026-06-07 โ 2026-07-08esp32: observed 2026-07-22esp32-eth: observed 2026-06-08
measure-two-layouts (measure) ๐¶
Pipeline still renders with two layouts wired (buffer non-null, fps measurable).
Setup (preceding non-measured steps):
- add-second-layout (add_module) โ Add a SECOND layout (a 64x64 grid) under Layouts โ two layouts now live under one container. prepareTree re-runs; the pipeline must still render. (Both grids share the 0..63 coordinate box, so the Layer buffer stays 64x64 โ see the scenario NOTE.)
Bounds: - FPS โฅ 1 (absolute)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 3,953-111,111 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 5,405-23,810 | โ / unlimited | โ / unlimited |
esp32 |
โ / 23,810-27,778 | โ / 127KB-129KB | โ / 108KB |
esp32-eth |
โ / 37,037 | โ / 223KB | โ / 108KB |
desktop-macos: observed 2026-06-05 โ 2026-06-27desktop-windows: observed 2026-06-07 โ 2026-07-08esp32: observed 2026-07-22esp32-eth: observed 2026-06-08
measure-after-replace (measure) ๐¶
Pipeline still renders after replacing a grid with a sphere (different layout type, same slot) โ buffer re-wires without crashing.
Setup (preceding non-measured steps):
- replace-second-layout (replace_module) โ Replace the second grid with a SphereLayout (different type, same slot). The first grid is untouched; the pipeline re-wires to the new layout's light count.
Bounds: - FPS โฅ 1 (absolute)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 1,957-100,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 1,880-9,009 | โ / unlimited | โ / unlimited |
esp32 |
โ / 27,027-29,412 | โ / 130KB-131KB | โ / 108KB |
esp32-eth |
โ / 38,462 | โ / 223KB | โ / 108KB |
desktop-macos: observed 2026-06-05 โ 2026-06-27desktop-windows: observed 2026-06-07 โ 2026-07-08esp32: observed 2026-07-22esp32-eth: observed 2026-06-08
measure-after-remove (measure) ๐¶
Pipeline renders with the single remaining grid, same as the baseline.
Setup (preceding non-measured steps):
- remove-second-layout (remove_module) โ Remove the sphere โ back to a single grid layout. Layer/Drivers shrink their buffers via prepareTree.
Bounds: - FPS โฅ 1 (absolute)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 6,623-125,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 5,435-38,462 | โ / unlimited | โ / unlimited |
esp32 |
โ / 27,778-30,303 | โ / 128KB-130KB | โ / 108KB |
esp32-eth |
โ / 41,667 | โ / 224KB | โ / 108KB |
desktop-macos: observed 2026-06-05 โ 2026-07-01desktop-windows: observed 2026-06-07 โ 2026-07-08esp32: observed 2026-07-22esp32-eth: observed 2026-06-08
MoonLiveEffect¶
scenario_MoonLiveEffect_controls¶
test/scenarios/light/scenario_MoonLiveEffect_controls.json โ Exercise MoonLive Stage-1 CONTROLS end-to-end as a wired module. A script declares a control (uint8_t speed = 7; // @control 0..15) and uses it (setRGB(speed, ...)); the engine surfaces the control, the binding creates a real uint8 MoonModule control bound to the live control-values arena slot. The scenario: add the effect with a control script (the control appears, renders), change the CONTROL value live (a slider move โ must NOT recompile; the arena byte updates and the next tick reads it), edit the SOURCE to add a second control (recompile re-derives the set, existing slider value preserved by the stable-address grow-only arena), edit the source to remove a control (the orphaned value drops), push a broken script (compile fails, renders dark, status shows the diagnostic, no crash), recover, and remove + re-add (resource teardown + re-acquire). A crash in the LoadCtrl codegen, a dangling arena pointer across a recompile, or a value change that wrongly triggers a recompile all show up as a failed measure or a tick spike. The codegen + live-read contract is pinned by unit_moonlive_ir / unit_moonlive_compiler; this is the wired-module gate.
Mode: mutate ยท Also touches: Layouts, GridLayout, Layers, Layer, Drivers, NetworkSendDriver
add-control-script (add_module) ๐¶
Add a MoonLiveEffect whose source declares a speed control and uses it. The control appears bound to the arena slot (seeded to its default 7); the wired effect renders one pixel.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 250,000-โ | โ / unlimited | โ / unlimited |
desktop-macos: observed 2026-06-28 โ 2026-07-01
set-source-with-control (set_control) ๐¶
Edit the source to the control script. A source edit recompiles (affectsPrepare gates on source); the engine derives the speed control and the binding surfaces it.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 1,000,000-โ | โ / unlimited | โ / unlimited |
desktop-macos: observed 2026-06-28 โ 2026-07-01
change-control-live (set_control) ๐¶
Change the speed control value (a slider move). This must NOT recompile โ affectsPrepare returns false for a scripted control; the arena byte updates and the next render tick reads it. Tick stays cheap (a recompile would spike it).
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 1,000,000-โ | โ / unlimited | โ / unlimited |
desktop-macos: observed 2026-06-28 โ 2026-07-01
edit-source-two-controls (set_control) ๐¶
Edit the source to add a second control. The recompile re-derives the control set; the stable-address grow-only arena keeps speed's live value while seeding the new slot.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 1,000,000-โ | โ / unlimited | โ / unlimited |
desktop-macos: observed 2026-06-28 โ 2026-07-01
edit-source-shrink-to-one-control (set_control) ๐¶
Edit the source back to a single control. The control set shrinks 2 -> 1: speed stays bound (its live value kept), the removed hue's value is dropped, and the value change path is exercised without an unexpected recompile crash.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 1,000,000-โ | โ / unlimited | โ / unlimited |
desktop-macos: observed 2026-06-28 โ 2026-07-01
edit-source-broken (set_control) ๐¶
Push a broken script. Compile fails, the previous code is freed, the effect renders dark and the parse error surfaces in status โ no crash.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 125,000-โ | โ / unlimited | โ / unlimited |
desktop-macos: observed 2026-06-28 โ 2026-07-01
edit-source-recover (set_control) ๐¶
Recover with a valid control script โ the effect compiles and renders again.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 1,000,000-โ | โ / unlimited | โ / unlimited |
desktop-macos: observed 2026-06-28 โ 2026-07-01
re-add-control-effect (add_module) ๐¶
Re-add a fresh effect after the remove โ exec memory + control arena re-acquired clean (it renders its default fill on add). Control re-acquisition itself is proven by the add-control-script step at the top: a freshly-added effect compiling a control source surfaces + seeds its control; construct-mode set_control can't apply a dynamically-added scripted control as the final asserted render, so the gate here is the bare re-add's liveness.
Setup (preceding non-measured steps):
- remove-control-effect (remove_module) โ Remove the effect โ the engine releases its exec block AND its control arena (release).
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 500,000-โ | โ / unlimited | โ / unlimited |
desktop-macos: observed 2026-06-28 โ 2026-07-01
scenario_MoonLiveEffect_livescript¶
test/scenarios/light/scenario_MoonLiveEffect_livescript.json โ Exercise a scripted MoonLiveEffect as a wired MoonModule end-to-end โ the integration layer the unit tests can't reach. The effect compiles its source text to native code on-device and renders it into the Layer buffer each tick. Prepares its own canvas: Layout(Grid 16x16) + Layer + MoonLiveEffect, measures the default compile, then edits source live (a new fill colour recompiles and keeps rendering), pushes a BROKEN script (compile fails, the previous code is freed, the effect renders dark and the parse error surfaces in status, no crash), recovers with a valid script, and finally removes + re-adds the effect (add/remove robustness in any order). A crash in the JIT/emit path, a failed recompile that wedges the tick, or a buffer overrun on an odd grid all show up as a failed measure. The compiler + emit golden bytes are pinned by unit_moonlive_compiler / unit_moonlive_fill; this is the live wired-module gate.
Mode: mutate ยท Also touches: Layouts, GridLayout, Layers, Layer, Drivers, NetworkSendDriver
add-moonlive (add_module) ๐¶
Add a MoonLiveEffect to the Layer. Its default source (random pixels) compiles on-device to native code; measure that the wired effect renders.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 2,545-โ | โ / unlimited | โ / unlimited |
esp32p4-eth |
โ / 88.6 | โ / 33211KB | โ / 376KB |
esp32s3-n16r8 |
โ / 249 | โ / 8341KB | โ / 104KB |
desktop-macos: observed 2026-06-26 โ 2026-06-27esp32p4-eth: observed 2026-06-27esp32s3-n16r8: observed 2026-06-27
edit-source-red (set_control) ๐¶
Live-edit the script source to a new colour. A source edit triggers a recompile (affectsPrepare gates on source); the new native code swaps in and keeps rendering.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 2,513-โ | โ / unlimited | โ / unlimited |
esp32p4-eth |
โ / 98.4 | โ / 33213KB | โ / 376KB |
esp32s3-n16r8 |
โ / 225 | โ / 8341KB | โ / 104KB |
desktop-macos: observed 2026-06-26 โ 2026-06-27esp32p4-eth: observed 2026-06-27esp32s3-n16r8: observed 2026-06-27
edit-source-broken (set_control) ๐¶
Push a script that fails to parse. The compile fails, the engine reports the diagnostic in the module status and renders dark, but the device keeps running (robust, no reboot) โ the script-editor failure path. The measure passes because the pipeline still ticks.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 3,745-โ | โ / unlimited | โ / unlimited |
esp32p4-eth |
โ / 94.6 | โ / 33209KB | โ / 376KB |
esp32s3-n16r8 |
โ / 229 | โ / 8340KB | โ / 104KB |
desktop-macos: observed 2026-06-26 โ 2026-06-27esp32p4-eth: observed 2026-06-27esp32s3-n16r8: observed 2026-06-27
edit-source-recover (set_control) ๐¶
Push a valid script again. The engine recompiles cleanly and rendering resumes โ a broken edit is fully recoverable.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 2,415-โ | โ / unlimited | โ / unlimited |
esp32p4-eth |
โ / 93.4 | โ / 33212KB | โ / 376KB |
esp32s3-n16r8 |
โ / 248 | โ / 8340KB | โ / 100KB |
desktop-macos: observed 2026-06-26 โ 2026-06-27esp32p4-eth: observed 2026-06-27esp32s3-n16r8: observed 2026-06-27
shrink-grid-1x1 (set_control) ๐¶
Resize the canvas to 1x1 while the scripted effect renders โ the smallest non-empty grid. The native fill loops over a single light; the run guards (non-null buffer, cpl>=3) keep it in-bounds. Pins the 'runs at every grid size' hard rule for the JIT'd routine.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 35,714-โ | โ / unlimited | โ / unlimited |
esp32p4-eth |
โ / 862 | โ / 33215KB | โ / 376KB |
esp32s3-n16r8 |
โ / 868 | โ / 8341KB | โ / 100KB |
desktop-macos: observed 2026-06-26 โ 2026-07-15esp32p4-eth: observed 2026-06-27esp32s3-n16r8: observed 2026-06-27
grow-grid-back (set_control) ๐¶
Resize back to a wider grid; the effect keeps rendering across the live dimension change (the no-reboot reconfiguration contract applied to scripted code).
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 14,493-โ | โ / unlimited | โ / unlimited |
esp32p4-eth |
โ / 97.0 | โ / 33209KB | โ / 376KB |
esp32s3-n16r8 |
โ / 136 | โ / 8333KB | โ / 100KB |
desktop-macos: observed 2026-06-26 โ 2026-07-15esp32p4-eth: observed 2026-06-27esp32s3-n16r8: observed 2026-06-27
remove-moonlive (remove_module) ๐¶
Remove the scripted effect. release frees the exec block; the Layer keeps rendering (now empty). Measures add/remove robustness.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 15,385-โ | โ / unlimited | โ / unlimited |
esp32p4-eth |
โ / 88.2 | โ / 33209KB | โ / 376KB |
esp32s3-n16r8 |
โ / 135 | โ / 8333KB | โ / 100KB |
desktop-macos: observed 2026-06-26 โ 2026-07-15esp32p4-eth: observed 2026-06-27esp32s3-n16r8: observed 2026-06-27
re-add-moonlive (add_module) ๐¶
Re-add a MoonLiveEffect after removal โ the exec memory is re-acquired fresh, no leak, no stale pointer. The scripted effect renders again.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 29,412-โ | โ / unlimited | โ / unlimited |
esp32p4-eth |
โ / 90.9 | โ / 33209KB | โ / 376KB |
esp32s3-n16r8 |
โ / 121 | โ / 8332KB | โ / 100KB |
desktop-macos: observed 2026-06-26 โ 2026-07-16esp32p4-eth: observed 2026-06-27esp32s3-n16r8: observed 2026-06-27
MoonModule¶
scenario_MoonModule_control_change¶
test/scenarios/core/scenario_MoonModule_control_change.json โ Measure the cost of control changes on a running pipeline. Toggles MultiplyModifier's mirrorX/Y at different points and verifies each change is applied without freezing the render loop. Companion to the MoonModule control-change gate unit tests (unit_MoonModule_control_change_gate.cpp) โ this is the live equivalent.
Mode: mutate ยท Also touches: MultiplyModifier, NoiseEffect
baseline (set_control) ๐¶
Set NoiseEffect.scale=4 and measure baseline FPS (mirror on). Effect controls don't rebuild the pipeline โ slider stutter check.
Setup (preceding non-measured steps):
- canvas-clear-layers (clear_children) โ Self-canvas: clear+rebuild the pipeline this scenario assumes, so it runs from any device state (order-independent in a chained live run). Pre-wired apparatus survives clear_children; replaces the old fixture+reset model.
- canvas-clear-layouts (clear_children)
- canvas-clear-drivers (clear_children)
- canvas-grid (add_module)
- canvas-layer (add_module)
- canvas-noise (add_module)
- canvas-mirror (add_module)
- canvas-artnet (add_module)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โฅ 8,333 / 1,515-10,309 | unlimited / unlimited | โ / unlimited |
desktop-windows |
โ / 2,611-4,405 | โ / unlimited | โ / unlimited |
esp32 |
โ / 3.9-4,444 | โ / 88KB-122KB | โ / 48KB-108KB |
esp32-eth |
โ / 10.5-10.6 | โ / 133KB | โ / 48KB-50KB |
esp32-eth-wifi |
โฅ 10.0 / 12.2 | โฅ 103KB / 94KB | โ / 48KB |
esp32p4-eth |
โ / 4,926-6,250 | โ / 33238KB | โ / 376KB |
desktop-macos: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02 โ 2026-07-10desktop-windows: observed 2026-06-07 โ 2026-07-08esp32: observed 2026-06-02 โ 2026-07-22esp32-eth: observed 2026-06-02esp32-eth-wifi: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02esp32p4-eth: observed 2026-06-17
disable-mirrorX (set_control) ๐¶
Disable mirrorX. Modifier control triggers a pipeline rebuild โ measures the rebuilt path.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โฅ 5,000 / 1,511-9,259 | unlimited / unlimited | โ / unlimited |
desktop-windows |
โ / 400-3,597 | โ / unlimited | โ / unlimited |
esp32 |
โ / 4.8-4,237 | โ / 88KB-122KB | โ / 48KB-108KB |
esp32-eth |
โ / 10.4 | โ / 132KB | โ / 48KB-50KB |
esp32-eth-wifi |
โฅ 10.0 / 12.0 | โฅ 103KB / 94KB | โ / 48KB |
esp32p4-eth |
โ / 5,952-6,135 | โ / 33238KB | โ / 376KB |
desktop-macos: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02 โ 2026-07-10desktop-windows: observed 2026-06-07 โ 2026-07-08esp32: observed 2026-06-02 โ 2026-07-22esp32-eth: observed 2026-06-02esp32-eth-wifi: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02esp32p4-eth: observed 2026-06-17
disable-mirrorY (set_control) ๐¶
Disable mirrorY. Mirror is now fully off โ should land on the no-LUT path.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โฅ 2,500 / 1,506-9,346 | unlimited / unlimited | โ / unlimited |
desktop-windows |
โ / 294-1,305 | โ / unlimited | โ / unlimited |
esp32 |
โ / 4.4-4,167 | โ / 88KB-122KB | โ / 48KB-108KB |
esp32-eth |
โ / 8.9-9.0 | โ / 132KB | โ / 48KB-50KB |
esp32-eth-wifi |
โฅ 10.0 / 11.1 | โฅ 103KB / 94KB | โ / 48KB |
esp32p4-eth |
โ / 5,587-6,061 | โ / 33238KB | โ / 376KB |
desktop-macos: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02 โ 2026-07-10desktop-windows: observed 2026-06-07 โ 2026-07-08esp32: observed 2026-06-02 โ 2026-07-22esp32-eth: observed 2026-06-02esp32-eth-wifi: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02esp32p4-eth: observed 2026-06-17
re-enable-mirrorY (set_control) ๐¶
Re-enable mirrorY and measure โ the heavy LUT path must recover (FPS within 50% of baseline) without staying degraded.
Setup (preceding non-measured steps):
- re-enable-mirrors (set_control) โ Re-enable mirrorX (rebuild back to LUT path).
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โฅ 8,333 / 1,499-10,417 | unlimited / unlimited | โ / unlimited |
desktop-windows |
โ / 294-4,854 | โ / unlimited | โ / unlimited |
esp32 |
โ / 4.4-4,292 | โ / 88KB-122KB | โ / 48KB-108KB |
esp32-eth |
โ / 10.5-10.6 | โ / 132KB | โ / 48KB-50KB |
esp32-eth-wifi |
โฅ 10.0 / 12.1 | โฅ 103KB / 94KB | โ / 48KB |
esp32p4-eth |
โ / 5,319-6,098 | โ / 33238KB | โ / 376KB |
desktop-macos: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02 โ 2026-07-10desktop-windows: observed 2026-06-07 โ 2026-07-08esp32: observed 2026-06-02 โ 2026-07-22esp32-eth: observed 2026-06-02esp32-eth-wifi: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02esp32p4-eth: observed 2026-06-17
MqttModule¶
scenario_MqttModule_haDiscovery_toggle¶
test/scenarios/core/scenario_MqttModule_haDiscovery_toggle.json โ Toggle the Home-Assistant MQTT-discovery announce on and off and confirm the render loop survives every change. Pins the robustness + No-reboot-to-apply guarantees for the new haDiscovery control: turning it on publishes a retained homeassistant/light/
Mode: mutate ยท live-only (skipped in-process)
baseline-discovery-on (set_control) ๐¶
haDiscovery on (default) โ captures the baseline FPS/heap for the next two steps.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
esp32 |
โ / 26,316-29,412 | โ / 129KB-135KB | โ / 108KB |
esp32-eth-wifi |
โฅ 10.0 / โ | โฅ 88KB / โ | โ / โ |
esp32: observed 2026-07-22esp32-eth-wifi: contract set 2026-07-06 "initial contract โ pending first hardware capture"
discovery-off (set_control) ๐¶
haDiscovery off โ the retract publish (empty retained config) goes out; FPS/heap must match or exceed the baseline.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
esp32 |
โ / 26,316-27,778 | โ / 128KB-136KB | โ / 108KB |
esp32-eth-wifi |
โฅ 10.0 / โ | โฅ 88KB / โ | โ / โ |
esp32: observed 2026-07-22esp32-eth-wifi: contract set 2026-07-06 "initial contract โ pending first hardware capture"
discovery-on-again (set_control) ๐¶
haDiscovery on again โ re-announce + re-subscribe live. Bound: FPS must stay within 20% of the baseline (proves toggling doesn't leave the network task in a degraded state).
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
esp32 |
โ / 26,316-28,571 | โ / 128KB-136KB | โ / 108KB |
esp32-eth-wifi |
โฅ 10.0 / โ | โฅ 88KB / โ | โ / โ |
esp32: observed 2026-07-22esp32-eth-wifi: contract set 2026-07-06 "initial contract โ pending first hardware capture"
MultiplyModifier¶
scenario_MultiplyModifier_memory_lut¶
test/scenarios/light/scenario_MultiplyModifier_memory_lut.json โ Verify that adding a MultiplyModifier allocates both the mapping LUT and the driver buffer (the heavy memory path). Companion to scenario_Layer_memory_1to1, which verifies the no-LUT path.
Mode: construct ยท Also touches: Layer, MappingLUT, BlendMap
add-artnet (add_module) ๐¶
Add NetworkSendDriver and run the bounded FPS measurement on the LUT path.
Setup (preceding non-measured steps):
- add-layout-group (add_module) โ Create the top-level Layouts container.
- add-grid (add_module) โ Add a 16x16 GridLayout.
- add-layer (add_module) โ Add a Layer wired to Layouts (RGB).
- add-noise (add_module) โ Add NoiseEffect as the Layer's effect.
- add-mirror (add_module) โ Add MultiplyModifier โ triggers LUT and driver-buffer allocation.
- add-driver-group (add_module) โ Add a Drivers container wired to the Layer.
Bounds: - FPS โฅ 80% of baseline
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โฅ 8,333 / 3,322-1,000,000 | unlimited / unlimited | โ / unlimited |
desktop-windows |
โ / 166,667-333,333 | โ / unlimited | โ / unlimited |
desktop-macos: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02 โ 2026-06-05desktop-windows: observed 2026-06-07
scenario_MultiplyModifier_pipeline¶
test/scenarios/light/scenario_MultiplyModifier_pipeline.json โ Pipeline with a mirror modifier: NoiseEffect renders one quadrant, MultiplyModifier reflects across X and Y to produce a kaleidoscope. Used to verify the MultiplyModifier wires into Layer cleanly and that the full pipeline still meets its FPS bound.
Mode: construct ยท Also touches: Layer, NoiseEffect, NetworkSendDriver
add-artnet (add_module) ๐¶
Add NetworkSendDriver and run the bounded FPS measurement (mirror + LUT path must stay at >=80% of the rated FPS).
Setup (preceding non-measured steps):
- add-layout-group (add_module) โ Create the top-level Layouts container.
- add-grid (add_module) โ Add a 128x128 GridLayout child to Layouts. Set explicitly (the module default is 16x16x1) so the tick is measurable above the host's microsecond clock.
- add-layer (add_module) โ Add a Layer wired to Layouts (RGB).
- add-noise (add_module) โ Add NoiseEffect as the Layer's effect.
- add-mirror (add_module) โ Add MultiplyModifier so logical pixels reflect across X and Y in the physical grid.
- add-driver-group (add_module) โ Add a Drivers container wired to the Layer's output buffer.
Bounds: - FPS โฅ 80% of baseline
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โฅ 8,333 / 1,546-1,000,000 | unlimited / unlimited | โ / unlimited |
desktop-windows |
โ / 2,247-4,444 | โ / unlimited | โ / unlimited |
desktop-macos: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02 โ 2026-07-10desktop-windows: observed 2026-06-07 โ 2026-07-08
NetworkModule¶
scenario_NetworkModule_eth_reconfigure¶
test/scenarios/core/scenario_NetworkModule_eth_reconfigure.json โ Cycle the Ethernet PHY type (ethType: None/LAN8720/IP101/W5500) live on a running device and confirm the render loop survives every transition. Pins the robustness principle for the runtime Ethernet config: changing ethType reshapes the platform eth config (NetworkModule.syncEthLive โ setEthConfig, with a live ethStop+ethInit on W5500), and no value or transition order may crash or wedge the tick. Runs live only โ the eth controls exist only on hasEthernet ESP32 builds, so the in-process desktop runner SKIPs it; on a device it drives the controls over HTTP. On RMII boards (Olimex) the change saves + asks for restart (no hot re-init), so the live HTTP connection is undisturbed; the W5500 path hot-reinits but the SPI bus teardown keeps the netif alive enough to keep serving.
Mode: mutate ยท live-only (skipped in-process)
ethType-lan8720 (set_control) ๐¶
LAN8720 (RMII) โ the classic-ESP32 default. Baseline: device renders with Ethernet RMII selected.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
esp32 |
โ / 133-146 | โ / 165KB | โ / 108KB |
esp32: observed 2026-06-15
ethType-none (set_control) ๐¶
Switch to None (no Ethernet) live. The eth pin rows hide; the device must keep rendering and stay reachable.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
esp32 |
โ / 145-147 | โ / 165KB | โ / 108KB |
esp32: observed 2026-06-15
ethType-w5500 (set_control) ๐¶
Switch to W5500 (SPI) live. On an S3 this hot-reinits eth (ethStop + ethInit); on RMII boards it saves + flags restart. Either way the render loop must survive.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
esp32 |
โ / 133-135 | โ / 165KB | โ / 108KB |
esp32: observed 2026-06-15
ethType-ip101 (set_control) ๐¶
Switch to IP101 (RMII) live โ the P4 PHY. Exercises the last dropdown value; render loop must survive.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
esp32 |
โ / 146-147 | โ / 165KB | โ / 108KB |
esp32: observed 2026-06-15
ethType-back-to-lan8720 (set_control) ๐¶
Return to LAN8720 โ confirms the cycle is reversible and leaves the device in a sane state.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
esp32 |
โ / 134-145 | โ / 165KB | โ / 108KB |
esp32: observed 2026-06-15
scenario_NetworkModule_mdns_toggle¶
test/scenarios/core/scenario_NetworkModule_mdns_toggle.json โ Toggle the mDNS responder on and off and measure render-FPS impact. Validates that mDNS announcement traffic doesn't degrade the render loop more than 20% on the busiest tick.
Mode: mutate ยท live-only (skipped in-process)
baseline-mdns-on (set_control) ๐¶
mDNS on (default) โ captures the baseline FPS for the next two steps.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
esp32 |
โ / 4.4-28,571 | โ / 88KB-134KB | โ / 48KB-108KB |
esp32-eth |
โ / 10.5-10.6 | โ / 132KB | โ / 48KB-50KB |
esp32-eth-wifi |
โฅ 10.0 / 12.2 | โฅ 103KB / 93KB | โ / 48KB |
esp32p4-eth |
โ / 47,619 | โ / 33245KB | โ / 376KB |
esp32: observed 2026-06-02 โ 2026-07-22esp32-eth: observed 2026-06-02esp32-eth-wifi: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02esp32p4-eth: observed 2026-06-17
mdns-off (set_control) ๐¶
mDNS off โ measured. Expected to match or exceed the baseline.
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
esp32 |
โ / 3.6-27,778 | โ / 88KB-134KB | โ / 48KB-108KB |
esp32-eth |
โ / 10.3-10.5 | โ / 137KB | โ / 48KB-52KB |
esp32-eth-wifi |
โฅ 10.0 / 12.0 | โฅ 93KB / 98KB | โ / 48KB |
esp32p4-eth |
โ / 47,619 | โ / 33245KB | โ / 376KB |
esp32: observed 2026-06-02 โ 2026-07-22esp32-eth: observed 2026-06-02esp32-eth-wifi: contract set 2026-06-02 "shared heap budget; cumulative sweep state reduces standalone-mDNS-off heap by ~15KB" ยท observed 2026-06-02esp32p4-eth: observed 2026-06-17
mdns-on-again (set_control) ๐¶
mDNS on again โ measured with a bound: FPS must stay within 20% of the baseline (proves toggling doesn't leave the network task in a degraded state).
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
esp32 |
โ / 4.3-28,571 | โ / 83KB-130KB | โ / 48KB-108KB |
esp32-eth |
โ / 9.1 | โ / 132KB | โ / 48KB-52KB |
esp32-eth-wifi |
โฅ 10.0 / 10.6 | โฅ 103KB / 93KB | โ / 48KB |
esp32p4-eth |
โ / 45,455-50,000 | โ / 33245KB | โ / 376KB |
esp32: observed 2026-06-02 โ 2026-07-22esp32-eth: observed 2026-06-02esp32-eth-wifi: contract set 2026-06-02 "initial contract" ยท observed 2026-06-02esp32p4-eth: observed 2026-06-17
NetworkSendDriver¶
scenario_Driver_mutation¶
test/scenarios/light/scenario_Driver_mutation.json โ Add / remove output drivers under the Drivers container while the render pipeline runs, proving the robustness rule for driver deletion (the LED-driver delete path the product owner asked to cover; on the host RmtLed/I80Led/Parlio are inert via platform constants, so the portable NetworkSendDriver + PreviewDriver exercise the SAME generic add/remove lifecycle through the Scheduler that the hardware drivers use). Drivers are consumers of the Layer's output buffer, added/removed at runtime in any order. The checks assert the pipeline keeps RENDERING (buffer non-null, fps measurable) through each mutation: adding a second driver, removing it, and crucially removing a driver while the pipeline is still live (a driver teardown must release its resources without stranding the buffer or the other drivers โ the same end-to-end Scheduler path the Audio producer/consumer scenario proves for peripherals, here for the output stage). Grid is 64x64 so the tick stays above the host microsecond clock at every step.
Mode: mutate ยท Also touches: GridLayout, Layer, RainbowEffect, Drivers, PreviewDriver
measure-one-driver (measure) ๐¶
Baseline: the pipeline renders with one driver (Preview) wired.
Bounds: - FPS โฅ 1 (absolute)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 12,346-125,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 19,608-27,778 | โ / unlimited | โ / unlimited |
esp32 |
โ / 31,250-32,258 | โ / 128KB | โ / 108KB |
desktop-macos: observed 2026-06-13 โ 2026-07-10desktop-windows: observed 2026-07-08esp32: observed 2026-07-22
measure-two-drivers (measure) ๐¶
Pipeline renders with both drivers wired.
Setup (preceding non-measured steps):
- add-second-driver (add_module) โ Add a NetworkSendDriver beside the Preview driver โ two consumers of the same Layer output buffer. The add must not disturb the running pipeline.
Bounds: - FPS โฅ 1 (absolute)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 10,204-125,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 14,286-28,571 | โ / unlimited | โ / unlimited |
esp32 |
โ / 20,408-26,316 | โ / 124KB-129KB | โ / 108KB |
desktop-macos: observed 2026-06-13 โ 2026-07-01desktop-windows: observed 2026-07-08esp32: observed 2026-07-22
measure-three-drivers (measure) ๐¶
Pipeline renders with three drivers wired.
Setup (preceding non-measured steps):
- add-third-driver (add_module) โ Add a second NetworkSendDriver โ three drivers now share the Layer output buffer (Preview + two ArtNet). Stacking editable drivers before tearing them down in sequence.
Bounds: - FPS โฅ 1 (absolute)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 10,526-125,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 11,905-23,256 | โ / unlimited | โ / unlimited |
esp32 |
โ / 25,000-27,027 | โ / 127KB | โ / 108KB |
desktop-macos: observed 2026-06-13 โ 2026-07-10desktop-windows: observed 2026-07-08esp32: observed 2026-07-22
measure-after-first-remove (measure) ๐¶
One ArtNet gone, Preview + ArtNet2 remain: pipeline keeps rendering (buffer non-null, fps measurable). No crash from the mid-list teardown.
Setup (preceding non-measured steps):
- remove-first-added-driver (remove_module) โ Remove the FIRST-added NetworkSendDriver while the others (Preview + ArtNet2) are still live โ delete a middle consumer, not the last. Its teardown must release its resources without stranding the buffer or the surviving drivers.
Bounds: - FPS โฅ 1 (absolute)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 12,048-125,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 15,152-23,810 | โ / unlimited | โ / unlimited |
esp32 |
โ / 25,641-27,778 | โ / 128KB-129KB | โ / 108KB |
desktop-macos: observed 2026-06-13 โ 2026-07-10desktop-windows: observed 2026-07-08esp32: observed 2026-07-22
measure-back-to-one-driver (measure) ๐¶
Both added drivers gone, back to the single Preview baseline, still rendering โ the add/remove cycle leaves the pipeline coherent.
Setup (preceding non-measured steps):
- remove-second-added-driver (remove_module) โ Remove the remaining editable driver (ArtNet2) too โ back to just the boot-wired Preview. Repeated driver teardown leaves no residue; the pipeline still renders. (PreviewDriver is userEditable=false, so it stays โ drivers always have at least the wired output consumer; this mirrors the live API, which forbids deleting code-wired modules.)
Bounds: - FPS โฅ 1 (absolute)
Performance (contract / observed) โ tick stored, FPS shown:
| Board | FPS | heap | block |
|---|---|---|---|
desktop-macos |
โ / 12,346-125,000 | โ / unlimited | โ / unlimited |
desktop-windows |
โ / 17,857-27,778 | โ / unlimited | โ / unlimited |
esp32 |
โ / 26,316-28,571 | โ / 130KB-131KB | โ / 108KB |
desktop-macos: observed 2026-06-13 โ 2026-07-10desktop-windows: observed 2026-07-08esp32: observed 2026-07-22