EXPERIMENT_042 // ADAPTIVE.INSTALLATION.CONTROLLER
An LLM planner reads a simulated real-time sensor/audience stream — footfall, dwell time, ambient noise, time of day — and drives a Canvas 2D installation scene's lighting, colour, motion, and pacing on a bounded cadence, toggleable against a fixed scripted cue stack that ignores the sensor reading entirely.
LOADING EXPERIMENT...
Every experiential installation makes a choice between two control models: a fixed timeline that plays the same sequence regardless of who walks in, or a system that actually reads the room and responds. This experiment builds both, side by side, against the same simulated sensor stream. Four presets (quiet morning, steady afternoon, peak crowd, closing wind-down) generate a footfall/dwell/noise/time-of-day reading every second; in Adaptive mode, that reading is sent to an Anthropic Haiku planner every few seconds, which emits a structured scene directive translating room conditions into lighting, colour, motion, and pacing. In Scripted mode, a fixed six-step cue stack advances on its own clock instead — the sensor keeps ticking, visibly ignored. This is a decision-layer build (sensing -> planning -> scene change), distinct from EXP_039's hardware-command-transport layer; the two do not share code.
HOW IT WORKS
sensorGenerator.ts defines four scenarios — quiet morning, steady afternoon, peak crowd, closing wind-down — each a baseline and variance for footfall, dwell time, and ambient noise, plus a slow-drifting simulated clock. A seeded mulberry32 PRNG (matching EXP_013's technique, duplicated locally rather than imported cross-experiment) makes every reading reproducible for the preset unit tests: the same seed, preset, and tick always produce the same sensor state. A footfall-override slider lets a viewer force a spike or lull on top of the preset's own random walk.
In Adaptive mode, every tick checks plannerGuard.ts before calling /api/experiments/adaptive-installation-controller/plan: a fixed cadence (one call per 6 seconds), a per-session USD spend cap computed from the real cost the LLM toolkit returns for every call, and a call-count backstop in case cost is ever unavailable. All three figures are visible in the status panel live, not just documented. The route calls Anthropic's claude-haiku-4-5-20251001 via @diabolicallabs/llm-client's structured() with a Zod schema, passing the previous directive alongside the new sensor reading so the planner can reason about continuity rather than starting fresh each call.
Every planner response is re-validated client-side against the same SceneDirectiveSchema the route already validated server-side (defense in depth, same posture as EXP_039's command re-validation) before it's ever applied to the scene. A malformed response, a failed request, or a guard trip (spend cap or call cap reached) all resolve the same way: the rejection is logged and the installation holds its last valid directive rather than flashing to a default or crashing — an unattended adaptive session degrades to "frozen at the last good state," never to garbage on screen.
The scene renderer chases whichever directive is active — planner output in Adaptive mode, cue-stack output in Scripted mode — with an exponential lerp whose rate comes from the directive's own pacing.transitionMs, the same actuation-lag idiom as EXP_039's simulated motors. Canvas 2D was chosen over WebGL/Three.js: a background wash, one radial accent, and a small particle field is the same rendering register Bloom Field and MHS Mock Hardware Controller already established for this Playground's simulated-installation experiments, at materially lower bundle cost than introducing a first WebGL dependency for this feature set. Crucially, the sensor generator keeps running in Scripted mode too, so a viewer can watch a footfall spike move the Adaptive scene while the Scripted scene visibly does not react.
WHAT THIS PROVES
The adaptive/scripted contrast is demonstrable, not asserted: running the identical simulated sensor scenario through both modes side by side makes the value of "reads the room" concrete rather than a claim in the copy — a viewer can watch the exact moment a sensor swing changes one scene and not the other.
A cadence and spend cap only counts as a real safety mechanism if it's enforced against a real number, not a proxy: this build wires the LLM toolkit's actual per-call USD cost into the session spend cap (not an estimated token count), with a call-count backstop for the case where cost can't be computed — the same discipline this repo's cost-tracked benchmark experiments already apply, extended to an autonomous, unattended calling loop rather than a user-triggered one.