docs(runbook): M10 mount-play blocked — Orion drops animate_initial#98
Open
ClodoCapeo wants to merge 11 commits into
Open
docs(runbook): M10 mount-play blocked — Orion drops animate_initial#98ClodoCapeo wants to merge 11 commits into
ClodoCapeo wants to merge 11 commits into
Conversation
…endpoint (like Prism) The runtime opens the Orion WS on the bare `orion=` URL it is handed and sends the token in-band (LSDP §8). But ZabGate gates /show/stream on the `?token=` query-string and 403s the WS upgrade before any frame, so the CEF Solar host looped on 403. Prism's getSolarSceneUrl (the reference that works) nests the show-token INSIDE the `orion=` WS URL's own query on the `.lsdp` endpoint, so the token is present at upgrade time. Match that contract exactly (Prism/src/main/broadcast-url.ts:84-97): compose `orion=` on /orion/api/v1/show/stream.lsdp?token=<enc show>, drop the useless+misleading top-level `&token=`, and align the loopback stand-in on the SAME nested form so the gating bug cannot re-slip in. Scheme follows the gateway (https->wss / http->ws). Unit tests mirror broadcast-url.test.ts. Refs #79 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The real Blue-VPS pushes the scene_control leaf as a JSON STRING — the LSDP codec forbids objects as leaf values (#31). The stand-in cut consumer ran validate_scene_control directly on the wire value and rejected it ("value must be an object, got str"); the loopback / C-INJ path injects the object form, which masked the gap. When the leaf value is a str (live-wire / real-orion off /show/stream.lsdp), decode_scene_control_leaf (the contract decoder — JSON-parse then validate); when it is already an object (loopback injection + the in-process C-INJ corpus), validate it directly. Both run the SAME frozen validate_scene_control on the decoded object — only the LSDP envelope is peeled. A malicious / undecodable string is rejected => 0 obs-ws (C-INJ preserved). Recovers Keeper's fix (keeper/m10-probe-decode-leaf-string e3061ce) and adds the round-trip + injection unit tests. Refs #79 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ab logo) + A→transition→B playout The M10 transition as a reusable STATIC Canvas/LSML scene (data), not transition code: scripts/fixtures/zab-transition.lsml.json is a franc full-screen white frame with the centred Zab logo embedded as a base64 data-URI in image.src. No keyframes, no wipe-cover element, no scene_control leaf — the dormant lower_wipe_cover path is unused; the scene round-trips as plain LSML 1.1 (props spread at the node top level, the Orion lsmlNode form the @lumencast/runtime frame/stack/image primitives read). No asset hosting, no Solar rebuild (the fast-track). probe-m10-canvas-live.py gains --transition-scene: a third OBS program scene (scene-transition) renders the active Orion zab-transition scene in a browser_source, and the playout cuts A (screen-1) → transition → B (screen-2) in two BARE SetCurrentProgramScene cuts with a hold between them — franc passage, no fade, NO OBS-native transition (C-MECH). The MID frame is checked near-white+logo (not magenta, not black). --no-broadcast (dry) / --broadcast and C-SEC token redaction are preserved. Verified offline (no VPS / no desktop): the fixture is valid LSML 1.1, round-trips, the logo data-URI decodes to the exact complete image; the playout parses, the 3 scenes + 2 franc cuts fire, C-MECH holds (zero native-transition requests), C-SEC clean. The real CEF-of-Solar render of the white+logo scene is Keeper's antenna run. Refs #79 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… for M10 dry run Combines forge/pulsar-m10-real-orion-url-fix (#94) into the zab-transition scene + playout branch (#95) to run the SANS-Twitch dry of the M10 franc-cut transition. Working/proof branch only — NOT for merge to main. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Destination
Prior M10 antenna runs declared "LIVE on Twitch" on
StartDestination.started=true. That flag only means obs_output_start was
ACCEPTED; the RTMP connect to Twitch is async on the output worker thread, so
it is NOT proof a single byte reached the channel — the porteur saw no stream.
This adds the real proof + a long on-air hold to --transition-scene:
- hold_on_air_with_ingest_proof: after the A->transition->B playout, STAY LIVE
for --on-air-secs (Keeper antenna run uses ~80) so Twitch has time to display
the stream, looping the franc playout to keep motion on air.
- Every 5s, read the REAL counters off the Twitch destination's rtmp_output via
obs-ws GetOutputStatus{outputName="PulsarDest_<id>"}: outputBytes
(obs_output_get_total_bytes) MUST grow, outputActive MUST stay true, plus
outputDuration / congestion / skipped frames.
- Scrape pulsar.exe stdout for the verbatim RTMP lines (rtmp-stream.c):
"Connecting to RTMP URL", "Connection ... successful", and any post-connect
"Disconnected"/"failed" (a refused/dropped key). The Connecting line embeds
the stream key in the URL -> always redacted (C-SEC).
- Verdict gates on measured ingest: bytes grew AND active stayed true AND no
post-connect RTMP drop. Otherwise it FAILS honestly ("request a fresh key"),
never a 'LIVE' false positive.
Measured antenna run: 59.9 MB pushed to rtmp://live.twitch.tv over 83s at
~6 Mbps, outputActive true throughout, RTMP "successful", no disconnect.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…stination) Document the incident (no stream visible / false-positive "LIVE"), the root cause (StartDestination.started is async-accept only + ~10s too short), the real proof (GetOutputStatus outputBytes growth + RTMP log), the antenna procedure, the 2026-06-09 measured result (59.9 MB / 83s), the dead-key failure mode, and the Orion-scene scope decision + rollback. Refs keeper/m10-real-ingest-proof Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y the Blue rule leaf The --transition-scene playout no longer hard-cuts between scenes: it arms the OBS-native Fade transition (SetCurrentSceneTransition=Fade, resolved by fade_transition kind off GetSceneTransitionList, + SetCurrentSceneTransitionDuration ~450ms) and verifies it via GetCurrentSceneTransition, so each SetCurrentProgramScene CROSSFADES (A dissolves into the white+logo zab-transition passage, the passage dissolves into B) — the scene fondu the porteur asked for. Target scene B is now driven by the REAL Blue blueprint rule: in --live-wire/ --real-orion the playout fires the Blue /trigger, reads the scene_control leaf off the gateway /show/stream, decodes the LSDP string-JSON envelope and validates it through the frozen contract, then fades to ctrl.target_scene — not a probe constant. The dry path (--loopback-leaf / no VPS) falls back to the demo leaf target. The franc C-MECH "no native transition" assertion was the OVERLAY-CUT pivot's invariant; it is scoped narrowly to that path and replaced for this playout by C-FADE, which asserts the Fade IS applied (the transition used = Fade, not the native stinger / media / a capture blend). A best-effort mid-fade frame is captured to show the A<->white blend. Tests: resolve_fade_transition_name (by kind, name fallback, none on a LIGHT build), resolve_transition_target (demo target on the dry path, the validated Blue-rule target in live-wire, reject of an invalid leaf). 94 offline tests green. The Fade actually compositing headless and the leaf actually read off the wire are the CTest build leg + Keeper's antenna run. Refs #79 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…uite Adds a --transition-scene proof leg to run-probes.ps1 (the pulsar-offline-probes CTest target) so the OBS Fade arming (GetSceneTransitionList -> SetCurrentSceneTransition=Fade + duration -> GetCurrentSceneTransition verify) and the two crossfade program switches A~>zab-transition~>B run against the real CI pulsar.exe, VPS-less (--loopback-leaf --allow-blank). This is the CI hedge against the fork's hard-cut history: if the build cannot arm a Fade at all, C-FADE degrades + logs it here instead of only surfacing on Keeper's antenna run. A light build / headless non-composite is tolerated (exit 3 / --allow-blank); a hard exit is a real regression. Refs #79 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…te) + leaf declaration; playout reloads browser to replay
The M10 zab-transition scene now (1) ANIMATES on mount and (2) DECLARES the
Blue scene_control leaf, so the white+logo passage both moves visibly and
delivers the Blue rule itself — no more magenta wipe-cover workaround.
scripts/fixtures/zab-transition.lsml.json
- Add an `animate` directive on the logo (LSMLAnimateDirective: a single
transition — transition{duration:550ms,easing:ease-out} + opacity +
transform.scale). Authored mount fade/scale-in, no keyframes-compiler.
Verified it compiles strict through @lumencast/compiler to per-prop
`transitions` (opacity/scale tween, duration_ms=550 → runtime 0.55s).
- Add operator_inputs declaring __inputs.blue.m10-scene-control.scene_control
(calque on m10-orion-scene.lsml.json) so Orion's sceneAcceptsPath (F2) fans
the Blue delta out while THIS scene is active — no silent-drop, no magenta.
- Render root STAYS white+logo: no keyframes block, no wipe-cover render
element (the leaf is declared for fan-out, not lowered to a cover).
scripts/probe-m10-canvas-live.py
- Local render page now plays the authored mount animation as CSS @Keyframes
keyed to page load (opacity 0→1, scale 0.85→1, the directive's
duration/easing) — a fresh CEF load replays it.
- Playout RE-MOUNTS the transition browser_source before each cut
(re-SetCaptureSource with a cache-busted _replay= URL → fresh CEF load →
the mount animation replays; obs-browser's same-URL Update() early-return
would otherwise skip the reload).
- Capture a SEQUENCE of frames (~8 @ ~80ms) across the mount window and
prove a RAMP (prove_mount_ramp): an intermediate frame neither blank nor
already-settled. If every frame is identical/settled → NOT VISIBLE, said
franchement (anti-faux-positif); --allow-blank degrades to INCONCLUSIVE.
- load_transition_bundle now asserts the animate directive + the leaf
declaration (and still that the render root stays white+logo).
- Keeps C-SEC, --no-broadcast/--broadcast, the white settled MID + A + B +
the Blue-rule target.
scripts/test_zab_transition.py — invert the static invariants (animate + leaf
declared), add coverage for animate_mount_params, prove_mount_ramp (ramp /
settled / blank / too-few), _bust_url. 24 tests, all green (103 across the CI
offline set).
KNOWN LIMITATION (authoring info, not a defect): in vanilla Solar today the
`animate` directive lowers to per-prop transition TIMING only and the runtime
primitives (Image/Frame) set framer `animate` WITHOUT `initial`, so a
static-prop `animate` does NOT itself mount-play. The VISIBLE mount animation
is produced by the browser_source REMOUNT (fresh page load replays the page's
mount animation). The real-Solar render at the antenna replays on reload too.
Carries the base-branch (#96 final run) WIP: the runbook addendum documenting
the real antenna run + the topology tension this resolves, and the env-driven
Blue rule `target` trigger input.
Refs #79
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nt-play initial state)
Author the mount-play INITIAL STATE on the fixture itself: the logo's
`animate` directive now carries `from: {opacity:0, transform:{scale:0.85}}`
(LSML 1.1 / @Lumencast 0.3.0, lumencast-js PR #23 / Solar v0.2.3). With
the patched runtime, this directive IS a native mount-play in Solar
(compiler lowers from -> animate_initial; primitives pass framer-motion
initial={from}) — the browser_source remount re-triggers the mount and
the runtime plays the fade+scale itself, no CSS-page trick needed for
the real bundle.
animate_mount_params now reads the authored `from` (single source of
truth for the local dry-render page) and falls back to the prior 0/0.85
hidden-start defaults when `from` is absent (older fixtures keep their
ramp — covered by a regression test).
Tests: from-state authored + strictly below settled targets; fallback
without from; full offline suite 105 passed.
Refs #79, refs #97.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document the root-cause-isolated contract hole found on the M10 antenna run: after shipping Lumencast v0.3.0 + Solar v0.2.3 + Pulsar #97, the real Orion serves the zab-transition render-bundle WITHOUT a flat `animate_initial` field — it nests the from-state as `transitions.from`, but @lumencast/runtime@0.3.0 reads the mount-play initial state off the flat `t.animate_initial` only. Result: the logo snaps, no ramp; no broadcast (guard respected). Fix owner = Orion (Go render-bundle lowering) + Conduit (contract realignment). Rollback to baseline done. Refs #79 #97 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Runbook for the M10 antenna run blocked at the author leg.
After the full mount-play unblock chain shipped green (Lumencast v0.3.0 on npm → Solar v0.2.3 vendored+served → Pulsar #97 animated fixture), the real Orion serves the
zab-transitionrender-bundle without a flatanimate_initialfield — it nests the from-state astransitions.from, but@lumencast/runtime@0.3.0reads the mount-play initial state offt.animate_initialonly. The logo snaps; no ramp. No broadcast (guard respected).Root cause isolated to Orion's Go render-bundle lowering (the
LayoutNoderound-trip Forge flagged). Fix owner: Conduit (render-bundle ↔ runtime contract) + Forge (Orion). Documents diagnostic, resolution criteria, fix owner, and the completed rollback to baseline18fecbd4.Descriptive doc only (
docs/runbooks/). Not the run fix.🤖 Generated with Claude Code