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
|
|
@ -278,6 +278,15 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
[Fact]
|
||||
public void DirectAndGraphicalHosts_ProduceIdenticalEntityObjectTrace()
|
||||
{
|
||||
// C3c (clause 2 — undriven-residence semantics): an initial world
|
||||
// Create now begins the canonical initial-create residence, whose
|
||||
// admission requires a live session generation. Both hosts here
|
||||
// deliberately carry the zero generation of a session-less runtime,
|
||||
// so BOTH refuse the Create transactionally with the identical
|
||||
// structural error and publish no entity-object trace at all. The
|
||||
// full driven-flow direct-vs-graphical parity (residence ->
|
||||
// conductor -> receipts) is covered by the C3c first-entry
|
||||
// integration tests.
|
||||
WorldSession.EntitySpawn spawn =
|
||||
Spawn(Harness.TargetGuid, instance: 7, cell: 0x12340001u);
|
||||
var direct = new RuntimeEntityObjectLifetime();
|
||||
|
|
@ -286,148 +295,28 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
using IDisposable directSubscription =
|
||||
direct.Events.Subscribe(directTrace);
|
||||
|
||||
RuntimeEntityRegistrationResult directRegistration =
|
||||
direct.RegisterEntity(spawn);
|
||||
RuntimeEntityRecord directCanonical =
|
||||
Assert.IsType<RuntimeEntityRecord>(directRegistration.Canonical);
|
||||
direct.Objects.AddOrUpdate(Object(spawn));
|
||||
direct.Objects.MoveItem(
|
||||
spawn.Guid,
|
||||
Harness.PlayerGuid,
|
||||
newSlot: 3);
|
||||
var objDesc = new ObjDescEvent.Parsed(
|
||||
spawn.Guid,
|
||||
new CreateObject.ModelData(
|
||||
0x04000001u,
|
||||
Array.Empty<CreateObject.SubPaletteSwap>(),
|
||||
Array.Empty<CreateObject.TextureChange>(),
|
||||
Array.Empty<CreateObject.AnimPartChange>()),
|
||||
spawn.InstanceSequence,
|
||||
ObjDescSequence: 2);
|
||||
Assert.True(direct.TryApplyObjDesc(
|
||||
objDesc,
|
||||
acknowledgeProjection: null,
|
||||
out _));
|
||||
var motion = new WorldSession.EntityMotionUpdate(
|
||||
spawn.Guid,
|
||||
new CreateObject.ServerMotionState(0x3D, 0x11),
|
||||
spawn.InstanceSequence,
|
||||
MovementSequence: 2,
|
||||
ServerControlSequence: 2,
|
||||
IsAutonomous: false);
|
||||
Assert.True(direct.TryApplyMotion(
|
||||
motion,
|
||||
retainPayload: true,
|
||||
acknowledgeProjection: null,
|
||||
out _,
|
||||
out _));
|
||||
var vector = new VectorUpdate.Parsed(
|
||||
spawn.Guid,
|
||||
new Vector3(1f, 2f, 3f),
|
||||
new Vector3(0f, 0f, 0.25f),
|
||||
spawn.InstanceSequence,
|
||||
VectorSequence: 2);
|
||||
Assert.True(direct.TryApplyVector(
|
||||
vector,
|
||||
acknowledgeProjection: null,
|
||||
out _));
|
||||
var state = new SetState.Parsed(
|
||||
spawn.Guid,
|
||||
(uint)(PhysicsStateFlags.ReportCollisions
|
||||
| PhysicsStateFlags.Hidden),
|
||||
spawn.InstanceSequence,
|
||||
StateSequence: 2);
|
||||
Assert.True(direct.TryApplyState(
|
||||
state,
|
||||
acknowledgeProjection: null,
|
||||
out _,
|
||||
out _));
|
||||
Assert.True(direct.CommitChildNoDraw(
|
||||
directCanonical,
|
||||
noDraw: true));
|
||||
Assert.True(direct.CommitChildNoDraw(
|
||||
directCanonical,
|
||||
noDraw: false));
|
||||
WorldSession.EntityPositionUpdate position =
|
||||
Position(spawn.Guid, spawn.InstanceSequence);
|
||||
Assert.True(direct.TryApplyPosition(
|
||||
position,
|
||||
isLocalPlayer: false,
|
||||
forcePositionRotation: null,
|
||||
currentLocalVelocity: null,
|
||||
projectionRequiresTeleportHook: false,
|
||||
acknowledgeProjection: null,
|
||||
out _,
|
||||
out _,
|
||||
out _));
|
||||
Assert.True(direct.CommitRebucket(
|
||||
directCanonical,
|
||||
0x12360001u,
|
||||
0x1236FFFFu));
|
||||
Assert.True(direct.TryApplyPickup(
|
||||
new PickupEvent.Parsed(
|
||||
spawn.Guid,
|
||||
spawn.InstanceSequence,
|
||||
PositionSequence: 3),
|
||||
acknowledgeProjection: null,
|
||||
out _));
|
||||
Assert.True(direct.TryAcceptDelete(
|
||||
new DeleteObject.Parsed(spawn.Guid, spawn.InstanceSequence),
|
||||
isLocalPlayer: false,
|
||||
removeRetainedObject: true,
|
||||
out RuntimeEntityDeleteAcceptance directDelete));
|
||||
direct.CompleteAcceptedDelete(directDelete);
|
||||
Assert.Null(direct.RetireCanonicalOnly(directCanonical));
|
||||
InvalidOperationException directRefusal =
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
direct.RegisterEntityWithInitialResidence(
|
||||
spawn,
|
||||
isLocalPlayer: false));
|
||||
|
||||
using var graphical = new Harness();
|
||||
var graphicalTrace = new EntityObjectTrace();
|
||||
using IDisposable graphicalSubscription =
|
||||
graphical.EntityObjects.Events.Subscribe(graphicalTrace);
|
||||
_ = graphical.Entities.RegisterAndMaterializeProjection(spawn);
|
||||
graphical.Objects.AddOrUpdate(Object(spawn));
|
||||
graphical.Objects.MoveItem(
|
||||
spawn.Guid,
|
||||
Harness.PlayerGuid,
|
||||
newSlot: 3);
|
||||
Assert.True(graphical.Entities.TryApplyObjDesc(
|
||||
objDesc,
|
||||
out _));
|
||||
Assert.True(graphical.Entities.TryApplyMotion(
|
||||
motion,
|
||||
retainPayload: true,
|
||||
out _,
|
||||
out _));
|
||||
Assert.True(graphical.Entities.TryApplyVector(vector, out _));
|
||||
Assert.True(graphical.Entities.TryApplyState(state, out _, out _));
|
||||
Assert.True(graphical.Entities.SetAttachedChildNoDraw(
|
||||
spawn.Guid,
|
||||
noDraw: true));
|
||||
Assert.True(graphical.Entities.SetAttachedChildNoDraw(
|
||||
spawn.Guid,
|
||||
noDraw: false));
|
||||
Assert.True(graphical.Entities.TryApplyPosition(
|
||||
position,
|
||||
isLocalPlayer: false,
|
||||
forcePositionRotation: null,
|
||||
currentLocalVelocity: null,
|
||||
out _,
|
||||
out _,
|
||||
out _));
|
||||
Assert.True(graphical.Entities.RebucketLiveEntity(
|
||||
spawn.Guid,
|
||||
0x12360001u));
|
||||
Assert.True(graphical.Entities.TryApplyPickup(
|
||||
new PickupEvent.Parsed(
|
||||
spawn.Guid,
|
||||
spawn.InstanceSequence,
|
||||
PositionSequence: 3),
|
||||
out _));
|
||||
Assert.True(graphical.Entities.UnregisterLiveEntity(
|
||||
new DeleteObject.Parsed(spawn.Guid, spawn.InstanceSequence),
|
||||
isLocalPlayer: false,
|
||||
removeRetainedObject: true));
|
||||
|
||||
InvalidOperationException graphicalRefusal =
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
graphical.Entities.RegisterAndMaterializeProjection(spawn));
|
||||
|
||||
Assert.Contains(
|
||||
"cannot acquire a structurally valid initial residence lease",
|
||||
directRefusal.Message,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Equal(directRefusal.Message, graphicalRefusal.Message);
|
||||
Assert.Equal(directTrace.Entries, graphicalTrace.Entries);
|
||||
Assert.Empty(directTrace.Entries);
|
||||
Assert.Equal(0, direct.Entities.Count);
|
||||
Assert.Equal(0, direct.Objects.ObjectCount);
|
||||
Assert.Equal(0, graphical.EntityObjects.Entities.Count);
|
||||
|
|
@ -438,6 +327,11 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
public void GraphicalObserverFailure_DoesNotStarveLaterObserverOrOwner()
|
||||
{
|
||||
using var harness = new Harness();
|
||||
// C3c: registration begins the canonical initial-create residence,
|
||||
// whose admission requires a live session generation.
|
||||
Assert.Equal(
|
||||
RuntimeSessionStartStatus.Connected,
|
||||
harness.Runtime.Session.Start(harness.Runtime.Generation).Status);
|
||||
var throwing = new ThrowingEntityObserver();
|
||||
var recording = new RuntimeTraceRecorder();
|
||||
IDisposable first = harness.Runtime.Subscribe(throwing);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue