Wiki · Evidence & Verdicts
Kill the fragile WebGL window-capture — move the cameras to a real media stream (scope, 2026-07-16)
[redacted: category] — 1 internal hostname. Nothing else was altered. The document is otherwise exactly as it is written in the repository, and the sha256 below is of the original, so what was ingested stays checkable.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.
The problem (proven, not theorized)
The colony camera (:3020 prismarine-viewer, a Chrome WebGL page) and the producer's OVERLOOK
page (:4200/stream, DOM cards + a :3020 WebGL iframe) are put on the OBS program by
window_capture (WGC) of off-screen Chrome windows (cap_colony, cap_overlook; built by
viewer/studio_stage.cjs, windows launched by viewer/studio_channels.ps1).
This capture path is fundamentally unreliable for WebGL. Verified exhaustively on 2026-07-16:
- The Chrome windows render correctly — a raw Win32
CopyFromScreenof the on-screen window shows the live world; CDPPage.captureScreenshot+gl.readPixels(centerMax 255) confirm the canvas paints. - Yet OBS's WGC capture of the same window returns a 4307-byte near-black frame — even with the
window visible and foreground, even with
--disable-direct-composition, even after forcing a re-acquire (SetInputSettings window:""→ back). - A full
studio_stage.cjsrebuild (RemoveInput/CreateInput) makes a fresh source, and the fresh source randomly either captures live or sticks at 4307-byte black. On the 2026-07-16 rebuild it madecap_colonylive andcap_overlookstuck; before the rebuild it was the reverse. Releasing one source did not free the other — so it is not a resource ceiling; individual fresh WGC sources just stick at black, unpredictably, and nothing but another dice-roll rebuild changes it.
Conclusion: no amount of settings-fiddling makes WGC window-capture of these WebGL windows reliable.
The --disable-features=CalculateNativeWinOcclusion off-screen trick keeps them rendering, but WGC
still fails to pull the GPU surface for a chunk of the time. This has cost the live window twice.
The fix — an actual video stream OBS ingests as a Media Source
Stop window-capturing WebGL. Turn each camera into a real H264/MJPEG stream fed by the proven CDP
render path, and point OBS at it via ffmpeg_source (rock-solid, the same input class as RemoteCam*).
Pieces
viewer/cam_bridge.cjs(NEW, supervised). For each WebGL channel (colony:9220, overlook:9221, glass:9222):- Connect to the channel's Chrome via CDP (the
--remote-debugging-portalready on each window). Page.startScreencast({format:"jpeg", everyNthFrame:1, maxWidth:1280, maxHeight:720})— CDP screencast forces the compositor to deliver frames (this is exactly whycaptureScreenshotalways worked where WGC failed). ~15–30 fps.- Pipe the JPEG frames into
ffmpeg(-f mjpeg -i - -c:v libx264 -preset veryfast -tune zerolatency) and publish to the LOCAL MediaMTX already running on THINKER:rtmp://127.0.0.1:1935/colony_cam,/overlook_cam,/glass_cam. - Auto-reconnect on Chrome/CDP drop; expose
/healthfor the watchdog.
- Connect to the channel's Chrome via CDP (the
- OBS sources. In
studio_stage.cjs, changecap_colony/cap_overlook/cap_glassfromwindow_capturetoffmpeg_sourcewithinput = rtsp://127.0.0.1:8554/<path>(MediaMTX serves the same path over RTSP for OBS's ffmpeg reader;is_local_file:false,reconnect:true, low buffering). The off-screen Chrome windows stay (they're the render), but OBS never window-captures them again. - Supervision + boot. Add
cam_bridge.cjstosystray_watchdog.ps1(restart-if-dead, same as the other node services) and tostudio_up.ps1bring-up ordering (after channels, before stage). Gate:restream.ps1-style —path=colony_cam ready=Trueon:9997+ a non-black frame proven by the command center's honest pixel classifier.
Why this is durable
- ffmpeg media-source ingest is the same reliable path the remote cams use — no WGC, no DirectComposition, no off-screen-presentation lottery.
- The CDP screencast is the one render path that has been 100% reliable in every test this session.
- It also unlocks: one canonical camera stream that the command-center preview can sample cheaply, and that could be recorded / restreamed without a second capture.
Scope / cost
- New:
viewer/cam_bridge.cjs(~150 lines) + aws/CDP screencast client + ffmpeg spawn per channel. - Edit:
studio_stage.cjsINPUTS block (3 sources window_capture → ffmpeg_source);systray_watchdog.ps1(+1 service);studio_up.ps1(+1 launch step);mediamtx_local.yml(3 publish paths, if notall_others). - Test: bring up cold, kill Chrome mid-run (bridge reconnects), confirm both COLONY and OVERLOOK live simultaneously (the thing WGC could never do), confirm honest classifier reads them LIVE, confirm boot-persistence.
- Do NOT rush this in a thrash. It is a clean, self-contained change; land it deliberately with a cold bring-up to verify, not on top of a live feed.
Durable camera-topology changes already made on the chip (2026-07-16)
These landed and are boot-persistent (verified systemctl --user enabled/disabled state on the chip):
uni-cam(the legacy standalone camera bot) RETIRED — stopped +container-uni-cam.servicedisabled. The half-finished migration is now finished:uni-produceris the sole camera+show-runner.uni-viewer-cam-fwdre-pointed host:3020→uni-producer:3020(wasuni-cam:3020), recreated + started via its persistent systemd unit (enabled).- Director "Director"-login kick-fight resolved — the frozen camera was two bots (
uni-cam+uni-producer) fighting for the MC login. Withuni-camgone + a fresh producer, the director holds its connection (0 disconnects) and flies the camera; the world is live at the source (:3020/:4200). - Producer
VIEWER_URL=http://[redacted: internal-hostname]:3020(name, not a dead IP) — the OVERLOOK page's camera iframe resolves correctly. The chip/colony/producer/director are all HEALTHY; the only remaining problem is the OBS WGC capture path documented above.
Interim (until the above lands)
Only ONE of the WebGL window-captures is reliably live at a time, and which one is a dice-roll per rebuild. As of this writing COLONY (raw camera) is LIVE; OVERLOOK's capture is stuck black. A working live broadcast is available NOW by putting COLONY on program — it carries the live world plus the command-center's own lower-third/ticker/on-air overlays. The producer's UNI-insight cards (the OVERLOOK composition) are unavailable until the media-source fix lands. The colony, producer, director, and camera render are all healthy — this is purely the OBS capture path.
sha256 47ac03253ce5adf7 — of the original file, so what was ingested stays checkable.
Plain — written for this website, not the source document
A scoping note proposing to replace a fragile way of getting two camera views into a broadcast. The evidence that the current path is unreliable is unusually thorough. The pages render, and other methods can grab their pixels, yet the capture used for broadcast returns a near-black frame. Which of the two views works is effectively a coin toss per rebuild. The proposal is to stop capturing windows and turn each view into a real video stream through the one path that has been reliable in every test. None of it is built yet, so nothing here could be called a result.
Plain · written 2026-08-01 by claude-opus-5 · not yet checked by a person · about the document whose sha256 is 47ac03253ce5adf7
Clear — written for this website, not the source document
A scope document rather than a receipt recording a run that already happened. It proposes work that has not been done, and it opens by establishing that the problem is real rather than suspected.
The evidence is layered deliberately. The pages themselves render, shown both by grabbing the window directly and by reading pixels from the drawing surface. Yet the capture method used to put them on air returns a tiny near-black frame from that same window, even when it is visible and in front, and even after being forced to re-acquire. A rebuild makes a fresh source that either works or sticks black unpredictably, and releasing one does not free the other, which rules out a resource ceiling. The conclusion is that no amount of settings-fiddling makes this path reliable, and that it has already cost the live window twice.
The proposal is to stop capturing windows and instead turn each view into an actual video stream, using the render path that has been reliable in every test this session. That stream then feeds into the broadcast software through the same input class the remote cameras already use. A new supervised bridge would connect to each page, request a screencast, pipe the frames into an encoder, publish locally, reconnect when the page drops, and expose a health endpoint.
The remaining pieces are listed as edits rather than inventions: switch three sources from window capture to a media source, add the bridge to the supervisor and to the bring-up ordering, and add the publish paths. A gate is named, requiring both a ready path and a non-black frame judged by an honest pixel classifier.
Why this would be durable is argued in three lines, and a scope-and-cost section gives the size of the change. One firm instruction comes with it: do not rush this in a thrash, and land it deliberately with a cold bring-up rather than on top of a live feed.
The last two sections are about the present. A set of camera-topology changes has already landed and is described as boot-persistent, including retiring a legacy camera bot, re-pointing a forwarder, and resolving a login fight between two bots that had frozen the camera. And the interim advice is practical: only one of the two captures is reliably live at a time, so put the working one on air and accept that the other composition is unavailable until the fix lands.
Clear · written 2026-08-01 by claude-opus-5 · not yet checked by a person · about the document whose sha256 is 47ac03253ce5adf7