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>
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):
RuntimeEntityDirectory.cs:359— insideGetOrCreatePhysicsBody(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.RuntimeEntityObjectLifetime.cs:766—Entities.SetPhysicsBody(canonical, null)inside a teardown method (need to confirm exact method — likely delete/retire path, paired withEntities.SetPhysicsBodyAcquisitionInProgress(canonical, false)at line 767 in the SAME method). Clears body on deletion/teardown.RuntimeLocalPlayerPhysicsPublicationState.cs:405—candidate.Record.SetPhysicsBody(candidate.Body)insideCommit(token, out activationToken)(lines 373-411). THIS IS THE DORMANT OPTION-2 MECHANISM — see section 4 below. Guarded byIsCurrent(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.RuntimeLocalPlayerPhysicsPublicationState.cs:1025—_entities.SetPhysicsBody(activation.Record, null)insideDiscardActivation()(994-1032), the rollback/teardown path for the SAME dormant mechanism — only fires if_entities.IsCurrent(activation.Record)ANDReferenceEquals(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).RuntimePhysicsState.cs:1558—Entities.SetPhysicsBody(record, candidateBody)— need full read; this is inside the remote/projectile body-binding family (see section 3).RuntimePhysicsState.cs:1666—Entities.SetPhysicsBody(record, candidate)— need full read; this is the OTHER binding site, guarded byPhysicsBodyAcquisitionInProgress(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 atPreparetime (:314).RuntimeLocalPlayerPhysicsActivationToken.PhysicsOwnershipEpoch(:70) captured astoken.PhysicsOwnershipEpoch + 1UL(:328) — i.e. the activation token encodes "the epoch AFTER my own commit bumps it", soIsActivationCurrent(931-962) andIsActivationOwnershipEnvelopeCurrent(717-745) comparingactivation.Record.PhysicsOwnershipEpoch == activation.Token.PhysicsOwnershipEpochwill FAIL (reject) the instant any OTHER writer (remote/projectile bind, GC clear, non-player static body creation viaRuntimeEntityDirectory— 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 comparescandidate.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:
_approachCompletions.BeginControllerLifetime()(250) — App-only approach lifecycle token.- Capture rollback snapshots:
_camera.CaptureState()(255),_shadow.Capture()(256) — presentation-only. new PlayerMovementController(_physics, playerRecord.ObjectClock, PlayerMovementConstructionOptions.From(_skills.Snapshot))(259-262) — uses the PUBLIC constructor, whose default publication lifecycle isStandalonePublished(PlayerMovementController.cs:617-626), NOTCandidatePreparing/CreatePublicationCandidate. This is the key divergence from the dormant mechanism (section 4): this controller never enters theCandidatePreparing -> CandidateSealed -> RuntimeOwnedDormant -> RuntimePublishedlifecycle at all.- Builds
MoveToManager/EntityPhysicsHostclosures over captured locals (267-346) — presentation-adjacent glue, host-specific. EntityPhysicsHostComposition.SelectStableHostWithoutRebind(347-350) — canonical-state read (checksLiveEntityRecord.PhysicsHost).RuntimeMovementSkillProjection.ApplyTo(_skills, controller)(366-368).ApplyStepHeights(controller, playerEntity, playerGuid)(375) — readsDatReaderWriter.DBObjs.Setupdirectly (per headless's own comment contrasting itself,HeadlessSessionWorldProjection.cs:685-689) — NOT through the prepared-collision/IPreparedCollisionSourceseam headless uses. Divergence #1._controllerSlot.BeginMotionPreparation(controller, drainPriorAnimationQueue)(404-407) — the ONE existing narrow "preparation lease" concept already inRuntimeLocalPlayerMovementState(separate from the dormant physics publication state) that lets a synchronous PartArray/type-5 completion reach the candidateMotionInterpreterbefore publish.- Duplicate authority —
_physics.Resolve(...)(409-413) then_physics.ResolvePlacement(...)(422-430) — direct canonical-state-free collision resolve, entirely outsideRuntimeSetPositionState. controller.PreparePositionForCommit(...)(434-437),controller.SetBodyOrientation(...)(438).- 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. - 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
ReferenceEqualsre-read). - Shadow sync (
_shadow.SyncPose(...), 460-466). EntityPhysicsHostComposition.InstallOrRebind(...)(472-475) + anotherReferenceEqualsstability re-check (476-480).- Duplicate authority — final commit (482-484):
playerEntity.SetPosition(initial.Position); playerEntity.ParentCellId = initial.CellId; controller.CommitPreparedPosition();— direct writes to the App-sideWorldEntity/render sidecar ANDcontroller's own internal frame, bypassing any RuntimePlacereceipt orRuntimeEntityRecordwrite.RuntimeEntityRecord.PhysicsBody/PhysicsOwnershipEpochare NEVER touched anywhere in this method —controller.PhysicsBody(the_bodyfield created in step 3) stays a private field of theStandalonePublishedcontroller; nothing callsEntities.SetPhysicsBody(playerRecord, controller.PhysicsBody). This means TODAY the canonicalRuntimeEntityRecord.PhysicsBodyslot for the graphical local player is never populated at all by this path — a previously-unstated confirmation thatSubmitPreparedPlacement'soperation.Record.PhysicsBody is not { } bodyrequirement (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 mutatingPlayerMovementController's own body directly viaBlipPosition, never touchingRuntimeEntityRecord.PhysicsBodyeither — internally consistent with each other, both equally disconnected from the canonical record.) - Slot commits (485-492):
_hostSlot.Host,_controllerSlot.Controller = controller(the public, unguarded setter — bumpsControllerOwnershipEpochunconditionally, see section 4),_chase.Legacy/Retail,_mode.IsPlayerMode = true. catch: rolls back camera + shadow only (494-518); does NOT roll back steps 15-16 because those are the LAST lines beforelifetimeCommitted = 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):
- Guards on
record.ServerGuid == _runtime.PlayerIdentity.ServerGuidand a presentSnapshot.Position(568-573). _collision.CenterOn(position.LandblockId)(575) — headless collision- neighborhood readiness, no graphical analog._runtime.MovementOwner.Controller ?? CreateController(record)(576-578) — lazy-construct-once via the SAME publicControllergetter/setterRuntimeLocalPlayerMovementStateexposes; no reentrancy guard against two concurrent calls both observingnull(single-threaded host loop makes this safe in practice today, not structurally)._runtime.EntityObjects.Physics.Engine.Resolve(...)(589-594) then.ResolvePlacement(...)(595-605) — the exact same duplicate-authority shape as graphical step 9, hardcodedDefaultRadius/DefaultHeightconstants (visible in the call, actual values not read here) instead of_motionBindings.GetSetupCylinder.controller.SetPosition(...)+controller.SetBodyOrientation(...)(610-614) — duplicate final commit, headless's version of graphical step 15. Also never touchesRuntimeEntityRecord.PhysicsBody.
CreateController (639-655):
new PlayerMovementController(_runtime.EntityObjects.Physics.Engine, record.ObjectClock, PlayerMovementConstructionOptions.From(_runtime.CharacterOwner.MovementSkills.Snapshot))(642-646) — same PUBLIC constructor /StandalonePublishedlifecycle as graphical step 3.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). ThrowsInvalidDataExceptionif the read status isn'tLoaded(672-675) — propagates uncaught up throughSynchronizeLocalPlayer->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.RuntimeMovementSkillProjection.ApplyTo(_runtime.CharacterOwner.MovementSkills, controller)(650-652)._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)
- Setup/collision data source: App reads raw DAT (
ApplyStepHeightsvia_dats/_datLock); headless reads the prepared/baked asset (ApplySetupStepHeightsviaIPreparedCollisionSource). Same target values, different pipeline — a real fidelity risk if the two ever diverge (baking staleness). - Default cylinder fallback: App falls back to
0.48f/1.835finline (PlayerModeController.cs:416-420) whenGetSetupCylinderreturns< 0.05fradius; headless uses namedDefaultRadius/DefaultHeightconstants at theResolvePlacementcall site (:599-600) — same intended values, defined in two places. - Failure handling: App's
BuildControllerAndCamerahas an explicit try/catch/rollback for camera+shadow; headless'sCreateController/ApplySetupStepHeightshas NO surrounding try/catch — a prepared-collision read failure is a raw unhandled exception today. - Presentation surface: App additionally owns approach-completion lifetime, motion-preparation lease, chase camera, shadow sync — none of which headless has or needs.
- Neither host touches
RuntimeEntityRecord.PhysicsBody,PhysicsOwnershipEpoch, or anyRuntimeSetPositionStateAPI — 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 eitherPlayerModeController.csorHeadlessSessionWorldProjection.cs).
3. Every other body binding/consumer
- Remote dead-reckoning (
RuntimeRemotePhysicsUpdater.cs— flagged protected/dirty, read-only, NOT modified): grep confirms it only READSrecord.PhysicsBodyviaReferenceEquals(record.PhysicsBody, remote.Body)currency checks (line 817) — it does not callSetPhysicsBody. The actual writer for remote motion isRuntimePhysicsState.SetRemoteMotion(RuntimePhysicsState.cs:1446-1570, full read) — throwsInvalidOperationExceptionon: 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 capturedsessionVersion/expectedBody/expectedRuntimetriple re-checked after the bind callback (1539-1549, "changed ownership during remote-motion binding"). CallsEntities.SetPhysicsBody(record, candidateBody)(:1558) ONLY whenexpectedBody is null(first bind) viaInitializeNewPhysicsBody(: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.cssimilarly only READSrecord.PhysicsBody(lines 447, 459,ReferenceEqualscurrency checks). The writer isRuntimePhysicsState.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,BindProjectilerequiresrecord.PhysicsBodyto ALREADY be non-null and matching BEFORE it will bind — it never callsInitializeNewPhysicsBody/SetPhysicsBodyitself for a first-time body; something else (route 5'sProjectileController.TryBind,ProjectileController.cs:176-265per the route inventory) must construct the body ad hoc first via a DIFFERENT path thanGetOrCreatePhysicsBody— worth flagging: this is a 7th, App-side, ad hoc body-construction site not funneled through any of the 6 canonical writer methods — App'sProjectileController.TryBindconstructs a body and must be setting it onto the record through some other route (not confirmed by this pass; App-sideProjectileController.cswas 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): requiresoperation.Record.PhysicsBody is not { } bodyto 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, onlyRuntimeLocalPlayerPhysicsPublicationState.Commit(section 4) does this today (dormant, unwired); in PRODUCTION, no writer ever populatesRuntimeEntityRecord.PhysicsBodyfor either host's local player (section 2 finding) — meaningSubmitPreparedPlacementwould 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 bypassRuntimeSetPositionStateentirely 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-openedOperationin stageAwaitingPreparationfromTryBeginExclusiveAuthoredPlacement.- Object-clock epoch transitions
(
RuntimeEntityRecord.SuspendObjectClock/ResetObjectClockForEnterWorld, bothinternal, bumpObjectClockEpoch): full call-site grep found BOTH the expectedRuntimeEntityDirectorywrapper call sites (which runEnsureKnown(record)first,RuntimeEntityDirectory.cs:311-321) AND direct unwrapped calls fromsrc/AcDream.App/World/LiveEntityRuntime.csat lines 879, 891, 897, 1258, 3030, 3033 — App callsrecord.SuspendObjectClock()/record.ResetObjectClockForEnterWorld(...)straight on theRuntimeEntityRecord(accessible because these areinternalandAcDream.ApphasInternalsVisibleTo), bypassing theRuntimeEntityDirectoryfacade'sEnsureKnowncheck entirely. This is insideLiveEntityRuntime'sRebucketLiveEntity-family code (comment referencesprepare_to_enter_world/retailupdate_object's parent early-out — matches the already-known route-1/prerequisite-DRebucketLiveEntityduplicate authority). Previously-unstated implication for C1: the SAME record whoseObjectClockEpochthe dormant publication mechanism gates on can have its epoch bumped by this direct-call path DURING the window betweenRuntimeLocalPlayerPhysicsPublicationState.Prepareand.Commit()/.CommitActivation()ifRebucketLiveEntityruns concurrently for the SAME entity (e.g. a second CreateObject/Position causing a re-rebucket mid-construction) — the epoch check (IsCurrent/IsActivationOwnershipEnvelopeCurrentcomparingrecord.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+) callsEntities.TryDeletethenEntities.RemoveActive(active)(1587) — this IMMEDIATELY flipsEntities.IsCurrent(record)tofalsefor that record (removes it from the active-by-guid table), which is the single checkCanPrepare/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 inRuntimeEntityObjectLifetime.CompleteProjectionRetirement(:745-771, called fromRetireCanonicalOnly/the graphical teardown-ack path):Entities.SetPhysicsBody(canonical, null)(:766) afterPhysics.SetPosition.Forget(canonical, releasePreparedMover: true)(:753, cancels any in-flight ordinary placement) andForgetInitialCreateResidence(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). RuntimePhysicsStateper-frame body access:RuntimeOrdinaryPhysicsUpdater.cs,RuntimeRemotePhysicsUpdater.cs,RuntimeProjectilePhysicsUpdater.cseach gate their per-tick work onrecord.PhysicsBody is not { } body/ReferenceEquals(record.PhysicsBody, body)currency checks (grep-confirmed, e.g.RuntimeOrdinaryPhysicsUpdater.cs:68,284) — read-only w.r.t. thePhysicsBodyreference itself (they mutate the BODY's internal fields every tick, which is expected/normal simulation, not an ownership-slot write). No workset iterates and callsSetPhysicsBody.RuntimePhysicsState.csitself has only two visible "workset" mentions (ClearSpatialWorksetsat :1951, a doc-comment at :1307) — the ordinary/remote/projectile worksets live in their respectiveRuntimeXPhysicsUpdaterfiles, 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
RuntimeSetPositionStatedormant-activation methods. Constructed once atGameRuntime.cs:261and exposed viaRuntimeLocalPlayerMovementState.PhysicsPublication(:59-61, itselfinternal, throws if unbound). Confirmed by exhaustive grep: ZERO production callers insrc/AcDream.App/orsrc/AcDream.Headless/— the only callers anywhere aretests/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/Discardtriad for the BODY/CONTROLLER pair (analogous to, and reusing, the SAME token-epoch pattern asRuntimeSetPositionState's ordinary placement operations); - a SEPARATE
EvaluateActivation/CommitActivation/DiscardActivationtriad 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, identityServerGuid+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:
- Nobody calls
TryBeginExclusiveAuthoredPlacement+ prerequisite B'sPrepareMover/ReadSetupCollisionchain +PhysicsPublication.Prepare/Commit/EvaluateActivation/CommitActivationfrom either host — this IS the wiring gap, exactly like every other route in the cutover. - The public
RuntimeLocalPlayerMovementState.Controllersetter (RuntimeLocalPlayerMovementState.cs:37-50) remains a live, unguarded escape hatch — bothPlayerModeController.BuildControllerAndCamera:486andHeadlessSessionWorldProjection.CreateController:653write 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. madeinternalto onlyRuntimeLocalPlayerPhysicsPublicationState/CommitRuntimeOwnedController). The setter has NO epoch/token check on write (CanCommitRuntimeOwnedControlleris 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 currentlyRuntimeOwnedDormant/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. - Neither
new PlayerMovementController(physics, objectClock, options)(public ctor,StandalonePublished) call site in the two hosts has been swapped forPlayerMovementController.CreatePublicationCandidate— until that swap happens, controllers built by either host never enter theCandidatePreparing/CandidateSealed/RuntimeOwnedDormant/RuntimePublishedlifecycle 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 ownRuntimePhysicsState/_runtime.EntityObjects.Physics.Engine).RetailObjectQuantumClock? objectClock— App passesplayerRecord.ObjectClock(the CANONICAL record's clock,RuntimeEntityRecord.ObjectClockatRuntimeEntityRecord.cs:62, always non-null per its field initializer); headless passesrecord.ObjectClockidentically. The dormant mechanism'sCreatePublicationCandidateinstead passes a THROWAWAYnew RetailObjectQuantumClock()(PlayerMovementController.cs:687-688) at construction time and only swaps in the REALcandidate.Record.ObjectClocklater, insideCommit, viacontroller.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 viaPlayerMovementConstructionOptions.From(<a RuntimeMovementSkillState snapshot>); the dormant mechanism'sPreparealso 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_skillsfield vs. headless's_runtime.CharacterOwner.MovementSkillsvs. 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; onlySetPhysicsBodyinsideCommit, and only afterPrepareDormantLocalActivationOwnershipsucceeds).RuntimeLocalPlayerMovementState.Controller/ControllerOwnershipEpoch(only viaCommitRuntimeOwnedController, 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 = trueat :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).
- Nested construction — Dormant:
CanPreparerequires_activation is nullANDrecord.PhysicsBody is nullAND_movement.Controller is null(RuntimeLocalPlayerPhysicsPublicationState.cs:862-889) — a secondPreparewhile one is in flight is REJECTED structurally. Today: NEITHERBuildControllerAndCameraNORCreateControllerhas 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. - Reentrant SetPosition — Dormant: every gate re-checks
PhysicsOwnershipEpoch/record.PositionAuthorityVersioncurrency. 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. - Remote and projectile binding/update — Dormant:
CanPrepare/IsCurrent/IsActivationCurrentall checkrecord.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. - Deletion and same-GUID new incarnation — Dormant:
_entities.IsCurrent(record)checked at every gate;TryAcceptDelete->RemoveActiveflips this immediately (section 3). Today:BuildControllerAndCamera/CreateControllertake a fixedRuntimeEntityRecord/WorldEntityparameter with NO re-validation against current canonical identity at the final commit. - Projection-owner replacement — Dormant:
_entities.SessionLifetimeVersion == token.SessionGenerationAuthoritychecked throughout. Today: no generation check in either direct path. - Object-clock epoch change — Dormant:
ObjectClockEpochcompared 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 directrecord.SuspendObjectClock()/ResetObjectClockForEnterWorld(...)calls inside theRebucketLiveEntityfamily (section 3) — this is not a hypothetical gate, it is a currently-active call path on the SAME record type. - Reset and disposal — Dormant:
ResetSession()/Dispose()onRuntimeLocalPlayerMovementStateexplicitly cascade into_physicsPublication?.ResetSession()/Dispose()(RuntimeLocalPlayerMovementState.cs:244-295), which tear down candidate/activation state viaReferenceEquals-gated clears (never clobbering a newer owner,DiscardActivation,RuntimeLocalPlayerPhysicsPublicationState.cs:1002-1032). Today's direct- construction controllers are built via the PUBLIC constructor (StandalonePublishedlifecycle) —RetireRuntimePublication()(PlayerMovementController.cs:837-846) only transitionsRuntimeOwnedDormant/RuntimePublishedstate; it is a NO-OP forStandalonePublishedcontrollers — a previously-unstated finding: TODAY,ResetSession()/Dispose()/replacing.Controlleron 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 readsIsRuntimePublishedfor these), but it means today's controllers are invisible to the exact teardown bookkeeping C1's target mechanism relies on. - 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 finalplayerEntity.SetPosition/ParentCellId/CommitPreparedPositiontriad (482-484) has nothing after it that can throw, so it's accidentally safe today, not structurally guaranteed. - Late graphical camera/shadow/host failure — Today:
BuildControllerAndCameraDOES handle this (explicit_camera.RestoreState/_shadow.Restorein 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. - Late headless prepared-collision failure — Today:
ApplySetupStepHeights(HeadlessSessionWorldProjection.cs:657-691) throws a raw, uncaughtInvalidDataException(672-675) if the prepared Setup collision isn'tLoaded— this propagates up throughCreateController->SynchronizeLocalPlayer->ProjectSpawn/ProjectPositionwith NO try/catch anywhere in between (grep-confirmed no surrounding try/catch inHeadlessSessionWorldProjection.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.SetPhysicsBodyacross 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.Controllersetter 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.