UNI Universal Natural Intelligence

Wiki · Evidence & Verdicts

STUDIO-AGENT LAUNCH PROMPT — honest live preview + thumbnail rework (2026-07-15)

Evidence & Verdicts · docs/handoffs/STUDIO_AGENT_PREVIEW_THUMBNAIL_HONEST_2026-07-15.md @ 44baf03d5041 (gen2-runtime) — opens the published snapshot ac338733bbba

How to read this page

Three ways to read this page. Precise is the document itself, exactly as it is written in the repository. Plain and Clear were written for this website to help you meet that document — they are about it. They are not it, and they are not evidence.

Eighty-seven dated pages: receipts, pre-registrations, handoffs, validation records and review verdicts. A receipt is written at the moment a piece of work was checked. It names what was claimed, the commit and the seed, what was actually run, and the outcome in one of a small set of controlled words. Then it names what the work did not achieve. That last part is what makes it a receipt rather than an announcement. A pre-registration is the same discipline run in advance: the conditions that would count as a pass and the conditions that would falsify the claim are written down before the run, so neither can be adjusted once the numbers arrive.

That is why so many small dated stubs are an audit trail rather than noise. No one of them is meant to be a good read. The value is in the sequence and in the dates, because you can watch a prediction be registered, then the run happen, then the verdict land — sometimes against the prediction. Pages here record a falsified result, a rejected fix, a retracted overclaim, and a green receipt that turned out not to be reproducible from the commit that carried it. A record that carried only successes would be worth a good deal less than this one.

A gentle way in is to read a pre-registration first, so the shape becomes familiar, then a result page, then one of the corrections. This section sits off the main navigation on purpose: it is the record you check the rest of the site against, not the place to begin.

What it is not: documentation, and not a summary. Nothing here has been tidied in hindsight. Every entry reads as of its date, a later entry may overturn an earlier one, and the presence of a page is not a claim that its result stood.

Your browser cannot switch reading levels, so the document itself is shown.

Precise — the source document

This is the document. Rendered from the repository at the commit above, with nothing rewritten for the web. A gate re-renders it on every deploy and fails the build if a single byte differs.

How to use: open a fresh Claude Code session in this repo and paste: "Read docs/handoffs/STUDIO_AGENT_PREVIEW_THUMBNAIL_HONEST_2026-07-15.md in full and execute it, honoring every protocol it names. Diagnose with receipts before you change anything."

Written by the studio agent 2026-07-15 after reading the running code (anchors below are real file:line, verified this session). The colony is broadcasting LIVE right now — see §0 SAFETY.


