Repository navigation
H1 slice 5: the shared room scene (conf.rooms.<room>.scene.*) + arisrv seed - #776
Merged
Merged
Conversation
…e 2) The one genuinely new mechanism H1 needs on top of the clock_driver + data() replication it already has: a participant's avatar is server-authoritative, so a client must move its own avatar without a round-trip (predict), correct when the authoritative state arrives (reconcile), and show peers lag-free without guessing ahead (interpolate). New cvc::ariadne (headless, engine-free, no GL / state tree / transport -- that plumbing is the holon, slice 3): - avatar_sim: a deterministic linear kinematic integrator over a fixed dt (position += velocity*dt, pure double arithmetic, no trig), so the same intents reproduce bit-for-bit -- what makes replay sound. A stub for the physics::world save/restore stepping slice 3 swaps in behind the same shape. - predicted_avatar: client prediction + server reconciliation. Applies local intents immediately, logs the unacknowledged ones; on an authoritative state for a past tick, keeps the prediction when it already matches within tolerance (no correction), else snaps to the authoritative state and REPLAYS the inputs issued after that tick, so in-flight player input is corrected-on-top, not dropped. - interpolate_peer: a non-local peer between its last two authoritative samples, alpha clamped to [0,1) so it never extrapolates past newest. avatar_sim_test (6 cases, all green): bit-reproducible stepping; a perfect prediction reconciles with no correction; prediction ahead of the server keeps inputs pending; a server override snaps + replays in-flight ticks (and really moves the avatar); a matching ack after a correction does not re-snap or jump backward; peer interpolation never overshoots (and clamps negative/NaN alpha to the previous sample). All comparisons are lib-step vs lib-step, so bit-equality holds without -ffp-contract=off. Branches off master (has #766 clock_driver + #767 H0). Independent of #768 (the physics codec); the MVP pose rides scalar keys, so this does not need the typed-object codec. Next: S3 wires this into a cvc::ariadne::holon on its own clock_driver.
… integrator tests Adversarial review of the predict/reconcile mechanism surfaced three items, all addressed here: - [med] predicted_avatar::reconcile had no guard against a non-monotonic server_tick. A reordered or duplicate ack (tick <= one already reconciled) failed the lookup, fell into the diverged branch, and snapped the avatar back to a stale authoritative state while dropping the inputs between the two acks (a visible backward rubber-band). Real datagram transports reorder/duplicate, so this was a live latent hazard for S3's caller. Fix: track the highest reconciled server_tick and no-op (return false, touch nothing) on any ack at or below it. Test: StaleOrDuplicateAckIsIgnored. - [med] the held-vs-snap tolerance was never exercised at a nonzero distance — every "no correction" test had a bit-identical prediction (d==0), so a tol/tol2 or threshold-direction regression would ship green. Test: ReconcileHoldsWithinToleranceAndSnapsBeyond (0.5*tol held, 2*tol snaps). - [low] the determinism test was tautological (two in-process runs of a pure function). Replaced with IntegratesConstantVelocityToClosedForm: a closed-form n*v*dt check (EXPECT_NEAR, not a bit-exact golden, so FMA contraction across toolchains can't flip it) that catches a formula regression, plus a retained purity assertion. Noted for S3 (dismissed as out-of-scope for the headless stub): the tolerance is position-only (fine while the stub integrates no rotation; revisit when physics::world adds orientation), and _log is unbounded if a caller never acks (a real server acks every tick). avatar_sim_test now 8 cases, all green.
… clock_driver (H1 slice 3) The holon object the roadmap §5h names: a server-authoritative, client-replicable bundle of ONE world_clock and the participant avatars on it. The server drives the clock and owns every avatar's authoritative pose; a client runs a replica that predicts its own avatar (slice 2) and reconciles to the authoritative state, and interpolates peers. cvc::ariadne::holon (inc/cvc/ariadne/holon.h, src/cvc/ariadne/holon.cpp): - Owns its world_clock AND cvc::clock_driver BY VALUE (not the app-global clock), so two holons in one process never fight over time. - Drives avatar integration as a clock_driver STEP observer and records the presentation alpha as a FRAME observer — the same step-then-present order tick_scene uses, so a frame observer never reads a pose the step has not yet produced. - Per participant: server integrates the latest intent into the authoritative pose over the quanta that fell due; a client-local avatar predicts + reconciles (slice 2's predicted_avatar); a peer is driven by authoritative samples and interpolated at the frame alpha. A client may set an intent only on its OWN avatar, never a peer — the proposed/ authoritative split (the verb path that enforces it server-side is slice 4). Deliberately the headless clock + avatar-pose core: the Jolt cvc::physics::world (replacing the linear avatar_sim behind the same step shape) and the cvcGL scene_realize / physics_scene_binding rendering are later slices, so this stays deterministically testable with no GL / state tree / transport. holon_test (5 cases, all green): each holon owns a distinct clock (advancing one never ticks another); a server + client holon reproduce slice 2's convergence through the holon API (perfect prediction holds bit-for-bit; a server +y override snaps the client off its wrong +x prediction); a client cannot drive a peer; a peer interpolates through the frame alpha and never overshoots. Stacks on #770 (slice 2). The scalar-key state wiring that connects this to replication lands with slice 4 (the intent/pose verb path).
…vable, replay/stale tests Adversarial review of the holon surfaced six items; addressed here: - [med] the peer interpolation path ignored server_tick while the local path guards against stale/duplicate/reordered acks. A reordered peer sample would rewind the interpolation window (prev <- newer, newest <- older) and jerk the peer backward; a duplicate would collapse it. apply_authoritative's peer branch now applies the same monotonic guard (drop a sample whose server_tick <= the newest applied). Test: PeerIgnoresStaleOrDuplicateSample. - [med] the convergence tests always acked the client's latest tick, so reconcile drained the whole log and the replay-suffix path (and on_step's tick numbering) was never exercised — an off-by-one would pass. Added ClientReplaysInFlightInputsOnServerCorrection: a divergent authoritative state for a PAST tick snaps the client and replays its inputs after that tick; the result is checked against a reference, so a wrong tick/replay-suffix fails. - [low/med] ClientCannotDriveAPeer passed vacuously (a peer is never intent-driven). Reframed as PeerIsSampleDrivenNotIntentDriven, stating what it actually proves; the set_intent guard stays (defensive). - [low] the holon was only implicitly non-movable (via world_clock's mutex). Made it explicit (deleted copy/move) so a future movable-clock refactor can't silently re-enable a move and dangle the observers' this. - [low] dropped the dead `have_prev` field; the peer window now tracks `have_sample` + `last_sample_tick`. - [low] strengthened the peer no-overshoot assertion with a lower bound (advanced most of the way, not frozen). No memory-safety/crash bugs were found. holon_test now 7 cases, green.
… core)
Wires cvc::ariadne::holon to the replicated conference room tree over
plain SCALAR leaf keys, under the participant subtree the conference
already uses (conf.rooms.<room>.participants.<pid>, via
room_participant_path):
<participant>.pose.pos/orient/vel.{...} + .pose.server_tick server-written
<participant>.intent.move.{x,y,z} client-written
cvc::ariadne holon_sync.{h,cpp}: write_pose/read_pose and
write_intent/read_intent. Scalar leaves (not a typed body_state data()
object) are deliberate — they already journal + replicate through the
existing "conf" interest filter with zero transport changes, and
sidestep the data()-object work (§52). Each double is written
round-trip-safe (%.17g, portable across toolchains, no std::to_chars FP
dependency) so a reader recovers it exactly. Writes use the room tree's
touch-before-value idiom so a fresh node's first write is not lost to the
distributed-state attach race.
Authority (the proposed/authoritative path pair): a client writes only
its own .intent.*; the server reads every participant's .intent,
integrates it into the holon, and is the SOLE writer of .pose +
.server_tick; the client reads .pose read-only and reconciles. Writes go
through a C++ host (never a DSL state-set, since ui.docs.* is excluded
from replication). H1 is loopback-trusted sole-writer-by-convention; the
connection<->pid admission binding is the §5e/mesh increment.
holon_sync_test (4 cases, green): pose + intent scalar round-trip recover
binary64 exactly; read returns false when absent; and an END-TO-END over
a real two-shard state_transport_inproc — the client proposes intent, it
replicates to the server, the server holon integrates it and writes the
authoritative pose, that replicates back, and the client reconciles and
tracks the authoritative avatar bit-for-bit across 8 frames.
Stacks on #772 (slice 3). The remaining live-integration piece (a C++
room-set-intent verb + arisrv hosting the server holon + a per-room
client pump) is the follow-on that the live two-client cyprty test
exercises.
…(H1 slice 4b) Wire the holon into the live relay so a participant's avatar becomes server-authoritative across the wire. - holon_sync: server_pump(holon, app, room, wall_dt=-1) — one authoritative frame against the replicated room tree: for every present participant (room_roster) add it to the holon if new, read its proposed .intent, advance the holon, and write each authoritative .pose + .server_tick. wall_dt defaults to measured real time (the relay); a test injects a fixed dt for a deterministic step count. - arisrv: construct one server holon for the room and call server_pump each relay tick, right after room_relay_tick, so the authoritative pose writes are local shard mutations the existing drain_local block fans to every client on both transports. (~5 Hz relay cadence -> the holon catches up several quanta per tick and clients interpolate; a smoother authoritative rate is a later tuning.) - room_intrinsics: a (room-set-intent HANDLE MOVE-X MOVE-Y MOVE-Z) C++ verb so a client proposes only its OWN avatar's intent (pid from the room-join handle, so it cannot propose for a peer). Writes via the holon_sync intent keys; a C++ verb because the DSL chroot ui.docs.* is excluded from replication (same reason room-join is C++). holon_sync_test gains ServerPumpIntegratesRosterFromIntents: two participants join + propose intent on a client app, replicate, and server_pump integrates each from its own intent and writes authoritative poses that replicate back (A moves +x, B moves +y). 5 cases green; arisrv builds + links. Extends #773 (slice 4). The remaining client-side piece (arigl driving its local avatar's intent from input + rendering peer avatars from the replicated poses) is S5/S6 + the live two-client cyprty test.
# Conflicts: # src/cvc/tests/CMakeLists.txt
# Conflicts: # src/cvc/tests/CMakeLists.txt
# Conflicts: # src/cvc/tests/CMakeLists.txt
# Conflicts: # src/cvc/CMakeLists.txt # src/cvc/tests/CMakeLists.txt
…v seed The server-authoritative 3D scene every client renders from its own camera (ARIADNE_CONFERENCE_ROADMAP.md §5g). It lives in the replicated room tree as scalar leaves, like the S4 pose keys, so it rides the "conf" interest filter with no transport change. The schema is the .ari scene's own types: objects are SceneNodes (a URI or a plane/box/sphere primitive, transform, material, visible), the environment is background + chrome + SceneLights. read_room_scene() turns the live subtree back into a cvc::ariadne::Scene, so the client render (S6) is realize_scene() over it. scene.rev is written last by every write so a renderer re-realizes only on change. Removal is soft (present = 0), as room_leave is. arisrv seeds the MVP environment once (gradient background, grid chrome, a sun, a 20 m ground plane, a 1 m reference box); seeding is a no-op when the room already has a scene. room_scene_test: 9 gtests, including the seeded scene replicating to a client over two in-process shards and tracking a move + removal.
# Conflicts: # src/arisrv/main.cpp # src/cvc/CMakeLists.txt # src/cvc/tests/CMakeLists.txt
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.
Targets
masternow that #773 (slices 3+4) is merged;masteris merged in (CMake test-list conflict resolved by keeping every entry).H1 slice 5 adds the server-authoritative shared 3D scene that every client renders from its own camera (ARIADNE_CONFERENCE_ROADMAP.md §5g). The scene lives in the replicated room tree under
conf.rooms.<room>.scene.*as scalar leaves, like S4's pose keys, so it travels over the existing"conf"interest filter with no transport changes.What it adds
cvc::ariadne::conference::room_scene(room_scene.{h,cpp}).SceneNodes: a URI or aplane/box/sphereprimitive, plus transform, material andvisible.SceneLights..ariscene's own types,read_room_scene()returns acvc::ariadne::Scene. The client render (S6) is thenrealize_scene(sg, read_room_scene(app, room)), with no second schema.scene.revis written last by every scene write. A renderer re-realizes only when it changes, and a reader that sees the new revision also sees the content it covers.present = 0), for the same reason asroom_leave: a hard delete gets resurrected by peers. Lights left out of a laterwrite_scene_envare soft-removed too. Ids containing.throwstd::invalid_argument.arisrvseeds the MVP scene once: a gradient background, the grid chrome, a sun, a 20 m ground plane, and a 1 m reference box standing on it. The seed is idempotent and does nothing if the room already has a scene, so restarting the relay won't overwrite edits. The writes go out to clients on the first pass of the relay loop, and late joiners get them in the snapshot.Tests
room_scene_testhas 9 gtests. They cover round-trips for the environment and objects, soft removal and revision bumps, id validation, idempotent seeding, mapping to aScene, and an end-to-end case: the seeded scene replicates to a client over two in-process shards, then the client follows a move and a removal. These, plusholon_sync_test(5) andholon_test(7), pass locally on Windows/MSVC (RelWithDebInfo, shared). The new files and thearisrvhunk are formatted with clang-format 18.1.8.Not in this slice
ariglre-realizes the room scene onscene.revchanges, draws avatars fromparticipants.<pid>.pose, and drives its own.intentfrom input..pose, it is loopback-trusted by convention for now.