The continuation executor (5db3de3c) completed the dormant residence
mechanism; the cutover is the campaign leg that makes it production truth.
The committed 8-route inventory maps every duplicate placement authority in
both hosts with exact call chains, confirms the placement-receipt observer
seam is fully built but unattached, and surfaces five pre-cutover gaps the
shipped mechanism cannot yet express (executor-to-channel bridge, atomic
controller/body publication, the 1,880 B/op activation budget, Runtime-side
live-input derivation, the portal-authority adapter). The plan decomposes
the cutover into C0-C5 bisectable slices under the campaign's standing
contract/dual-review/gate discipline, ending at the connected routes and
the user visual matrix that retire AP-1/AD-1.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1042 lines
64 KiB
Markdown
1042 lines
64 KiB
Markdown
# Production cutover — 8-route inventory (2026-08-02)
|
|
|
|
Repo: `C:\Users\erikn\.codex\worktrees\af5e\acdream`, branch `codex/port-claude-agents`,
|
|
HEAD `9ad590dc`. READ-ONLY research; this file is the only write target.
|
|
|
|
Sources read first:
|
|
- `docs/research/2026-08-02-runtime-continuation-executor-handoff.md` (executor mechanism, complete/dormant)
|
|
- `docs/research/2026-07-31-remaining-physics-campaign-handoff.md` sections "What remains" (prereqs A-E) and "Production route cutover" (routes 1-8)
|
|
- `docs/research/2026-07-31-canonical-set-position.md` (1,880 B/op allocation finding, line 327-333)
|
|
- scratchpad `runtime-surface.md` sections 8-9 (GameRuntime construction, the two legacy `RegisterEntity` call sites)
|
|
|
|
Key confirmed facts carried in from the map:
|
|
- `RuntimeInitialCreateResidenceState`/executor is a COMPLETE, TESTED, but 100% UNWIRED
|
|
mechanism. Grep confirms (map file, line 545-549) the ONLY two production Create call
|
|
sites are:
|
|
- `src/AcDream.Runtime/Session/RuntimeLiveEntitySessionController.cs:76` — `Entities.RegisterEntity(spawn)` (headless/direct-host)
|
|
- `src/AcDream.App/World/LiveEntityRuntime.cs:537` — `_entityObjects.RegisterEntity(...)` (graphical/App)
|
|
Neither calls `RegisterEntityWithInitialResidence`. `CompleteInitialCreateResidence`/
|
|
`AcknowledgeInitialCreateResidenceAdoption` have NO production caller anywhere.
|
|
- `GameRuntime.cs:180-183` constructs `RuntimeEntityObjectLifetime` once as `context.EntityObjects`.
|
|
`BindEventContext` at `GameRuntime.cs:266-269` supplies the generation function
|
|
`() => generationReset.ActiveRetiringGeneration ?? context.Session.Generation`.
|
|
- 1,880 B/op measured on the warmed dormant placement commit/ack route vs a 2,048 B cap
|
|
(`docs/research/2026-07-31-canonical-set-position.md:327-333`) — explicit 4B2 activation
|
|
blocker, not yet resolved (pool/remove envelopes or record an approved budget).
|
|
|
|
---
|
|
|
|
## Cross-cutting: observer-seam status (grep results)
|
|
|
|
`IRuntimePlacementObserver` **already exists** as a Runtime-side interface —
|
|
it is NOT vaporware, contrary to a naive reading of "prerequisite D" as
|
|
all-still-to-build:
|
|
|
|
- Defined `src/AcDream.Runtime/Entities/RuntimeEntityObjectEventStream.cs:18-21`
|
|
(`interface IRuntimePlacementObserver { void OnPlacement(in RuntimePlacementDelta delta); }`).
|
|
- `RuntimeEntityObjectEventStream` (same file) already has the full pub/sub
|
|
plumbing: `_placementObservers` array (line 40), `SubscribePlacement`
|
|
(119-133, copy-on-write add), `UnsubscribePlacement` (356-369), dispatch
|
|
loop (302-310ish inside the drain routine), `PublishPlacement` (164-171,
|
|
builds a `RuntimePlacementDelta(NextStamp(), placement)` and enqueues it
|
|
through the same ordered synchronous drain as entity/inventory deltas).
|
|
- Public host seam: `src/AcDream.Runtime/Physics/RuntimePlacementProjectionChannel.cs`
|
|
(88 lines, read in full) — `Subscribe(IRuntimePlacementObserver)` (31-32,
|
|
thin passthrough to `_events.SubscribePlacement`), `Acknowledge(expectedGeneration, token)`
|
|
(51-55, generation-gated call into `_setPosition.AcknowledgeProjection`),
|
|
`RetryPending` (61-67), `TryPeek` (72-80, non-consuming), `PendingCount` (82).
|
|
This channel is constructed once inside `RuntimeEntityObjectLifetime`'s ctor
|
|
(`Placements = new RuntimePlacementProjectionChannel(Events, Physics.SetPosition)`,
|
|
per the surface map's file-1 section) and exposed on `GameRuntime.Placements`.
|
|
- Receipt shape: `RuntimePlacementProjectionSnapshot` (`RuntimeSetPositionState.cs:210-217`):
|
|
`Token, Kind (Withdraw|Place|Discard — enum at line 57-61), WorldPosition,
|
|
Orientation, CellLocalPosition, InContact, OnWalkable`.
|
|
- **What does NOT exist**: grep across `src/AcDream.App/**/*.cs` and
|
|
`src/AcDream.Headless/**/*.cs` for `IRuntimePlacementObserver`,
|
|
`SubscribePlacement`, or `Placements.Subscribe` returns ZERO matches
|
|
(confirmed via `grep -rln` across both trees; the only hits anywhere in the
|
|
repo outside `src/AcDream.Runtime/` are 5 Runtime-test fake-observer classes
|
|
in `tests/AcDream.Runtime.Tests/**`, e.g.
|
|
`RuntimeInitialCreateResidenceStateTests.cs:2651`,
|
|
`RuntimeSetPositionStateTests.cs:2610`/`2641` (`ThrowingPlacementObserver`),
|
|
`RuntimeCollisionPrefixQuiescenceTests.cs:877`,
|
|
`RuntimeLocalPlayerPhysicsPublicationStateTests.cs:2326`). **Neither host
|
|
has ever constructed a production `IRuntimePlacementObserver` implementation.**
|
|
Prerequisite D ("Add one graphical and one headless `IRuntimePlacementObserver`
|
|
using `GameRuntime.Placements`") is 100% unbuilt on the host side even though
|
|
the Runtime-side plumbing it will attach to is complete and tested.
|
|
|
|
## Cross-cutting: connected-gate harness (exact scripts/env)
|
|
|
|
- **Exact lifecycle/reconnect gate**: `tools/run-connected-world-lifecycle-gate.ps1`
|
|
(477 lines, read in full). Drives two sequential `AcDream.App.exe` sessions
|
|
against the always-up local ACE (127.0.0.1:9000):
|
|
1. `capped` session using route script `tools/connected-world-lifecycle.route.txt`,
|
|
expects exactly 6 named checkpoints (`capped_login, aerlinthe_first, rynthid,
|
|
facility_hub, holtburg_after_dungeon, aerlinthe_revisit`) and 5 screenshots.
|
|
2. `uncapped-reconnect` session (starts as soon as ACE's own log records the
|
|
first session's transport Disconnect — no artificial settle delay) using
|
|
`tools/connected-world-reconnect.route.txt`, expects one checkpoint
|
|
`uncapped_reconnect`.
|
|
Per-session env (lines 250-281): `ACDREAM_DAT_DIR`, `ACDREAM_LIVE=1`,
|
|
`ACDREAM_TEST_HOST=127.0.0.1`, `ACDREAM_TEST_PORT=9000`,
|
|
`ACDREAM_TEST_USER`/`ACDREAM_TEST_PASS`, `ACDREAM_RETAIL_UI=1`,
|
|
`ACDREAM_FRAME_PROF=1`, `ACDREAM_UNCAPPED_RENDER` (1 for the reconnect leg
|
|
only), `ACDREAM_UI_PROBE_SCRIPT=<route.txt path>`,
|
|
`ACDREAM_AUTOMATION_ARTIFACT_DIR=<per-session artifact dir>`; several other
|
|
vars explicitly cleared (`ACDREAM_RENDER_BACKEND`, `ACDREAM_NET_DROP_*`,
|
|
`ACDREAM_DUMP_MOVE_TRUTH`, `ACDREAM_WB_DIAG`) to keep the gate a clean
|
|
baseline. `Validate-Checkpoint` (140-227) asserts, per checkpoint: reveal
|
|
readiness/no invariant failures, Runtime environment ownership initialized
|
|
with exactly one active day group, **every `transitOwnership` counter
|
|
(`bufferedTeleportDestinationCount, pendingTeleportStartCount,
|
|
activeTeleportCount, acceptedTeleportDestinationCount, activeRevealCount,
|
|
pendingDestinationReadinessCount, hostProjectionCount,
|
|
pendingHostAcknowledgementCount`) is exactly zero at a stable checkpoint**,
|
|
zero pending live teardowns/landblock retirements/staged mesh
|
|
uploads/composite warmups, and (optionally) zero collision-shadow
|
|
mismatches/faults. Client must exit only via graceful `WM_CLOSE`
|
|
(`Close-ClientGracefully`, 85-92) and ACE's own log must record
|
|
`graceful logout confirmed` + a `PacketHeader Disconnect` drop
|
|
(347-354) — **this cutover MUST add placement-owner counters
|
|
(pending placement receipts, active leases, pending FIFO continuations) to
|
|
this SAME zero-at-stable-checkpoint discipline**, matching the existing
|
|
`transitOwnership`/`resources` pattern, or the gate will not actually prove
|
|
placement convergence.
|
|
- **Canonical nine-stop route**: `tools/run-connected-r6-soak.ps1` (only first
|
|
60 lines read) driving `tools/connected-r6-soak.route.txt`, 9 named
|
|
checkpoints in order: `caul-baseline, sawato-baseline, rynthid, aerlinthe,
|
|
sawato-return, holtburg, caul-return, sawato-plateau, caul-plateau` (Caul/
|
|
Sawato/Holtburg legs additionally "Exercise"d — likely movement stress, not
|
|
just a static stop). Supports `-Uncapped`/`-DenseTown` switches (DenseTown
|
|
swaps in `connected-dense-town.route.txt` against a single `arwic-dense`
|
|
checkpoint instead). Same env-var family as the lifecycle gate
|
|
(`-SkipBuild`, `-LoginTimeoutSeconds`, `-CollisionShadowEvery`). Historical
|
|
evidence of both capped and uncapped passing runs:
|
|
`docs/research/2026-07-25-slice-g5-production-profile.md:38-39`
|
|
(`logs/connected-r6-soak-20260725-105133.report.json` /
|
|
`...-121035.report.json`).
|
|
- Both scripts require a pre-existing, already-listening local ACE on UDP
|
|
9000 and a live `AceLogPath` (default `C:\ACE\Server\ACE_Log.txt`) and will
|
|
themselves invoke `dotnet build ... -c Release` unless `-SkipBuild`.
|
|
|
|
## Cross-cutting: 1,880 B/op allocation budget (4B2 activation blocker)
|
|
|
|
`docs/research/2026-07-31-canonical-set-position.md:327-333`: "The warmed
|
|
immediate commit/ack route currently measures exactly **1,880 managed bytes
|
|
per operation** in the Release Runtime test host (1,000 iterations after 64
|
|
warmups); the regression gate caps it at 2,048 bytes. This dormant-path
|
|
result is an explicit 4B2 activation blocker rather than a claim of
|
|
allocation-free production readiness: 4B2 must either pool/remove the
|
|
operation and projection envelopes or record an approved measured budget
|
|
before routing frame-frequency placement through this owner." Restated
|
|
verbatim in the executor handoff
|
|
(`docs/research/2026-08-02-runtime-continuation-executor-handoff.md:181-184`).
|
|
**Not yet resolved as of this research pass** — no commit after
|
|
`2026-07-31-canonical-set-position.md` claims pooling/removal of the
|
|
operation/projection envelopes, and the executor handoff still calls it "the
|
|
standing 4B2 activation blocker." This matters most for routes 2 (Local
|
|
ForcePosition, frame-frequency-ish under repeated corrections), 4 (remote
|
|
Position, genuinely per-network-tick high frequency), and 5 (projectile
|
|
per-quantum integration, explicitly excluded from SetPosition routing for
|
|
exactly this reason per route 5's "Do not route ordinary per-quantum
|
|
projectile integration through SetPosition").
|
|
|
|
## Cross-cutting: `Execute`'s caller-supplied live inputs are unwired in BOTH hosts
|
|
|
|
`RuntimeInitialCreateContinuationExecutor.Execute` takes an
|
|
`in RuntimeInitialCreateExecutionInputs inputs` parameter
|
|
(`RuntimeInitialCreateContinuationExecutor.cs:47-49`):
|
|
`bool UsePositionFromServer, float PlayerDistance` — doc comment (38-46):
|
|
"Executor-time inputs sampled at the retail decision point. These cannot be
|
|
retained at admission time because they describe LIVE state (the local
|
|
player, the current physics simulation)... contact is NOT one of these — it
|
|
comes solely from the retained wire packet's own `IsGrounded` bit, never from
|
|
a live body query." Both fields feed directly into
|
|
`RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition`'s
|
|
`RuntimeAcceptedPositionRouteRequest.UsePositionFromServer`/`PlayerDistance`
|
|
fields (`RuntimeAuthoritativePositionRouteClassifier.cs:137`, consumed at
|
|
line 338 for the LocalPlayer interpolate-vs-not branch and later for the
|
|
Remote/Projectile near/far 96 m threshold). **Grep across
|
|
`src/AcDream.App/` and `src/AcDream.Headless/` for `UsePositionFromServer`
|
|
returns ZERO source hits (only compiled `bin`/`obj` binary matches).**
|
|
Neither host currently computes or tracks a `UsePositionFromServer` concept
|
|
anywhere in production code — this is a genuinely new per-call input a
|
|
cutover caller must supply on every `Execute` call, not something that can be
|
|
threaded through unchanged from an existing option/flag. `PlayerDistance` is
|
|
likely derivable today from existing local-player/WorldEntity state (it is
|
|
NOT a new concept — some form of distance-to-local-player computation
|
|
already exists for other purposes, e.g. streaming radius), but it has never
|
|
been wired specifically into this call contract either.
|
|
|
|
---
|
|
|
|
## Prerequisite C context (atomic local controller/body publication)
|
|
|
|
Campaign handoff (`docs/research/2026-07-31-remaining-physics-campaign-handoff.md:203-221`)
|
|
requires ONE Runtime-owned exclusive/versioned controller/body publication
|
|
transaction "respected by **every** canonical body writer" — rejects the
|
|
prior prototype (a snapshot/rollback lease) as failure-atomic-only, not
|
|
reentrant-safe (line 143-168: a nested SetPosition/remote-bind/GUID-reuse/
|
|
clock-epoch-change/disposal racing the open lease could let the outer
|
|
rollback erase newer authority or detach a body another owner already uses).
|
|
|
|
Today's per-host body construction (both confirmed by direct read, both are
|
|
literally the two things prerequisite C must unify):
|
|
- **Graphical**: `PlayerModeController.BuildControllerAndCamera`
|
|
(`src/AcDream.App/Input/PlayerModeController.cs:244-...`, only the first
|
|
~140 lines read directly by me) constructs `new PlayerMovementController(...)`
|
|
(line 259-262) directly against `_physics`/`playerRecord.ObjectClock`, wires
|
|
a `MoveToManager` facade and `EntityPhysicsHost` closures over captured
|
|
locals (267-345+), and stores the result in the App-local `_controllerSlot.Controller`
|
|
(not `_runtime.MovementOwner.Controller` directly at this point in the file —
|
|
Agent 1's detailed route-1 report is authoritative for exactly where/whether
|
|
this crosses into `RuntimeLocalPlayerMovementState`/`GameRuntime.MovementOwner`).
|
|
- **Headless**: `HeadlessSessionWorldProjection.CreateController`
|
|
(`src/AcDream.Headless/Hosting/HeadlessSessionWorldProjection.cs:639-655`)
|
|
constructs `new PlayerMovementController(_runtime.EntityObjects.Physics.Engine,
|
|
record.ObjectClock, PlayerMovementConstructionOptions.From(...))` (642-646),
|
|
applies physics state/step heights/movement skills (647-652), and is the
|
|
ONLY production writer of `_runtime.MovementOwner.Controller = controller`
|
|
in the entire repo (confirmed by grep — the graphical host does NOT write
|
|
`_runtime.MovementOwner.Controller` anywhere findable by that exact token;
|
|
it may go through a different Runtime seam — flag as an open question for
|
|
whoever designs prerequisite C: **confirm whether the graphical host's
|
|
built controller is ever actually published into `RuntimeLocalPlayerMovementState`
|
|
the same way headless's is, or whether this is itself an existing
|
|
graphical/headless asymmetry prerequisite C must resolve**).
|
|
- `HeadlessSessionWorldProjection.SynchronizeLocalPlayer`
|
|
(566-615, read in full) is the duplicate placement authority for headless
|
|
route 1/8: calls `_runtime.EntityObjects.Physics.Engine.Resolve(...)` (589-594)
|
|
then `.ResolvePlacement(...)` (595-605) directly, then
|
|
`controller.SetPosition(resolved.Position, resolved.CellId, wirePosition)`
|
|
(610-613) and `controller.SetBodyOrientation(orientation)` (614) — entirely
|
|
independent of `RuntimeSetPositionState`/`RuntimeInitialCreateResidenceState`.
|
|
`HeadlessSessionWorldProjection.BlipLocalPlayer` (617-637) similarly calls
|
|
`controller.BlipPosition(...)` (633-636) directly with no Runtime placement
|
|
commit/ack in between — this is the headless twin of route 2's "direct
|
|
BlipPosition/pre-commit acknowledgement" duplicate authority.
|
|
|
|
---
|
|
|
|
## Cross-cutting: accessibility is NOT a blocker (correction of an initial hypothesis)
|
|
|
|
I initially suspected the residence/executor surface being entirely `internal`
|
|
(`RegisterEntityWithInitialResidence`, `InitialCreateResidences`,
|
|
`InitialCreateExecution`, `Execute`, `CompleteInitialCreateResidence`,
|
|
`AcknowledgeInitialCreateResidenceAdoption` — confirmed all `internal` via
|
|
direct grep of `RuntimeEntityObjectLifetime.cs` and
|
|
`RuntimeInitialCreateContinuationExecutor.cs:426`) might block the graphical
|
|
host (a different assembly) from calling them at all. **This is WRONG —
|
|
verify before citing it as a gap.** `src/AcDream.Runtime/AcDream.Runtime.csproj:11-16`
|
|
declares `InternalsVisibleTo` for `AcDream.Runtime.Tests`, **`AcDream.App`**,
|
|
`AcDream.App.Tests`, `AcDream.Core.Tests`, **`acdream-headless`**, and
|
|
`AcDream.Headless.Tests` — both hosts already have full internal-member
|
|
access to `AcDream.Runtime`. Route 1's research agent independently confirmed
|
|
this at the exact same csproj lines. **There is no accessibility barrier to
|
|
either host calling `RegisterEntityWithInitialResidence`/driving the executor
|
|
today** — the reason nobody does is purely that the call sites haven't been
|
|
switched yet, not an assembly-boundary problem. Do not resurrect this as a
|
|
"gap" in the final report.
|
|
|
|
---
|
|
|
|
## Route 1 — Initial login and CreateObject (from parallel research agent, spot-checked)
|
|
|
|
### Graphical host chain (wire -> presentation)
|
|
|
|
1. `src/AcDream.App/Net/LiveEntitySessionController.cs:51-53` — `OnSpawned` wire
|
|
entry, routes `CreateObject` via `RetailInboundEventDispatcher.Run` to
|
|
`LiveEntityHydrationController.OnCreate`.
|
|
2. `src/AcDream.App/World/LiveEntityHydrationController.cs:226-250` — `OnCreate`
|
|
(dormant/stale-generation classification) -> `OnCreateCore`.
|
|
3. `LiveEntityHydrationController.cs:259-262` — `OnCreateCore` (under `_datLock`)
|
|
calls `_runtime.RegisterLiveEntity(spawn)`.
|
|
4. `src/AcDream.App/World/LiveEntityRuntime.cs:526-539`, **line 537**:
|
|
`_entityObjects.RegisterEntity(incoming, RetirePriorProjection)` — the
|
|
LEGACY non-residence call, confirmed exact line from the handoff doc.
|
|
5. `src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs:315-322` —
|
|
`RegisterEntity` -> `RegisterEntityCore(incoming, beginInitialResidence:false, ...)`
|
|
— never touches `InitialCreateResidences`/`InitialCreateExecution`.
|
|
6. `LiveEntityHydrationController.cs:288-294` — `ApplyAcceptedSpawn(...)` commits
|
|
the accepted snapshot (still cellless authority, no position/rebucket yet).
|
|
7. `LiveEntityHydrationController.cs:938-1040` (`ProjectExactOnce`), line 1036:
|
|
`_materializer.TryMaterialize(...)`.
|
|
8. `src/AcDream.App/Rendering/DatLiveEntityProjectionMaterializer.cs:407-427` ->
|
|
`MaterializeProjection` (680-857).
|
|
9. **Duplicate authority** — `DatLiveEntityProjectionMaterializer.cs:712-737`:
|
|
`_runtime.MaterializeLiveEntity(..., out expectedRecord)` with the
|
|
`residence` parameter OMITTED, defaulting to
|
|
`LiveEntityMaterializationResidence.LegacyImmediate`
|
|
(`LiveEntityRuntime.cs:596-597`).
|
|
10. `LiveEntityRuntime.cs:589-789` (`MaterializeLiveEntity`) — constructs the
|
|
`WorldEntity` (sets Position/Rotation/ParentCellId directly from
|
|
wire-decoded world position, ~718-729); because residence is
|
|
`LegacyImmediate` not `AwaitRuntimePlacement`, falls through at **line 777**
|
|
to `RebucketLiveEntity(serverGuid, fullCellId)`.
|
|
11. **Duplicate authority** — `LiveEntityRuntime.cs:792-931` (`RebucketLiveEntity`):
|
|
immediately calls `_spatial.RebucketLiveEntity(...)` (spatial bucket
|
|
mutation), sets `IsSpatiallyProjected/Visible`, calls
|
|
`_entityObjects.CommitRebucket(...)` (canonical `FullCellId`/
|
|
`CanonicalLandblockId` write), resets/suspends the object clock — all
|
|
synchronous, no Runtime placement receipt involved anywhere in this path.
|
|
12. `DatLiveEntityProjectionMaterializer.cs:791-820` — builds collision, binds
|
|
projectile runtime; for static-animating physics objects,
|
|
`RegisterAnimation` (859-1021) at **1003-1016** calls
|
|
`_runtime.GetOrCreatePhysicsBody(spawn.Guid, incarnation => new
|
|
PhysicsBody{...}.SnapToCell(...))` — a SECOND, narrower body-construction
|
|
duplicate authority (non-player static-animation bodies).
|
|
13. `LiveEntityHydrationController.cs:739-759`/`1070-1098` — publish-ready via
|
|
`ILiveEntityReadyPublisher.Publish`.
|
|
14. `src/AcDream.App/Input/PlayerModeAutoEntry.cs:214-230` — per-frame guard;
|
|
once `IsPlayerEntityPresent` + `IsWorldReady` (world-reveal, unrelated to
|
|
any Runtime placement receipt) -> `EnterPlayerMode()` ->
|
|
`PlayerModeController.EnterFromAutoEntry`.
|
|
15. `src/AcDream.App/Input/PlayerModeController.cs:126-133,217-242` ->
|
|
`TryEnter` -> **`BuildControllerAndCamera`** (244-525).
|
|
16. **Duplicate authority** — `PlayerModeController.cs:409-413`:
|
|
`_physics.Resolve(playerEntity.Position, initialCellId, Vector3.Zero, 100f)`.
|
|
17. **Duplicate authority** — `PlayerModeController.cs:422-430`:
|
|
`_physics.ResolvePlacement(...)` using a locally computed Setup cylinder
|
|
(`_motionBindings.GetSetupCylinder`) — no shared "exact Setup mover" step.
|
|
18. **Duplicate authority** — `PlayerModeController.cs:449-480`: builds
|
|
`EntityPhysicsHost`/`MoveToManager`, installs via
|
|
`EntityPhysicsHostComposition.SelectStableHostWithoutRebind`/`InstallOrRebind`
|
|
(`src/AcDream.App/Physics/EntityPhysicsHostComposition.cs:16,35`) — its own
|
|
body/controller construction, independent of headless's copy.
|
|
19. Camera — `PlayerModeController.cs:440-447`: constructs
|
|
`ChaseCamera`/`RetailChaseCamera`, `_camera.EnterChaseMode(...)`
|
|
(host-only concern, stays, but must be gated by the Runtime ack not
|
|
ad-hoc resolve success).
|
|
20. **Duplicate authority — final commit** — `PlayerModeController.cs:482-484`:
|
|
`playerEntity.SetPosition(initial.Position); playerEntity.ParentCellId =
|
|
initial.CellId; controller.CommitPreparedPosition();` — direct write,
|
|
bypasses any Runtime `Place` receipt.
|
|
|
|
### Headless host chain
|
|
|
|
Same shape as the graphical route through `RegisterEntity`/`ApplyAcceptedSpawn`
|
|
(`RuntimeLiveEntitySessionController.cs:73-95`, see Route 8 section above for
|
|
the full per-line breakdown), then `IRuntimeDirectWorldProjection.ProjectSpawn`
|
|
-> `HeadlessSessionWorldProjection.SynchronizeLocalPlayer`
|
|
(566-615)/`CreateController` (639-655) — same duplicate-authority shape as
|
|
graphical hops 16-20, but a SEPARATELY HAND-WRITTEN copy, not shared code
|
|
(confirmed by the codebase's own comment at
|
|
`HeadlessSessionWorldProjection.cs:685-689` contrasting itself with "the
|
|
graphical `PlayerModeController.ApplyStepHeights`"). No headless equivalent of
|
|
graphical hops 12-14/19 exists (no `WorldEntity`/render sidecar/camera).
|
|
|
|
### What each hop must become / capability gaps (route 1)
|
|
|
|
- Switch `RegisterLiveEntity`/`OnSpawned` from `RegisterEntity` to
|
|
`RegisterEntityWithInitialResidence` — **no accessibility blocker** (see
|
|
correction above); this is a pure call-site + follow-up-loop change.
|
|
- DELETE `MaterializeProjection`'s direct `RebucketLiveEntity` fall-through;
|
|
pass `residence: LiveEntityMaterializationResidence.AwaitRuntimePlacement`
|
|
(the enum value ALREADY EXISTS and is used by other routes via
|
|
`LiveEntityRuntime.cs:765-776`/`938-1127`'s
|
|
`TryApplyRuntimePlacementPlace`/`Withdrawal` — this route just isn't
|
|
plumbed into it yet).
|
|
- DELETE `PlayerModeController.cs:409-430` (own Resolve/ResolvePlacement) and
|
|
`482-484` (direct SetPosition/CommitPreparedPosition); body/controller
|
|
construction becomes acknowledge-only from the shared Runtime-resolved
|
|
position.
|
|
- DELETE headless `SynchronizeLocalPlayer:589-607` and `610-614` the same way.
|
|
- DELETE the legacy `RegisterEntity` overload entirely once both hosts move
|
|
(`RegisterEntityWithInitialResidence` is already a strict superset).
|
|
- **Gap 1 (real)**: no shared "exact Setup mover" / DAT-preparation API.
|
|
`RuntimeInitialCreateResidenceState.Own` only OPENS the placement slot
|
|
(`TryBeginExclusiveAuthoredPlacement`); nothing in Runtime resolves a
|
|
Setup-derived cylinder/sphere-list and feeds it into
|
|
`RuntimeSetPositionState.SubmitPreparedPlacement`. Today that DAT read +
|
|
Resolve/ResolvePlacement call is hand-duplicated with DIFFERENT defaults in
|
|
App (`_motionBindings.GetSetupCylinder`) vs. headless (hardcoded
|
|
`DefaultRadius=0.48f`/`DefaultHeight=1.835f` unless `_preparedCollision`
|
|
supplies a Setup) — this is exactly prerequisite B, confirmed still open.
|
|
- **Gap 2 (real)**: no shared "atomic Runtime controller/body" owner exists
|
|
anywhere (`RuntimeEntityRecord` has storage slots — `PhysicsBody`,
|
|
`PhysicsHost` — but no construction logic); `BuildControllerAndCamera` and
|
|
`HeadlessSessionWorldProjection.CreateController` are two independently
|
|
written, divergent implementations of the same "build a
|
|
`PlayerMovementController`/host from a `RuntimeEntityRecord`" job. This IS
|
|
prerequisite C, confirmed still unbuilt in production code.
|
|
- **Gap 1 REFINEMENT (verified directly, not from an agent)**: "no shared Setup
|
|
mover API" is not quite right — `RuntimeSetPositionState.PrepareMover`
|
|
(`RuntimeSetPositionState.cs:1079-1133`) + `RuntimeSetPositionMoverPreparer.TryBuild`
|
|
(`RuntimeSetPositionMoverPreparation.cs:69+`) **already exist** as the shared
|
|
authored-mover-preparation API prerequisite B calls for, and
|
|
`SubmitPreparedPlacement` (`RuntimeSetPositionState.cs:2041`) already
|
|
requires `operation.Record.PhysicsBody is not { } body` to be non-null
|
|
BEFORE a placement can even be submitted — i.e. prerequisite C's body must
|
|
exist first, structurally enforced. What's genuinely missing is the HOST
|
|
input: `PrepareMover` takes a `RuntimeSetPositionMoverPreparation` whose
|
|
`Setup` field is a host-supplied `RuntimeSetPositionMoverSetup` (Setup
|
|
table ID + `FlatSetupCollision?`, `RuntimeSetPositionMoverPreparation.cs:23-42`)
|
|
— Runtime does NOT read DAT itself; the host must call
|
|
`IPreparedCollisionSource.ReadSetupCollision(setupId)` (already used by
|
|
headless at `HeadlessSessionWorldProjection.cs:668-679`) and feed the
|
|
result in. **No production call site anywhere chains
|
|
`ReadSetupCollision` -> `PrepareMover` -> `SubmitPreparedPlacement`** — both
|
|
hosts instead read Setup DAT/prepared-collision data ad hoc and call
|
|
`PhysicsEngine.Resolve`/`ResolvePlacement` directly, entirely bypassing this
|
|
already-built pipeline. This is a wiring gap, not a missing-mechanism gap.
|
|
- **Gap 3 (real, previously unstated)**: the executor publishes only generic
|
|
`RuntimeEntityChange` via `_events.PublishEntity`
|
|
(`RuntimeInitialCreateContinuationExecutor.cs:2053-2061`), NOT through
|
|
`RuntimePlacementProjectionChannel`/`Placements` — the channel the existing
|
|
App `TryApplyRuntimePlacementProjection`/headless placement sinks already
|
|
consume for OTHER routes. Whether a residence-driven initial commit
|
|
actually surfaces a `Place` token on the SAME channel other routes use is
|
|
unconfirmed/unbuilt — this bridge must be built, not assumed.
|
|
- Confirms the executor handoff's own claim: zero production callers of
|
|
`Execute`/`RegisterEntityWithInitialResidence` today.
|
|
|
|
---
|
|
|
|
## Route 2 — Local ForcePosition (from parallel research agent)
|
|
|
|
Graphical-only (headless equivalent is folded into Route 8's ForcePosition
|
|
branch, already covered above).
|
|
|
|
### Call chain
|
|
|
|
`src/AcDream.Core.Net/WorldSession.cs:1767` (wire opcode `0xF748`
|
|
UpdatePosition — same opcode for ordinary and force; "force" is purely a
|
|
`ForcePositionSequence` freshness fact) -> `WorldSession.cs:1774-1786` raises
|
|
`PositionUpdated` -> `src/AcDream.App/Net/LiveEntitySessionController.cs:67-69`
|
|
-> `src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs:1024` `OnPosition`
|
|
-> `:1035-1048` `_authorityGate.TryAcceptPosition` ->
|
|
`LiveEntityInboundAuthorityGate.cs:148-206` -> `_liveEntities.TryApplyPosition` ->
|
|
`PhysicsTimestampGate.cs:181-218` `TryAcceptPositionEvent` (retail
|
|
`SmartBox::HandleReceivedPosition` 0x00453FD0 FORCE_POSITION branch) returns
|
|
`ForcePosition` when newer AND `teleport == current teleport stamp` ->
|
|
`LiveEntityNetworkUpdateController.cs:1101-1117` `forceLocal` computed ->
|
|
**`LocalForcePositionTransaction.Apply`** (`src/AcDream.App/Physics/LocalForcePositionTransaction.cs:10-28`,
|
|
**duplicate authority**): `if (!isCurrent()) return false; blip(); acknowledge();
|
|
return isCurrent();` where `blip` = `PlayerMovementController.BlipPosition`
|
|
(`src/AcDream.Runtime/Gameplay/PlayerMovementController.cs:1759-1773`: `_body.SnapToCell`,
|
|
`UpdateCellId` at `:1577-1599` which ALSO publishes the render root via
|
|
`_physics.UpdatePlayerCurrCell`) and `acknowledge` = `LocalPlayerOutboundController.SendImmediatePosition`
|
|
(`src/AcDream.Runtime/Gameplay/LocalPlayerOutboundController.cs:152-185`,
|
|
**sends the outbound `AutonomousPosition` ack BEFORE any Runtime canonical
|
|
commit exists — the exact pre-commit-ack bug the campaign handoff names**).
|
|
Falls through unconditionally into the **generic tail**
|
|
(`LiveEntityNetworkUpdateController.cs:1252-1270`, **second duplicate
|
|
authority**): `entity.SetPosition`/`ParentCellId`/`Rotation` +
|
|
`_liveEntities.RebucketLiveEntity(...)` on the render-facing `WorldEntity` —
|
|
a SECOND independent mutation of the SAME accepted Position, parallel to the
|
|
`PlayerMovementController` body mutated by the first authority (two-store
|
|
divergence, the exact bug class the executor handoff says it already fixed
|
|
for Create).
|
|
|
|
### What becomes what / gaps
|
|
|
|
- DELETE `LocalForcePositionTransaction` outright; its job (validate
|
|
ownership, blip, ack-once) becomes `RuntimeSetPositionState.Apply`/
|
|
`BeginAcceptedPlacement` with `RuntimeSetPositionOperationKind.LocalAuthoritative`.
|
|
- `OnPosition`'s force branch reduces to: classify via
|
|
`RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition` (its
|
|
`ForcePosition` branch — lines 291-313 — **already produces exactly**
|
|
`SetPositionSimple`, `PreserveHeading: true`, `SendPositionImmediately: true`
|
|
— this route kind is FULLY MODELED already, just unused) -> submit through
|
|
`RuntimeSetPositionState` -> only call `SendImmediatePosition` AFTER commit
|
|
(fixes the ordering bug).
|
|
- `BlipPosition` becomes acknowledge-only (applies the already-committed
|
|
Runtime projection instead of being an independent authority).
|
|
- DELETE the generic tail's second `entity.SetPosition`/`RebucketLiveEntity`
|
|
for the local player (1252-1270) — dead code once the host projects only
|
|
the one canonical result.
|
|
- **Gap (confirmed)**: zero production references to
|
|
`RuntimeAuthoritativePositionRouteClassifier`/`RuntimeSetPositionState`
|
|
anywhere in `src/AcDream.App` for Position handling — tracked by issue
|
|
**#275** ("post-cutover unification of the legacy Position path onto the
|
|
classifier"), cited in both the executor handoff and the divergence
|
|
register's AP-131 row.
|
|
- **Gap (confirmed)**: "reject a stale host ack" already exists structurally
|
|
(`RuntimeSetPositionState.IsPlacementCurrent`/`ConsumeAcknowledgedPlacement`,
|
|
exact-token requirement) but is unwired for Route 2.
|
|
- **Scope note**: the continuation EXECUTOR (`Execute`/FIFO drain) is scoped
|
|
to initial-Create admission, not steady-state Position on an
|
|
already-resident entity — Route 2's correct integration point is
|
|
`RuntimeSetPositionState`/`InboundPhysicsStateController.ApplyAcceptedPositionSnapshot`
|
|
directly, NOT `Execute`. The `AwaitingContinuationPlacement` retry flavors
|
|
are documented generically but demonstrated only for Create — their
|
|
applicability to a live ForcePosition is unproven, not just unwired.
|
|
- The 1,880/2,048-byte allocation budget applies directly here: ForcePosition
|
|
is genuinely frame-frequency-ish traffic.
|
|
|
|
---
|
|
|
|
## Route 3 — Portal transit and materialization (from parallel research agent)
|
|
|
|
`LocalPlayerTeleportPlacement` exists verbatim (not renamed) inside
|
|
`src/AcDream.App/Streaming/LocalPlayerTeleportController.cs:183-290`.
|
|
|
|
### Call chain
|
|
|
|
Two wire inputs join in `RuntimeWorldTransitState`: **F751 start** (`WorldSession.cs:1972-1987`
|
|
opcode `0xF751`) -> `LiveEntitySessionController.cs:83-85` `OnTeleportStarted`
|
|
-> `LocalPlayerTeleportController.cs:436-459` gates on
|
|
`LiveLocalPlayerTeleportAuthority.IsFreshStart` ->
|
|
`PhysicsTimestampGate.IsFreshTeleportStart` and
|
|
`RuntimeWorldTransitState.TryQueueTeleportStart` (`:426-442`). **Destination
|
|
Position** rides the SAME wire path as Route 2, then at
|
|
`LiveEntityNetworkUpdateController.cs:1906-1913` (guarded on `disposition==Apply
|
|
&& guid==playerServerGuid`) calls `_localPlayerTeleport.OfferDestination(...)`
|
|
-> `RuntimeTeleportDestinationAdapter.cs:15-36` (pure mapping) ->
|
|
`RuntimeWorldTransitState.OfferTeleportDestination` (`:480-520`). Every frame,
|
|
`LocalPlayerTeleportController.Tick` (`:472-570`) drives:
|
|
`TryAimAcceptedDestination`/`AimDestination` (`:626-734`, classifies via
|
|
`TeleportLandblockTransition.Classify` — streaming-only, no world mutation) ->
|
|
`WorldRevealCoordinator.TryBeginPortal` -> `RuntimeWorldTransitState.TryBeginPortalReveal`
|
|
(mints reveal generation + host projection token). On `TeleportAnimEvent.Place`:
|
|
preflight `RuntimeWorldTransitState.CanPlacePortalDestination` (read-only gate)
|
|
-> **`LocalPlayerTeleportPlacement.Place`** (`LocalPlayerTeleportController.cs:214-278`,
|
|
**the duplicate authority**) -> THEN `WorldRevealCoordinator.ObserveMaterialized`
|
|
-> `RuntimeWorldTransitState.AcknowledgePortalMaterialized` runs AFTER `Place`
|
|
has already fully mutated world state — **a rubber-stamp, not a gate**. On
|
|
`TeleportAnimEvent.FireLoginComplete`: `_session.SendLoginComplete()` (outbound
|
|
ack) -> `RuntimeWorldTransitState.Complete`.
|
|
|
|
`LocalPlayerTeleportPlacement.Place` (full method, 214-278) mutates in order:
|
|
`_physics.Resolve(...)` (own collision placement) -> `controller.SetPosition(...)`
|
|
(body+cell) -> `entity.SetPosition`/`ParentCellId`/`Rotation` on `WorldEntity`
|
|
-> `_liveEntities.RebucketLiveEntity(...)` (throws on failure) ->
|
|
`_host.Host?.NotifyTeleported()` (retail `teleport_hook` tail) ->
|
|
`controller.SetBodyOrientation(rotation)` -> camera reset
|
|
(`_cameras.Legacy?.Update`/`_cameras.Retail?.ResetViewerToPlayer`, **unique to
|
|
this route vs. Route 2**) -> `_spatial.Reconcile()`.
|
|
`TeleportViewPlaneController` (view-plane/FOV easing) is presentation-only,
|
|
NOT in scope for deletion.
|
|
|
|
### What becomes what / gaps
|
|
|
|
- DELETE `Place`'s resolve/controller/world-entity/rebucket/host-notify/
|
|
orientation mutations outright; replace with
|
|
`RuntimeSetPositionState.BeginAuthoredPlacement`/`Apply` carrying a
|
|
populated `RuntimePortalPlacementAuthority`, then project only the
|
|
committed result.
|
|
- Re-sequence `WorldRevealCoordinator.CanPlacePortalDestination`/
|
|
`ObserveMaterialized` to be the ACTUAL gate/confirmation around the
|
|
canonical commit (not rubber-stamps around a host-owned mutation) — i.e.
|
|
materialization fires from the Runtime commit callback, not immediately
|
|
after `Place()`'s direct mutations.
|
|
- Camera reset and `_spatial.Reconcile()` become acknowledge-only reactions.
|
|
- Headless has the analogous duplicate authority in
|
|
`HeadlessSessionWorldProjection.PrepareDestination`/`SynchronizeLocalPlayer`
|
|
(already documented in the Route 8 section above) — must cut over in
|
|
lockstep, same target (`RuntimeSetPositionState`).
|
|
- **Gap (confirmed, important)**: `RuntimeWorldTransitState` (1012 lines,
|
|
Slice J6) already fully owns reveal generation/sequence/destination-cell,
|
|
readiness, materialization/completion/cancellation, and the 4-stage host
|
|
acknowledgement protocol (`ProjectionRegistered -> SimulationReleaseProjected
|
|
-> DestinationReservationReleased -> TerminalProjected`) — but ONLY for
|
|
streaming/render-resource bookkeeping, NEVER for gating an actual placement
|
|
mutation. `RuntimePortalPlacementAuthority` (`RuntimeSetPositionState.cs:64-78`,
|
|
carrying `RevealGeneration`/`TeleportSequence`/`RuntimeWorldHostProjectionToken`)
|
|
is a REAL, VALIDATED constructor parameter on `RuntimeSetPositionCommand.Portal`
|
|
(validated in `BeginAcceptedPlacementCore`, `:978-984`) and
|
|
`LiveEntityRuntime.cs:1162-1171` even has an `IsValidPortalPlacementAuthority`
|
|
presentation-side validator — **but grep across App and Headless finds ZERO
|
|
call sites that ever construct one with `Present: true`.** The binding
|
|
machinery is real but 100% dormant end to end.
|
|
- **Missing for Route 3 specifically**: (a) the adapter that reads
|
|
`WorldRevealCoordinator`'s current generation/sequence/destination-cell/host
|
|
projection token into a `RuntimePortalPlacementAuthority`; (b) the actual
|
|
`RuntimeSetPositionState.BeginAuthoredPlacement` call site using
|
|
`LocalAuthoritative` + that authority (does not exist anywhere today); (c)
|
|
re-sequencing `ObserveMaterialized` to run from the commit callback.
|
|
- **Discard/cancellation semantics** (a stale generation/sequence/cell/token
|
|
must never reveal) look structurally sufficient
|
|
(`RuntimePlacementProjectionKind.Discard`, `IsPlacementCurrent`,
|
|
`RuntimeWorldTransitState.Cancel`/`BeginHostProjectionSupersession`) but are
|
|
UNEXERCISED together for a real placement — treat as an explicit cutover
|
|
test, not an assumed-correct combination.
|
|
- Executor scope note: same as Route 2 — the FIFO/`Execute` mechanism is
|
|
Create-admission-scoped; Route 3's target is `RuntimeSetPositionState`
|
|
directly.
|
|
|
|
---
|
|
|
|
## Route 4 — Remote CreateObject and Position (from parallel research agent)
|
|
|
|
### Foundational fact (independently confirmed by this agent AND by direct
|
|
grep during this research pass)
|
|
|
|
`InboundPhysicsStateController.cs:596-608`'s own doc comment states verbatim:
|
|
"this file's `TryApplyPosition` is today's **only PRODUCTION Position wire
|
|
caller**; the classifier-based path is **test-only** until a host wires the
|
|
executor." `RuntimeEntityObjectLifetime.TryApplyPosition` only enters the
|
|
classifier/executor path when `TryGetPendingInitialResidence` finds an
|
|
in-flight CreateObject continuation (a narrow transient window) — steady-state
|
|
remote UpdatePosition always falls to the legacy unconditional-merge path
|
|
with **no route-classification concept at all**.
|
|
|
|
### Call chain
|
|
|
|
`LiveEntitySessionController.cs:67-69` -> `LiveEntityNetworkUpdateController.OnPosition`
|
|
(`:1035`) -> `LiveEntityInboundAuthorityGate.cs:161` `TryApplyPosition` ->
|
|
`LiveEntityRuntime.cs:2116,2135` -> `RuntimeEntityObjectLifetime.cs:1229,1299`
|
|
steady-state branch -> `RuntimeEntityDirectory.cs:478` ->
|
|
`InboundPhysicsStateController.cs:610` (legacy unconditional timestamp-gated
|
|
merge). Then `LiveEntityNetworkUpdateController.cs:1197-1211` computes
|
|
`remoteHardTeleport`/`remotePlacementRequired` from `timestamps.TeleportHookRequired`
|
|
and `_remoteTeleportController.HasPending`; `RunRemoteTeleportHook` (`:1210`)
|
|
-> `RemoteTeleportHook.Execute` (`RemoteTeleportHook.cs:17`, CancelMoveTo/
|
|
UnStick/StopInterpolating/UnConstrain/NotifyTeleported/ReportCollisionEnd).
|
|
**Then, UNCONDITIONALLY** (`LiveEntityNetworkUpdateController.cs:1252-1270`,
|
|
**duplicate authority**, BEFORE any placement/teleport branch runs):
|
|
`entity.SetPosition`/`ParentCellId`/`Rotation` + `RebucketLiveEntity`. If
|
|
`remotePlacementRequired`: `RemoteTeleportController.BeginPlacement` (`:269`)
|
|
-> `TryApply` (`:117/151`) -> `Resolve` (`:278`) -> `_physics.ResolvePlacement`
|
|
(`:303`) -> `CommitResolved` (`:320`) -> `RemoteTeleportPlacement.Apply`
|
|
(`RemoteTeleportPlacement.cs:15`) -> `PhysicsObjUpdate.CommitSetPositionTransition`.
|
|
Else (ordinary MoveOrTeleport): **inline classification hand-duplicated in
|
|
App**, `LiveEntityNetworkUpdateController.cs:1594-1900` (local constants
|
|
`MaxPhysicsDistance=96f`/`BodySnapThreshold=4f` duplicated for player vs NPC)
|
|
decides far-snap/near-interpolate/airborne-noop/landing, then
|
|
`entity.SetPosition`/`ParentCellId`/`Rotation` + `LiveEntityShadowPublisher.TryPublishRemote`.
|
|
Ongoing per-tick DR: `RemotePhysicsUpdater.cs:220` ->
|
|
`RuntimeRemotePhysicsUpdater.Tick` (`:61`) — interp catch-up + `ResolveWithTransition`
|
|
sweep + shadow sync, independent of SetPosition (retail's continuous
|
|
`UpdatePositionInternal` — NOT itself a wire route, out of cutover scope).
|
|
|
|
### Duplicate authorities
|
|
|
|
- `LiveEntityNetworkUpdateController.cs:1252-1270` — position/cell/rotation
|
|
AND full rebucket, unconditionally, ahead of any classification.
|
|
- `RemoteTeleportController.cs:37,77-109` — `_pending` dictionary, rollback
|
|
capture/restore (`:94-109,477-569`), lost-cell tracking
|
|
(`:529-532`) — exactly the bookkeeping the residence+executor's
|
|
25-second lost-cell lifetime already owns generically.
|
|
- `RemoteTeleportPlacement.cs:43-77` — direct `body.SnapToCell`,
|
|
contact-plane fields, `PhysicsObjUpdate.CommitSetPositionTransition` — a
|
|
hand-rolled SetPosition commit parallel to `RuntimeSetPositionState`.
|
|
- `LiveEntityNetworkUpdateController.cs:1594-1900` — the "ordinary"
|
|
MoveOrTeleport classification hand-duplicated in App, when
|
|
`RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition`
|
|
ALREADY implements identical near(<96m,Interpolate)/far(SetPositionSimple)/
|
|
teleport-advanced(SetPosition)/cellless rules (classifier lines 360-439).
|
|
- `RemoteTeleportController.cs:382-475` — `ParkPending`/`OnProjectionVisibilityChanged`
|
|
re-implements deferred-until-visible placement replay, another thing the
|
|
executor's FIFO/replay machinery already generalizes.
|
|
|
|
### What becomes what
|
|
|
|
- DELETE outright: `RemoteTeleportController.cs`, `RemoteTeleportPlacement.cs`,
|
|
their pending dictionary/rollback/`ParkPending`, and the pre-placement
|
|
`entity.SetPosition`/`RebucketLiveEntity` at `:1252-1270`.
|
|
- DELETE + replace with classifier delegation: the inline near/far/airborne/
|
|
landing block (`:1594-1900`) -> route through
|
|
`ClassifyAcceptedPosition` + `RuntimeSetPositionState` Begin/Watch/resume.
|
|
- Reduce to acknowledge-only: `RemoteTeleportHook.cs` stays as the App-side
|
|
execution seam, but its trigger condition should come from the classified
|
|
route's `RuntimeTeleportHookPhase`, not the App's own `remoteHardTeleport`
|
|
flag.
|
|
- `RuntimeRemotePhysicsUpdater` per-tick DR is OUT of cutover scope (retail's
|
|
continuous simulation, not a wire route).
|
|
|
|
### Gap
|
|
|
|
The classifier's `Remote` branch is FULLY implemented for both `ClassifyCreate`
|
|
and `ClassifyAcceptedPosition` — the only gap is wiring (no production caller
|
|
ever constructs a `RuntimeAcceptedPositionRouteRequest` for a Remote entity
|
|
outside the narrow pending-residence window). The 1,880 B/op budget applies
|
|
directly here too (remote Position is genuinely per-network-tick frequency).
|
|
|
|
---
|
|
|
|
## Route 5 — Projectile authoritative create/corrections (from parallel research agent)
|
|
|
|
### Call chain
|
|
|
|
**Create**: `DatLiveEntityProjectionMaterializer.cs:807` `_projectiles.TryBind` ->
|
|
`ProjectileController.TryBind` (`ProjectileController.cs:133`) — constructs/
|
|
adopts the shared `PhysicsBody` (`:176-265`), calls `body.SnapToCell` directly
|
|
(`:214/256`), then `entity.SetPosition`/`ParentCellId` + `RebucketLiveEntity`
|
|
(`:268-271`) and `ShadowPositionSynchronizer.Sync` (`:306`) — all ad hoc, no
|
|
classifier. **Vector** (launch velocity): `LiveEntityNetworkUpdateController.cs:840`
|
|
`OnVector` -> `ProjectileController.ApplyAuthoritativeVector` (`:345`) ->
|
|
`RuntimeProjectilePhysicsUpdater.ApplyAuthoritativeVector` (`:207`). **State**
|
|
(Missile bit): `:992` `OnState` -> `ApplyAuthoritativeState` (`:410`) ->
|
|
`RuntimeProjectilePhysicsUpdater.ApplyAuthoritativeState` (`:252`, direct
|
|
`SnapToCell` at `:288`). **Authoritative Position correction**:
|
|
`LiveEntityNetworkUpdateController.cs:1217-1233` (inside `OnPosition`, AHEAD of
|
|
the generic remote path — returns early if handled) ->
|
|
`ProjectileController.ApplyAuthoritativePosition` (`:508`) ->
|
|
`RuntimeProjectilePhysicsUpdater.ApplyAuthoritativePosition` (`:301`, direct
|
|
`body.SnapToCell` at `:346`, `CommitProjectileCell` at `:370`, direct
|
|
`ShadowPositionSynchronizer.Sync`/`Suspend` `:402-421`). **Ordinary per-quantum
|
|
integration**: `ProjectileController.Tick` (`:613`) -> `AdvanceQuantum`/
|
|
`TryBeginQuantum`/`CompleteQuantum` (`:761-836`) ->
|
|
`RuntimeProjectilePhysicsUpdater.TryBegin`/`Complete` (`:37,94`) — `Complete`
|
|
does direct `body.SnapToCell` at line 145 + direct shadow sync — correctly
|
|
NOT routed through SetPosition today, but ALSO not through any shared
|
|
cell-commit authority; its own ad hoc `CommitProjectileCell` + shadow write.
|
|
|
|
### Duplicate authorities
|
|
|
|
- `ProjectileController.cs:214-271` — body construction, `SnapToCell`,
|
|
`entity.SetPosition`/`ParentCellId`, `RebucketLiveEntity`, shadow sync — full
|
|
ad hoc placement authority for missile creation.
|
|
- `RuntimeProjectilePhysicsUpdater.cs:145-198` (per-quantum `Complete`) and
|
|
`:346-421` (`ApplyAuthoritativePosition`) — BOTH do direct `SnapToCell` +
|
|
`CommitProjectileCell` + shadow sync, bypassing `RuntimeSetPositionState`
|
|
entirely; **neither distinguishes "ordinary integration" from "authoritative
|
|
correction" at the cell/shadow-commit layer** — only the caller's semantic
|
|
label differs.
|
|
|
|
### What becomes what
|
|
|
|
- DELETE outright: the authoritative-placement code in `TryBind`'s create
|
|
branch (`:214-271`) and `ApplyAuthoritativePosition` (`:508-578`); and the
|
|
direct `SnapToCell`/shadow commit inside
|
|
`RuntimeProjectilePhysicsUpdater.ApplyAuthoritativePosition` (`:301-424`).
|
|
- REPLACE with `ProjectileAuthoritative`-tagged routes via the classifier
|
|
driving `RuntimeSetPositionState`, using "the same Runtime body and exact
|
|
projectile Setup sphere" — i.e. `RuntimeProjectile.Body`/`CollisionSphere`
|
|
(`RuntimeProjectile.cs:10-40`) plugged into the shared SetPosition
|
|
transaction instead of `CommitProjectileCell`.
|
|
- KEEP UNCHANGED (must NOT route through SetPosition): per-quantum
|
|
integration (`TryBegin`/`Complete`, `:37-205`) — correctly outside
|
|
SetPosition today; only its cell/shadow-commit CALL is a candidate to unify
|
|
onto a shared (non-SetPosition) cell-commit primitive.
|
|
- Reduce to acknowledge-only: `ApplyAuthoritativeVector`/`ApplyAuthoritativeState`
|
|
(`:332-478`) — smaller scope of change since they don't do placement/rebucket
|
|
themselves today.
|
|
|
|
### Gap
|
|
|
|
`ProjectileAuthoritative` exists TODAY ONLY as an `OperationKind` label
|
|
(`RuntimeSetPositionState.cs:16`), produced generically by the classifier's
|
|
`OperationKind(...)` helper for both entry points — **it is not a distinct
|
|
routing rule**: `ClassifyCreate` gives Projectile the exact same
|
|
`SetPosition`/`InitialCreateFlags` route as any non-local entity, and
|
|
`ClassifyAcceptedPosition` gives Projectile the IDENTICAL near/far/teleport/
|
|
cellless branches as Remote (proven by the test name
|
|
`ProjectilePosition_UsesRemoteMoveOrTeleportClassification`,
|
|
`RuntimeAuthoritativePositionRouteClassifierTests.cs:418-436`). So: PARTIAL —
|
|
operation-kind plumbing + generic MoveOrTeleport-shape routing exist; missing
|
|
is (a) any production wiring at all (same dormancy as route 4), and (b)
|
|
projectile-specific POLICY (should a "near" wire correction ever return
|
|
`Interpolate` for a ballistic body, or always hard-correct?) — today's
|
|
"never route per-quantum integration through SetPosition" constraint holds
|
|
only because per-quantum integration never calls the classifier at all (a
|
|
separate simulation-tick path), not because of an explicit guard.
|
|
|
|
---
|
|
|
|
## Route 6 — Drops and unparent-to-world (from parallel research agent)
|
|
|
|
Graphical-only; no headless drop UI exists.
|
|
|
|
### Call chain
|
|
|
|
**Whole-item/normal drop**: `ItemInteractionController.ExecutePlacementActions`,
|
|
`DropToWorld` case (`src/AcDream.App/UI/ItemInteractionController.cs:976-993`) —
|
|
optimistic move + send drop; ordinary server CreateObject flows through the
|
|
normal wire pump: `LiveEntityHydrationController.OnCreate`/`OnCreateCore`
|
|
(`:226-299`) -> `_runtime.RegisterLiveEntity(spawn)` -> `LiveEntityRuntime.cs:526-556`
|
|
-> `_entityObjects.RegisterEntity(...)` (line 537) -> `RuntimeEntityObjectLifetime.RegisterEntity`
|
|
(315-322, legacy, `beginInitialResidence:false`). **Split-to-world** (ACE omits
|
|
CreateObject, sends only the new GUID's Position first):
|
|
`ItemInteractionController.cs:994-1015` (`SplitToWorld` case, raises
|
|
`WorldDropDispatched`) -> `InventoryWorldDropProjectionController.OnWorldDropDispatched`
|
|
(`:81-98`, records `PendingSplitToWorldProjection`) -> wire Position (F748) for
|
|
the new GUID arrives at `LiveEntityNetworkUpdateController.OnPosition:1024-1029`,
|
|
FIRST calls `_worldDropProjection.TryRecoverUnknownPosition(update)` (1026) ->
|
|
`InventoryWorldDropProjectionController.cs:54-68`: builds a synthetic
|
|
`EntitySpawn` via `PendingSplitToWorldProjection.BuildSpawn` (171-209, clones
|
|
the SOURCE item's full spawn/PhysicsSpawnData, overriding
|
|
Guid/Position/StackSize/Container/Wielder/*Sequence/Parent*) -> calls
|
|
`_hydration.OnCreate(spawn)` (66) — **the IDENTICAL entry point as the
|
|
ordinary path**, converging on the same `RegisterEntity` call.
|
|
`ShortcutDropPlanner` is unrelated to this route (toolbar-slot reshuffling
|
|
only, never touches position/physics).
|
|
|
|
### Duplicate authority / gap
|
|
|
|
Neither drop flavor calls `Physics.SetPosition.Begin*` or
|
|
`RuntimeInitialCreateResidenceState.Begin` today — **there is no App-vs-Runtime
|
|
competing mutation for drops specifically**; the gap is identical to routes
|
|
1-5: `RegisterEntityWithInitialResidence` is never called from any production
|
|
path (confirmed by full-repo grep — only self-referential inside
|
|
`RuntimeEntityObjectLifetime`'s own constructors, as the executor's
|
|
child-replay delegate, plus tests). Both drop flavors equally bypass the
|
|
canonical create-placement transaction — this is a wiring gap shared with
|
|
routes 1-5, not a route-6-specific duplicate.
|
|
- **Real, previously-unstated Route-6-specific gap**: `PendingSplitToWorldProjection.BuildSpawn`
|
|
clones the source's DTO wholesale (correct for Position, which IS
|
|
explicitly overridden) but presentation-effect replay (create-pop VFX/sound
|
|
driven off `LiveEntityReadyPublisher.Publish`/`EntityEffectController.ReplayPendingForLiveEntity`)
|
|
has NO SIGNAL today distinguishing "real spawn" from "split continuation" —
|
|
and the executor's receipt/trace has no field for "this create is a
|
|
split-recovery continuation, suppress create-time effect replay." This must
|
|
be ADDED, not just wired — a genuine new capability, not merely dormant.
|
|
|
|
### What becomes what
|
|
|
|
`LiveEntityHydrationController.OnCreate`/`OnCreateCore` switches to
|
|
`RegisterEntityWithInitialResidence` for EVERY CreateObject (both flavors) so
|
|
both acquire a lease and drain through `Execute`.
|
|
`TryRecoverUnknownPosition`'s call shape doesn't need to change, but the
|
|
residence/executor layer needs the new "suppress effect replay for split
|
|
continuation" capability above. `WorldDropDispatch`/`ShortcutDropPlanner`/
|
|
`ItemInteractionController` dispatch (pure UI/wire trigger) is unaffected.
|
|
|
|
---
|
|
|
|
## Route 7 — Pickup, Parent, and Delete (from parallel research agent)
|
|
|
|
### Confirms/refutes "Runtime hooks already exist"
|
|
|
|
**Largely CONFIRMED at the App/headless-caller level**: none of
|
|
`LiveEntityHydrationController`, `EquippedChildRenderController`,
|
|
`LiveEntityDeletionController`, `LiveEntityRuntimeTeardownController` do their
|
|
own `SetFullCell`/`SuspendObjectClock`/rebucket math — all of it lives inside
|
|
`RuntimeEntityObjectLifetime` (`TryApplyPickup:805-868`,
|
|
`TryApplyParent/TryApplyCreateParent` -> shared helper
|
|
`CommitPositionChannelUpdate:1705-1737`, `TryCommitParent:944-974`,
|
|
`CommitAcceptedParentCellless:976-1007`, `CommitWithdrawal:1401-1422`,
|
|
`TryAcceptDelete:1466-1527`). App/headless work is exclusively presentation
|
|
(render subtree, shadows/effects/animation/selection teardown) — legitimate,
|
|
not duplicated physics authority.
|
|
|
|
**BUT the claim needs an important qualification (previously-unstated
|
|
finding)**: every `ForgetInitialCreateResidence(...)` call in this family
|
|
(849, 1720-1721, 990, 1411, 1501) cancels a residence lease that is **NEVER
|
|
ACQUIRED** anywhere in production (same Route-1/6 finding —
|
|
`RegisterEntityWithInitialResidence` unreached), and every paired
|
|
`Physics.SetPosition.Forget(...)` cancels a placement transaction that is
|
|
likewise **NEVER BEGUN** anywhere in production
|
|
(`BeginAcceptedPlacement`/`BeginAuthoredPlacement`,
|
|
`RuntimeSetPositionState.cs:803,815`, zero external callers repo-wide). **So
|
|
today these cancellation calls are structurally present but functionally
|
|
no-ops against always-empty state** — `InitialCreateResidences.Forget`
|
|
returns false; `PreferCancellation` always falls through to the equally-empty
|
|
"ordinary" receipt. Route 7 is correctly "already cut over" for the parts
|
|
that currently do anything (SetFullCell/SuspendObjectClock/RefreshSnapshot/
|
|
AdvanceXAuthority/CollisionReports/EndChildProjection are genuinely
|
|
Runtime-owned) — but the specific behavior the campaign doc worries about
|
|
("cancel the exact active placement/lost-cell family first") is INERT
|
|
because there is nothing upstream yet to cancel. Once routes 1-5 wire the
|
|
create/position path live, these existing calls become live with NO code
|
|
change required — genuinely pre-built and correctly sequenced already.
|
|
|
|
### Two internal asymmetries flagged for cutover review (new findings)
|
|
|
|
- `TryCommitParent` (944-974) is the ONLY method in the family with NO
|
|
`ForgetInitialCreateResidence`/`PreferCancellation` call at all.
|
|
- `CommitWithdrawal` (1401-1422) calls `ForgetInitialCreateResidence` but NOT
|
|
`Physics.SetPosition.Forget`/`PreferCancellation`, unlike Pickup/
|
|
`CommitAcceptedParentCellless`/`TryAcceptDelete` which cancel both families.
|
|
Once ordinary placement transactions go live,
|
|
`WithdrawLiveEntityProjectionToCellless` (`LiveEntityRuntime.cs:1315-1331`,
|
|
calls `CommitWithdrawal`) could leave an active mover/lost-cell watch
|
|
dangling. **Flag for explicit cutover test, do not assume symmetric with
|
|
the other four methods.**
|
|
|
|
### Headless-specific gap (real, pre-existing, adjacent to but distinct from the cutover)
|
|
|
|
`RuntimeLiveEntitySessionController.OnParentUpdated` (216-220) calls ONLY
|
|
`Entities.TryApplyParent` directly — **headless NEVER calls
|
|
`TryCommitParent`/`CommitAcceptedParentCellless`** — there is no headless
|
|
equivalent of the App parent-realize sequence
|
|
(`EquippedChildRenderController.ResolveAndTryRealize`/`PrepareAndTryRealize`,
|
|
`LiveEntityHydrationController.cs:470` chain) AT ALL. This looks like a live
|
|
gap independent of the residence/executor cutover — a headless parented
|
|
child's `FullCellId` may never clear. Needs new code, not just wiring.
|
|
`HeadlessSessionWorldProjection`/`IRuntimeDirectWorldProjection` has no
|
|
pickup/parent/delete hook at all (only `ProjectSpawn`/`ProjectPosition`/
|
|
`BeginTeleport`/`PrepareDestination`) — headless has ZERO duplicate
|
|
placement work for route 7 (consistent with "already cut over" for the parts
|
|
that do anything).
|
|
|
|
### Gap vs. the executor
|
|
|
|
"What the executor owns" already anticipates Route 7's "pickup during
|
|
preparation/deferred residence" and "parent during pending withdrawal" test
|
|
scenarios via the `TryGetPendingInitialResidence`/`EnqueueDormant` branches
|
|
already present in `TryApplyPickup`/`TryApplyParent` — no NEW executor
|
|
capability is needed for route 7's steady-state (post-create) behavior; the
|
|
gap is entirely "wire up creates to use residence" (routes 1-3's job). One
|
|
real gap: the executor's receipt/trace has no concept of "a pickup/delete
|
|
cancelled my pending residence" — `RuntimePlacementCancellationReceipt`
|
|
contents route only to `Physics.SetPosition.PublishCancellation`, never
|
|
surfaced to the calling host for observability/testing.
|
|
|
|
---
|
|
|
|
## Route 8 — Headless parity (researched directly, not delegated)
|
|
|
|
Files read in full: `src/AcDream.Headless/Hosting/HeadlessSessionWorldProjection.cs`
|
|
(693 lines), `src/AcDream.Runtime/Session/RuntimeLiveEntitySessionController.cs`
|
|
(349 lines).
|
|
|
|
### Current production call chain — headless host
|
|
|
|
Entry: `RuntimeLiveEntitySessionController.CreateSink()` (56-68) wires a
|
|
`LiveEntitySessionSink` whose delegates are the ONLY headless wire-dispatch
|
|
entry points for entity/position/motion/vector/state/parent/teleport traffic.
|
|
Each handler below calls straight into `RuntimeEntityObjectLifetime` (the
|
|
LEGACY, non-residence overloads — every call passes `acknowledgeProjection: null`)
|
|
and then, only for specific cases, into `IRuntimeDirectWorldProjection`
|
|
(implemented by `HeadlessSessionWorldProjection`):
|
|
|
|
1. `OnSpawned` (73-95) — `Entities.RegisterEntity(spawn)` (75-76, **the
|
|
legacy Create entry point — confirmed by the surface map's grep as one of
|
|
only two production Create call sites in the repo**) → if `registration.Canonical`
|
|
present, `Entities.ApplyAcceptedSpawn(canonical, integrationVersion,
|
|
canonical.Snapshot, replaceGeneration: ...)` (81-87) → if applied,
|
|
`_worldProjection?.ProjectSpawn(canonical, isLocalPlayer)` (90-93) →
|
|
`HeadlessSessionWorldProjection.ProjectSpawn` (507-513): **no-op for any
|
|
non-local entity** (`if (isLocalPlayer) SynchronizeLocalPlayer(record);` —
|
|
line 511-512 IS the entire method body); for the local player, calls
|
|
`SynchronizeLocalPlayer` (566-615) which is the duplicate placement
|
|
authority (see below).
|
|
2. `OnPositionUpdated` (137-201) — `Entities.TryApplyPosition(update, isLocal,
|
|
forcePositionRotation: localController?.BodyOrientation,
|
|
currentLocalVelocity: localController?.BodyVelocity,
|
|
projectionRequiresTeleportHook: false, acknowledgeProjection: null, ...)`
|
|
(144-153, legacy path) → if `!isLocal` or `Rejected`, return (154-159) →
|
|
if `disposition is Apply`, builds a `RuntimeTeleportDestination` from the
|
|
wire position and calls `_runtime.TransitOwner.OfferTeleportDestination(destination,
|
|
timestamps.TeleportAdvanced)` (161-184, **every accepted local Position is
|
|
offered to the transit owner as a POTENTIAL portal destination — the
|
|
transit owner itself decides whether it's actually a portal**) → looks up
|
|
the active record and calls `_worldProjection?.ProjectPosition(record,
|
|
isLocalPlayer: true, disposition)` (185-193) → `HeadlessSessionWorldProjection.ProjectPosition`
|
|
(515-531): no-op for non-local; if no controller yet, `SynchronizeLocalPlayer`
|
|
(525); **else only for `disposition is ForcePosition` does it call
|
|
`BlipLocalPlayer`** (529-530) — an ordinary `Apply` disposition with an
|
|
existing controller does NOTHING here (ordinary interpolation must happen
|
|
elsewhere, at the physics tick) → back in the session controller, if
|
|
`disposition is ForcePosition`, `_localPlayerOutbound.SendImmediatePosition(_session,
|
|
_runtime.MovementOwner.Controller)` (194-199, **sends the outbound wire ack
|
|
BEFORE any Runtime placement commit/host-acknowledgement — the headless
|
|
twin of route 2's "direct BlipPosition/pre-commit acknowledgement"**) →
|
|
`TryCompletePortal()` (200, 243-332) unconditionally at the end.
|
|
3. `OnPickedUp`/`OnMotionUpdated`/`OnVectorUpdated`/`OnStateUpdated`/`OnParentUpdated`/`OnAppearanceUpdated`
|
|
(118-122, 124-135, 203-207, 209-214, 216-220, 237-241) — thin 1-line
|
|
pass-throughs to `Entities.TryApply*(..., acknowledgeProjection: null, out _)`,
|
|
no independent headless placement work.
|
|
4. `OnDeleted` (97-116) — `Entities.TryAcceptDelete(...)` →
|
|
`Entities.CompleteAcceptedDelete(acceptance)` → `Entities.RetireCanonicalOnly(retired)`.
|
|
No independent headless placement work (matches route 7's claim that
|
|
pickup/parent/delete are already mostly Runtime-owned — see route 7's
|
|
section for the App-side confirmation).
|
|
5. `OnTeleportStarted` (222-235) → `transit.TryQueueTeleportStart` →
|
|
`_worldProjection?.BeginTeleport()` → `HeadlessSessionWorldProjection.BeginTeleport`
|
|
(533-537): sets `controller.State = PlayerState.PortalSpace` directly (no
|
|
Runtime commit gate) → `transit.ActivateQueuedTeleport()` → `TryCompletePortal()`.
|
|
6. `TryCompletePortal` (243-332) — a **fully Runtime-driven, already-cutover
|
|
state machine** for the portal handshake itself: `TryGetAcceptedTeleportDestination`
|
|
→ `TryBeginPortalReveal` → `TryRegisterHostProjection` → `Acknowledge(...ProjectionRegistered)`
|
|
→ `_worldProjection?.PrepareDestination(generation, destination)` (271-274,
|
|
**the ONE point where the headless host does its own destination-prep
|
|
work** — see below) → `transit.AcknowledgeDestinationReadiness(readiness)`
|
|
→ `transit.AcknowledgePortalMaterialized(...)` →
|
|
`Acknowledge(...SimulationReleaseProjected)` →
|
|
`transit.RequireDestinationReservationRelease(projection)` →
|
|
`Acknowledge(...DestinationReservationReleased)` →
|
|
`transit.AcknowledgeWorldViewportVisible` + `transit.Complete(generation)` →
|
|
`Acknowledge(...TerminalProjected)` → `_session.SendGameAction(GameActionLoginComplete.Build())`
|
|
→ `transit.EndTeleport()`. **This entire sequence is already Runtime-owned
|
|
generation/token bookkeeping (J6.2-J6.4 landed) — it is NOT a route-8
|
|
duplicate authority; it's the shared portal-lifecycle plumbing routes 3
|
|
and 8 both sit on top of.**
|
|
|
|
### Duplicate authorities — headless (what the residence+executor owner must take over)
|
|
|
|
- **`HeadlessSessionWorldProjection.SynchronizeLocalPlayer`** (566-615, read
|
|
in full): `_collision.CenterOn(position.LandblockId)` (575) →
|
|
`CreateController(record)` if none exists (577-578, 639-655 — constructs
|
|
`PlayerMovementController` directly, see prerequisite-C section above) →
|
|
`_runtime.EntityObjects.Physics.Engine.Resolve(wirePosition, position.LandblockId,
|
|
Vector3.Zero, 100f)` (589-594) → `.ResolvePlacement(resolved.Position,
|
|
resolved.CellId, DefaultRadius, DefaultHeight, controller.StepUpHeight,
|
|
controller.StepDownHeight, ObjectInfoState.IsPlayer | EdgeSlide,
|
|
record.LocalEntityId ?? 0u)` (595-605) → `controller.SetPosition(resolved.Position,
|
|
resolved.CellId, wirePosition)` + `controller.SetBodyOrientation(orientation)`
|
|
(610-614). **This is a COMPLETE independent physics resolve + placement
|
|
commit, entirely bypassing `RuntimeSetPositionState`.** It is the headless
|
|
mirror of route-1's "Headless performs its own initial resolve/placement/body
|
|
construction" callout in the campaign handoff.
|
|
- **`HeadlessSessionWorldProjection.BlipLocalPlayer`** (617-637): direct
|
|
`controller.BlipPosition(wirePosition, position.LandblockId, wirePosition)`
|
|
(633-636) with zero Runtime commit/ack gating — this is exactly the
|
|
"direct Blip" duplicate the campaign handoff names for route 8 ("Delete the
|
|
independent resolve/placement/direct SetPosition and Blip logic in
|
|
`HeadlessSessionWorldProjection`").
|
|
session controller's `_localPlayerOutbound.SendImmediatePosition(...)` at
|
|
`RuntimeLiveEntitySessionController.cs:196-198` fires on the SAME
|
|
`ForcePosition` branch, before any placement receipt exists — pre-commit ack.
|
|
- **`HeadlessSessionWorldProjection.PrepareDestination`** (539-564): calls
|
|
`_collision.CenterOn(destination.CellId)` (543) then, if the destination
|
|
entity is active, `SynchronizeLocalPlayer(record)` again (544-549) — i.e.
|
|
portal arrival re-runs the SAME duplicate resolve/placement authority.
|
|
Also directly sets `controller.State = PlayerState.InWorld` (550-551)
|
|
outside any Runtime placement gate.
|
|
- **`HeadlessCollisionNeighborhood`** (168-469): NOT a placement-authority
|
|
duplicate per se (it manages per-landblock collision generation
|
|
publication/retirement, a different concern from entity placement) but it
|
|
IS the thing `RuntimeSetPositionState`/`RuntimeInitialCreateResidenceState`
|
|
must be able to wait on (its `IsReady`/`CenterOn` gate the destination
|
|
readiness the residence lease's placement depends on) — this file already
|
|
looks like a reasonable candidate for the "exact authored mover preparation"
|
|
(prerequisite B) collision-readiness signal, not something to delete.
|
|
|
|
### What each headless hop must become post-cutover
|
|
|
|
- `OnSpawned` → keep the legacy timestamp-gate call semantics but swap
|
|
`Entities.RegisterEntity(spawn)` for `Entities.RegisterEntityWithInitialResidence(spawn, ...)`
|
|
(the wrapper already exists per the surface map, just unused) so headless
|
|
enters the SAME residence lease as graphical; `ProjectSpawn` becomes
|
|
acknowledge-only: subscribe an `IRuntimePlacementObserver` (does not exist
|
|
yet — see observer-seam section) and, on a `Place` receipt for this
|
|
entity's token, set `controller.SetPosition`/`SetBodyOrientation` from the
|
|
IMMUTABLE `RuntimePlacementProjectionSnapshot` fields only. DELETE
|
|
`SynchronizeLocalPlayer`'s direct `Physics.Engine.Resolve`/`ResolvePlacement`
|
|
calls (589-605) outright — the sweep must happen exactly once, inside
|
|
`RuntimeSetPositionState`, driven by the residence lease.
|
|
- `OnPositionUpdated`'s ForcePosition branch → DELETE the direct
|
|
`BlipLocalPlayer`/`controller.BlipPosition` call (633-636) and the
|
|
immediate `SendImmediatePosition` pre-commit ack (196-198 in the session
|
|
controller); replace with: accept timestamp only (still call
|
|
`TryApplyPosition`/`TryAcceptDeferredPosition` for the timestamp-gate
|
|
side-effects already established) → begin the Runtime placement (route 2's
|
|
"LocalAuthoritative" route kind) → wait for the `Place` receipt via the new
|
|
headless `IRuntimePlacementObserver` → apply position from the receipt →
|
|
THEN send the single outbound ack.
|
|
- `PrepareDestination` → keep the collision-neighborhood prep (`_collision.CenterOn`/`IsReady`)
|
|
as the readiness signal Runtime's portal-placement authority polls, but
|
|
DELETE the second `SynchronizeLocalPlayer` call (548) — arrival placement
|
|
must come from the SAME Runtime portal-placement commit route 3 defines,
|
|
not a second independent resolve.
|
|
- `CreateController` → becomes the headless half of prerequisite C's atomic
|
|
controller/body publication transaction; must stop being callable
|
|
ad hoc from three different places (`SynchronizeLocalPlayer` twice via
|
|
`OnSpawned`+`PrepareDestination`, indirectly via `OnPositionUpdated`) and
|
|
instead run exactly once, gated by the same atomic commit graphical
|
|
route 1 uses.
|
|
|
|
### Route-8-specific capability gap
|
|
|
|
The residence+executor mechanism (per
|
|
`docs/research/2026-08-02-runtime-continuation-executor-handoff.md`,
|
|
"What the executor owns") is host-agnostic by design — nothing in its
|
|
API is graphical-only. The concrete gap for route 8 is therefore NOT in the
|
|
executor; it is that **no headless `IRuntimePlacementObserver` implementation
|
|
exists to consume `GameRuntime.Placements` receipts at all** (see
|
|
observer-seam section above — zero hits in `src/AcDream.Headless/`), so
|
|
today headless has no mechanism to learn "the Runtime commit landed, project
|
|
it" even in principle. Headless also has no analog to a "camera" but DOES
|
|
have the same controller/body atomicity requirement as graphical
|
|
(prerequisite C applies equally to both hosts — confirmed by the very
|
|
existence of `HeadlessSessionWorldProjection.CreateController` as a second,
|
|
independent constructor of `PlayerMovementController` next to
|
|
`PlayerModeController.BuildControllerAndCamera`).
|
|
|
|
---
|
|
|
|
(routes 1-7 sections below filled in as each parallel research agent reports)
|