acdream/src/AcDream.App/Composition/SessionPlayerComposition.cs
Erik 6dc7ba51ee feat(physics): C4 route 4b-3 — remote teleport + cell-less through the canonical placement
Flips the last remote classification (SetPosition: teleport-advanced and
cell-less) onto 4b-1's RuntimeRemotePlacementDriveController, runs retail's
teleport_hook before the placement, and deletes the legacy remote-teleport
machinery. Contract: docs/research/2026-08-04-c4-route-4b-3-contract.md.

Retail: MoveOrTeleport @0x00516330's branch @0x00516386 -> teleport_hook
@0x005163EF -> SetFlags(0x1012) @0x00516414 -> SetPosition @0x00516420 ->
return 1 @0x00516438. The hook @0x00514ED0 runs BEFORE the placement and
regardless of its outcome. Retail places this branch unconditionally, at any
distance and any contact state (arg4 is read only @0x0051638E, after the
branch) — which is what retires AP-137's cell-less enqueue-vs-place delta.

D1 — the classifier's cell-less input is now the PRE-merge committed cell.
Retail's predicate is `this_1->cell == 0`, the BODY's own cell at
MoveOrTeleport entry (this_1 is assigned from this @0x00516334). acdream fed
the POST-merge canonical.FullCellId, which RefreshSnapshot ->
RefreshDerivedState -> SetFullCell has already stamped with the accepted wire
cell; a zero wire cell fails validation into RejectedData first. The shipped
remote cell-less predicate was therefore dead code, not merely different from
remotePlacementRequired. Threaded via a builder overload; route 1's overload
is untouched. The graphical !IsSpatiallyVisible arm of
projectionRequiresTeleportHook is deleted — a presentation predicate with no
retail analogue that fired the teleport machinery on a routine hot path.

Deleted: RemoteTeleportController (605), RemoteTeleportPlacement (85),
RemoteShadowPlacementSynchronizer (49), their 1,709 lines of tests, the
remotePlacementRequired predicate, the TeleportHookRequired plumbing, the
legacy pre-operation ConstrainTo fallback, and the player arm's legacy
!IsGrounded fallback. Net -2,030 lines.

Structural fix (two independent Opus reviews, round 1 FAIL/FAIL): three of the
four MAJORs were one defect — OnPosition carried two parallel inline copies of
the routing tail (player-guid, NPC-guid) that had drifted. Extracted
RunRemoteArmTail (3 call sites) and ApplyWireAirborneLeftoverBookkeeping (2),
both branches now share one implementation.

  A1  ToConstraintArm mapped AirborneSnap -> AirborneNoOperation, so the NPC
      arm armed ConstrainTo ZERO times for an out-of-contact wire-grounded
      creature — a regression this slice introduced while closing a
      structurally identical hole. Now maps to NearInterpolate; switch made
      total with a throwing default proven unreachable.
  R1  D2's write-nothing shape existed on the player arm only; NPC packets
      fell through and wrote the body. Retail makes no player/NPC distinction.
  R2  report_collision_end(this,1) @0x00514F31 was bound to
      ShadowObjects.Suspend, a port of a DIFFERENT retail function
      (remove_shadows_from_cells) that teleport_hook never calls. Now routes
      to RuntimeCollisionReportingState.LeaveWorld, which wraps the private
      ForceEnd in an admission-blocking transaction so a DoCollisionEnd
      callback cannot recreate the contact table.
  R3/A2 A teleported NPC synthesized ServerVelocity from the teleport distance
      (~1,000+ m/s) and planned a run cycle from it. Both the install and
      RemoteServerControlledVelocityCycle.Apply now gate on !isTeleportRoute.

BISECT HAZARD — A1's fix is correct only BECAUSE R1 landed. AirborneSnap is
reachable wire-airborne on the NPC arm only while D2's shape is missing there.
Reverting R1 alone silently inverts A1 into the opposite divergence: arming
where retail returns 0. Revert both or neither.

Also in the velocity hunk: the NPC block's two !IsPlayerGuid(update.Guid)
guards were dropped when it was wrapped in `if (!isTeleportRoute)`. Safe — all
five exit paths of the enclosing IsPlayerGuid block return, so the predicate is
unconditionally false below it — but it was unremarked by both reviews.

Register: AP-137 REWRITTEN (not deleted) to the surviving acdream-only
divergences — null classification during the login window and Rejected*
through UnroutedCatchUp keep a row. AD-42's RemoteTeleportController citation
retired; AP-136/AP-138 writer lists corrected to the two surviving non-Position
rebucket writers; AP-138 gains the teleport arm as a second producer of the
visible-without-collision residual (retirement path remains #309). AP-135 is
untouched and its two airborne bookkeeping writes are preserved on both arms.
AP-131 does not retire; #276 does not close.

Proof obligation 1: ParkCollisionResidents' overlap throw stays unreachable —
the teleport arm adds packets to the same TryBeginExclusiveAuthoredPlacement
one-operation-per-key machinery the far arm uses, opens no new operation shape,
and every DeferredCell outcome cancels synchronously with
restoreCancelledPark: true. The guarded property remains
HasOldPrefixPlacementDebt's stall, not a throw (4b-1's B2 caveat stands).

Correction to an earlier claim: LiveEntityPresentationController's
_activePlacementOwners was NOT write-never at HEAD —
remotePlacementRequired -> BeginPlacement -> Begin -> BeginAuthoritativePlacement
was a live writer chain. It becomes write-never BECAUSE this slice deletes that
chain, which is why deleting the dead half is behaviour-preserving.

Probe: ACDREAM_PROBE_REMOTE_TELEPORT=1 emits one [remote-teleport] line per
routed arm (guid, cause, hook-ran, placement status). TEMPORARY, strip with the
probe family.

