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
|
|
@ -826,13 +826,22 @@ public sealed class LiveEntityRuntimeTests
|
|||
{
|
||||
const uint parentGuid = 0x70000020u;
|
||||
const uint childGuid = 0x70000021u;
|
||||
var runtime = LiveEntityRuntimeFixture.Create(new GpuWorldState(), new RecordingResources());
|
||||
// C3c: initial residences defer wire applies into their FIFO until a
|
||||
// host pump drives the conductors; use the driven fixture and pump
|
||||
// after each Create so the parent-event tail commits as before.
|
||||
LiveEntityRuntimeFixture.DrivenLiveEntityRuntime driven =
|
||||
LiveEntityRuntimeFixture.CreateDriven(
|
||||
new GpuWorldState(),
|
||||
new RecordingResources());
|
||||
LiveEntityRuntime runtime = driven.Runtime;
|
||||
runtime.RegisterLiveEntity(Spawn(parentGuid, 9, 1, 0x01010001u));
|
||||
driven.Pump();
|
||||
runtime.ParentAttachments.Enqueue(new ParentEvent.Parsed(
|
||||
parentGuid, childGuid, 1, 2, 9, 5));
|
||||
ResolveParent(runtime, childGuid);
|
||||
|
||||
runtime.RegisterLiveEntity(Spawn(childGuid, 3, 4, 0x01010001u));
|
||||
driven.Pump();
|
||||
ResolveParent(runtime, childGuid);
|
||||
|
||||
Assert.True(runtime.ParentAttachments.TryGetProjection(
|
||||
|
|
@ -1261,6 +1270,143 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.Equal(0.0, clock.PendingSeconds, 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// C3c-R1 review R2: the presentation-only rebucket shortcut is scoped
|
||||
/// to the ACTIVE initial-create residence (where the public API is
|
||||
/// suppressed outright — the conductor's completion receipt is the only
|
||||
/// presentation channel). A RETIRED-residence entity's sticky
|
||||
/// MaterializationResidence must NOT keep it on the shortcut: the
|
||||
/// unflipped legacy update routes (network position/state, teleports,
|
||||
/// streaming reprojection, hydration recovery — all callers of this one
|
||||
/// public RebucketLiveEntity chokepoint) are the position authority
|
||||
/// again, so post-residence moves take the FULL legacy branch:
|
||||
/// CommitRebucket writes the canonical cell and retail's
|
||||
/// prepare_to_enter_world (0x00511FA0) clock rebase runs on every
|
||||
/// root-workset membership edge.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void PostResidenceRebucket_TakesTheFullLegacyPathIncludingTheClockEdge()
|
||||
{
|
||||
const uint guid = 0x7000004Au;
|
||||
var spatial = new GpuWorldState();
|
||||
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
|
||||
spatial.AddLandblock(EmptyLandblock(0x0102FFFFu));
|
||||
LiveEntityRuntimeFixture.DrivenLiveEntityRuntime driven =
|
||||
LiveEntityRuntimeFixture.CreateDriven(
|
||||
spatial,
|
||||
new RecordingResources());
|
||||
LiveEntityRuntime runtime = driven.Runtime;
|
||||
RuntimeEntityRecord canonical =
|
||||
Assert.IsType<RuntimeEntityRecord>(runtime.RegisterLiveEntity(
|
||||
Spawn(guid, 1, 1, 0x01010001u)).Canonical);
|
||||
runtime.MaterializeLiveEntity(
|
||||
canonical,
|
||||
0x01010001u,
|
||||
id => Entity(id, guid),
|
||||
LiveEntityProjectionKind.World,
|
||||
initializeProjection: null,
|
||||
out _,
|
||||
LiveEntityMaterializationResidence.AwaitRuntimePlacement);
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord record));
|
||||
|
||||
// ACTIVE residence: the public API stays suppressed (the completion
|
||||
// receipt is the entity's first world-visible moment) and no legacy
|
||||
// cell commit can race the conductor's pending placement.
|
||||
Assert.True(runtime.HasActiveInitialCreateResidence(canonical));
|
||||
Assert.False(runtime.RebucketLiveEntity(guid, 0x01020001u));
|
||||
Assert.Equal(0u, canonical.FullCellId);
|
||||
// C3c-R1 review F5: the tracked-but-undriven entry is visible in
|
||||
// the entity-object ownership ledger while it awaits its pump.
|
||||
Assert.Equal(
|
||||
1,
|
||||
driven.Lifetime.CaptureOwnership().FirstEntryDrivePendingCount);
|
||||
|
||||
driven.Pump();
|
||||
Assert.False(runtime.HasActiveInitialCreateResidence(canonical));
|
||||
Assert.Equal(
|
||||
LiveEntityMaterializationResidence.AwaitRuntimePlacement,
|
||||
record.MaterializationResidence);
|
||||
Assert.Equal(0x01010001u, canonical.FullCellId);
|
||||
|
||||
// C3c-R1 review F5: the drive's tracked entries fold into the
|
||||
// entity-object ownership ledger — one pending entry while the
|
||||
// residence awaited its pump, zero after.
|
||||
Assert.Equal(
|
||||
0,
|
||||
driven.Lifetime.CaptureOwnership().FirstEntryDrivePendingCount);
|
||||
|
||||
// RETIRED residence, loaded-to-loaded: full legacy branch commits
|
||||
// the canonical cell (the presentation-only shortcut never did) and
|
||||
// preserves the running clock.
|
||||
RetailObjectQuantumClock clock = record.ObjectClock;
|
||||
Assert.Equal(0, clock.Advance(0.02).Count);
|
||||
Assert.True(runtime.RebucketLiveEntity(guid, 0x01020001u));
|
||||
Assert.Equal(0x01020001u, canonical.FullCellId);
|
||||
Assert.Same(clock, record.ObjectClock);
|
||||
Assert.True(clock.IsActive);
|
||||
Assert.Equal(0.02, clock.PendingSeconds, 8);
|
||||
|
||||
// Membership edge into a pending bucket suspends the clock; the
|
||||
// pending drain's reentry rebases it for enter-world — the retail
|
||||
// prepare_to_enter_world edge the shortcut skipped.
|
||||
Assert.True(runtime.RebucketLiveEntity(guid, 0x02020001u));
|
||||
Assert.Equal(0x02020001u, canonical.FullCellId);
|
||||
Assert.False(clock.IsActive);
|
||||
spatial.AddLandblock(EmptyLandblock(0x0202FFFFu));
|
||||
Assert.True(clock.IsActive);
|
||||
Assert.Equal(0.0, clock.PendingSeconds, 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// C3c-R1 review F1: converting the sticky residence-managed
|
||||
/// presentation kind to LegacyImmediate (the equipped-child
|
||||
/// world→attached transition) is the owner's explicit API — it refuses
|
||||
/// while the initial-create residence lease is still active, because an
|
||||
/// attached materialization would otherwise race the conductor's
|
||||
/// pending placement.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ResidenceConversionToLegacyImmediate_RefusesWhileTheResidenceIsActive()
|
||||
{
|
||||
const uint guid = 0x7000004Bu;
|
||||
var spatial = new GpuWorldState();
|
||||
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
|
||||
LiveEntityRuntimeFixture.DrivenLiveEntityRuntime driven =
|
||||
LiveEntityRuntimeFixture.CreateDriven(
|
||||
spatial,
|
||||
new RecordingResources());
|
||||
LiveEntityRuntime runtime = driven.Runtime;
|
||||
RuntimeEntityRecord canonical =
|
||||
Assert.IsType<RuntimeEntityRecord>(runtime.RegisterLiveEntity(
|
||||
Spawn(guid, 1, 1, 0x01010001u)).Canonical);
|
||||
runtime.MaterializeLiveEntity(
|
||||
canonical,
|
||||
0x01010001u,
|
||||
id => Entity(id, guid),
|
||||
LiveEntityProjectionKind.World,
|
||||
initializeProjection: null,
|
||||
out _,
|
||||
LiveEntityMaterializationResidence.AwaitRuntimePlacement);
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord record));
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
runtime.ConvertMaterializationResidenceToLegacyImmediate(record));
|
||||
Assert.Equal(
|
||||
LiveEntityMaterializationResidence.AwaitRuntimePlacement,
|
||||
record.MaterializationResidence);
|
||||
|
||||
driven.Pump();
|
||||
runtime.ConvertMaterializationResidenceToLegacyImmediate(record);
|
||||
Assert.Equal(
|
||||
LiveEntityMaterializationResidence.LegacyImmediate,
|
||||
record.MaterializationResidence);
|
||||
// Idempotent once converted (and a no-op for legacy records).
|
||||
runtime.ConvertMaterializationResidenceToLegacyImmediate(record);
|
||||
Assert.Equal(
|
||||
LiveEntityMaterializationResidence.LegacyImmediate,
|
||||
record.MaterializationResidence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InitiallyVisibleStaticObject_RebasesWithoutBecomingActive()
|
||||
{
|
||||
|
|
@ -1428,10 +1574,27 @@ public sealed class LiveEntityRuntimeTests
|
|||
{
|
||||
const uint stateBeforeBindGuid = 0x70000037u;
|
||||
const uint bindBeforeStateGuid = 0x70000038u;
|
||||
var runtime = LiveEntityRuntimeFixture.Create(new GpuWorldState(), new RecordingResources());
|
||||
runtime.RegisterLiveEntity(Spawn(stateBeforeBindGuid, 1, 1, 0x01010001u));
|
||||
runtime.RegisterLiveEntity(Spawn(bindBeforeStateGuid, 1, 1, 0x01010001u));
|
||||
// C3c: initial residences defer wire applies into their FIFO until a
|
||||
// host pump drives the conductors.
|
||||
LiveEntityRuntimeFixture.DrivenLiveEntityRuntime driven =
|
||||
LiveEntityRuntimeFixture.CreateDriven(
|
||||
new GpuWorldState(),
|
||||
new RecordingResources());
|
||||
LiveEntityRuntime runtime = driven.Runtime;
|
||||
RuntimeEntityRecord stateBeforeBindCanonical =
|
||||
Assert.IsType<RuntimeEntityRecord>(runtime.RegisterLiveEntity(
|
||||
Spawn(stateBeforeBindGuid, 1, 1, 0x01010001u)).Canonical);
|
||||
RuntimeEntityRecord bindBeforeStateCanonical =
|
||||
Assert.IsType<RuntimeEntityRecord>(runtime.RegisterLiveEntity(
|
||||
Spawn(bindBeforeStateGuid, 1, 1, 0x01010001u)).Canonical);
|
||||
|
||||
// C3c/C3b: the first-entry conductor constructs the canonical body
|
||||
// at Create (never-clobber: a fixture can no longer seed a
|
||||
// replacement RemoteMotionRuntime over it). "Arrival order" is now
|
||||
// SetState-before-the-drain (FIFO'd into the pending residence,
|
||||
// applied against the conductor-built body at Execute) versus
|
||||
// SetState-after-completion (legacy immediate apply). Both must
|
||||
// leave the canonical body's state synchronized.
|
||||
PhysicsStateFlags firstState = PhysicsStateFlags.Hidden
|
||||
| PhysicsStateFlags.Gravity
|
||||
| PhysicsStateFlags.ReportCollisions;
|
||||
|
|
@ -1439,18 +1602,30 @@ public sealed class LiveEntityRuntimeTests
|
|||
new SetState.Parsed(stateBeforeBindGuid, (uint)firstState, 1, 2),
|
||||
out _));
|
||||
runtime.MaterializeLiveEntity(
|
||||
stateBeforeBindGuid,
|
||||
stateBeforeBindCanonical,
|
||||
0x01010001u,
|
||||
id => Entity(id, stateBeforeBindGuid));
|
||||
var lateBody = new RemoteMotionRuntime();
|
||||
runtime.SetRemoteMotionRuntime(stateBeforeBindGuid, lateBody);
|
||||
|
||||
id => Entity(id, stateBeforeBindGuid),
|
||||
LiveEntityProjectionKind.World,
|
||||
initializeProjection: null,
|
||||
out _,
|
||||
LiveEntityMaterializationResidence.AwaitRuntimePlacement);
|
||||
runtime.MaterializeLiveEntity(
|
||||
bindBeforeStateGuid,
|
||||
bindBeforeStateCanonical,
|
||||
0x01010001u,
|
||||
id => Entity(id, bindBeforeStateGuid));
|
||||
var earlyBody = new RemoteMotionRuntime();
|
||||
runtime.SetRemoteMotionRuntime(bindBeforeStateGuid, earlyBody);
|
||||
id => Entity(id, bindBeforeStateGuid),
|
||||
LiveEntityProjectionKind.World,
|
||||
initializeProjection: null,
|
||||
out _,
|
||||
LiveEntityMaterializationResidence.AwaitRuntimePlacement);
|
||||
driven.Pump();
|
||||
PhysicsBody lateBody = runtime.GetOrCreatePhysicsBody(
|
||||
stateBeforeBindGuid,
|
||||
static _ => throw new InvalidOperationException(
|
||||
"The conductor-built canonical body should already exist."));
|
||||
PhysicsBody earlyBody = runtime.GetOrCreatePhysicsBody(
|
||||
bindBeforeStateGuid,
|
||||
static _ => throw new InvalidOperationException(
|
||||
"The conductor-built canonical body should already exist."));
|
||||
PhysicsStateFlags secondState = PhysicsStateFlags.Static
|
||||
| PhysicsStateFlags.Ethereal
|
||||
| PhysicsStateFlags.NoDraw;
|
||||
|
|
@ -1460,8 +1635,8 @@ public sealed class LiveEntityRuntimeTests
|
|||
|
||||
Assert.Equal((firstState & ~PhysicsStateFlags.ReportCollisions)
|
||||
| PhysicsStateFlags.IgnoreCollisions,
|
||||
lateBody.Body.State);
|
||||
Assert.Equal(secondState, earlyBody.Body.State);
|
||||
lateBody.State);
|
||||
Assert.Equal(secondState, earlyBody.State);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1573,9 +1748,17 @@ public sealed class LiveEntityRuntimeTests
|
|||
public void PositionAfterPickup_RequiresTeleportHookEvenWithEqualTeleportStamp()
|
||||
{
|
||||
const uint guid = 0x70000043u;
|
||||
var runtime = LiveEntityRuntimeFixture.Create(new GpuWorldState(), new RecordingResources());
|
||||
// C3c: initial residences defer wire applies into their FIFO until a
|
||||
// host pump drives the conductors; use the driven fixture and pump
|
||||
// after the Create so the pickup/position tail flows as before.
|
||||
LiveEntityRuntimeFixture.DrivenLiveEntityRuntime driven =
|
||||
LiveEntityRuntimeFixture.CreateDriven(
|
||||
new GpuWorldState(),
|
||||
new RecordingResources());
|
||||
LiveEntityRuntime runtime = driven.Runtime;
|
||||
WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u);
|
||||
runtime.RegisterLiveEntity(spawn);
|
||||
driven.Pump();
|
||||
Assert.True(runtime.TryApplyPickup(
|
||||
new PickupEvent.Parsed(guid, 1, 2),
|
||||
out _));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue