Skip to content

conference: reliable roster re-broadcast (fixes browser 'can't see each other') + scene roadmap - #757

Merged
transfix merged 9 commits into
masterfrom
feat/conference-reliability-scene
Oct 22, 2026
Merged

transfix merged 9 commits into
masterfrom
feat/conference-reliability-scene

Conversation

@transfix

Copy link
Copy Markdown
Owner

Reliability fix (deployed)

The WS relay re-fanned a new client's join to established connections only once, with no retry, so at low traffic a late joiner could be missed by an established client and never recover — the browser 'clients can't see each other' report. (The name-collision interest filter was innocent; confirmed by disabling it.)

Fix: state_transport_ws::broadcast_snapshot() re-pushes the full room snapshot to every connection; arisrv calls it ~1s in its loop so every client idempotently re-converges. Verified live with two browser tabs on cyberpcangel.com/cyprty (established client picks up a late joiner) and native WS clients over the internet. Already deployed to the paladin relay.

Roadmap §5g (next work)

Server-authoritative shared 3D scene tree (conf.rooms.<room>.scene.*) every client renders from its own camera, + per-participant avatar pose. Scene-tree implementation + the UI fixes (Set button, flashing, browser aspect ratio) land next.

…st; scene roadmap

The WS relay re-fans a client's join to already-connected peers once, with no retry, so a
join that races an established peer's socket can be missed at low traffic (the single-threaded
browser's weak spot) and never recover. Add state_transport_ws::broadcast_snapshot() and call
it ~once a second from arisrv's loop so every client idempotently re-converges on the room (and
later scene) tree. Verified live: two browser tabs on cyberpcangel.com/cyprty see each other
(an established client picks up a late joiner), and native WS clients over the internet.

Also roadmap §5g: the server-authoritative shared 3D scene tree (conf.rooms.<room>.scene.*) that
all clients render from their own camera, plus per-participant avatar pose, recorded as next work.
…tall the roster

The relay fans video + state to each client over one connection with synchronous writes. Under a
live conference's continuous video, a client that can't drain fast enough fills its OS send buffer and
ws->write() blocks on it — and because the ~1s roster re-broadcast runs in one loop, blocking on ONE
slow client stalls roster delivery (join/leave) for EVERY client. Observed live: a fast probe got the
join snapshot but never saw a later join-once peer on the loaded relay, yet did instantly on an idle one.

Add connection::send_backlog() (SIOCOUTQ, Linux) and, before each fan write, skip a backed-up peer:
drop the loss-tolerant media frame above ~128KB queued, and defer the (reliable, retried-next-tick)
mutation/broadcast writes above ~512KB. State is never starved by video; a slow peer drops invisible
video frames instead of freezing everyone's roster. Verified under live browser-tab load: an
established client now picks up a late join-once joiner, and a throttled background tab converges too.
Three compounding bugs left deployed clients unable to see each other's
name or camera once real video was flowing:

1. Relay deferred the roster broadcast behind the video flood. Drop media
   hard (>32KB send backlog) to keep a peer's buffer tiny, and only defer
   a state write at 2MB — i.e. for an effectively-dead socket, never a
   merely-loaded one. Validated: 5 clients cross-fanning video, roster
   delivered to all every tick (wrote=5 skipped=0) while ~27% of video is
   shed. Env-gated CVC_WS_BROADCAST_DEBUG per-tick summary for observability.

2. Client roster collapsed to empty. The ari per-frame reactive budget
   (10ms) was exhausted mid-roster under wasm video decode, so the
   repeat-count expr (peer.count) fail-safed to 0 and the whole
   participant list + peer camera windows vanished that frame. Raise the
   budget to 30ms (clears ~6-7 peers) and cache the last-good repeat count
   so a transient skip can never blank a repeated list.

3. Peer camera windows all stacked at one authored pos. Pre-seed each
   cam_<i> geometry into a 2-column grid right of the Users window, so the
   roster is never covered and up to four cameras show side-by-side.

Also seed peer.count="0" at host init so the roster's repeat count is a
valid integer from frame 0 (no startup "did not yield an integer" warn).
cyprty: V -> cyprty-ui6.
… kills on_tick)

On the browser real-camera path the roster was always empty — no peers, no
names, no cameras, stale peers that never cleared — while synthetic test
clients worked. Root cause: state-get on an UNSET node returns nil (not ""),
and the on_tick's real-camera-only device-switch block guards with
(= cam.want_device ""), which is false for nil. So before the host first
populates that field the block runs and evaluates (int nil), which throws —
and a throw in the resident on_tick handler kills it permanently, so the
roster-build never runs. Synthetic clients skip the whole block
(cam_synthetic=1), which is why the bug was invisible in every synthetic test.

Fix: seed cam/mic.want_device and cam/mic.cur_device to "" in run_init, so the
guard is correct (= "" "") and the block is skipped until a real device switch
is requested. Verified by reproduction: two real-camera-path clients that
showed a blank "in the room:" before now show "in the room: 2" and list each
other. cyprty: V -> cyprty-ui7.
…riadne DSL roadmap

Extends §5g with the holon architecture the design discussion scoped:
- §5h — the holon = {world_clock + physics(Jolt) + scene/pose} as a server-
  authoritative, client-replicable, composable unit: frame-loop driver, client
  prediction + reconcile, pose propose/veto/override, spatial-interest + geometry
  LOD (landed) + physics LOD (new), subdelegation/holarchy on the lease manager,
  cross-node clock sync, with data() object replication (DISTRIBUTED_STATE §52) as
  the leading prerequisite. Phasing H0-H4.
- §5i — the DSL as a first-class read+write holon participant: client mods
  (local + propose), server/admin authoritative scripts, and cross-injection under
  a capability grant (intrinsics allowlist / state chroot / channel scoping /
  resource limits — all existing enforced primitives). Phasing D1-D4, threat model
  first.
- §10 — baseline-shipped note (cyprty live on the WS-relay path; holon/DSL is the
  next arc).
- §13 — consolidates the (substantially complete) Ariadne cvcGL UI/scene DSL
  roadmap: near-term fold-ins (input_text, multi-view §9.5 -> H1, light node, HTTP
  cache, async-scheduler parity), backlog retained (TF editor, JSON-Schema
  validator, s-expr dialect, program modularity, pycvc URI), and a stale-doc
  refresh (geometry LOD landed PR #249+, tvision + EGL harness done).

Docs only; no code change.
…code, not the stale doc

The first §13 pass trusted a doc-audit of CVCGL-UI-DSL-ROADMAP.md, which is badly
stale. Verified against the tree instead: input_text/input_int (loader.cpp:650,
#636), RenderView/VisibilityMask (RenderView.cpp), light node (LightNode.cpp),
clipboard (Clipboard*.cpp), async-scheduler parity (receive_message/sleep/
yield_frame in async_scheduler.cpp), and the JSON-Schema validator integration are
ALL landed — not gaps. §13 now lists verified-landed vs genuinely-remaining (TF
editor §10 and program modularity §19 have no code; the rest need per-item code
checks), and flags that even this file's §9/§10 are stale on input_text. Also fixed
§5h H1 to USE the existing RenderView rather than "pull it in". Docs only.
@transfix
transfix merged commit a8809cd into master Oct 22, 2026
24 checks passed
@transfix
transfix deleted the feat/conference-reliability-scene branch October 22, 2026 05:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant