acdream/docs/research/2026-08-02-canonical-body-writer-map.md
Erik 6460596b56 docs(physics): C1 satisfied by the existing publication mechanism
The C1 body-writer research found the atomic controller/body transaction
already built and tested: RuntimeLocalPlayerPhysicsPublicationState plus
the dormant local-activation family implement the sanctioned
off-canonical-prepare + validated-atomic-commit shape end-to-end, with
zero production callers. The committed writer map records the six
canonical body writers, the two host escape hatches (the public
Controller setter both hosts write directly; App's object-clock facade
bypasses), the headless prepared-collision fragility, and both hosts'
construction divergences. C1 therefore collapses into C3's route-1 flip
— the remaining work is production wiring, not mechanism design — and
C2 (the placement allocation budget) becomes the next slice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 05:33:29 +02:00

42 KiB

C1 body/controller-publication writer map (2026-08-02)

Repo: C:\Users\erikn\.codex\worktrees\af5e\acdream, branch codex/port-claude-agents, HEAD ae296393. READ-ONLY research; this file is the only write target.

Context read: docs/plans/2026-08-02-placement-cutover.md (slice C1), docs/research/2026-07-31-remaining-physics-campaign-handoff.md (rejected-prototype section, lines 143-168; prerequisite C, lines 203-221), and docs/research/2026-08-02-cutover-route-inventory.md route 1 + prerequisite-C section (lines 174-220) + route 8 (headless).


1. Every writer of RuntimeEntityRecord.PhysicsBody

PhysicsBody is public PhysicsBody? PhysicsBody { get; private set; } (src/AcDream.Runtime/Entities/RuntimeEntityRecord.cs:72). The ONLY mutator is the internal method SetPhysicsBody(PhysicsBody? body) (RuntimeEntityRecord.cs:176-182):

internal void SetPhysicsBody(PhysicsBody? body)
{
    if (ReferenceEquals(PhysicsBody, body)) return;
    PhysicsBody = body;
    PhysicsOwnershipEpoch++;   // <-- the ONLY place PhysicsOwnershipEpoch is bumped
}

So every "writer" is a caller of .SetPhysicsBody(...) (all 6 call sites, confirmed by full-repo grep, zero others):

  1. RuntimeEntityDirectory.cs:359 — inside GetOrCreatePhysicsBody(RuntimeEntityRecord record, Func<incarnation,PhysicsBody> factory) (need exact surrounding signature — read below). Public/internal API used by the route-1 "SECOND, narrower body-construction duplicate authority" for non-player static-animating physics objects (DatLiveEntityProjectionMaterializer.cs:1003-1016, per the route inventory). Guard: only sets if record has no body yet (idempotent-create pattern) — see full read below for exact guard.
  2. RuntimeEntityObjectLifetime.cs:766Entities.SetPhysicsBody(canonical, null) inside a teardown method (need to confirm exact method — likely delete/retire path, paired with Entities.SetPhysicsBodyAcquisitionInProgress(canonical, false) at line 767 in the SAME method). Clears body on deletion/teardown.
  3. RuntimeLocalPlayerPhysicsPublicationState.cs:405candidate.Record.SetPhysicsBody(candidate.Body) inside Commit(token, out activationToken) (lines 373-411). THIS IS THE DORMANT OPTION-2 MECHANISM — see section 4 below. Guarded by IsCurrent(candidate) (epoch/session/identity/null-state re-check, lines 891-922) immediately before, and by _physics.SetPosition.PrepareDormantLocalActivationOwnership(...) called first (line 394) as the "seal the exact SetPosition owner before the irreversible no-fail suffix" step — i.e. this call site DOES chain into PrepareDormantLocalActivationOwnership per task 4's target.
  4. RuntimeLocalPlayerPhysicsPublicationState.cs:1025_entities.SetPhysicsBody(activation.Record, null) inside DiscardActivation() (994-1032), the rollback/teardown path for the SAME dormant mechanism — only fires if _entities.IsCurrent(activation.Record) AND ReferenceEquals(activation.Record.PhysicsBody, activation.Body) (i.e. never clobbers a body some OTHER newer owner already installed — the exact anti-pattern the rejected prototype failed on).
  5. RuntimePhysicsState.cs:1558Entities.SetPhysicsBody(record, candidateBody) — need full read; this is inside the remote/projectile body-binding family (see section 3).
  6. RuntimePhysicsState.cs:1666Entities.SetPhysicsBody(record, candidate) — need full read; this is the OTHER binding site, guarded by PhysicsBodyAcquisitionInProgress (set true at :1645, cleared at :1676/1678).

Writer count: 6 call sites, across 3 files (RuntimeEntityDirectory.cs x1, RuntimeEntityObjectLifetime.cs x1, RuntimeLocalPlayerPhysicsPublicationState.cs x2, RuntimePhysicsState.cs x2).

Consumers of PhysicsOwnershipEpoch

Only bumped in one place (RuntimeEntityRecord.SetPhysicsBody, above). Consumers (all in RuntimeLocalPlayerPhysicsPublicationState.cs) treat it as a compare-and-reject epoch stamped into every token/activation struct:

  • RuntimeLocalPlayerPhysicsPublicationToken.PhysicsOwnershipEpoch (field, :53) captured at Prepare time (:314).
  • RuntimeLocalPlayerPhysicsActivationToken.PhysicsOwnershipEpoch (:70) captured as token.PhysicsOwnershipEpoch + 1UL (:328) — i.e. the activation token encodes "the epoch AFTER my own commit bumps it", so IsActivationCurrent (931-962) and IsActivationOwnershipEnvelopeCurrent (717-745) comparing activation.Record.PhysicsOwnershipEpoch == activation.Token.PhysicsOwnershipEpoch will FAIL (reject) the instant any OTHER writer (remote/projectile bind, GC clear, non-player static body creation via RuntimeEntityDirectory — none of which should ever touch a local-player record, but the check is defense-in-depth) touches the same record's PhysicsBody between prepare and commit.
  • IsCurrent(candidate) (891-922, pre-Commit re-check) also compares candidate.Record.PhysicsOwnershipEpoch == candidate.Token.PhysicsOwnershipEpoch (unincremented — i.e. "nobody touched the body between Prepare and Commit").

This IS the reentrancy defense the rejected prototype lacked — see section 6.

2. The two production local-player controller constructions, end to end

Graphical: PlayerModeController.BuildControllerAndCamera

src/AcDream.App/Input/PlayerModeController.cs:244-525. Constructor list (52-74) shows it is injected with RuntimeLocalPlayerMovementState controllerSlot (the SAME slot type headless writes) — confirms the route-inventory's "open question" (2026-08-02-cutover-route-inventory.md:204-207): App DOES write _controllerSlot.Controller = controller directly, at line 486. Not a mystery/asymmetry — both hosts write the exact same public setter.

Steps, in order:

  1. _approachCompletions.BeginControllerLifetime() (250) — App-only approach lifecycle token.
  2. Capture rollback snapshots: _camera.CaptureState() (255), _shadow.Capture() (256) — presentation-only.
  3. new PlayerMovementController(_physics, playerRecord.ObjectClock, PlayerMovementConstructionOptions.From(_skills.Snapshot)) (259-262) — uses the PUBLIC constructor, whose default publication lifecycle is StandalonePublished (PlayerMovementController.cs:617-626), NOT CandidatePreparing/CreatePublicationCandidate. This is the key divergence from the dormant mechanism (section 4): this controller never enters the CandidatePreparing -> CandidateSealed -> RuntimeOwnedDormant -> RuntimePublished lifecycle at all.
  4. Builds MoveToManager/EntityPhysicsHost closures over captured locals (267-346) — presentation-adjacent glue, host-specific.
  5. EntityPhysicsHostComposition.SelectStableHostWithoutRebind (347-350) — canonical-state read (checks LiveEntityRecord.PhysicsHost).
  6. RuntimeMovementSkillProjection.ApplyTo(_skills, controller) (366-368).
  7. ApplyStepHeights(controller, playerEntity, playerGuid) (375) — reads DatReaderWriter.DBObjs.Setup directly (per headless's own comment contrasting itself, HeadlessSessionWorldProjection.cs:685-689) — NOT through the prepared-collision/IPreparedCollisionSource seam headless uses. Divergence #1.
  8. _controllerSlot.BeginMotionPreparation(controller, drainPriorAnimationQueue) (404-407) — the ONE existing narrow "preparation lease" concept already in RuntimeLocalPlayerMovementState (separate from the dormant physics publication state) that lets a synchronous PartArray/type-5 completion reach the candidate MotionInterpreter before publish.
  9. Duplicate authority_physics.Resolve(...) (409-413) then _physics.ResolvePlacement(...) (422-430) — direct canonical-state-free collision resolve, entirely outside RuntimeSetPositionState.
  10. controller.PreparePositionForCommit(...) (434-437), controller.SetBodyOrientation(...) (438).
  11. Camera construction + _camera.EnterChaseMode(...) (440-447) — presentation-only, but happens BEFORE the final canonical commit (445-447 precede line 482-484) — i.e. camera activation today is NOT gated on a Runtime placement acknowledgement.
  12. Re-check host stability (449-458) — throws if the host changed during camera activation (defensive, but ad hoc — not an epoch/token check, a bespoke ReferenceEquals re-read).
  13. Shadow sync (_shadow.SyncPose(...), 460-466).
  14. EntityPhysicsHostComposition.InstallOrRebind(...) (472-475) + another ReferenceEquals stability re-check (476-480).
  15. Duplicate authority — final commit (482-484): playerEntity.SetPosition(initial.Position); playerEntity.ParentCellId = initial.CellId; controller.CommitPreparedPosition(); — direct writes to the App-side WorldEntity/render sidecar AND controller's own internal frame, bypassing any Runtime Place receipt or RuntimeEntityRecord write. RuntimeEntityRecord.PhysicsBody/PhysicsOwnershipEpoch are NEVER touched anywhere in this methodcontroller.PhysicsBody (the _body field created in step 3) stays a private field of the StandalonePublished controller; nothing calls Entities.SetPhysicsBody(playerRecord, controller.PhysicsBody). This means TODAY the canonical RuntimeEntityRecord.PhysicsBody slot for the graphical local player is never populated at all by this path — a previously-unstated confirmation that SubmitPreparedPlacement's operation.Record.PhysicsBody is not { } body requirement (section 3) would REJECT any ordinary (non-initial) SetPosition submitted for the graphical local player today, because no writer ever puts a body on that record. (Route 2's "ForcePosition" duplicate authority, LocalForcePositionTransaction, works around this by mutating PlayerMovementController's own body directly via BlipPosition, never touching RuntimeEntityRecord.PhysicsBody either — internally consistent with each other, both equally disconnected from the canonical record.)
  16. Slot commits (485-492): _hostSlot.Host, _controllerSlot.Controller = controller (the public, unguarded setter — bumps ControllerOwnershipEpoch unconditionally, see section 4), _chase.Legacy/Retail, _mode.IsPlayerMode = true.
  17. catch: rolls back camera + shadow only (494-518); does NOT roll back steps 15-16 because those are the LAST lines before lifetimeCommitted = true — structurally "hope nothing after this throws" rather than an explicit no-fail invariant.

Canonical-state mutations in this method: NONE on RuntimeEntityRecord (no SetPhysicsBody, no SetFullCell, no object-clock call) — everything mutated is App-local (WorldEntity, PlayerMovementController's private body, RuntimeLocalPlayerMovementState.Controller, LocalPlayerPhysicsHostSlot, camera, shadow). The ONLY canonical-record writes for the local player's initial placement happen earlier in the hydration pipeline (LiveEntityRuntime.MaterializeLiveEntity/ RebucketLiveEntity, route 1 hops 9-11) — entirely disjoint from this method.

Headless: HeadlessSessionWorldProjection.CreateController + SynchronizeLocalPlayer

src/AcDream.Headless/Hosting/HeadlessSessionWorldProjection.cs:566-655 (read in full).

SynchronizeLocalPlayer (566-615):

  1. Guards on record.ServerGuid == _runtime.PlayerIdentity.ServerGuid and a present Snapshot.Position (568-573).
  2. _collision.CenterOn(position.LandblockId) (575) — headless collision- neighborhood readiness, no graphical analog.
  3. _runtime.MovementOwner.Controller ?? CreateController(record) (576-578) — lazy-construct-once via the SAME public Controller getter/setter RuntimeLocalPlayerMovementState exposes; no reentrancy guard against two concurrent calls both observing null (single-threaded host loop makes this safe in practice today, not structurally).
  4. _runtime.EntityObjects.Physics.Engine.Resolve(...) (589-594) then .ResolvePlacement(...) (595-605) — the exact same duplicate-authority shape as graphical step 9, hardcoded DefaultRadius/DefaultHeight constants (visible in the call, actual values not read here) instead of _motionBindings.GetSetupCylinder.
  5. controller.SetPosition(...) + controller.SetBodyOrientation(...) (610-614) — duplicate final commit, headless's version of graphical step 15. Also never touches RuntimeEntityRecord.PhysicsBody.

CreateController (639-655):

  1. new PlayerMovementController(_runtime.EntityObjects.Physics.Engine, record.ObjectClock, PlayerMovementConstructionOptions.From(_runtime.CharacterOwner.MovementSkills.Snapshot)) (642-646) — same PUBLIC constructor / StandalonePublished lifecycle as graphical step 3.
  2. ApplySetupStepHeights(record, controller) (649, body at 657-691) — reads via _preparedCollision.ReadSetupCollision(setupId) (668-679), the prepared-asset seam, NOT raw DAT — divergence #1 mirrored (headless uses the "correct"/prerequisite-B-aligned source; graphical does not). Throws InvalidDataException if the read status isn't Loaded (672-675) — propagates uncaught up through SynchronizeLocalPlayer -> ProjectSpawn/ProjectPosition -> the wire-dispatch call chain. This IS gate 10 (late headless prepared-collision failure) manifesting today as an unhandled exception, not a retry.
  3. RuntimeMovementSkillProjection.ApplyTo(_runtime.CharacterOwner.MovementSkills, controller) (650-652).
  4. _runtime.MovementOwner.Controller = controller; (653) — the exact same public setter graphical step 16 uses.

No headless equivalent of graphical steps 1 (approach lifetime), 8 (motion preparation lease), 11-14 (camera + host-stability re-checks), 17 (camera/ shadow rollback) — headless has no camera/shadow/approach concept at all (confirmed, matches the route inventory's "No headless equivalent of graphical hops 12-14/19").

Top divergences between the two hosts (summary)

  1. Setup/collision data source: App reads raw DAT (ApplyStepHeights via _dats/_datLock); headless reads the prepared/baked asset (ApplySetupStepHeights via IPreparedCollisionSource). Same target values, different pipeline — a real fidelity risk if the two ever diverge (baking staleness).
  2. Default cylinder fallback: App falls back to 0.48f/1.835f inline (PlayerModeController.cs:416-420) when GetSetupCylinder returns < 0.05f radius; headless uses named DefaultRadius/DefaultHeight constants at the ResolvePlacement call site (:599-600) — same intended values, defined in two places.
  3. Failure handling: App's BuildControllerAndCamera has an explicit try/catch/rollback for camera+shadow; headless's CreateController/ ApplySetupStepHeights has NO surrounding try/catch — a prepared-collision read failure is a raw unhandled exception today.
  4. Presentation surface: App additionally owns approach-completion lifetime, motion-preparation lease, chase camera, shadow sync — none of which headless has or needs.
  5. Neither host touches RuntimeEntityRecord.PhysicsBody, PhysicsOwnershipEpoch, or any RuntimeSetPositionState API — both are 100% off to the side of the canonical record, confirmed by exhaustive grep (section 1's 6 writer call sites do not include either PlayerModeController.cs or HeadlessSessionWorldProjection.cs).

3. Every other body binding/consumer

  • Remote dead-reckoning (RuntimeRemotePhysicsUpdater.cs — flagged protected/dirty, read-only, NOT modified): grep confirms it only READS record.PhysicsBody via ReferenceEquals(record.PhysicsBody, remote.Body) currency checks (line 817) — it does not call SetPhysicsBody. The actual writer for remote motion is RuntimePhysicsState.SetRemoteMotion (RuntimePhysicsState.cs:1446-1570, full read) — throws InvalidOperationException on: binding-already-in-progress (1460-1464), body-would-be-replaced when a body already exists and doesn't match (1487-1492), losing an existing remote-placement contract (1493-1498), or post-callback ownership drift detected via a captured sessionVersion/expectedBody/expectedRuntime triple re-checked after the bind callback (1539-1549, "changed ownership during remote-motion binding"). Calls Entities.SetPhysicsBody(record, candidateBody) (:1558) ONLY when expectedBody is null (first bind) via InitializeNewPhysicsBody (:1556) — i.e. this is throw-on-conflict exclusivity (Option-1 flavor), not epoch/token gating. For a LOCAL PLAYER record this path should never fire (remote motion is for non-local entities) but the guard is defense-in-depth and IS one of the explicit gate checks (!activation.Record.RemoteMotionBindingInProgress/RemoteMotion is null) the dormant local-publication mechanism re-validates at every stage (section 4).
  • Projectile binding: RuntimeProjectilePhysicsUpdater.cs similarly only READS record.PhysicsBody (lines 447, 459, ReferenceEquals currency checks). The writer is RuntimePhysicsState.BindProjectile (RuntimePhysicsState.cs:1309-1382, full read) — same throw-on-conflict shape: binding-in-progress (1343-1347), body-mismatch on rebind (1330-1337), must-already-own-canonical-body-before-binding (1348-1352, "projectile must borrow its canonical physics body" — i.e. UNLIKE remote motion, BindProjectile requires record.PhysicsBody to ALREADY be non-null and matching BEFORE it will bind — it never calls InitializeNewPhysicsBody/SetPhysicsBody itself for a first-time body; something else (route 5's ProjectileController.TryBind, ProjectileController.cs:176-265 per the route inventory) must construct the body ad hoc first via a DIFFERENT path than GetOrCreatePhysicsBody — worth flagging: this is a 7th, App-side, ad hoc body-construction site not funneled through any of the 6 canonical writer methods — App's ProjectileController.TryBind constructs a body and must be setting it onto the record through some other route (not confirmed by this pass; App-side ProjectileController.cs was not read in full — flag as open item, but it is explicitly OUT of C1's local-player scope per the campaign handoff's gate list item "remote and projectile binding/update" being about interaction with the local-player transaction, not projectile's own authority).
  • RuntimeSetPositionState.SubmitPreparedPlacement (RuntimeSetPositionState.cs:2224-2274, full read): requires operation.Record.PhysicsBody is not { } body to already be true (line 2254) — i.e. EVERY non-initial-construction SetPosition submission (ForcePosition, portal, remote Position, projectile correction) requires a body to already exist on the record, confirming the 6 writer sites in section 1 are the exhaustive set of "who can put the FIRST body on a record." For the local player specifically, only RuntimeLocalPlayerPhysicsPublicationState.Commit (section 4) does this today (dormant, unwired); in PRODUCTION, no writer ever populates RuntimeEntityRecord.PhysicsBody for either host's local player (section 2 finding) — meaning SubmitPreparedPlacement would reject a local-player submission in production today, which is consistent with the route inventory's finding that Route 2 (ForcePosition) and Route 3 (portal) both bypass RuntimeSetPositionState entirely via their own duplicate authorities instead.
  • RuntimeSetPositionState.PrepareDormantLocalActivationOwnership: see section 4 — the ONE place the local-player-specific dormant body attach happens; requires a pre-opened Operation in stage AwaitingPreparation from TryBeginExclusiveAuthoredPlacement.
  • Object-clock epoch transitions (RuntimeEntityRecord.SuspendObjectClock/ResetObjectClockForEnterWorld, both internal, bump ObjectClockEpoch): full call-site grep found BOTH the expected RuntimeEntityDirectory wrapper call sites (which run EnsureKnown(record) first, RuntimeEntityDirectory.cs:311-321) AND direct unwrapped calls from src/AcDream.App/World/LiveEntityRuntime.cs at lines 879, 891, 897, 1258, 3030, 3033 — App calls record.SuspendObjectClock()/record.ResetObjectClockForEnterWorld(...) straight on the RuntimeEntityRecord (accessible because these are internal and AcDream.App has InternalsVisibleTo), bypassing the RuntimeEntityDirectory facade's EnsureKnown check entirely. This is inside LiveEntityRuntime's RebucketLiveEntity-family code (comment references prepare_to_enter_world/retail update_object's parent early-out — matches the already-known route-1/prerequisite-D RebucketLiveEntity duplicate authority). Previously-unstated implication for C1: the SAME record whose ObjectClockEpoch the dormant publication mechanism gates on can have its epoch bumped by this direct-call path DURING the window between RuntimeLocalPlayerPhysicsPublicationState.Prepare and .Commit()/ .CommitActivation() if RebucketLiveEntity runs concurrently for the SAME entity (e.g. a second CreateObject/Position causing a re-rebucket mid-construction) — the epoch check (IsCurrent/IsActivationOwnershipEnvelopeCurrent comparing record.ObjectClockEpoch == token.ObjectClockEpoch) WOULD catch and reject this correctly (fail-safe), but it confirms the gate is load- bearing against a REAL, already-existing production writer, not a hypothetical.
  • Deletion/teardown: RuntimeEntityObjectLifetime.TryAcceptDelete (RuntimeEntityObjectLifetime.cs:1555+) calls Entities.TryDelete then Entities.RemoveActive(active) (1587) — this IMMEDIATELY flips Entities.IsCurrent(record) to false for that record (removes it from the active-by-guid table), which is the single check CanPrepare/IsCurrent/IsActivationCurrent/every gate in section 4 depends on — so a delete landing at any point rejects the in-flight publication transaction on its NEXT check. Full body clear happens later in RuntimeEntityObjectLifetime.CompleteProjectionRetirement (:745-771, called from RetireCanonicalOnly/the graphical teardown-ack path): Entities.SetPhysicsBody(canonical, null) (:766) after Physics.SetPosition.Forget(canonical, releasePreparedMover: true) (:753, cancels any in-flight ordinary placement) and ForgetInitialCreateResidence(canonical) (:751, cancels any in-flight residence lease) — i.e. deletion cancels BOTH placement-lease families before clearing the body, consistent with prerequisite E's "quiesce before demote" discipline (though for landblock collision, not entity teardown — the pattern rhymes).
  • RuntimePhysicsState per-frame body access: RuntimeOrdinaryPhysicsUpdater.cs, RuntimeRemotePhysicsUpdater.cs, RuntimeProjectilePhysicsUpdater.cs each gate their per-tick work on record.PhysicsBody is not { } body / ReferenceEquals(record.PhysicsBody, body) currency checks (grep-confirmed, e.g. RuntimeOrdinaryPhysicsUpdater.cs:68,284) — read-only w.r.t. the PhysicsBody reference itself (they mutate the BODY's internal fields every tick, which is expected/normal simulation, not an ownership-slot write). No workset iterates and calls SetPhysicsBody. RuntimePhysicsState.cs itself has only two visible "workset" mentions (ClearSpatialWorksets at :1951, a doc-comment at :1307) — the ordinary/remote/projectile worksets live in their respective RuntimeXPhysicsUpdater files, out of this pass's read budget beyond the grep-confirmed read-only currency pattern above.

4. RuntimeSetPositionState.PrepareDormantLocalActivationOwnership — nucleus or dead end?

Definition (RuntimeSetPositionState.cs:1026-1052, full read):

internal void PrepareDormantLocalActivationOwnership(
    RuntimeEntityRecord record, PhysicsBody body,
    in RuntimeEntityPlacementToken token)
{
    ...
    if (!token.IsValid
        || record.Key != token.Entity
        || !_operations.TryGetValue(token.Entity, out Operation? operation)
        || operation.Token != token
        || operation.Stage is not RuntimeEntityPlacementStage.AwaitingPreparation
        || !ReferenceEquals(operation.Record, record)
        || record.PhysicsBody is not null       // <- record must have NO body yet
        || !IsCurrent(operation)
        || body.InWorld
        || (body.TransientState & TransientStateFlags.Active) != 0)
    {
        throw new InvalidOperationException(
            "Dormant local activation must bind to the exact current placement owner.");
    }
    operation.Body = body;
    operation.DormantLocalActivation = true;
}

It THROWS (does not return a status) on any invariant violation — by design a "this should be structurally impossible if the caller validated first" assertion, not a retryable rejection. It requires a PRE-EXISTING placement Operation already opened via TryBeginExclusiveAuthoredPlacement (RuntimeSetPositionState.cs:1004-1024) in stage AwaitingPreparation — i.e. it is NOT a standalone entry point; it is ONE STEP inside a larger chain that also needs prerequisite B's mover-preparation authority (IsExactPreparedPlacementCurrent, RuntimeSetPositionState.cs:1318-1340) satisfied for the SAME token/command before RuntimeLocalPlayerPhysicsPublicationState.CanPrepare will even call it.

What it was built for: it is called from exactly ONE place in the whole repo — RuntimeLocalPlayerPhysicsPublicationState.Commit (RuntimeLocalPlayerPhysicsPublicationState.cs:394-397), as the "seal the exact SetPosition owner before the irreversible no-fail suffix" step, immediately before candidate.Controller.CommitRuntimeOwnership(...) and candidate.Record.SetPhysicsBody(candidate.Body). It exists purely to make the PLACEMENT OPERATION (owned by RuntimeSetPositionState) and the BODY (owned by RuntimeEntityRecord) become mutually aware atomically, so that the SAME operation can later be walked through the full retail SetPosition staged commit (ground phase -> collision dispatch -> response -> final commit) via TryEvaluateDormantLocalActivation -> TryPrepareDormantLocalActivationCommit -> TryApplyDormantLocalActivationCommit -> TryPrepareDormantLocalActivationFinalCommit -> TryApplyDormantLocalActivationFinalCommit (RuntimeSetPositionState.cs:2025-2077, full read of the final-commit method) — the LAST of which is where _entities.SetFullCell, _entities.AdvancePlacementCommit, body.InWorld = true, _entities.SetPhysicsHost, controller.CommitRuntimeActivationFrame(), _physics.Engine.UpdatePlayerCurrCell, _physics.AcknowledgeSpatialProjection, _entities.ResetObjectClockForEnterWorld (object-clock epoch bump, task 3), and controller.ActivateRuntimePublication() (controller goes LIVE) ALL happen in one synchronous, no-branch-for-failure block (:2025-2077), gated immediately before by IsDormantLocalActivationPrephaseCurrent/re-validated epoch checks. This is genuinely the full retail SetPosition commit, already ported, already wired to the same body/controller the dormant publication candidate built.

Verdict: NUCLEUS, not a dead end — but it is only ONE LOAD-BEARING STEP inside a much larger, ALREADY-COMPLETE mechanism: RuntimeLocalPlayerPhysicsPublicationState (1033 lines, src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerPhysicsPublicationState.cs)

  • its ~15 RuntimeSetPositionState dormant-activation methods. Constructed once at GameRuntime.cs:261 and exposed via RuntimeLocalPlayerMovementState.PhysicsPublication (:59-61, itself internal, throws if unbound). Confirmed by exhaustive grep: ZERO production callers in src/AcDream.App/ or src/AcDream.Headless/ — the only callers anywhere are tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerPhysicsPublicationStateTests.cs. This is, functionally, Option 2 from the rejected-prototype note ("Off- canonical preparation followed by one validated atomic Runtime commit that publishes the prepared controller/body relationship without copying stale state over newer authority") already built end to end — complete with:
  • a Prepare/Commit/Discard triad for the BODY/CONTROLLER pair (analogous to, and reusing, the SAME token-epoch pattern as RuntimeSetPositionState's ordinary placement operations);
  • a SEPARATE EvaluateActivation/CommitActivation/DiscardActivation triad for actually driving the body through retail SetPosition's staged commit once the body/controller pair is sealed;
  • re-validation of PhysicsOwnershipEpoch, ObjectClockEpoch, ControllerOwnershipEpoch, SessionLifetimeVersion, identity ServerGuid+Revision, and null/in-progress state for RemoteMotion/ Projectile/PhysicsHost/DeleteAcceptedForTeardown at EVERY external entry point (CanPrepare, IsCurrent, IsActivationCurrent, IsActivationOwnershipEnvelopeCurrent, IsCommittedActivationSuffixCurrent) — this IS the reentrancy defense the rejected snapshot-lease prototype explicitly lacked (see section 6).

What is genuinely missing (the real C1 work), given this mechanism already exists:

  1. Nobody calls TryBeginExclusiveAuthoredPlacement + prerequisite B's PrepareMover/ReadSetupCollision chain + PhysicsPublication.Prepare/ Commit/EvaluateActivation/CommitActivation from either host — this IS the wiring gap, exactly like every other route in the cutover.
  2. The public RuntimeLocalPlayerMovementState.Controller setter (RuntimeLocalPlayerMovementState.cs:37-50) remains a live, unguarded escape hatch — both PlayerModeController.BuildControllerAndCamera:486 and HeadlessSessionWorldProjection.CreateController:653 write it directly today, and NOTHING stops either host from continuing to do so even after C1 wires the dormant mechanism, unless that direct-write path is deleted/sealed off (e.g. made internal to only RuntimeLocalPlayerPhysicsPublicationState/CommitRuntimeOwnedController). The setter has NO epoch/token check on write (CanCommitRuntimeOwnedController is a SEPARATE, unused-by-the-setter validation method) — it will happily accept a second unguarded assignment even while a dormant activation is in flight, silently retiring whatever the dormant mechanism just published (_controller?.RetireRuntimePublication() at :45, which is a real no-op for anything not currently RuntimeOwnedDormant/ RuntimePublished — see section 6 gate 7). This is the single most important pre-existing defect C1 must close: the "exclusive" adjective in prerequisite C's "one Runtime-owned exclusive/versioned...transaction" is not yet true while this direct setter remains reachable from hosts.
  3. Neither new PlayerMovementController(physics, objectClock, options) (public ctor, StandalonePublished) call site in the two hosts has been swapped for PlayerMovementController.CreatePublicationCandidate — until that swap happens, controllers built by either host never enter the CandidatePreparing/CandidateSealed/RuntimeOwnedDormant/RuntimePublished lifecycle the dormant mechanism's gates all key off of.

5. PlayerMovementController construction requirements

Constructor needs (from both direct-ctor call sites AND CreatePublicationCandidate, PlayerMovementController.cs:617-690):

  • PhysicsEngine physics (shared engine reference, both hosts pass their own RuntimePhysicsState/_runtime.EntityObjects.Physics.Engine).
  • RetailObjectQuantumClock? objectClock — App passes playerRecord.ObjectClock (the CANONICAL record's clock, RuntimeEntityRecord.ObjectClock at RuntimeEntityRecord.cs:62, always non-null per its field initializer); headless passes record.ObjectClock identically. The dormant mechanism's CreatePublicationCandidate instead passes a THROWAWAY new RetailObjectQuantumClock() (PlayerMovementController.cs:687-688) at construction time and only swaps in the REAL candidate.Record.ObjectClock later, inside Commit, via controller.CommitRuntimeOwnership(candidate.Record.ObjectClock) (RuntimeLocalPlayerPhysicsPublicationState.cs:403-404 -> PlayerMovementController.cs:774-786) — i.e. the dormant candidate is built against a SCRATCH clock so construction can never observe or mutate the canonical record's real clock before the atomic commit swaps it in. This is exactly the "off-canonical preparation" half of Option 2.
  • PlayerMovementConstructionOptions (RunSkill/JumpSkill) — both hosts build via PlayerMovementConstructionOptions.From(<a RuntimeMovementSkillState snapshot>); the dormant mechanism's Prepare also takes this as a caller- supplied parameter (Prepare(..., PlayerMovementConstructionOptions options, ...), :191) — no divergence in shape, only in WHERE the skill snapshot is read from (App's local _skills field vs. headless's _runtime.CharacterOwner.MovementSkills vs. the dormant mechanism taking it as a caller parameter either way).

What construction MUTATES (beyond the private _body): LocalEntityId, StepUpHeight/StepDownHeight (Setup-derived), SphereList (Setup-derived, prerequisite B territory), ObjectScale, initial position/orientation via PreparePositionForCommit/SetBodyOrientation, physics state via ApplyPhysicsState, MoveToFactory/PositionManager (MovementManager/MotionInterpreter wiring). ALL of this is exactly what RuntimeLocalPlayerPhysicsPublicationState.Prepare (RuntimeLocalPlayerPhysicsPublicationState.cs:187-355) already does against its private CreatePublicationCandidate-built controller, reading command.Physics.StepUpHeight/StepDownHeight/Spheres/Scale/Position/CellId/ CellLocalPosition/Orientation from the CALLER-SUPPLIED RuntimeSetPositionCommand (i.e. the command already carries everything prerequisite B's mover-preparation chain produces) rather than reaching into DAT/prepared-collision itself.

What an "off-canonical preparation followed by one validated atomic commit" must DEFER (confirmed by the dormant mechanism's own design, section 4):

  • The record's REAL ObjectClock (use a scratch clock during prep).
  • RuntimeEntityRecord.PhysicsBody/PhysicsOwnershipEpoch (never touch the canonical record during prep; only SetPhysicsBody inside Commit, and only after PrepareDormantLocalActivationOwnership succeeds).
  • RuntimeLocalPlayerMovementState.Controller/ControllerOwnershipEpoch (only via CommitRuntimeOwnedController, never the public setter, during prep).
  • body.InWorld/TransientState.Active (explicitly forced false during prep, Prepare, :292-293) — the body must not be simulatable until the LATER activation commit flips it (TryApplyDormantLocalActivationFinalCommit, body.InWorld = true at :2056).
  • World-residence/host/shadow/camera publication (all deferred to the activation phase / presentation-observer layer, never inside Prepare).

6. Adversarial gate list — exact code paths that would race TODAY

(Campaign handoff's list, docs/research/2026-07-31-remaining-physics-campaign-handoff.md:210-221.) For each: what the ALREADY-BUILT dormant mechanism does (if wired) vs. what the CURRENT production direct-construction paths do (today, unwired).

  1. Nested construction — Dormant: CanPrepare requires _activation is null AND record.PhysicsBody is null AND _movement.Controller is null (RuntimeLocalPlayerPhysicsPublicationState.cs:862-889) — a second Prepare while one is in flight is REJECTED structurally. Today: NEITHER BuildControllerAndCamera NOR CreateController has any such guard — CreateController's _runtime.MovementOwner.Controller ?? CreateController(record) (HeadlessSessionWorldProjection.cs:576-578) is a bare null-coalesce, not an atomic test-and-set; only single-threaded host-loop scheduling prevents an actual race today.
  2. Reentrant SetPosition — Dormant: every gate re-checks PhysicsOwnershipEpoch/record.PositionAuthorityVersion currency. Today: BuildControllerAndCamera's final mutation (playerEntity.SetPosition/ParentCellId/CommitPreparedPosition, PlayerModeController.cs:482-484) has zero epoch check — a same-thread reentrant call (e.g. from a nested wire dispatch) would silently clobber with no detection.
  3. Remote and projectile binding/update — Dormant: CanPrepare/ IsCurrent/IsActivationCurrent all check record.RemoteMotion is null, record.Projectile is null, !RemoteMotionBindingInProgress, !ProjectileBindingInProgress (defense-in-depth; should never legitimately fire for a local-player record). Today: no such check exists in either host's direct construction path.
  4. Deletion and same-GUID new incarnation — Dormant: _entities.IsCurrent(record) checked at every gate; TryAcceptDelete -> RemoveActive flips this immediately (section 3). Today: BuildControllerAndCamera/CreateController take a fixed RuntimeEntityRecord/WorldEntity parameter with NO re-validation against current canonical identity at the final commit.
  5. Projection-owner replacement — Dormant: _entities.SessionLifetimeVersion == token.SessionGenerationAuthority checked throughout. Today: no generation check in either direct path.
  6. Object-clock epoch change — Dormant: ObjectClockEpoch compared at every gate (section 3/4). Today: no check; AND there is a REAL, live concurrent writer already in production — LiveEntityRuntime.cs:879/891/897/1258/3030/3033's direct record.SuspendObjectClock()/ResetObjectClockForEnterWorld(...) calls inside the RebucketLiveEntity family (section 3) — this is not a hypothetical gate, it is a currently-active call path on the SAME record type.
  7. Reset and disposal — Dormant: ResetSession()/Dispose() on RuntimeLocalPlayerMovementState explicitly cascade into _physicsPublication?.ResetSession()/Dispose() (RuntimeLocalPlayerMovementState.cs:244-295), which tear down candidate/activation state via ReferenceEquals-gated clears (never clobbering a newer owner, DiscardActivation, RuntimeLocalPlayerPhysicsPublicationState.cs:1002-1032). Today's direct- construction controllers are built via the PUBLIC constructor (StandalonePublished lifecycle) — RetireRuntimePublication() (PlayerMovementController.cs:837-846) only transitions RuntimeOwnedDormant/RuntimePublished state; it is a NO-OP for StandalonePublished controllers — a previously-unstated finding: TODAY, ResetSession()/Dispose()/replacing .Controller on either host's directly-built controller produces NO explicit lifecycle transition at all; the controller is simply dropped/GC'd. Not a visible bug today (nothing reads IsRuntimePublished for these), but it means today's controllers are invisible to the exact teardown bookkeeping C1's target mechanism relies on.
  8. Commit and rollback after replacement — Dormant: ALL validation happens before the single canonical mutation (PrepareDormantLocalActivationOwnership, which itself throws leaving state untouched on failure); everything after is documented as "callback-free, non-allocating, and cannot fail" (RuntimeLocalPlayerPhysicsPublicationState.cs:391-393) — no rollback- after-newer-authority path exists BECAUSE nothing after that point can fail by construction. Today: BuildControllerAndCamera's try/catch rolls back camera+shadow only; the final playerEntity.SetPosition/ ParentCellId/CommitPreparedPosition triad (482-484) has nothing after it that can throw, so it's accidentally safe today, not structurally guaranteed.
  9. Late graphical camera/shadow/host failure — Today: BuildControllerAndCamera DOES handle this (explicit _camera.RestoreState/_shadow.Restore in the catch block, 494-518) — this is the ONE gate the CURRENT graphical path already handles reasonably. The dormant Runtime-side mechanism has NO camera/shadow concept (presentation-independent by design) — C1 must layer this handling in the PRESENTATION/observer phase (post-Runtime- commit), matching prerequisite D's rule that a host exception must not roll Runtime back, only retry the FIFO head.
  10. Late headless prepared-collision failure — Today: ApplySetupStepHeights (HeadlessSessionWorldProjection.cs:657-691) throws a raw, uncaught InvalidDataException (672-675) if the prepared Setup collision isn't Loaded — this propagates up through CreateController -> SynchronizeLocalPlayer -> ProjectSpawn/ ProjectPosition with NO try/catch anywhere in between (grep-confirmed no surrounding try/catch in HeadlessSessionWorldProjection.cs's these methods) — a genuinely unhandled-exception risk in production headless TODAY, not just a hypothetical C1 gate.

Summary for the C1 contract

  • Writer count: 6 confirmed call sites of RuntimeEntityRecord.SetPhysicsBody across 3 files (RuntimeEntityDirectory.cs:359, RuntimeEntityObjectLifetime.cs:766, RuntimeLocalPlayerPhysicsPublicationState.cs:405,1025, RuntimePhysicsState.cs:1558,1666) — plus a probable 7th App-side ad hoc projectile body-construction site not yet traced to a canonical writer (flagged, out of local-player scope).
  • The dormant RuntimeLocalPlayerPhysicsPublicationState + RuntimeSetPositionState's ~15 dormant-activation methods already implement essentially the COMPLETE Option 2 transaction (off-canonical prepare against a scratch clock/sealed candidate controller, single validated atomic commit, full retail-staged SetPosition activation) with epoch/token/generation/identity re-validation at every external entry point — it has ZERO production callers in either host.
  • The single largest remaining defect even AFTER wiring: the public RuntimeLocalPlayerMovementState.Controller setter is an unguarded escape hatch both hosts currently use directly; it must be sealed (made unreachable from hosts, or itself epoch-gated) for the word "exclusive" in prerequisite C to be true.