# CultureHub spatial audio — Will’s Codex handoff Build a read-only web UI for the current two-machine ACOS rehearsal on **CULTUREHUB LA** Wi-Fi. **Seat 1 is left; seat 2 is right.** Both are online and synthesize their own speaker output from the shared spatial score. Microphones must stay closed; do not request microphone access, record audio, or issue device commands. The rehearsal host provides a read-only bridge at **`http://192.168.1.235:8787`**. Start with `GET /api/seats` or subscribe to `/events`; no source checkout or credentials are needed to consume the running bridge. Addresses are local to this Wi-Fi. ## Connect Current rehearsal addresses, September 18, 2026: | Screen label | JSON `seat` | Name | HTTP base | | --- | --- | --- | --- | | Seat 1 — left | 0 | `ac-device` | `http://192.168.1.236` | | Seat 2 — right | 1 | `ac5` | `http://192.168.1.237` | Both currently run build `dusted-mantella-tideline`. Only these two machines are in the active bridge and presence feed. Use explicit IPs. Read each snapshot’s `machineName` and `ip`, also shown on its screen. DHCP addresses can change; verify the runtime identity and seat assignment whenever connecting. Preserve seat numbers when a device goes offline; do not renumber the remaining laptops. ```sh curl --fail --max-time 2 http://192.168.1.237/status curl --fail --max-time 2 http://192.168.1.237/pieces/spatial-rehearsal-status.json ``` | Read endpoint | Meaning | | --- | --- | | `/status` | Runtime `{name, build, piece, ip, uptime, port}`. Check that `piece` is `spatial-rehearsal`; `uptime` is LAN service uptime in seconds. | | `/pieces/spatial-rehearsal-status.json` | Latest rehearsal snapshot, rewritten about every 100 ms while the piece runs. | | `/pieces/spatial-rehearsal-config.json` | `{seat, seats, maxSeconds}`. Use the live snapshot’s `duration` for the current run length. | | `/pieces/spatial-rehearsal.nsscore` | Shared score JSON: duration, lanes/events, and spatial control ribbons. Fetch once per score change. | | `/pieces/spatial-rehearsal.mjs` | Deployed piece source, including bundled deterministic world math. Read as text for implementation reference; do not execute fetched source automatically. | These reads need no credentials. Keep this integration to GET requests. ## Read the snapshot | Field | Interpretation | | --- | --- | | `machineName`, `ip` | Device name and current IP, also displayed on its screen. | | `seat`, `seats` | Original zero-based seat index and registered ensemble size. Currently two seats, indices 0–1. | | `geometry`, `seatOrder` | Runtime layout and ordered zero-based seat indices. Currently `"line"` and `[0,1]`: seat 1 left, seat 2 right. Build the layout from these fields. | | `connectivity` | `{stale, seats: [{seat, state}]}` from a separate controller presence publisher. States are `online`, `offline`, `unstable`, `unknown`, or `error`. Honor `stale`; this is separate from the bridge’s polling health. | | `phase` | `ready`, `prepared`, `countdown`, `playing`, `finished`, or `error`. | | `mode` | `beeps` or `score`. Beep mode gives each laptop the same scheduled pulse. | | `runId` | Preparation identifier for the run; `null` before preparation. Compare score times only within the same run. | | `duration`, `scoreDuration`, `scoreName` | Current run length, full score length (both seconds), and score name. Read these dynamically; beep mode lasts 20 seconds. | | `error` | Empty string normally; display nonempty errors. | | `command` | Last processed command identifier. Useful context; not a sample sequence number. | | `audioTime` | This device’s audio clock, in seconds. Origins differ between devices: never subtract one seat’s `audioTime` from another’s. | | `origin` | Scheduled score start in that same device’s audio clock; `null` before scheduling. | | `scoreTime` | Seconds relative to `origin`; `null` when unarmed, negative before the scheduled start. Compare between seats during the same playing run, allowing for unequal sample arrival times. A value alone does not prove playback: always inspect `phase`. | | `output.left`, `output.right` | Current digital speaker amplitudes. Use these for live meters and screen brightness; these are not calibrated sound-pressure measurements. | | `glow` | Screen brightness, 0–1. Lights only when a routed source has at least 90% power on this seat (`seatGain² ≥ 0.9`) and output amplitude exceeds 0.002. Use it to mirror the laptop’s focus display. | | `outputPeak` | Largest observed output amplitude since preparation. Historical peak, not a live meter. | | `sources` | Live source positions and this seat’s gains; see Spatial view below. | | `beepCount` | Pulses emitted in the current beep run; resets on preparation. | | `maxFrameGap` | Largest simulation step in audio-clock seconds since preparation. Shows scheduling stalls, not measured acoustic skew. | | `networkHalfRttMs` | Controller’s half-round-trip timing estimate, in milliseconds; may be `null` before preparation. | | `timing` | Human-readable timing limitation. Current scheduling estimates audio clocks over Wi-Fi; output latency and drift are uncalibrated. | | `microphone.hot`, `microphone.recording` | Both must be `false`. Display any unexpected true value; do not try to calibrate using microphones. | | `battery` | `{percent, charging, status, minutesLeft}`. Negative percentage or minutes means unavailable. `minutesLeft` is an estimate when discharging. | | `screen` | `{width, height}` in runtime pixels. | Treat missing or additional fields defensively. This is a rehearsal interface without a versioned schema. The telemetry contains numeric synthesis state, not a microphone recording or an audio stream. ## Browser bridge | Bridge endpoint | Response | | --- | --- | | `http://192.168.1.235:8787/api/seats` | Latest `{updatedAt, seats: [...]}` snapshot. | | `http://192.168.1.235:8787/events` | Server-Sent Events named `seats`, carrying the same JSON snapshot. | | `http://192.168.1.235:8787/health` | Bridge health. Check seat freshness separately. | Each item in `seats` is `{host, connected, stale, ageMs, receivedAt, data, error}`. `data` is the native snapshot described above; it is `null` before the first successful read. `updatedAt` and `receivedAt` are host-side ISO timestamps; `receivedAt` is the last valid fetch time. `ageMs` measures time since the last change in that seat’s `audioTime`, or is `null` before any sample. `connected` describes the latest poll; `error` is `null` on success. Failed reads retain the last valid `data`. The bridge polls each seat at 5 Hz with at most one request in flight per seat; it marks data stale on a failed read or when the audio clock stops advancing for two seconds. HTTP success alone does not establish that the piece is running. Preserve host associations and show connection failures and stale data separately. Exclude stale data when choosing the current score or layout. The bridge sends `Access-Control-Allow-Origin: *`. Save this as `index.html`, serve its directory with `python3 -m http.server 8000 --bind 127.0.0.1`, and open `http://127.0.0.1:8000` for a minimal live reader: ```html