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>
This commit is contained in:
Erik 2026-08-02 05:33:29 +02:00
parent ae29639307
commit 6460596b56
2 changed files with 699 additions and 6 deletions

View file

@ -0,0 +1,681 @@
# 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:766`** — `Entities.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:405`** — `candidate.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:1558`** — `Entities.SetPhysicsBody(record, candidateBody)`
— need full read; this is inside the remote/projectile body-binding family
(see section 3).
6. **`RuntimePhysicsState.cs:1666`** — `Entities.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 method** — `controller.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):
```csharp
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.