Carried, disclosed not fixed: no dedicated bidirectional collision-partner test
for R2 (the wiring, not LeaveWorld itself, is what lacks coverage); the
stress test's teleport step drives hand-written field assignments rather than
the canonical arm; the per-packet runTeleportHook closure allocation (network
path, not the resolve path Slice I's 0 B discipline governs — file before
route 5 adds a fourth call site). B2: IRuntimeCollisionReportObserver has zero
production implementations, so retail's bidirectional DoCollisionEnd half still
reaches no gameplay consumer — this fix closes the wrong-function binding, not
that nobody listens.

Complete Release suite MEASURED at 11,013 passed / 4 skipped / 0 failed
(baseline 11,027/4/0; net -14 = ~33 deleted test cases against ~19 added).
Neither known flake fired (#302 PortalProjectionTests GC-allocation, #308
NakEmissionTests wall-clock).

STILL OWED: the two-client connected gate, which MUST use an NPC/creature
teleport target. Both round-1 MAJORs lived on the NPC arm and the velocity
cycle early-returns for 0x50xxxxxx guids, so a player target structurally
cannot observe A1, A2, or R3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 16:00:10 +02:00

1145 lines
45 KiB
C#

using AcDream.App.Combat;
using AcDream.App.Input;
using AcDream.App.Interaction;
using AcDream.App.Diagnostics;
using AcDream.App.Net;
using AcDream.App.Physics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Scene;
using AcDream.App.Rendering.Vfx;
using AcDream.App.Rendering.Wb;
using AcDream.App.Runtime;
using AcDream.App.Settings;
using AcDream.App.Streaming;
using AcDream.App.Update;
using AcDream.App.World;
using AcDream.Content;
using AcDream.Core.Combat;
using AcDream.Core.Items;
using AcDream.Core.Chat;
using AcDream.Core.Physics;
using AcDream.Core.Physics.Motion;
using AcDream.Core.Plugins;
using AcDream.Core.Rendering;
using AcDream.Core.Selection;
using AcDream.Core.Player;
using AcDream.Core.Social;
using AcDream.Core.Spells;
using AcDream.Core.World;
using AcDream.Runtime;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Gameplay;
using AcDream.Runtime.Session;
using Silk.NET.Windowing;
namespace AcDream.App.Composition;
internal sealed record SessionPlayerDependencies(
RuntimeOptions Options,
GameRuntime Runtime,
IWindow Window,
object DatLock,
RuntimeSettingsController Settings,
SettingsDevToolsResult SettingsDevTools,
WorldEnvironmentController WorldEnvironment,
WorldSceneDebugState WorldSceneDebugState,
RuntimeDiagnosticCommandSlot RuntimeDiagnosticCommands,
LiveCombatModeCommandSlot CombatModeCommands,
RuntimeCombatModeOperationsSlot CombatModeOperations,
HostQuiescenceGate HostQuiescence,
PhysicsEngine PhysicsEngine,
PhysicsDataCache PhysicsDataCache,
WorldGameState WorldGameState,
WorldEvents WorldEvents,
EntityClassificationCache ClassificationCache,
LiveEntityRuntimeSlot RuntimeSlot,
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> AnimatedEntities,
RemoteMovementObservationTracker RemoteMovementObservations,
RemotePhysicsUpdater RemotePhysicsUpdater,
RemoteInboundMotionDispatcher RemoteInboundMotion,
RetailInboundEventDispatcher InboundEntityEvents,
DeferredLiveEntityMotionRuntimeBindings MotionBindings,
LocalPlayerIdentityState PlayerIdentity,
LocalPlayerPhysicsHostSlot PlayerHost,
LocalPlayerModeState PlayerMode,
ChaseCameraInputState ChaseCameraInput,
LocalPlayerOutboundController PlayerOutbound,
DeferredLocalPlayerTeleportNetworkSink TeleportSink,
LiveWorldOriginState WorldOrigin,
WorldRenderRangeState RenderRange,
LocalPlayerShadowState PlayerShadow,
ViewportAspectState ViewportAspect,
PlayerApproachCompletionState PlayerApproachCompletions,
PointerPositionState PointerPosition,
DispatcherMovementInputSource MovementInput,
IInputCaptureSource InputCapture,
AcDream.App.Rendering.Vfx.ParticleVisibilityController ParticleVisibility,
TranslucencyFadeManager TranslucencyFades,
EntityEffectPoseRegistry EffectPoses,
UpdateFrameClock UpdateClock,
MovementTruthDiagnosticController MovementDiagnostics,
CombatAttackOperationsSlot CombatAttackOperations,
CombatFeedbackSlot CombatFeedback,
TransferableResourceSlot<PortalTunnelPresentation> PortalTunnelFallback,
Action<string> Log)
{
public RuntimeActionState Actions => Runtime.ActionOwner;
public RuntimeEntityObjectLifetime EntityObjects =>
Runtime.EntityObjects;
public RuntimeLocalPlayerMovementState PlayerController =>
Runtime.MovementOwner;
public RuntimeInventoryState Inventory => Runtime.InventoryOwner;
public RuntimeCommunicationState Communication =>
Runtime.CommunicationOwner;
public RuntimeCharacterState Character => Runtime.CharacterOwner;
}
internal sealed record SessionPlayerResult(
LandblockStreamer Streamer,
StreamingController Streaming,
StreamingOriginRecenterCoordinator StreamingOriginRecenter,
WorldRevealCoordinator WorldReveal,
DatSpawnClaimHydrationClassifier SpawnClaimHydration,
LiveSessionController LiveSession,
LiveEntityHydrationController Hydration,
LiveEntityNetworkUpdateController NetworkUpdates,
LiveEntityLivenessController Liveness,
LiveEntitySessionController SessionEvents,
GameplayInputFrameController GameplayInput,
StreamingFrameController StreamingFrame,
LocalPlayerAnimationController LocalPlayerAnimation,
LocalPlayerShadowSynchronizer LocalPlayerShadow,
LiveLocalPlayerFrameRuntime LocalPlayerFrameRuntime,
RetailLocalPlayerFrameController LocalPlayerFrame,
LiveSpatialPresentationReconciler LiveSpatialReconciler,
LiveObjectFrameController LiveObjectFrame,
PlayerModeController PlayerMode,
PlayerModeAutoEntry PlayerModeAutoEntry,
LocalPlayerTeleportController LocalTeleport,
LiveSessionHost SessionHost,
RuntimePlacementProjectionRetrySlot PlacementProjectionRetry,
CurrentGameRuntimeAdapter GameRuntime,
GameplayInputActionRouter? GameplayActions,
SessionPlayerRuntimeBindings RuntimeBindings);
internal interface IGameWindowSessionPlayerPublication
{
void PublishSessionPlayer(SessionPlayerResult result);
}
internal enum SessionPlayerCompositionPoint
{
StreamingRadiiResolved,
StreamerCreated,
StreamerStarted,
StreamingCreated,
RuntimeSettingsBound,
WorldRevealCreated,
LiveSessionCreated,
HydrationCreated,
LiveEntityGraphBound,
CombatOperationsBound,
GameplayInputBound,
UpdateLeavesCreated,
PlayerModeBound,
PortalTransferred,
TeleportBound,
SessionHostCreated,
CommandsBound,
GameplayActionsAttached,
ResultPublished,
}
internal sealed class SessionPlayerCompositionPhase
: ISessionPlayerCompositionPhase<
HostInputCameraResult,
ContentEffectsAudioResult,
SettingsDevToolsResult,
WorldRenderResult,
InteractionRetainedUiResult,
LivePresentationResult,
SessionPlayerResult>
{
private readonly SessionPlayerDependencies _dependencies;
private readonly IGameWindowSessionPlayerPublication _publication;
private readonly Action<SessionPlayerCompositionPoint>? _faultInjection;
public SessionPlayerCompositionPhase(
SessionPlayerDependencies dependencies,
IGameWindowSessionPlayerPublication publication,
Action<SessionPlayerCompositionPoint>? faultInjection = null)
{
_dependencies = dependencies
?? throw new ArgumentNullException(nameof(dependencies));
_publication = publication
?? throw new ArgumentNullException(nameof(publication));
_faultInjection = faultInjection;
}
public SessionPlayerResult Compose(
HostInputCameraResult host,
ContentEffectsAudioResult content,
SettingsDevToolsResult settings,
WorldRenderResult world,
InteractionRetainedUiResult interaction,
LivePresentationResult live)
{
ArgumentNullException.ThrowIfNull(host);
ArgumentNullException.ThrowIfNull(content);
ArgumentNullException.ThrowIfNull(settings);
ArgumentNullException.ThrowIfNull(world);
ArgumentNullException.ThrowIfNull(interaction);
ArgumentNullException.ThrowIfNull(live);
if (!ReferenceEquals(_dependencies.SettingsDevTools, settings))
{
throw new InvalidOperationException(
"Session/player dependencies do not match the ordered settings result.");
}
var scope = new CompositionAcquisitionScope();
SessionPlayerRuntimeBindings? bindings = null;
bool bindingsOwnedByScope = false;
try
{
SessionPlayerResult result = ComposeCore(
host,
content,
world,
interaction,
live,
scope,
ref bindings,
ref bindingsOwnedByScope);
scope.Complete();
return result;
}
catch (Exception failure)
{
if (bindings is not null && !bindingsOwnedByScope)
{
scope.Own(
"session/player runtime bindings",
bindings,
static value => value.Dispose());
bindingsOwnedByScope = true;
}
scope.RollbackAndThrow(failure);
throw new System.Diagnostics.UnreachableException();
}
}
private SessionPlayerResult ComposeCore(
HostInputCameraResult host,
ContentEffectsAudioResult content,
WorldRenderResult world,
InteractionRetainedUiResult interaction,
LivePresentationResult live,
CompositionAcquisitionScope scope,
ref SessionPlayerRuntimeBindings? bindings,
ref bool bindingsOwnedByScope)
{
SessionPlayerDependencies d = _dependencies;
WorldRenderFoundation foundation = world.Foundation;
int nearRadius = d.Settings.ResolvedQuality.NearRadius;
int farRadius = d.Settings.ResolvedQuality.FarRadius;
if (d.Options.LegacyStreamRadius is { } legacyRadius)
{
nearRadius = legacyRadius;
farRadius = Math.Max(legacyRadius, farRadius);
}
d.RenderRange.NearRadius = nearRadius;
d.RenderRange.FarRadius = farRadius;
d.Log(
$"streaming: nearRadius={nearRadius} " +
$"(window={2 * nearRadius + 1}x{2 * nearRadius + 1}) " +
$"farRadius={farRadius} " +
$"(window={2 * farRadius + 1}x{2 * farRadius + 1})");
Fault(SessionPlayerCompositionPoint.StreamingRadiiResolved);
IPreparedCollisionSource preparedCollisions =
content.PreparedAssets as IPreparedCollisionSource ??
throw new NotSupportedException(
"Production prepared assets must expose the matching " +
"prepared-collision catalog.");
var landblockBuildFactory = new LandblockBuildFactory(
content.Dats,
preparedCollisions,
d.DatLock,
world.TerrainBuild.HeightTable,
d.Options.DumpSceneryZ);
var streamerLease = scope.Acquire(
"landblock streamer",
() => LandblockStreamer.CreateForRequests(
landblockBuildFactory.Build,
(id, landblock) =>
{
if (landblock is null)
return null;
uint x = (id >> 24) & 0xFFu;
uint y = (id >> 16) & 0xFFu;
return AcDream.Core.Terrain.LandblockMesh.Build(
landblock.Heightmap,
x,
y,
world.TerrainBuild.HeightTable,
world.TerrainBuild.Blending,
world.TerrainBuild.SurfaceCache);
}),
static value => value.Dispose());
Fault(SessionPlayerCompositionPoint.StreamerCreated);
streamerLease.Resource.Start();
Fault(SessionPlayerCompositionPoint.StreamerStarted);
var streaming = new StreamingController(
enqueueLoad: (id, kind, generation) => streamerLease.Resource.EnqueueLoad(
new LandblockBuildRequest(
id,
kind,
generation,
new LandblockBuildOrigin(
d.WorldOrigin.CenterX,
d.WorldOrigin.CenterY))),
enqueueUnload: streamerLease.Resource.EnqueueUnload,
completionSource: streamerLease.Resource,
state: live.WorldState,
nearRadius: nearRadius,
farRadius: farRadius,
presentationPipeline: live.LandblockPipeline,
clearPendingLoads: streamerLease.Resource.ClearPendingLoads,
workBudgetOptions: d.Options.StreamingWorkBudgets);
var streamingOriginRecenter = new StreamingOriginRecenterCoordinator(
streaming,
d.WorldOrigin);
streaming.MaxCompletionsPerFrame =
d.Settings.ResolvedQuality.MaxCompletionsPerFrame;
Fault(SessionPlayerCompositionPoint.StreamingCreated);
bindings = new SessionPlayerRuntimeBindings();
var settingsTargets = new RuntimeSettingsTargets(
new SilkRuntimeDisplayWindowTarget(d.Window),
live.DrawDispatcher,
foundation.TerrainAtlas,
streaming,
d.RenderRange,
interaction.RetainedUi?.Host.Root,
d.Log);
bindings.Adopt(
"runtime settings targets",
d.Settings.BindRuntimeTargetsOwned(settingsTargets));
Fault(SessionPlayerCompositionPoint.RuntimeSettingsBound);
var spawnClaimClassifier = new DatSpawnClaimHydrationClassifier(
content.Dats,
d.DatLock);
var worldQuiescence = new WorldGenerationQuiescence(
d.Actions.Selection,
live.WorldState,
guid => live.LiveEntities.TryGetProjectionKey(
guid,
out AcDream.Runtime.Entities.RuntimeEntityKey key)
? key
: null,
content.Audio?.Engine);
var compositeWarmupSource =
new CompositeWarmupEntitySource(live.WorldState);
// Campaign V slice V6h: composite-texture warmup is a property of the
// draw dispatcher, and mesh/texture reveal priority of the world upload
// path. On a backend with neither, the reveal gate's render-resource
// condition is trivially satisfied — there is nothing to warm — and
// every other reveal condition (streaming residence, spawn-cell
// readiness, terrain residence, quiescence) is unchanged.
WbDrawDispatcher? revealDispatcher = live.DrawDispatcher;
// #262 (Campaign P P6): the #111 [snap] apparatus was designed as a
// permanent low-volume diagnostic (one line per login/teleport entry
// snap) but PhysicsEngine.DiagnosticLog was never wired in production,
// so the 2026-07-29 Coldeve acceptance log was structurally silent on
// which Resolve branch the run-on-the-spot login took. Wire it per
// session composition (a session reset constructs a fresh engine).
d.PhysicsEngine.DiagnosticLog =
static line => System.Console.WriteLine(line);
var revealRenderResources = new WorldRevealRenderResourceScheduler(
foundation.MeshAdapter is { } revealMeshes
? revealMeshes.SetDestinationRevealUploadPriority
: static _ => { },
foundation.TextureCache.SetDestinationRevealUploadPriority);
var worldReveal = new WorldRevealCoordinator(
live.WorldTransit,
streaming.IsRenderNeighborhoodResident,
d.PhysicsEngine.IsSpawnCellReady,
d.PhysicsEngine.IsNeighborhoodTerrainResident,
() => revealDispatcher?.CompositeTexturesReady ?? true,
(destinationCell, radius) =>
{
if (revealDispatcher is null)
return;
compositeWarmupSource.Refresh(destinationCell, radius);
revealDispatcher.PrepareCompositeTextures(
compositeWarmupSource.Entities,
compositeWarmupSource.Generation,
destinationCell,
radius);
},
() =>
{
compositeWarmupSource.Reset();
revealDispatcher?.InvalidateCompositeWarmupReadiness();
},
spawnClaimClassifier.IsUnhydratable,
worldQuiescence,
streaming,
revealRenderResources);
Fault(SessionPlayerCompositionPoint.WorldRevealCreated);
return CompleteSessionPlayer(
host,
content,
world,
interaction,
live,
scope,
streamerLease,
streaming,
streamingOriginRecenter,
worldReveal,
spawnClaimClassifier,
bindings,
ref bindingsOwnedByScope);
}
private SessionPlayerResult CompleteSessionPlayer(
HostInputCameraResult host,
ContentEffectsAudioResult content,
WorldRenderResult world,
InteractionRetainedUiResult interaction,
LivePresentationResult live,
CompositionAcquisitionScope scope,
CompositionAcquisitionScope.CompositionAcquisitionLease<LandblockStreamer>
streamerLease,
StreamingController streaming,
StreamingOriginRecenterCoordinator streamingOriginRecenter,
WorldRevealCoordinator worldReveal,
DatSpawnClaimHydrationClassifier spawnClaimClassifier,
SessionPlayerRuntimeBindings bindings,
ref bool bindingsOwnedByScope)
{
SessionPlayerDependencies d = _dependencies;
var networkUpdateBridge = new DeferredLiveEntityNetworkUpdateSink();
var sealedDungeonCells = new DatSealedDungeonCellClassifier(
content.Dats,
d.DatLock);
var projectionMaterializer = new DatLiveEntityProjectionMaterializer(
d.Options,
content.Dats,
live.LiveEntities,
content.CollisionAssets,
content.AnimationLoader,
live.EntitySpawnAdapter,
world.Foundation.TextureCache,
d.ClassificationCache,
d.EffectPoses,
live.EquippedChildren,
d.WorldGameState,
d.WorldEvents,
d.PhysicsEngine.ShadowObjects,
content.CollisionBuilder,
live.ProjectileController,
live.AnimationPresenter,
live.StaticAnimationScheduler,
d.WorldOrigin,
d.UpdateClock);
var originCoordinator = new LiveEntityWorldOriginCoordinator(
d.WorldOrigin,
streaming,
live.WorldState,
worldReveal,
d.PlayerIdentity,
sealedDungeonCells,
d.Log);
LiveSessionController liveSession = d.Runtime.Session;
var liveSessionCommands = new LiveSessionCommandSurface();
var liveSessionSource = new LiveSessionAppSource(
liveSession,
liveSessionCommands);
bindings.Adopt(
"retained-UI live session",
interaction.LateBindings.Session.Bind(liveSessionSource));
var localPhysicsTimestamps =
new LiveSessionLocalPhysicsTimestampPublisher(
d.PlayerIdentity,
liveSessionSource);
Fault(SessionPlayerCompositionPoint.LiveSessionCreated);
var teardown = new LiveEntityRuntimeTeardownController(
live.LiveEntities,
live.Presentation,
live.EntityEffects,
live.SelectionInteractions,
d.Actions.Selection,
d.AnimatedEntities,
d.RemoteMovementObservations,
d.TranslucencyFades,
live.ProjectionWithdrawal,
live.EquippedChildren,
d.PhysicsEngine.ShadowObjects,
live.Lights,
d.ClassificationCache,
d.PlayerIdentity);
var dormantLiveEntities = new DormantLiveEntityStore();
var deletion = new LiveEntityDeletionController(
live.LiveEntities,
d.EntityObjects,
teardown,
d.PlayerIdentity,
dormantLiveEntities,
d.Options.DumpLiveSpawns ? d.Log : null);
// C3c: the graphical first-entry drive controller — walks every
// initial-Create residence through its Runtime conductor with the
// production prepared-collision source, the live movement-skill
// options, and the truthful local-player activation preparation
// (authored-cylinder radius/height with the legacy fallbacks, and
// the shadow disposition read from the exact shadow registry the
// activation commit validates against).
IPreparedCollisionSource firstEntryCollision =
content.PreparedAssets as IPreparedCollisionSource
?? throw new NotSupportedException(
"Production prepared assets must expose the matching "
+ "prepared-collision catalog.");
// C3c-R1 review F9: the provider runs on EVERY drive pump while the
// entity's conductor is still yielding, and the Setup cylinder per
// incarnation is immutable (SourceGfxObjOrSetupId and the spawn
// record's ObjScale are fixed at Create) — cache the resolved
// world-entity lookup + GetSetupCylinder per pending incarnation
// (keyed by the incarnation-unique local id; an unresolved/default
// result is NOT cached so late hydration still upgrades it). The
// shadow disposition deliberately stays live: shadow registration
// can land between pumps and the activation commit validates the
// disposition against the exact registry.
uint firstEntryCylinderLocalId = 0u;
float firstEntryCylinderRadius = 0f;
float firstEntryCylinderHeight = 0f;
var firstEntryDrive = new RuntimeFirstEntryDriveController(
d.EntityObjects,
d.Runtime.Clock,
firstEntryCollision,
() => PlayerMovementConstructionOptions.From(
d.Runtime.CharacterOwner.MovementSkills.Snapshot),
record =>
{
float radius = 0.48f;
float height = 1.835f;
uint localId = record.Key?.LocalEntityId ?? 0u;
if (localId != 0u && localId == firstEntryCylinderLocalId)
{
radius = firstEntryCylinderRadius;
height = firstEntryCylinderHeight;
}
else if (live.LiveEntities.TryGetWorldEntity(
record.ServerGuid,
out WorldEntity? playerEntity)
&& playerEntity is not null)
{
(float setupRadius, float setupHeight) =
d.MotionBindings.GetSetupCylinder(
record.ServerGuid,
playerEntity);
if (setupRadius >= 0.05f)
{
radius = setupRadius;
height = setupHeight;
if (localId != 0u)
{
firstEntryCylinderLocalId = localId;
firstEntryCylinderRadius = radius;
firstEntryCylinderHeight = height;
}
}
}
bool hasAuthoredShadow = record.Key is { } key
&& d.PhysicsEngine.ShadowObjects.HasLogicalOwner(
key.LocalEntityId);
return new RuntimeLocalPlayerPhysicsActivationPreparation(
radius,
height,
hasAuthoredShadow
? RuntimeLocalPlayerShadowDisposition
.RegisteredAuthoredPayload
: RuntimeLocalPlayerShadowDisposition.ProvenShapeless);
});
// C4 route 2 (2026-08-03): the graphical accepted-Position drive
// controller — the Runtime-owned execution seam for a ForcePosition
// on the already-live local player. Constructed once per session
// route alongside firstEntryDrive; both share the SAME entity
// lifetime, collision source, and clock.
var acceptedPositionDrive = new RuntimeAcceptedPositionDriveController(
d.EntityObjects,
d.Runtime.Clock,
firstEntryCollision,
d.PlayerOutbound,
() => d.Runtime.Generation,
() => d.PlayerIdentity.ServerGuid,
() => d.PlayerController.Controller,
() => d.Character.UsePositionFromServer,
() => liveSessionSource.CurrentSession);
// C4 route 4b-2 (2026-08-04): the graphical remote-placement drive
// controller — route 4b-1's dormant owner, now driven by the remote
// far snap. Its service window is the graphical host's near-tier
// residency (GraphicalRemotePlacementServiceWindow's own remarks
// justify IsNearTier over IsNearTierOrPending). Shares the SAME entity
// lifetime, clock, and prepared-collision source as the two drive
// controllers above.
var remotePlacementDrive = new RuntimeRemotePlacementDriveController(
d.EntityObjects,
d.Runtime.Clock,
firstEntryCollision,
new GraphicalRemotePlacementServiceWindow(live.WorldState));
var hydration = new LiveEntityHydrationController(
live.LiveEntities,
d.EntityObjects,
d.DatLock,
projectionMaterializer,
new LiveEntityRelationshipProjection(live.EquippedChildren),
new LiveEntityReadyPublisher(
live.LiveEntities,
live.EntityEffects,
live.Presentation,
live.RenderSceneShadow?.LiveProjections),
originCoordinator,
networkUpdateBridge,
localPhysicsTimestamps,
d.PlayerIdentity,
deletion,
dormantLiveEntities,
d.Options.DumpLiveSpawns ? d.Log : null,
firstEntryDrive,
acceptedPositionDrive);
bindings.Adopt(
"landblock-loaded hydration",
live.LandblockLoaded.Bind(hydration));
Fault(SessionPlayerCompositionPoint.HydrationCreated);
var worldDropProjection =
new InventoryWorldDropProjectionController(
interaction.ItemInteraction,
d.EntityObjects.Objects,
live.LiveEntities,
hydration,
() => d.UpdateClock.SimulationTimeSeconds);
bindings.Adopt(
"inventory world-drop projection",
worldDropProjection);
var networkUpdates = new LiveEntityNetworkUpdateController(
live.LiveEntities,
d.EntityObjects.Objects,
hydration,
live.EntityEffects,
live.Presentation,
live.Lights,
live.EquippedChildren,
live.ProjectileController,
d.AnimatedEntities,
d.RemoteMovementObservations,
d.RemotePhysicsUpdater,
d.RemoteInboundMotion,
live.MotionRuntime,
d.PhysicsEngine,
content.Dats,
content.AnimationLoader,
d.Actions.CombatTarget,
d.WorldOrigin,
d.TeleportSink,
d.PlayerController,
d.PlayerOutbound,
d.PlayerHost,
d.PlayerIdentity,
d.UpdateClock,
liveSessionSource,
localPhysicsTimestamps.Publish,
d.MovementDiagnostics,
acceptedPositionDrive,
remotePlacementDrive,
worldDropProjection);
var liveness = new LiveEntityLivenessController(
live.LiveEntities,
d.PlayerIdentity,
deletion,
dormantLiveEntities);
var sessionEvents = new LiveEntitySessionController(
d.InboundEntityEvents,
hydration,
networkUpdates,
d.TeleportSink,
live.EntityEffects);
bindings.Adopt(
"live parent acceptance",
live.ParentAcceptance.BindOwned(
hydration.TryAcceptParentForProjection));
bindings.Adopt(
"same-generation network updates",
networkUpdateBridge.BindOwned(networkUpdates));
bindings.BindEntityReady(
live.EquippedChildren,
candidate => _ = hydration.OnEntityReady(candidate));
bindings.BindAppearanceApplied(
hydration,
guid =>
{
if (guid == d.PlayerIdentity.ServerGuid)
live.PaperdollPresenter?.MarkDirty();
});
Fault(SessionPlayerCompositionPoint.LiveEntityGraphBound);
d.WorldOrigin.SetPlaceholder(
world.TerrainBuild.InitialCenterX,
world.TerrainBuild.InitialCenterY);
bindings.Adopt(
"combat attack operations",
d.CombatAttackOperations.BindOwned(
new LiveCombatAttackOperations(
d.Actions.Combat,
new CombatAttackTargetSource(
d.Actions.Selection,
live.LiveEntities,
d.EntityObjects.Objects,
d.PlayerIdentity),
d.Settings,
d.PlayerController,
d.PlayerOutbound,
liveSessionSource,
liveSessionSource,
d.CombatFeedback)));
Fault(SessionPlayerCompositionPoint.CombatOperationsBound);
MouseLookController? mouseLook =
host.MouseSource is not null && host.MouseLookCursor is not null
? new MouseLookController(
host.MouseSource,
d.PointerPosition,
d.PlayerMode,
d.PlayerController,
host.CameraController,
d.ChaseCameraInput,
d.MovementInput,
d.PlayerOutbound,
liveSessionSource,
host.MouseLookCursor,
new EnvironmentInputMonotonicClock())
: null;
var gameplayInput = new GameplayInputFrameController(
host.InputDispatcher,
d.MovementInput,
mouseLook,
new CombatAttackInputFrameAdapter(interaction.CombatAttack));
if (host.CameraPointerInput is { } cameraPointer)
{
bindings.Adopt(
"camera pointer gameplay frame",
cameraPointer.BindGameplayFrameOwned(gameplayInput));
}
Fault(SessionPlayerCompositionPoint.GameplayInputBound);
var streamingFrame = new StreamingFrameController(
d.Options.LiveMode,
d.PlayerMode,
d.PlayerController,
liveSessionSource,
d.WorldOrigin,
networkUpdates,
new FlyCameraStreamingObserverSource(host.CameraController),
new PhysicsStreamingDungeonCellSource(d.PhysicsEngine),
streamingOriginRecenter,
streaming,
new LiveProjectionRescueRebucketter(
live.WorldState,
live.LiveEntities));
var liveEffectFrame = new LiveEffectFrameController(
d.TranslucencyFades,
content.AnimationHookFrames,
live.EntityEffects,
content.ParticleSink,
live.Lights,
d.ParticleVisibility,
content.ParticleSystem,
content.ScriptRunner,
d.UpdateClock,
new SettingsParticleRangeSource(d.Settings));
var liveSpatialReconciler = new LiveSpatialPresentationReconciler(
live.EntityEffects,
live.EquippedChildren,
content.ParticleSink,
live.Lights,
live.RenderSceneShadow?.LiveProjections);
var localPlayerAnimation = new LocalPlayerAnimationController(
live.LiveEntities,
d.PlayerIdentity,
d.AnimatedEntities,
live.AnimationPresenter,
content.AnimationHookFrames);
var localPlayerShadow = new LocalPlayerShadowSynchronizer(
d.PhysicsEngine,
live.LiveEntities,
d.PlayerIdentity,
d.WorldOrigin,
d.PlayerShadow);
var localPlayerProjection = new LocalPlayerProjectionController(
new LiveLocalPlayerProjectionRuntime(
live.LiveEntities,
d.PlayerIdentity,
d.WorldOrigin,
localPlayerShadow));
var localPlayerFrameRuntime = new LiveLocalPlayerFrameRuntime(
host.CameraController,
d.PlayerMode,
d.PlayerController,
d.ChaseCameraInput,
d.MovementInput,
d.InputCapture,
live.LiveEntities,
d.PlayerIdentity,
d.PlayerHost,
localPlayerProjection,
d.PlayerOutbound,
liveSessionSource);
var localPlayerFrame = new RetailLocalPlayerFrameController(
d.Runtime,
localPlayerFrameRuntime,
d.MovementInput);
var liveObjectFrame = new LiveObjectFrameController(
d.InboundEntityEvents,
localPlayerFrame,
live.SelectionInteractions,
live.LiveEntities,
d.PlayerIdentity,
d.WorldOrigin,
live.AnimationScheduler,
live.StaticAnimationScheduler,
live.AnimationPresenter,
d.AnimatedEntities,
live.EquippedChildren,
liveEffectFrame,
live.RenderSceneShadow?.LiveProjections,
live.RenderSceneShadow?.StaticProjections);
Fault(SessionPlayerCompositionPoint.UpdateLeavesCreated);
var playerMode = new PlayerModeController(
d.PlayerMode,
d.PlayerController,
d.PlayerHost,
d.ChaseCameraInput,
host.CameraController,
d.PhysicsEngine,
live.LiveEntities,
d.PlayerIdentity,
d.WorldOrigin,
d.MotionBindings,
content.Dats,
d.DatLock,
content.CollisionAssets,
d.AnimatedEntities,
localPlayerAnimation,
localPlayerShadow,
d.PlayerApproachCompletions,
gameplayInput,
liveSessionSource,
d.MovementDiagnostics,
d.Character.MovementSkills,
d.ViewportAspect);
var playerModeAutoEntry = new PlayerModeAutoEntry(
new LivePlayerModeAutoEntryContext(
liveSessionSource,
live.LiveEntities,
d.PlayerIdentity,
worldReveal,
d.PlayerMode,
playerMode));
playerMode.BindAutoEntry(playerModeAutoEntry);
Fault(SessionPlayerCompositionPoint.PlayerModeBound);
// Campaign V slice V6m: the portal tunnel draws on both arms, so the
// presentation phase always leaves one in the fallback slot and the
// transfer is unconditional again. V6h's null presentation — the
// "no tunnel showing" stand-in a backend without a GL context used to
// drive — is deleted with the backend condition that produced it.
LocalPlayerTeleportController localTeleport =
d.PortalTunnelFallback.Transfer(CreateLocalTeleportWithTunnel);
LocalPlayerTeleportController CreateLocalTeleport(
ILocalPlayerTeleportPresentation presentation) =>
new LocalPlayerTeleportController(
new LiveLocalPlayerTeleportAuthority(
live.LiveEntities,
d.PlayerIdentity),
gameplayInput,
playerMode,
new LocalPlayerTeleportStreamingOperations(
d.WorldOrigin,
streamingOriginRecenter,
streaming,
sealedDungeonCells),
live.WorldTransit,
worldReveal,
new LocalPlayerTeleportPlacement(
d.PhysicsEngine,
live.LiveEntities,
d.PlayerIdentity,
d.PlayerController,
d.PlayerHost,
d.ChaseCameraInput,
d.WorldOrigin,
liveSpatialReconciler),
new LocalPlayerTeleportSession(liveSessionSource),
presentation);
LocalPlayerTeleportController CreateLocalTeleportWithTunnel(
PortalTunnelPresentation portalTunnel) =>
CreateLocalTeleport(
new LocalPlayerTeleportPresentation(portalTunnel));
var teleportLease = scope.Own(
"local-player teleport",
localTeleport,
static value => value.Dispose());
Fault(SessionPlayerCompositionPoint.PortalTransferred);
bindings.Adopt(
"local teleport network sink",
d.TeleportSink.BindOwned(localTeleport));
bindings.Adopt(
"selection view plane",
interaction.LateBindings.SelectionViewPlane.Bind(localTeleport));
Fault(SessionPlayerCompositionPoint.TeleportBound);
IDisposable componentLifecycleBinding =
live.ComponentLifecycle.BindOwned(teardown);
IDisposable componentLifecycleAdoption;
try
{
componentLifecycleAdoption = live.RuntimeBindings.AdoptOwned(
"live runtime component teardown",
componentLifecycleBinding);
}
catch
{
componentLifecycleBinding.Dispose();
throw;
}
var componentLifecycleLease = scope.Own(
"live runtime component teardown adoption",
componentLifecycleAdoption,
static value => value.Dispose());
AcDream.UI.Abstractions.Panels.Vitals.VitalsVM? vitals =
interaction.RetainedUi?.Vitals;
var placementProjectionRetry =
new RuntimePlacementProjectionRetrySlot(
() => d.Runtime.Generation);
var sessionRuntimeFactory = new LiveSessionRuntimeFactory(
new LiveSessionPlayerRuntime(
d.PlayerIdentity,
d.PlayerController,
d.WorldOrigin),
new LiveSessionDomainRuntime(
d.Runtime,
d.EntityObjects,
d.Character,
d.Actions,
d.Inventory,
d.Communication),
new LiveSessionUiRuntime(
interaction.RetainedUi?.Runtime,
vitals,
interaction.RetainedUi?.CharacterSheet,
interaction.Magic,
live.PaperdollPresenter),
new LiveSessionInteractionRuntime(
d.Settings,
gameplayInput,
playerMode,
playerModeAutoEntry,
interaction.ItemInteraction,
interaction.CombatAttack,
live.SelectionInteractions),
new LiveSessionWorldRuntime(
content.Dats,
live.WorldState,
live.LiveEntities,
sessionEvents,
d.WorldEnvironment,
d.TeleportSink,
spawnClaimClassifier,
live.EquippedChildren,
live.SelectionScene,
d.ParticleVisibility,
d.InboundEntityEvents,
liveness,
networkUpdates,
hydration,
live.EntityEffects,
content.AnimationHookFrames,
live.Presentation,
d.RemoteMovementObservations,
live.RenderSceneShadow,
live.PlacementProjection,
placementProjectionRetry,
firstEntryDrive,
acceptedPositionDrive,
remotePlacementDrive),
liveSessionCommands,
d.Log);
LiveSessionHost sessionHost = sessionRuntimeFactory.Create(
liveSession,
new LiveSessionConnectOptions(
d.Options.LiveMode,
d.Options.LiveHost,
d.Options.LivePort,
d.Options.LiveUser ?? string.Empty,
d.Options.LivePass ?? string.Empty));
Fault(SessionPlayerCompositionPoint.SessionHostCreated);
// The ImGui developer-tools debug toast sink was removed at Campaign V
// slice V11 along with the rest of the ImGui frontend; there is no
// replacement toast surface yet.
Action<string>? debugToast = null;
var combatModeOperations = new LiveCombatModeOperations(
new LiveSessionCombatModeAuthority(sessionHost),
new LocalPlayerCombatEquipmentSource(
d.EntityObjects.Objects,
d.PlayerIdentity),
new ItemInteractionCombatModeIntentSink(
interaction.ItemInteraction));
bindings.Adopt(
"runtime combat-mode operations",
d.CombatModeOperations.BindOwned(combatModeOperations));
var combatCommand = new RuntimeCombatModeCommandAdapter(
d.Actions.CombatMode,
d.Log,
debugToast,
text => d.Communication.Chat.OnSystemMessage(text, 0x1Au));
bindings.Adopt(
"live combat-mode commands",
d.CombatModeCommands.BindOwned(combatCommand));
var gameRuntime = new CurrentGameRuntimeAdapter(
d.Runtime,
sessionHost,
liveSessionCommands,
live.SelectionInteractions);
bindings.Adopt("current game runtime adapter", gameRuntime);
bindings.Adopt(
"retained-UI game runtime commands",
interaction.LateBindings.GameRuntime.Bind(gameRuntime, gameRuntime));
var nearbyDiagnostics = new NearbyWorldDiagnosticDumper(
new RuntimeNearbyWorldDiagnosticSource(
d.PlayerMode,
d.PlayerController,
host.CameraController,
d.WorldOrigin,
live.WorldState,
d.PhysicsEngine),
d.Log);
var runtimeDiagnostics = new RuntimeDiagnosticCommandController(
d.WorldEnvironment,
d.WorldSceneDebugState,
host.CameraPointerInput,
nearbyDiagnostics,
debugToast);
bindings.Adopt(
"runtime diagnostic commands",
d.RuntimeDiagnosticCommands.BindOwned(runtimeDiagnostics));
Fault(SessionPlayerCompositionPoint.CommandsBound);
CompositionAcquisitionScope.CompositionAcquisitionLease<
GameplayInputActionRouter>? gameplayActionsLease = null;
if (host.InputDispatcher is { } dispatcher)
{
CameraPointerInputController pointer = host.CameraPointerInput
?? throw new InvalidOperationException(
"Gameplay action routing requires the composed pointer owner.");
var commands = new GameplayInputCommandController(
new RetainedGameplayWindowCommands(
interaction.RetainedUi?.Runtime),
new DevToolsGameplayCommands(),
runtimeDiagnostics,
new PlayerModeGameplayCommands(
d.PlayerMode,
playerMode),
new ItemTargetModeCommands(interaction.ItemInteraction),
new GameplayCameraModeCommands(host.CameraController),
gameRuntime,
gameRuntime.Combat,
new GameplayWindowCommands(d.Window.Close));
var targets = new RuntimeGameplayInputPriorityTargets(
gameplayInput,
pointer,
interaction.RetainedUi?.Runtime,
live.SelectionInteractions,
gameRuntime,
gameRuntime.Selection,
gameRuntime.MovementCommands,
commands);
GameplayInputActionRouter gameplayActions =
GameplayInputActionRouter.Create(
dispatcher,
d.Actions.Combat,
targets,
d.HostQuiescence,
d.Log);
gameplayActionsLease = scope.Own(
"gameplay input actions",
gameplayActions,
static value => value.Dispose());
gameplayActions.Attach();
}
Fault(SessionPlayerCompositionPoint.GameplayActionsAttached);
var bindingsLease = scope.Own(
"session/player runtime bindings",
bindings,
static value => value.Dispose());
bindingsOwnedByScope = true;
var result = new SessionPlayerResult(
streamerLease.Resource,
streaming,
streamingOriginRecenter,
worldReveal,
spawnClaimClassifier,
liveSession,
hydration,
networkUpdates,
liveness,
sessionEvents,
gameplayInput,
streamingFrame,
localPlayerAnimation,
localPlayerShadow,
localPlayerFrameRuntime,
localPlayerFrame,
liveSpatialReconciler,
liveObjectFrame,
playerMode,
playerModeAutoEntry,
teleportLease.Resource,
sessionHost,
placementProjectionRetry,
gameRuntime,
gameplayActionsLease?.Resource,
bindings);
_publication.PublishSessionPlayer(result);
streamerLease.Transfer();
teleportLease.Transfer();
gameplayActionsLease?.Transfer();
componentLifecycleLease.Transfer();
bindingsLease.Transfer();
Fault(SessionPlayerCompositionPoint.ResultPublished);
return result;
}
private void Fault(SessionPlayerCompositionPoint point) =>
_faultInjection?.Invoke(point);
}