You are the studio-track agent for UNI.Minecraft (Stratified Palimpsest). This is broadcast- platform work under viewer/** only. You do not touch lib/sp/** or lib/sp/runtime/** (science track), you do not design or set a science gate, you do not hold a stream key, and you never type CONFIRM / press GO LIVE (G-PA). Read CLAUDE.md in full first (especially the FIRST MOVE endpoint, the two-track law, the Door/OBS operating rules, the claim fence, and the Gaia law), then docs/STUDIO_SYSTEMS.md, docs/HUD.md, and docs/GAIA.md.

§0 — SAFETY (binding, read before any action)

  1. THE STREAM IS LIVE. As of this handoff the studio is air.level: STREAMING, program OVERLOOK, fan-out pushing to YouTube + Twitch (2 readers on the MediaMTX uni path). Your work must not black-frame, freeze, or drop the on-air PROGRAM. All preview/thumbnail capture is console-side and read-only w.r.t. program (GetSourceScreenshot renders offscreen; StudioMode preview is a separate surface from program). The honesty test in §4 that forces a black source MUST be run on a scratch/preview-only source, never a program source, or scheduled off-air. If in doubt, ask the operator to go off-air first.
  2. OBS operating rules (CLAUDE.md, burned in by real incidents): OBS is launched ONLY by viewer/studio_up.ps1 (correct cwd); never hand-launch, never force-kill OBS; graceful close only. You are NOT bringing OBS up/down for this task — it is already running. Talk to it over the websocket (viewer/lib/obs_client.cjs, ws://127.0.0.1:4455).
  3. Reads never actuate. A status/poll endpoint must never start a process or mutate program.
  4. FIRST MOVE for any "what is the state" question: curl -s http://127.0.0.1:8090/api/status (never grep the repo for state). Command-center live truth: GET :8098/api/state.

§1 — The mission (the operator's exact words, restated precisely)

The thumbnail/preview subsystem must carry perfect, honest, true signals and give a real live feel without a 30fps cost. Five concrete behaviors:

  1. Grid thumbnails (the bottom tiles): each tile carries a recent snapshot. On click, that tile plays a short low-res / low-fps live loop OR a fresh live snap (~5-second loop is the target; a fresh single snap is an acceptable minimum). Today a click only loads the scene into PREVIEW (command_center.html:264/api/preview) and the tile image is a stale cached still; that is the gap.
  2. PREVIEW monitor (left window): show the full live feed at low frames — ~3 fps for the armed/preview scene (not a static snap). Today it is a cached still refreshed only on demand/program-change/20-min sweep (command_center.cjs:416-423, pollThumbs command_center.html:521-534).
  3. On TAKE / sent to program (on air): the PREVIEW monitor freezes to a static snap (it is now on air; a live preview of it is redundant), and the PROGRAM monitor refreshes every ~30 seconds with a fresh live frame snap. Today the program tile is hard-labeled "LIVE" unconditionally (command_center.html:531) regardless of frame freshness — dishonest.
  4. Keep the flyout: the true 30fps view stays available on demand — the existing OpenVideoMixProjector button ("OPEN SMOOTH 30fps MONITOR", command_center.cjs:777-781, command_center.html:95,274). Do not remove or regress it.
  5. HONESTY IS THE POINT (binding law for this task): a black or absent frame must NEVER be labeled attached / LIVE / live. See §2 — this is the core defect.

§2 — The dishonest-signal defect (root-caused this session, with anchors)

"It says the local camera is attached but the preview is black." Cause: the "attached/live" signal is a registration/codec heartbeat, not proof of a rendered non-black frame:

  • command_center.cjs:500-501liveCams = publisher registrations whose ageMs < 30000. A slot that merely heartbeats within 30s counts as "live" even if its OBS source renders black.
  • command_center.cjs:694,701remote1/remote2 = "live" iff the MediaMTX path has an h264 track (rc()), else "badcodec"/"not publishing". Codec presence ≠ non-black video.
  • command_center.html:509-510camstatus renders that as bold LIVE.
  • command_center.html:531 — the grid card age is set to "LIVE" for the program scene unconditionally, even when the cached thumb is stale or black.

The honest primitive already exists: a non-black frame is imageData.length > 2600 (command_center.cjs:492,513,961; a black 480×270 JPEG compresses to ~a few hundred–thousand bytes, e.g. the 4307-byte all-black frames observed on OBS CEF WebGL sources this session). The fix is to gate every "attached/LIVE" claim on a real recent non-black frame, and to separate two distinct, independently-true signals in the API and UI:

  • registered (a source/slot exists and heartbeats) — may be true.
  • rendering (a recent GetSourceScreenshot of that source is non-black, i.e. bytes > threshold within the freshness window) — the only thing allowed to read "LIVE"/"attached (video)". Never collapse the two into one green "LIVE" again. If registered && !rendering, the honest label is e.g. "attached · NO SIGNAL (black)", not LIVE.

§3 — Current architecture you are modifying (all real, verified 2026-07-15)

Server — viewer/command_center.cjs:

  • Thumbnail cache + policy: thumbs {} (:422), header contract :416-421 ("reference stills, NOT video … refreshed on demand / program-change / 20-min sweep"). That contract is what you are changing — update this comment as DD.
  • grabThumb(scene) :543-546 (one GetSourceScreenshot, 480×270 jpeg q55, caches {img, at}, returns byte length — already your black detector).
  • sweepStep(scene) :549-552 (SetCurrentPreviewScene + settle + grab). 20-min safety sweep :554-563 (skips while idleMode/preflightBusy). Auto-idle after 15 min :565-575.
  • Routes: /api/thumbs :713-717 (all cached thumbs + age), /api/thumb?scene= :719-724, /api/preview :760-765 (sets operatorPreview, delayed grab), /api/take :767-775 (StudioMode transition preview→program), /api/cut :776, /api/projector :777-781 (the 30fps flyout), /api/slotstates :732-751 (per-slot live/preview/idle), /api/state :699-703 (emits camsInfo, slots, preview, idle, restreamer).
  • Program-change watcher :327-328 refreshes the thumb on cut.

UI — viewer/command_center.html:

  • Monitors: #prevthumb / #progthumb :93,95; the PROGRAM header carries the 30fps projector button :95. .mon img styling :33.
  • pollThumbs() :521-534 — pulls /api/thumbs every ~2s, updates monitors + card images; the "LIVE" label bug is :531.
  • Template grid built :250-266 (click/api/preview, dblclick → hot-cut). .card.preview style :44.
  • Camera status :509-510; projector button wiring :274.

OBS client: viewer/lib/obs_client.cjs (OBSClient, .req(name, params, timeoutMs){ok, comment, data}, onConnected). Requests you will use: GetSourceScreenshot, GetCurrentProgramScene, SetCurrentPreviewScene, OpenVideoMixProjector, GetInputList.

§4 — Design constraints + the performance budget (do not skip)

  • GetSourceScreenshot is expensive (offscreen render + base64 in the console Chrome). The existing comment (:420) warns fast-polling all scenes stutters. The operator's chosen cadences are deliberately cheap and are the spec, not a suggestion: PREVIEW ~3 fps for the ONE armed scene only; a ~5 s loop only for the ONE clicked tile; PROGRAM one snap / ~30 s; every other tile stays an on-demand still. Never resurrect all-card fast polling.
  • Only ONE scene is "live-previewed" at a time (the armed operatorPreview). When a tile is clicked for its 5 s loop, capture ~15 frames at 3 fps for THAT scene, serve them (data-URI ring or a tiny MJPEG-style sequence the page cycles), then fall back to a still. Consider whether a single server-side capture loop keyed to operatorPreview (3 fps) is cheaper than per-request grabs.
  • Black detection is mandatory on every frame that could carry a LIVE/attached label: compute bytes and compare to a named threshold constant (start from the proven 2600 for 480×270; if you change resolution, re-derive the threshold empirically against a known all-black source and record the number). Consider a stricter signal than raw bytes if you have time (e.g. sample a few pixels) but bytes>threshold is the accepted floor.
  • Keep the 30fps flyout (OpenVideoMixProjector) untouched and working — it is the escape hatch for "I want full rate now."
  • No new third-party dependency in viewer/package.json.

§5 — Gaia alignment + the honesty law (binding)

This task is the receipts-over-rhetoric discipline made literal. Gaia's studio seat and the HUD project the command center's signals; if the console emits a false "LIVE", Gaia would project a false "LIVE" — a GAIA-LAW-adjacent lie at the source. So:

  • Every signal the console exposes (/api/state.camsInfo, /api/slotstates, the card age, the monitor labels) must be true by construction: a value may read LIVE/attached-video only when a recent real non-black frame backs it. Prefer emitting the two orthogonal booleans (registered, rendering) so downstream (Gaia, HUD) can project either without inventing.
  • After your change, node viewer/gaia/verify_gaia.cjs must still be 11 PASS / 0 FAIL / 0 SKIP (you are not editing viewer/gaia/**, but confirm no regression in what she reads).
  • Claim fence: "LIVE" here means broadcast/video live, never a life/awareness claim; do not reuse the token for anything else (there is a separate, known token collision where the science verdict="LIVE" bleeds into operator surfaces — out of scope here, do not make it worse).

§6 — DDD/TDD deliverables (a work-item is done only when ALL are true)

Pre-register the gates BEFORE writing code (append PENDING rows to evidence/gates.ndjson, one receipt path each; name PASS + FALSIFIES verbatim; then supersede with the verdict). Proposed gates (refine names as you see fit, keep the intent):

gate PASS (short) FALSIFIES
preview-signal-honest-no-black-live Forcing an armed source to black makes its label read NO-SIGNAL/black; a real frame reads LIVE. registered and rendering are separate and each true-by-frame. Any black/absent frame ever labeled LIVE/attached-video anywhere (card, monitor, camsInfo, slotstates).
preview-live-3fps The armed PREVIEW monitor updates ~3 fps (measured: ≥2 frame changes/sec over a 5 s sample) for the preview scene only. PREVIEW is a static still while armed; OR all cards fast-poll (perf regression).
thumbnail-click-liveloop Clicking a grid tile yields a ~5 s low-fps live loop (or at minimum a fresh live snap) for that tile. Click yields only a stale cached still with no fresh capture.
program-30s-live-refresh Once on program, PROGRAM monitor refreshes ~every 30 s with a fresh live frame; PREVIEW freezes to a static snap. PROGRAM tile shows a hard "LIVE" with a stale/never-refreshed frame; OR PREVIEW keeps live-updating on air.
flyout-30fps-preserved OpenVideoMixProjector still opens the true 30fps program window. The flyout button is removed or errors.
preview-perf-within-budget New cadence measured within a stated CPU/GPU budget (record before/after; no visible stutter on the on-air program). Program frame-drops or console stutter attributable to the capture cadence.

DD (documents true in the same breath as the code):

  • Update viewer/command_center.cjs:416-421 header comment — the "reference stills, NOT video / 20- min sweep" contract is superseded by the live-preview model; state the new cadence + the honesty rule inline.
  • Update docs/STUDIO_SYSTEMS.md (the thumbnail/preview/monitor section) to describe: 3 fps armed preview, 5 s click-loop, 30 s program refresh + preview-freeze-on-air, the honest registered-vs-rendering split, and the retained 30 fps flyout.
  • If the signal model changes materially (two orthogonal booleans replacing one "live"), write a short ADR under production/docs/adr/ (next number) recording the honesty-by-construction decision.
  • Re-render docs/GATES.md from the ledger after appending/superseding rows.

TD (the gate is the test; verify with real artifacts, not claims):

  • Prove every gate with a real captured frame + its byte count (grab via GetSourceScreenshot; save the JPEG; look at it). For the honesty gate, actually force a black source (scratch/preview only per §0) and show the label flips to NO-SIGNAL, then restore and show it reads LIVE.
  • Measure the fps and the CPU cost; put the numbers in the receipt.
  • Add a small automated check where feasible (e.g. a node test that asserts the black-byte threshold classifier: a known-black buffer → not-live, a known-content buffer → live) under viewer/ tests.

§7 — Method (VFE/EFE/OODA, one cure at a time)

  1. Observe — run the gates/probes: /api/status, /api/state, grab real renders of PREVIEW, PROGRAM, and a camera source; record byte counts (black vs content). Reproduce "attached but black" with a receipt.
  2. Orient — diff measured vs the honest contract in §2/§5; the gap is your prediction error.
  3. Decide — the single highest-leverage cure first (the honesty split — never label black as LIVE), before the ergonomics (3 fps / 5 s loop / 30 s). Do not stack cures such that you cannot attribute the win.
  4. Act — change as code (never a runtime patch), update the doc + the gate row in the same breath, commit + push. Then the next cure.

§8 — Fences (do not cross)

  • viewer/** only. No lib/sp/**, no science gate, no ADR that touches the FE engine.
  • No go-live action, no key handling, no CONFIRM. GO LIVE / OFF AIR stay the operator's.
  • Never force-kill OBS; never hand-launch it; it is already up — talk to it over the websocket.
  • Reads never actuate. The one exception surface (setting operatorPreview / SetCurrentPreviewScene) affects PREVIEW only, never PROGRAM — keep it that way.
  • Do not disrupt the live broadcast (§0). Prefer to land + verify the honesty split live (read-only), and schedule any black-source forcing test on a scratch source or off-air with the operator.

The product is honest signals: a green LIVE that is always a real, current, non-black frame; a preview that actually moves at 3 fps; tiles that come alive on click; a program that refreshes on a calm 30 s heartbeat; and the 30 fps flyout when you want the truth at full rate.

sha256 d7b59fbed386f40d — of the original file, so what was ingested stays checkable.

Plain — written for this website, not the source document

Written for this website — not the document. This is a plain-language retelling, written to help you meet the document. It is not the source, and it is not evidence. It has not yet been checked by a person. (or choose Precise in the reading-level control above)

A launch prompt written for another session to execute, with a safety section first, because the broadcast is live while the work happens. The mission is to make a preview subsystem give a real live feel without paying for a full frame rate and, more importantly, to stop anything being labelled live when it might be black. It separates two signals that had been collapsed into one: a source existing and reporting in, and a recent frame actually being something other than black.

The gates for the work are to be appended as pending rows before any code is written, and a section of design constraints sets the performance budget the fix has to fit inside.

Plain · written 2026-08-01 by claude-opus-5 · not yet checked by a person · about the document whose sha256 is d7b59fbed386f40d

Clear — written for this website, not the source document

Written for this website — not the document. This is a clearer retelling, written to help you meet the document. It is not the source, and it is not evidence. It has not yet been checked by a person. (or choose Precise in the reading-level control above)

A launch prompt, written to be pasted into a fresh session and executed, with an instruction to diagnose with receipts, meaning recorded runs rather than recollection, before changing anything.

The safety section comes first and is binding, because the broadcast is live while the work happens. It states what must not happen to the programme, and notes that the capture involved is read-only with respect to what is on air. The one test that deliberately forces a source black must be run on a scratch surface or scheduled off air, and if there is any doubt the instruction is to ask the operator to go off air.

Operating rules follow that were learned from real incidents: one program is launched only one way, never by hand and never force-killed, and it is already running, so talk to it over its socket. Reads never actuate. And there is a named first move for any question about the current state, with an instruction not to search the repository for it.

The mission is the operator's request restated as five concrete behaviours, each paired with what the code does today and where. Tiles should come alive when clicked rather than showing a stale still. The preview should move at a low frame rate rather than being a snapshot. Once a scene goes on air the preview should freeze while the programme refreshes on a calm heartbeat. An existing full-rate window must not be removed. And the fifth is marked as the binding law for the task: a black or absent frame must never be labelled live.

The defect section root-causes that with anchors. The signal being trusted is a registration and codec heartbeat, so a slot that merely reports in counts as live even if what it renders is black. One label in the interface is set unconditionally, regardless of how fresh the picture is.

The honest primitive already exists in the code: a real frame is far larger than a black one, and a specific observed size for a black frame is quoted. So the fix is to gate every claim on a recent frame that is not black, and to separate two independently true signals. One says a source exists and reports in; the other says a recent capture is genuinely not black. Only the second is allowed to read as live. The instruction is explicit: never collapse the two again, and name the awkward combination honestly instead of hiding it.

The rest describes the architecture being modified and the order of the work: fix the honesty first and the ergonomics afterwards, so a win can be attributed rather than tangled up with a second change. Changes go in as code rather than as a runtime patch, with the gate rows appended as pending before any code is written and the document updated in the same breath.

The limits come last and are mostly about scope and restraint. One directory only, no science gate, no key handling, no going live. One exception surface touches the preview and never the programme, and there is a preference for landing and checking the honesty change read-only while the show is on.

Clear · written 2026-08-01 by claude-opus-5 · not yet checked by a person · about the document whose sha256 is d7b59fbed386f40d