feat(runtime): C3c - production placement cutover: both hosts on the residence conductors (routes 1+8)
Campaign P remaining-physics-divergence, placement cutover slice C3c
(docs/plans/2026-08-02-placement-cutover.md). Both production hosts now
register every initial Create through the residence + continuation-
executor + first-entry-conductor machinery (C0-C3b):
- Graphical (route 1): RegisterEntityWithInitialResidence at Create; the
shared RuntimeFirstEntryDriveController pumps both conductors from the
placement-receipt flow; MaterializeProjection and RebucketLiveEntity
are presentation-only while a residence is ACTIVE (ExecutorCompleted is
the presentation-binding receipt); post-residence entities take the
full legacy path including the prepare_to_enter_world clock edges.
PlayerModeController attaches presentation to the Runtime-published
controller; its legacy resolve/step-heights/host-construction path is
deleted; presentation-only rollback (retail has no entry-flow rollback).
- Headless (route 8): OnSpawned registers with residence when a drive
exists; content-less sessions keep the pre-flip direct registration;
SynchronizeLocalPlayer/CreateController/ApplySetupStepHeights deleted;
prepared-collision read failure is a typed AwaitingCollisionSource
retry; far remotes outside the service window complete celless.
- RuntimeLocalPlayerMovementState.Controller setter sealed internal; all
controller mutation flows through the publication lifecycle.
Fix slices landed within this cutover, each dual-gated:
- F1: live movement-stat/server-physics application routed through the
Runtime ownership seam (post-logout ingest crash on the retired
controller eliminated; RuntimeMovementSkillProjection deleted).
- F2: login activation wedge - collision-admission prefix gate factored
out of the seal (reentrant-commit RejectedAuthority), rearm generation
identity corrected, PlayerModeAutoEntry requires the Runtime-published
controller (world reveal can no longer seal unmaterialized).
- F3: landblock-prefix 0-sentinel replaced by explicit absent-id guards;
map-corner landblocks (grid row/col 0) fully legal through admission,
park/rearm/retire, quiescence, and outdoor shadow seeds.
- F5: local-player first-entry ground contact seeded by the shared
SpawnPlacementSettler (moved App->Core) at FinalizeActivation - the
retail first-gravity-frame touch (enter_world 0x00516170 carries no
seed); the legacy unconditional force-seed is overwritten by a real
floor-found contact; airborne spawns stay airborne; outbound contact
bit verified end-to-end. Fixes the standing-cast 'You can't do that
while in the air!' rejections.
- R1 (dual-review round): login constraint leash armed at the committed
placement (HandleReceivedPosition 0x00453FD0 analog); register rows
AD-61 (settle-timing compression now covering the local player) and
AD-42 (repointed off the deleted resolve split) in this commit;
residence-conversion owner API; wire-landblock guards; drive-pending
ledger in IsConverged; route attach/detach latch; executor-drain drift
model documented + source-pinned.
Gates: Runtime 1,003, App 4,039/3 skips, Headless 79, complete solution
10,816/0 failed/4 skips (Release, -m:1); connected lifecycle/reconnect
gate PASS (logs/connected-world-gate-20260802-175401; graceful exits,
world-visible, zero airborne rejections). The nine-stop soak remains red
for the pre-existing 6b28ff99 whole-world collision-clone throughput
regression (attributed with evidence; scheduled as its own slice before
C5). Dual Opus reviews (retail-conformance + adversarial): delta PASS.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
78f1eb1896
commit
529e0e9d88
68 changed files with 5977 additions and 831 deletions
|
|
@ -370,12 +370,14 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
fixture.Controller.OnCreate(Spawn(Generation: 1, PositionSequence: 1));
|
||||
LiveEntityRecord record = fixture.Record;
|
||||
WorldEntity entity = record.WorldEntity!;
|
||||
var body = new PhysicsBody();
|
||||
Assert.Same(
|
||||
body,
|
||||
fixture.Runtime.GetOrCreatePhysicsBody(
|
||||
record.ServerGuid,
|
||||
_ => body));
|
||||
// C3c/C3b: the first-entry conductor constructs the canonical
|
||||
// physics body at Create (retail ACCObjectMaint::CreateObject /
|
||||
// set_description). Capture that existing body — the identity the
|
||||
// pickup/re-enter cycle must preserve — instead of seeding one.
|
||||
PhysicsBody body = fixture.Runtime.GetOrCreatePhysicsBody(
|
||||
record.ServerGuid,
|
||||
static _ => throw new InvalidOperationException(
|
||||
"The conductor-built canonical body should already exist."));
|
||||
fixture.Relationships.OnUnparentAction = _ =>
|
||||
fixture.Runtime.WithdrawLiveEntityProjection(record)
|
||||
? ChildUnparentDisposition.Completed
|
||||
|
|
@ -962,6 +964,14 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
|
||||
// Local-player records ordinarily do not rebucket from streaming
|
||||
// callbacks; an incomplete initial transaction must still recover.
|
||||
// C3c: the failed Create transaction unwound before OnCreateCore's
|
||||
// own drive pump ran, leaving the residence pending with FullCellId
|
||||
// 0 (streaming callbacks key candidates off the committed cell). In
|
||||
// production the per-frame first-entry pump completes the conductor
|
||||
// independently of the failed hydration transaction; model that
|
||||
// pump here, then let the streaming callback recover the partial
|
||||
// projection exactly as before.
|
||||
fixture.FirstEntry.DriveAll();
|
||||
fixture.Controller.OnLandblockLoaded(Cell);
|
||||
|
||||
Assert.Same(partial, record.WorldEntity);
|
||||
|
|
@ -1042,7 +1052,13 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
Assert.Equal("same generation newer", fixture.Objects.Get(Guid)!.Name);
|
||||
Assert.True(fixture.Record.InitialHydrationCompleted);
|
||||
Assert.Single(fixture.Materializer.Calls);
|
||||
Assert.Equal((ushort)2, fixture.Materializer.PositionSequences[0]);
|
||||
// C3c: the nested fresher same-generation Create is admitted into
|
||||
// the outer create's ACTIVE residence FIFO (AD-59) and its facts
|
||||
// commit at the executor drain, in array order. The single
|
||||
// materialization therefore runs from the admission-frozen seq-1
|
||||
// create; the seq-2 facts (including the name asserted above) land
|
||||
// through the drain and bind via the completion receipt.
|
||||
Assert.Equal((ushort)1, fixture.Materializer.PositionSequences[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1114,10 +1130,16 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
Assert.Equal((ushort)1, fixture.Record.Generation);
|
||||
Assert.Equal("same generation newer", fixture.Objects.Get(Guid)!.Name);
|
||||
Assert.True(fixture.Record.InitialHydrationCompleted);
|
||||
Assert.Equal((ushort)3, fixture.Materializer.PositionSequences[^1]);
|
||||
// C3c: the nested fresher same-generation Create is admitted into
|
||||
// the replacement generation's ACTIVE residence FIFO (AD-59) and its
|
||||
// facts commit at the executor drain, in array order. The
|
||||
// materialization therefore runs from the admission-frozen seq-2
|
||||
// replacement create; the seq-3 facts (including the name asserted
|
||||
// above) land through the drain and never re-materialize.
|
||||
Assert.Equal((ushort)2, fixture.Materializer.PositionSequences[^1]);
|
||||
Assert.DoesNotContain(
|
||||
(ushort)2,
|
||||
fixture.Materializer.PositionSequences.Skip(1));
|
||||
(ushort)3,
|
||||
fixture.Materializer.PositionSequences);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1210,11 +1232,19 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
|
||||
Assert.True(record.InitialHydrationCompleted);
|
||||
Assert.Equal("same generation newer", fixture.Objects.Get(Guid)!.Name);
|
||||
Assert.Equal([1, 1, 2], fixture.Materializer.PositionSequences);
|
||||
// C3c: a post-residence same-generation Create is description-only
|
||||
// at registration — its position churn flows through the
|
||||
// freshness-gated events tail (which is what makes RecoverProjection
|
||||
// return false above), and create authority advances only inside the
|
||||
// residence transaction. No drift means no nested
|
||||
// CreateSupersessionRecovery re-materialization: the recovery's own
|
||||
// SpatialRecovery attempt stays the last call at the original
|
||||
// installed version.
|
||||
Assert.Equal([1, 1], fixture.Materializer.PositionSequences);
|
||||
Assert.Equal(
|
||||
LiveProjectionPurpose.CreateSupersessionRecovery,
|
||||
LiveProjectionPurpose.SpatialRecovery,
|
||||
fixture.Materializer.Calls[^1].Purpose);
|
||||
Assert.Equal(2UL, fixture.Materializer.InstalledCreateIntegrationVersion);
|
||||
Assert.Equal(1UL, fixture.Materializer.InstalledCreateIntegrationVersion);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -1236,10 +1266,23 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
if (refreshed || spawn.PositionSequence != 1)
|
||||
return;
|
||||
refreshed = true;
|
||||
fixture.Controller.OnCreate(Spawn(
|
||||
Generation: 1,
|
||||
PositionSequence: 2,
|
||||
Name: "recovery v2"));
|
||||
// C3c: a fresher same-generation CreateObject no longer advances
|
||||
// create authority at registration (its advance lands at the
|
||||
// residence drain's WeenieDescription stage). Model that exact
|
||||
// advance directly so the drift-retry machinery under test still
|
||||
// fires.
|
||||
// C3c-R1 F3 (coordinator resolution): this hand-call is an
|
||||
// honest MODEL of the executor drain's advance
|
||||
// (RuntimeInitialCreateContinuationExecutor
|
||||
// .ApplyWeenieDescriptionAction — the sole production site,
|
||||
// source-pinned by C3cR1F3DriftModelSourcePinTests). A nested
|
||||
// production OnCreate can no longer reach it here:
|
||||
// post-residence ExistingGeneration registration is
|
||||
// description-only (RuntimeEntityObjectLifetime gates the
|
||||
// advance on !beginInitialResidence, :660-665) and
|
||||
// ConsumeExecuted already removed the completed residence
|
||||
// entry, closing the FIFO-adoption path.
|
||||
record.Canonical.AdvanceCreateAuthority();
|
||||
};
|
||||
fixture.Materializer.ThrowAfterMaterializePurposeOnce =
|
||||
LiveProjectionPurpose.CreateSupersessionRecovery;
|
||||
|
|
@ -1270,8 +1313,12 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
|
||||
Assert.False(record.CreateProjectionSynchronizationPending);
|
||||
Assert.Same(retained, record.WorldEntity);
|
||||
// C3c: the retry retransmit (a post-residence same-generation
|
||||
// Create) no longer advances create authority itself, so both retry
|
||||
// paths install the drift probe's version (2), not a
|
||||
// retransmit-advanced 3.
|
||||
Assert.Equal(
|
||||
retryFromLandblock ? 2UL : 3UL,
|
||||
2UL,
|
||||
fixture.Materializer.InstalledCreateIntegrationVersion);
|
||||
Assert.Equal(
|
||||
LiveProjectionPurpose.CreateSupersessionRecovery,
|
||||
|
|
@ -1357,10 +1404,23 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
if (refreshed || spawn.PositionSequence != 1)
|
||||
return;
|
||||
refreshed = true;
|
||||
fixture.Controller.OnCreate(Spawn(
|
||||
Generation: 1,
|
||||
PositionSequence: 2,
|
||||
Name: "ready v2"));
|
||||
// C3c: a fresher same-generation CreateObject no longer advances
|
||||
// create authority at registration (its advance lands at the
|
||||
// residence drain's WeenieDescription stage). Model that exact
|
||||
// advance directly so the drift-retry machinery under test still
|
||||
// fires.
|
||||
// C3c-R1 F3 (coordinator resolution): this hand-call is an
|
||||
// honest MODEL of the executor drain's advance
|
||||
// (RuntimeInitialCreateContinuationExecutor
|
||||
// .ApplyWeenieDescriptionAction — the sole production site,
|
||||
// source-pinned by C3cR1F3DriftModelSourcePinTests). A nested
|
||||
// production OnCreate can no longer reach it here:
|
||||
// post-residence ExistingGeneration registration is
|
||||
// description-only (RuntimeEntityObjectLifetime gates the
|
||||
// advance on !beginInitialResidence, :660-665) and
|
||||
// ConsumeExecuted already removed the completed residence
|
||||
// entry, closing the FIFO-adoption path.
|
||||
record.Canonical.AdvanceCreateAuthority();
|
||||
};
|
||||
fixture.Ready.FailPublishCount = 1;
|
||||
|
||||
|
|
@ -1486,6 +1546,12 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
{
|
||||
const uint parentGuid = 0x70000002u;
|
||||
using var fixture = new Fixture(originKnown: true);
|
||||
// C3c: a Create whose parent is not addressable is now queued under
|
||||
// the parent's GUID (retail QueueBlobForObject) instead of applying
|
||||
// immediately. Register the parent so the nested parented Create
|
||||
// routes exactly as before.
|
||||
fixture.Runtime.RegisterLiveEntity(
|
||||
Spawn(Generation: 1, PositionSequence: 1) with { Guid = parentGuid });
|
||||
fixture.Controller.OnCreate(Spawn(Generation: 1, PositionSequence: 1));
|
||||
LiveEntityRecord record = fixture.Record;
|
||||
WorldEntity retained = record.WorldEntity!;
|
||||
|
|
@ -1522,6 +1588,17 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
record,
|
||||
positionVersion));
|
||||
Assert.True(fixture.Runtime.WithdrawLiveEntityProjection(record));
|
||||
// C3c: mirror the production relationship owner
|
||||
// (EquippedChildRenderController.TryAttach), which converts a
|
||||
// residence-managed child's sticky residence to LegacyImmediate
|
||||
// at the world -> attached kind transition.
|
||||
if (record.MaterializationResidence is
|
||||
AcDream.App.World.LiveEntityMaterializationResidence
|
||||
.AwaitRuntimePlacement)
|
||||
{
|
||||
record.MaterializationResidence = AcDream.App.World
|
||||
.LiveEntityMaterializationResidence.LegacyImmediate;
|
||||
}
|
||||
WorldEntity? attached = fixture.Runtime.MaterializeLiveEntity(
|
||||
Guid,
|
||||
Cell,
|
||||
|
|
@ -1540,6 +1617,12 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
fixture.Controller.OnCreate(CelllessSpawn(
|
||||
PositionSequence: 2,
|
||||
parentGuid));
|
||||
// C3c: a post-residence same-generation Create no longer
|
||||
// advances create authority at registration (the advance lands
|
||||
// at a residence drain's WeenieDescription stage). Model that
|
||||
// advance directly so the supersession-recovery machinery under
|
||||
// test still fires and completes at the attached-ready boundary.
|
||||
record.Canonical.AdvanceCreateAuthority();
|
||||
};
|
||||
|
||||
Assert.False(fixture.Controller.RecoverProjection(
|
||||
|
|
@ -1575,6 +1658,25 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
using var fixture = new Fixture(
|
||||
originKnown: true,
|
||||
playerGuid: retryFromLandblock ? Guid : 0u);
|
||||
// C3c: a Create whose parent is not addressable is now queued under
|
||||
// the parent's GUID (retail QueueBlobForObject) instead of applying
|
||||
// immediately. Register the parent so the initial parented Create
|
||||
// routes exactly as before — placed in a DIFFERENT landblock so this
|
||||
// scaffolding identity is not itself a candidate for the recovered
|
||||
// landblock's projection sweep (the child's RegisterCount assertions
|
||||
// count only the child's resources).
|
||||
WorldSession.EntitySpawn parentSpawn =
|
||||
Spawn(Generation: 1, PositionSequence: 1) with { Guid = parentGuid };
|
||||
var parentPosition = new CreateObject.ServerPosition(
|
||||
0x01020001u, 10f, 10f, 5f, 1f, 0f, 0f, 0f);
|
||||
fixture.Runtime.RegisterLiveEntity(parentSpawn with
|
||||
{
|
||||
Position = parentPosition,
|
||||
Physics = parentSpawn.Physics!.Value with
|
||||
{
|
||||
Position = parentPosition,
|
||||
},
|
||||
});
|
||||
fixture.Ready.FailPublishCount = 2;
|
||||
fixture.Network.ApplyAction = events =>
|
||||
{
|
||||
|
|
@ -1659,8 +1761,20 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
if (!replaced && replacementStage == stage)
|
||||
{
|
||||
replaced = true;
|
||||
fixture.Runtime.RegisterLiveEntity(
|
||||
Spawn(Generation: 1, PositionSequence: 2));
|
||||
// C3c: a fresher same-generation CreateObject no longer
|
||||
// advances create authority at registration — its advance
|
||||
// lands at the residence drain's WeenieDescription stage.
|
||||
// Model that exact advance directly so the between-stage
|
||||
// revalidation guard stays covered.
|
||||
// C3c-R1 F3: honest MODEL of the executor drain's advance
|
||||
// (ApplyWeenieDescriptionAction — the sole production site,
|
||||
// source-pinned by C3cR1F3DriftModelSourcePinTests); a
|
||||
// nested production OnCreate can no longer reach it —
|
||||
// post-residence registration is description-only
|
||||
// (RuntimeEntityObjectLifetime :660-665,
|
||||
// !beginInitialResidence gate) and ConsumeExecuted already
|
||||
// removed the completed residence entry.
|
||||
expected.Canonical.AdvanceCreateAuthority();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1768,8 +1882,18 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
var projection = new RecordingLiveProjectionSink(
|
||||
_ =>
|
||||
{
|
||||
fixture.Runtime.RegisterLiveEntity(
|
||||
Spawn(Generation: 1, PositionSequence: 2));
|
||||
// C3c: a fresher same-generation CreateObject's authority
|
||||
// advance now lands at the residence drain's
|
||||
// WeenieDescription stage; model that advance directly.
|
||||
// C3c-R1 F3: honest MODEL of the executor drain's advance
|
||||
// (ApplyWeenieDescriptionAction — the sole production site,
|
||||
// source-pinned by C3cR1F3DriftModelSourcePinTests); a
|
||||
// nested production OnCreate can no longer reach it —
|
||||
// post-residence registration is description-only
|
||||
// (RuntimeEntityObjectLifetime :660-665,
|
||||
// !beginInitialResidence gate) and ConsumeExecuted already
|
||||
// removed the completed residence entry.
|
||||
fixture.Record.Canonical.AdvanceCreateAuthority();
|
||||
return true;
|
||||
});
|
||||
var publisher = new LiveEntityReadyPublisher(
|
||||
|
|
@ -1792,10 +1916,19 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
WorldEntity entity = record.WorldEntity!;
|
||||
ulong capturedCreateIntegrationVersion = record.CreateIntegrationVersion;
|
||||
|
||||
// Models ProjectionPoseReady synchronously accepting a fresher
|
||||
// same-generation CreateObject before EntityReady is emitted.
|
||||
fixture.Runtime.RegisterLiveEntity(
|
||||
Spawn(Generation: 1, PositionSequence: 2));
|
||||
// Models ProjectionPoseReady synchronously observing a fresher
|
||||
// same-generation CreateObject's authority advance before
|
||||
// EntityReady is emitted. C3c: that advance now lands at the
|
||||
// residence drain's WeenieDescription stage
|
||||
// (AdvanceCreateAuthority), not at registration; model it directly.
|
||||
// C3c-R1 F3: honest MODEL of the executor drain's advance
|
||||
// (ApplyWeenieDescriptionAction — the sole production site,
|
||||
// source-pinned by C3cR1F3DriftModelSourcePinTests); a nested
|
||||
// production OnCreate can no longer reach it — post-residence
|
||||
// registration is description-only (RuntimeEntityObjectLifetime
|
||||
// :660-665, !beginInitialResidence gate) and ConsumeExecuted
|
||||
// already removed the completed residence entry.
|
||||
record.Canonical.AdvanceCreateAuthority();
|
||||
bool published = false;
|
||||
|
||||
Assert.False(EquippedChildRenderController.PublishEntityReadyExact(
|
||||
|
|
@ -1937,6 +2070,55 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
uint playerGuid = 0u)
|
||||
{
|
||||
Resources = resources ?? new RecordingResources();
|
||||
// C3c: initial-Create registration begins the canonical
|
||||
// residence, whose admission requires a live generation; the
|
||||
// fixture also commits the wire landblock's collision generation
|
||||
// and wires the production first-entry drive pump so each Create
|
||||
// transaction completes its conductor synchronously, exactly
|
||||
// like the composed graphical host.
|
||||
EntityObjects.BindEventContext(
|
||||
static () => new AcDream.Runtime.RuntimeGenerationToken(1UL),
|
||||
static () => 1UL);
|
||||
EntityObjects.Physics.SetPosition.BeginCollisionGeneration(
|
||||
Cell & 0xFFFF0000u, 1UL);
|
||||
EntityObjects.Physics.Engine.AddLandblock(
|
||||
Cell & 0xFFFF0000u,
|
||||
new AcDream.Core.Physics.TerrainSurface(
|
||||
new byte[81], new float[256]),
|
||||
Array.Empty<AcDream.Core.Physics.CellSurface>(),
|
||||
Array.Empty<AcDream.Core.Physics.PortalPlane>(),
|
||||
worldOffsetX: 0f,
|
||||
worldOffsetY: 0f);
|
||||
EntityObjects.Physics.SetPosition.CommitCollisionGeneration(
|
||||
Cell & 0xFFFF0000u, 1UL, ready: true);
|
||||
Movement = new AcDream.Runtime.Gameplay.RuntimeLocalPlayerMovementState();
|
||||
IdentityState = new AcDream.Runtime.Gameplay.RuntimeLocalPlayerIdentityState();
|
||||
var publication = new AcDream.Runtime.Gameplay
|
||||
.RuntimeLocalPlayerPhysicsPublicationState(
|
||||
EntityObjects.Entities,
|
||||
EntityObjects.Physics,
|
||||
Movement,
|
||||
IdentityState);
|
||||
Movement.AttachPhysicsPublication(publication);
|
||||
EntityObjects.LocalPlayerFirstEntry.BindPublication(publication);
|
||||
IdentityState.ServerGuid = playerGuid;
|
||||
FirstEntry = new AcDream.Runtime.Session.RuntimeFirstEntryDriveController(
|
||||
EntityObjects,
|
||||
new AcDream.Runtime.GameRuntimeClock(),
|
||||
new HydrationNullCollisionSource(),
|
||||
() => AcDream.Runtime.Gameplay.PlayerMovementConstructionOptions.Fallback,
|
||||
static _ => new AcDream.Runtime.Gameplay
|
||||
.RuntimeLocalPlayerPhysicsActivationPreparation(
|
||||
0.48f,
|
||||
1.835f,
|
||||
AcDream.Runtime.Gameplay
|
||||
.RuntimeLocalPlayerShadowDisposition.ProvenShapeless));
|
||||
// C3c: the real per-session placement subscription — without it
|
||||
// the first entity's unacknowledged ExecutorCompleted receipt
|
||||
// wedges the one ordered FIFO and every later Create's conductor
|
||||
// yields AwaitingReceiptAcknowledgement forever. Ack rules mirror
|
||||
// production: Discard/ExecutorCompleted acknowledge-only;
|
||||
// Place/Withdraw stay at the head for the conductor machinery.
|
||||
var spatial = new GpuWorldState();
|
||||
spatial.AddLandblock(new LoadedLandblock(
|
||||
0x0101FFFFu,
|
||||
|
|
@ -1947,6 +2129,11 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
Resources,
|
||||
Teardown,
|
||||
EntityObjects);
|
||||
_placements = new AcDream.Runtime.Physics
|
||||
.RuntimePlacementProjectionSubscription(
|
||||
EntityObjects.Placements,
|
||||
static () => new AcDream.Runtime.RuntimeGenerationToken(1UL),
|
||||
new FixturePlacementSink(Runtime));
|
||||
Materializer = new RecordingMaterializer(Runtime, Operations);
|
||||
Relationships = new RecordingRelationships(Operations);
|
||||
Ready = new RecordingReadyPublisher(Operations);
|
||||
|
|
@ -1988,7 +2175,90 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
Timestamps,
|
||||
identity,
|
||||
deletion,
|
||||
Dormant);
|
||||
Dormant,
|
||||
firstEntry: FirstEntry);
|
||||
}
|
||||
|
||||
public AcDream.Runtime.Session.RuntimeFirstEntryDriveController FirstEntry { get; }
|
||||
private readonly AcDream.Runtime.Physics
|
||||
.RuntimePlacementProjectionSubscription _placements;
|
||||
|
||||
private sealed class FixturePlacementSink(LiveEntityRuntime runtime)
|
||||
: AcDream.Runtime.Physics.IRuntimePlacementProjectionSink
|
||||
{
|
||||
public bool TryApply(
|
||||
in AcDream.Runtime.Physics.RuntimePlacementProjectionSnapshot projection)
|
||||
{
|
||||
if (projection.Kind is AcDream.Runtime.Physics
|
||||
.RuntimePlacementProjectionKind.Discard)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (projection.Kind is AcDream.Runtime.Physics
|
||||
.RuntimePlacementProjectionKind.ExecutorCompleted)
|
||||
{
|
||||
return projection.Token.ExactCellId == 0u
|
||||
|| runtime.TryApplyInitialCreateCompletionPresentation(
|
||||
in projection);
|
||||
}
|
||||
return !runtime.HasActiveInitialCreateResidence(
|
||||
projection.Token.Entity)
|
||||
&& runtime.TryApplyRuntimePlacementProjection(in projection);
|
||||
}
|
||||
}
|
||||
public AcDream.Runtime.Gameplay.RuntimeLocalPlayerMovementState Movement { get; }
|
||||
public AcDream.Runtime.Gameplay.RuntimeLocalPlayerIdentityState IdentityState { get; }
|
||||
|
||||
private sealed class HydrationNullCollisionSource
|
||||
: AcDream.Content.IPreparedCollisionSource
|
||||
{
|
||||
public AcDream.Content.PreparedAssetPresence ProbeCollision(
|
||||
AcDream.Content.Pak.PakAssetType type,
|
||||
uint sourceFileId) =>
|
||||
AcDream.Content.PreparedAssetPresence.Available;
|
||||
|
||||
public AcDream.Content.PreparedCollisionReadResult<
|
||||
AcDream.Core.Physics.FlatSetupCollision> ReadSetupCollision(
|
||||
uint sourceFileId,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
AcDream.Content.PreparedCollisionReadResult<
|
||||
AcDream.Core.Physics.FlatSetupCollision>.Loaded(
|
||||
new AcDream.Core.Physics.FlatSetupCollision(
|
||||
System.Collections.Immutable.ImmutableArray<
|
||||
AcDream.Core.Physics.FlatCollisionCylinder>.Empty,
|
||||
[new AcDream.Core.Physics.FlatCollisionSphere(
|
||||
System.Numerics.Vector3.Zero, 0.48f)],
|
||||
height: 0f,
|
||||
radius: 0f,
|
||||
stepUpHeight: 0.4f,
|
||||
stepDownHeight: 0.4f));
|
||||
|
||||
public AcDream.Content.PreparedCollisionReadResult<
|
||||
AcDream.Core.Physics.FlatGfxObjCollisionAsset>
|
||||
ReadGfxObjCollision(
|
||||
uint sourceFileId,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
public AcDream.Content.PreparedCollisionReadResult<
|
||||
AcDream.Core.Physics.FlatCellStructureCollisionAsset>
|
||||
ReadCellStructureCollision(
|
||||
uint sourceFileId,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
public AcDream.Content.PreparedCollisionReadResult<
|
||||
AcDream.Core.Physics.FlatEnvCellTopology> ReadEnvCellTopology(
|
||||
uint sourceFileId,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
public AcDream.Content.PreparedCollisionSourceStats CollisionStats =>
|
||||
default;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public LiveEntityRecord Record
|
||||
|
|
@ -2165,7 +2435,34 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
},
|
||||
LiveEntityProjectionKind.World,
|
||||
initializeProjection: null,
|
||||
out LiveEntityRecord? expectedRecord);
|
||||
out LiveEntityRecord? expectedRecord,
|
||||
// C3c: mirror the production materializer
|
||||
// (DatLiveEntityProjectionMaterializer.MaterializeProjection),
|
||||
// which materializes route-1 world creates residence-managed.
|
||||
// The legacy-immediate default would commit the wire cell
|
||||
// out-of-band and retire the fresh residence lease before the
|
||||
// fixture's drive pump ever ran (diagnosed RejectedToken).
|
||||
AcDream.App.World.LiveEntityMaterializationResidence
|
||||
.AwaitRuntimePlacement);
|
||||
// C3c: mirror the production materializer's self-projection
|
||||
// branch — when the residence-driven placement already committed
|
||||
// (or a legacy post-residence path committed the cell) before
|
||||
// this sidecar could exist, its completion receipt is gone, so
|
||||
// presentation self-projects from the committed canonical state
|
||||
// through the presentation-only bucket path.
|
||||
if (entity is not null
|
||||
&& expectedRecord is not null
|
||||
&& runtime.IsCurrentCreateIntegration(
|
||||
expectedCanonical,
|
||||
expectedCreateIntegrationVersion)
|
||||
&& expectedCanonical.FullCellId != 0u
|
||||
&& !runtime.HasActiveInitialCreateResidence(expectedCanonical)
|
||||
&& !runtime.RebucketLiveEntity(
|
||||
canonicalSpawn.Guid,
|
||||
expectedCanonical.FullCellId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (ThrowAfterMaterializePurposeOnce == purpose)
|
||||
{
|
||||
ThrowAfterMaterializePurposeOnce = null;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue