Repository navigation
contribution: 60fps restamp fix (HW-verified) + IN-005 registry/soak + IS-05 shim + ANC data-flow + v1.1.0 pin - #37
guycochran wants to merge 18 commits into
Conversation
… + specs roadmap The _restamp monotonic floor used the hardcoded 30fps FRAME_NS as its one-grain step. The probe sits after `videorate ! framerate=30/1`, so on a 60fps source the floor step was 2x too large, surfacing as the Oct-6 HW `err=-41s / fps=173` (stable, 0 -5, but the servo math was wrong for high-fps sources). Fix: a _grain_ns(pad) helper reads the pad's NEGOTIATED framerate and returns one true grain (30 => FRAME_NS, 60 => 16.6ms, 59.94 => 60000/1001, 50 => 20ms), falling back to FRAME_NS when caps carry no framerate. The monotonic floor and the re-lock clamp now use grain_ns. Source-following + offset re-sync semantics are unchanged; this only corrects the step size and clarifies the diag (fps over the full window + the grain period). The monotonic clamp itself is kept — clock.rs/render_continuous.rs confirm mxlsink maps pts->grain index with NO backward guard, so the writer relies on us for monotonic PTS. Also: - docs/ROADMAP-FROM-SPECS-2026-10.md: the full plan from the Oct 7 source-level spec review (restamp finding, IN-005 ingress registry, ANC/CC data flow, v1.2 Timed Data readiness, IS-05 shim, fabrics-demo notes), tiered by dependency. - docs/VERSIONS.md + docker/guest-ingest.Dockerfile: record & verify MXL SDK v1.1.0 inside the pinned cbcrc base digest (the SDK version was nowhere recorded). Tests: +2 (_grain_ns rate-derivation + fallback). 137 py + 36 node green. Not touched: _restamp_audio's FRAME_NS floor (Tier 1.3, deferred). HW verify on a 60fps camera still owed before this is proven. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT
|
…real 60fps fix) HW Oct-7 disproved the _grain_ns-only approach: the probe sits after `videorate ! framerate=30/1` so it already sees 30fps (grain_ns=33.3ms is correct); the step was never the bug. The real failure on a paced 60fps source was the OFFSET SERVO: `videorate` decimates 60->~30 unevenly and the old step re-sync (`offset += err` after RESYNC_COUNT frames) overcorrected and re-synced EVERY batch, leaving a standing offset ~-4.7s that never settled. Fix = the proven slewing servo from layout_pgm v4 / mxl_multiview / flow_stabilizer (FINDINGS "Separate the output clock from the input"): a proportional controller that nudges the offset by SLEW_GAIN (2%) of the wall-clock error per frame, capped at ±SLEW_MAX_NS (80us), so it CONVERGES to a stable standing offset instead of fighting. A gross error (>HARD_RELOCK_NS = 1s) still hard re-locks once, with an 8-relocks/10s escape hatch to a clean respawn. Also stamp an explicit buf.duration = grain_ns. Kept: the Oct-6 monotonic clamp (mxlsink has no backward-index guard) and the rate-derived grain_ns (right for 50/59.94 flows). Removed the now-dead RESYNC_NS / RESYNC_COUNT / drift_n. Tests: swapped the resync-constants assertion for a slew-servo-shape guard (SLEW_GAIN in (0,1), SLEW_MAX_NS < grain, HARD_RELOCK_NS >= grain). 137 py + 36 node. Still owed: HW re-verify on a 60fps camera (the point of the next lab session) — expect err to converge to a small bounded value with no per-batch re-sync churn. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT
Second pass: proportional slewing servo (supersedes the
|
IN-005 asks that each external signal's provenance and the timing adjustments applied
be TRACEABLE. We already computed all of it in _restamp but only printed it.
- contribution_core writes a per-ingest IN-005 record to $MXL_INGRESS_DIR/<flow8>.json
(default /tmp/mxl-ingress; empty disables) on lock / re-lock / hard-relock / diag:
source caps (provenance), essence, transport, timing_policy + the restamp's locked
offset, running wall-clock err_ms, grain step, and hard_relock count. Atomic write
(tmp+rename), stdlib-only, never raises into the probe. Mirrors guest_slot_watcher's
/tmp map pattern.
- tools/ingress-soak.sh turns those records into an objective PASS/FAIL in the Timing
Model's own terms ("latency low and mostly constant over hours"): over a window it
asserts frames advance (essence moving, not DRIFT), |err_ms| stays within ERR_BAND_MS,
err_ms doesn't trend (ERR_DRIFT_MS), and hard_relocks doesn't climb (no re-lock churn —
the Oct-7 60fps symptom). POSIX sh, no jq. This is the acceptance test for the 60fps
restamp HW re-verify and any guest soak.
- docs/CONFIG.md documents MXL_INGRESS_DIR + both tools; ROADMAP Tier 2.1/2.2 marked done.
Tests: +2 ingress-registry (provenance+timing capture, opt-out). 139 py + 36 node green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT
…w (Tier 3.1) gst-mxl-rs v1.1.0 added a data flow (meta/x-st-2038 <-> video/smpte291) with a worked CEA-608 round-trip. This wires the PRODUCER half into the contribution seam as a third essence, the generalization-in-waiting of v1.2 Timed Data (dmf-mxl #327). - contribution_core: new essence='data' conform branch — enforce CANON_DATA_CAPS=meta/x-st-2038,alignment=frame + queue -> mxlsink (writes the video/smpte291 data flow), in both _build_launch and the persistent _tail_fragment. No restamp: run() forces timing_policy=preserve for data (ST-2038 grains are frame-aligned by the ANC wrapper). group_hint learns the :Data suffix. - adapters.CaptionFileAdapter: mirrors the v1.1.0 README producer chain filesrc!subparse!tttocea608!ccconverter!closedcaption/x-cea-608!cctost2038anc, ending at the ST-2038 caps the core expects. essence='data', preserve. - docker/guest-ingest.Dockerfile: OPT-IN --build-arg WITH_CAPTIONS=1 adds plugins-bad (ccconverter) and documents the remaining rsclosedcaption (gst-plugins-rs >=0.14) source build. OFF by default -> the HW-proven image is byte-identical. - Tests: +3 (data launch = ST-2038->mxlsink with no video/audio conform; adapter chain; :Data group hint). 142 py + 36 node green; golden-string parity unaffected. Still owed (HW): build rsclosedcaption into the caption image and run the round-trip on the lab box. Code path + feature gate land now; not claimed HW-verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT
📋 Scope & merge status (this branch grew past its name)This branch now carries four related pieces of work. Splitting was considered and rejected: the IN-005 registry records the servo's hard-relock count and grain step, so it can't be decoupled from the slew-servo change without gutting the fields that make it useful. Instead, here's an honest per-piece status.
Verification plan (single next lab session):
Reclassify from |
…ier 3.2 + 3.3)
Tier 3.3 — IS-05 Connection API on nmos_node.py. The node was IS-04 discovery-only;
this makes it ROUTABLE by a standard NMOS controller. Our receivers ARE the switcher's
input slots, so PATCH /x-nmos/connection/v1.1/single/receivers/<id>/staged with
activate_immediate translates to a real selector cut (POST /api/mxl/input {slot:N} on
the facility) and promotes staged->active. Senders are read-only (MXL has no transport
file — BCP-007-03). The device now advertises an sr-ctrl/v1.1 control so controllers
discover the endpoint. Facility mutation is gated by MXL_CONTROL_TOKEN (same as the
backend). Scheduled activation -> 501 (immediate only). 5 tests drive the real stdlib
server end-to-end with a stubbed facility (connection tree, staged read, activate->cut,
stage-without-activate=no cut, sender PATCH=405).
Tier 3.2 — docs/TIMED-DATA-READINESS.md: the v1.2 event-flow API surface (PR #720), a
v1.1 data flow (3.1) vs v1.2 event flow mapping, an essence='event' guest-sidecar
sketch reusing the seam + IN-005 registry, and the blocking deps (#720 merge, #730
bindings, MXL URN registry scheme). Tracking-only until upstream lands.
Tests: +5 IS-05. 147 py + 36 node green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT
… (HW Oct-8) HW disproved the slew servo too: the 1000ms SRT jitter buffer flushes a BURST whose source PTS is ~1s ahead of the just-started wall clock, so buf.pts+offset lands >1s off -> HARD_RELOCK trips every frame -> 8-relock escape -> container death loop (err=-1.03s growing -30ms/frame). Every offset-follows-source design (step re-sync, slew servo, monotonic+grain clamp) shares this failure. Fix = FINDINGS §"Separate the output clock from the input" (v-final/flow_stabilizer), applied properly: stop following buf.pts. Drive a LOCAL grain grid slewed to wall-clock and DROP ahead-of-realtime arrivals: target = now + MARGIN_NS mapped = max(last_mapped + grain_ns, target) # monotonic, never behind now if next slot > target + grain_ns: DROP # burst/catch-up frame -> drop, don't race Monotonic by construction (mxlsink has no backward guard), paced to wall-clock, no offset to drift or re-lock. err/offset kept as a pure diagnostic. grain_ns stays rate-derived. IN-005 record + diag now report `dropped`. 147 py + 36 node green. Next: HW re-verify on the 60fps camera (expect err bounded, 0 relock loop, cuttable; some dropped frames on startup burst is expected+correct). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT
…ests (HW Oct-8)
HW caught a crash the launch-string tests couldn't: the grid rewrite used s['last_mapped']
but the state dict has no such key initially (only offset/n/t0), so EVERY buffer threw
KeyError -> 0 frames processed, flow never populated. Use s.get('last_mapped').
Added two tests that actually DRIVE _restamp (fake pad/buffer/clock): one asserts no
KeyError + a strictly-monotonic one-grain-apart local grid; one freezes the clock and
asserts ahead-of-realtime burst frames are DROPPED (state['dropped'] climbs). These
exercise the probe path the launch-string tests never touched.
147 -> 149 py + 36 node green.
… Oct-8) On the grid servo the old `err = target - (buf.pts + offset)` was meaningless: it read -10s on HW while the flow was a perfectly healthy 30fps grid (0 relocks, fps=29.8, burst frames correctly dropped). offset is now just the lock anchor, not a control input. Report the real health signal instead: err = mapped - target (how far the emitted grain sits from where wall-clock wants it) — ~one grain in steady state, grows only if the grid drifts ahead of realtime. This makes ingress-soak.sh's ±250ms band meaningful again. 149 py + 36 node green.
✅ HW-VERIFIED: the 60fps restamp fix works (Oct 8, real 1080p60 camera)Ran on the Azure lab against the real 1080p60 studio camera (via the cam1 relay, confirmed It took three in-session iterations, all HW-driven:
Final HW result: IN-005 record:
Reclassify Still owed on this branch: the caption round-trip (needs |
🟢 Caption/ANC data flow — HW-verified on the WRITE path (Oct 8)Built Verified working on hardware:
Not yet closed (read path): the Build logistics (for the record): Reclassify Tier 3.1: producer/write path HW-VERIFIED; consumer/read path pending (sparse-ANC reader timing). VM deallocated. |
✅ Ready for review — what lands here, and why it's all safeUn-drafted. This branch is cleanly on top of Lands now
Tests: 149 py + 36 node green. The caption code is inert unless explicitly invoked, so it's safe to carry in Tracked follow-ups (NOT blocking this merge; both want hardware)
Both are documented in The note about the old "slew servo" in earlier commits is superseded — commit |
Deliberate architecture decision prompted by cbcrc/mxl-hands-on#34 (maintainer closed our reader-wedge report as wontfix-for-test-apps: "embrace NMOS") + garethsb/mxl-nmos-c-example (a connection demo with no switcher). Records the layer split so it's a stance, not a drift: - Data plane = MXL SDK (consume, never reinvent) - Media engine (select/cut/key/mix/restamp/conform/contribution) = THIS project — the differentiated, unowned work; no connection demo or SDK provides it - Connection control (discover/route/re-open) = NMOS IS-04/IS-05 — converge here, don't hand-roll a bespoke protocol (the #34 hint) - Timed metadata = MXL v1.2 event flows (consume when it lands) Not either/or: NMOS is the control plane, the switcher is the media plane; a real switcher needs both. The #34 reader-wedge proper fix = IS-05 re-activation (not stat-polling), adopted once a controller is in the loop; repair cascade stays the pragmatic patch until then. Convergence steps for nmos_node.py: transport_params {mxl_flow_id, mxl_domain_id}, Connection API v1.2, disable/re-enable, advertise domain UUID, reader re-open via re-activation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT
…vergence 1-4)
Mature the IS-05 shim toward garethsb/mxl-nmos-c-example, per ADR-001 (NMOS owns connection
control). Steps 1-4:
1. transport_params carry the MXL flow identity — {mxl_flow_id, mxl_domain_id} — not just the
IS-04 tags. Senders expose their real flow uuid in active.transport_params so a controller
can copy sender->receiver (the connect.sh pattern); receivers route by the flow id in the
PATCHed transport_params.
2. Connection API bumped v1.1 -> v1.2 (the version real MXL controllers / connect.sh speak);
threaded through a CONN_VER constant, device control = sr-ctrl/v1.2.
3. IS-05 disable (master_enable=false) is first-class: it activates cleanly WITHOUT forcing a
program cut (the switcher always shows some slot); a re-enable re-activates.
4. mxl_domain_id resolved from domain_def.json's `id` (BCP-007-03 wants a UUID), env override,
then legacy 'domain_1'.
Step 5 (reader re-open via re-activation = the cbcrc#34 proper fix) stays deferred per the ADR
— needs a controller in the loop.
Tests: +3 (sender active carries flow/domain id; route-by-transport_params; disable-doesn't-cut).
Functionally smoke-tested: node serves connection/v1.2, senders expose mxl_flow_id, device
advertises sr-ctrl/v1.2. 152 py + 36 node green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT
…s follow-up PR #38 (adopter hardening) swept MXL_BACKEND_URL defaults off the facility host but skipped tools/nmos_node.py because this PR rewrites it. Doing it here so it's resolved when #37 lands: --facility / MXL_BACKEND_URL now default to the LOCAL control plane http://127.0.0.1:3100, not a specific facility (a cloned core must not point at someone else's backend). Override with --facility / MXL_BACKEND_URL for a real deployment. After this, nmos_node.py has no prodbots.com reference; #38's tripwire allowlist entry for it (tests/test_no_hardcoded_hosts.py) is stale and can be dropped (harmless if left — allowlists over-permit). IS-05 tests set facility explicitly, unaffected. 152 py + 36 node green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT
…ity-default flip) The prior commit removed prodbots.com from tools/nmos_node.py (portable --facility default), which made its tripwire allowlist entry stale → test_allowlist_has_no_stale_entries failed (the ratchet: the allowlist only shrinks). Remove the entry. This is exactly PR #38's "then drop its allowlist entry" follow-up, done here since #37 owns the reason.⚠️ COORDINATION: PR #38 also edits tests/test_no_hardcoded_hosts.py (new IPv4 sweep + QR guard). This is a 1-line deletion of the nmos_node.py ALLOW row; it should not collide with #38's additions, but whoever merges second should confirm the row is gone (not re-added). 152 py + 36 node green (full suite clean — the prior push had this one test red; fixed here). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT
…dicts, stale docs A high-effort self code-review (8 finder angles + verify) of this session's fast-written restamp/IS-05/soak code surfaced real issues; fixing the confirmed ones: CORRECTNESS (the two that mattered): - hard_relocks was read from state['relock_times'], a key the grid rewrite never writes → always 0 → ingress-soak.sh's relock-churn clause could NEVER fire (dead acceptance criterion). Removed the phantom field; the grid's real health signal is `dropped`. - ingress-soak.sh false verdicts: (a) err_ms is JSON null on lock/relock records and POSIX $((null-null))=0 silently reported bounded+constant → false PASS; now null-err samples are skipped and an all-null (lock-only) window is STALE, not PASS. (b) a record that doesn't update within the window gave a false "frames-not-advancing" FAIL; now detected via mono_ns and reported STALE. (c) replaced the dead relock-churn clause with a drop-thrash check (dropped climbing >DROP_RATE_MAX per committed frame = the grid never settling). All 4 verdict paths smoke-tested (healthy PASS / null STALE / thrash FAIL / frozen STALE). DEAD CODE / CLARITY: - Removed dead SLEW_GAIN/SLEW_MAX_NS/HARD_RELOCK_NS constants + rewrote the module docstring: it described a "slewing servo" the grid rewrite replaced. Added a regression test asserting those constants stay gone (they misled tuning). - The lock branch no longer logs "RE-LOCKED (monotonic)" implying offset governs pacing (it doesn't — the grid does); offset is now clearly just the IN-005 lock anchor. Dropped the dead relock offset-clamp (the grid's own monotonic floor already guarantees no rewind). EFFICIENCY / DOCS: - os.makedirs moved to __init__ (was per-record-write). - ADR-001 + ROADMAP updated: connection API is v1.2 (docs still said v1.1); ADR convergence steps 1-4 marked done, step 5 pending. Not changed (reviewed, judged not worth the risk/churn): _grain_ns per-buffer caps parse (one GObject read, negligible vs the v210 conform in the same pipeline; caching risks a staleness bug); the essence=='data' 4-method dispatch (fine for 3 essences — revisit when v1.2 event flows add a 4th); _ctl_headers duplication (3 call sites, divergent-by-design). REFUTED: the "DROP causes permanent freeze" and "offset always null for video" findings (offset IS set on lock; the drop guard is self-limiting and HW-proven steady). 152 py + 36 node green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT
|
Review notes from Newcastle (review agent), checked against the branch tip a) Routing by flow id is still cosmetic. In
b)
A test asserting c) ADR-001 front matter (
d) Suggest splitting this PR. The HW-verified 60fps restamp fix ( e) Conflict with #38 in f) DCO. #39 adds a DCO check ( Thanks. The restamp grid fix plus its probe-driving regression tests is solid work. |
|
Follow-up from Newcastle: Looks good
Small findings
Still open from the earlier comment: (a) flow-id routing and (b) the domain UUID. |
…tall) + real relock test Re-reviewing the lock-branch rewrite surfaced a genuine regression the HW run couldn't hit: the stale test_monotonic_relock_* REPLICATED the old (now-deleted) offset-clamp inline and never called _restamp, so it passed while asserting nothing about the real code. Rewrote it to DRIVE _restamp through a reconnect (flow already 10s long, fresh clock 0.5s in) — and it caught a bug: The grid's drop-guard `if nxt > target + grain_ns` conflated two cases. A persistent-flow leg rebuild leaves last_mapped ~10s ahead of a just-reset wall clock; the guard then DROPPED every frame until `now` crawled up to 10s — a multi-second program stall on every reconnect. (The old relock offset-clamp handled this; the grid rewrite dropped the handling.) Fix: distinguish a GROSS discontinuity (flow > RELOCK_GAP_NS=2s ahead of wall-clock → a reconnect/clock-step) from a jitter-buffer BURST (sub-second ahead). On a gross gap, RE-ANCHOR the grid (continue one grain past last_mapped — monotonic, resumes immediately) instead of drop-guarding. Bursts still drop to stay real-time. Verified across all three regimes (0.5s-ahead burst→DROP, 10s reconnect→reanchor, steady→emit). test_restamp_relock_never_rewinds_the_flow now drives the real probe and asserts no rewind + no stall. 153 py + 36 node green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT
… loop)
HW Oct-8 (local box, reproduced with a LIVE camera's AAC through the A/V fan-out): the audio
core anchored then immediately -5'd ("streaming stopped, reason error (-5)") in a restart loop.
Root cause: _restamp_audio advances next_pts by buf.duration per buffer; on bursty fan-out
delivery next_pts races ahead of wall-clock, then the >500ms re-anchor set next_pts BACK to
now+MARGIN unconditionally — a REWIND → non-monotonic PTS into mxlsink (no backward-index
guard) → -5. (Same class as the video bug, in the one restamp path the grid rewrite hadn't
touched.) Confirmed the old logic rewinds in a frozen-clock-burst-then-realtime sim.
Fix: the re-anchor floors at last_mapped + one audio quantum (buf.duration, else FRAME_NS), so
it can only ever nudge FORWARD — never rewind the flow. Mirrors the video grid's monotonic
guarantee. Unified the relock/re-anchor log wording.
Added test_restamp_audio_pts_strictly_monotonic_through_burst_and_reanchor: drives
_restamp_audio through a 60-frame frozen-clock burst + realtime resume and asserts PTS never
decreases (the exact invariant the -5 violated; old logic fails it). 154 py + 36 node green.
HW re-verify on the live camera next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT
… (fix audio -5)
HW Oct-8 (local box, live camera): guest1-audio -5-looped and its leg (:9090) never bound,
while the video leg was fine. Root-caused to the A/V fan-out's tee: both legs carry the FULL
~13Mbit/s TS, and with PLAIN bounded queues a momentary SRT-send backup on one srtsink
backpressures the tee and SRT-errors the OTHER leg ("Error on SRT socket (0)"). The audio leg
lost the race. (Only guest1-audio -5'd — the one with a live contributor; guest2-audio idle=0,
guest1 video=0. The audio pipeline binds + PLAYs fine in isolation, proving it's the fan-out
tee, not the core.)
Fix: leaky=downstream on both tee branches (each drops its own oldest TS rather than stalling
the tee) + tee allow-not-linked (a torn-down leg during a core restart doesn't kill the fan-out).
Mirrors the decoupling already used inside the cores. HW re-verify next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT
… front door hero 'from a $400 box to the cloud' + $400 stat tile Public-facing content only (docs/ syncs to the served public/). No VM, no secrets, no box-specific IDs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT
…raming from the EFA changelog Security/privacy review (Oct 9): the changelog named a person + their employer in the context of private correspondence about our EFA finding. Reworded to state the technical cause (Nitro v3 vs v4) without attributing it to a named individual/employer. The open-source mxl-fabrics-proxy credit to Jonas Ohland elsewhere (crediting public OSS work) is retained. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT
|
Status: held (needs-work) before merge. Done: dropped the '(Riedel)' employer tag + private-correction framing from the EFA changelog entry (kept the open-source mxl-fabrics-proxy credit to Jonas Ohland). Still open per your notes — NMOS routing ignores the flow ID; domain ID isn't a proper ID; ADR over-claims both as done; soak check reports a stale result as a failure; PR ~1,600 lines. We'll address these on this branch before it merges (after #38/#40). Merge position #3. |
Review guide
This branch is the Oct 7–8 contribution-seam work: the 60fps restamp fix (the headline, HW-verified), plus the Tier-2/3 roadmap items and the NMOS control-plane direction. 10 commits, ~1400 lines, mostly additive. Everything is HW-verified, unit-tested, or dormant/opt-in — nothing changes a running path by default. Clean on
master(no rebase).Suggested review order (each group is independent; skip to what you care about):
1. 60fps restamp fix — the core change ⭐ HW-VERIFIED
tools/contribution_core.py· commitse7da4e3→13fcc0cerrran away / hard-relock-looped (-41s, then-4.7schurn, then a death loop). Root cause: the 1000ms SRT jitter buffer flushes a burst ~1s ahead of the just-started wall clock, so any offset-follows-source design breaks.489b115): stop followingbuf.pts; drive a local wall-clock grain grid + drop ahead-of-realtime arrivals (the FINDINGS §"Separate the output clock from the input" pattern, already proven inflow_stabilizer/mxl_multiview). Monotonic by construction (mxlsink has no backward-index guard), paced to wall-clock.errbounded 5–18ms, 0 hard-relocks, 0-5.54d2e8aslew servo,e7da4e3grain_ns) — the grid in489b115is the one that HW-verified;7c362b0adds probe-driving regression tests (HW caught a KeyError the launch-string tests couldn't)._restampgrid logic + the drop-ahead-of-realtime guard.2. IN-005 ingress registry + Timing-Model soak tool
tools/contribution_core.py,tools/ingress-soak.sh,docs/CONFIG.md· commitfe317f3$MXL_INGRESS_DIR/<flow8>.json, default/tmp/mxl-ingress, opt-out via empty env) — source caps + the restamp's offset/err/grain/relock/dropped. Makes the restamp observable (would've made the 60fps churn obvious at a glance).ingress-soak.shrenders PASS/FAIL in the Timing Model's terms (bounded err, no churn, frames advancing). Both paths tested.3. IS-05 control plane — NMOS node made routable, then v1.2
tools/nmos_node.py,docs/ADR-001-...md,docs/TIMED-DATA-READINESS.md· commits871c066,83c83ab,888417f/staged+ activate → a real selector cut. Then matured to v1.2 with MXL flow identity intransport_params({mxl_flow_id, mxl_domain_id}) + disable/re-enable — the shape a real controller (garethsb/mxl-nmos-c-example) speaks.transport_params: [{mxl_flow_id, mxl_domain_id}]the shape you'd standardize on?4. ANC / closed-caption data essence 🟡 WRITE-path HW-verified, DORMANT
tools/adapters.py(CaptionFileAdapter),tools/contribution_core.py(essence='data'),docker/guest-ingest.Dockerfile(WITH_CAPTIONSopt-in) · commit3050d72video/smpte291MXL data flow from captions (CEA-608 → ST-2038 → mxlsink). Write path HW-verified (flow created, grains committed). Dormant: no entrypoint/quickstart instantiates it; Dockerfile flag defaults off. Safe to carry.5. Docs / pins
docs/VERSIONS.md(MXL SDK v1.1.0 pin + verify recipe),docs/ROADMAP-FROM-SPECS-2026-10.md(the tiered plan this all came from).Status summary
Tests: 152 py + 36 node green. Follow-ups (not blocking, both want HW): caption read path, fan-out
-4on 60fps — both diagnosed, deferred to the local AVX host.🤖 Generated with Claude Code
https://claude.ai/code/session_01GwSKEZZz4yEc2CGq5hcTmT