acdream/tests/AcDream.Headless.Tests/HeadlessSessionHostTests.cs
Erik ff100cf33f fix(runtime): give the no-window host a post-merge canonical cell commit (D1, AD-60/AD-64, AP-146/#320)
C5b (735f0a72) made the steady-state accepted-Position merge stop writing
residency. That is retail-correct — HandleReceivedPosition @0x00453FD0 reads
the wire objcell_id into a local and never assigns the object's cell — and it
stays. What C5b did not account for is that its replacement writers both live
in AcDream.App: the OnPosition prologue rebucket (AD-60's W2) and the
post-routing wire-cell adopt (W3, AP-135).

The two hosts run parallel, non-shared inbound routes. LiveEntitySessionController
-> LiveEntityNetworkUpdateController.OnPosition is graphical-only;
RuntimeLiveEntitySessionController.OnPositionUpdated is the no-window route and
is constructed only at HeadlessSessionHost.cs:682. So AcDream.Headless had NO
post-merge cell writer at all. Every remote's FullCellId was written at
create/placement and then frozen for the session — and RuntimeEntityObjectViews
.Snapshot projects exactly that field as RuntimeEntitySnapshot.CellId, i.e. every
bot's entire world view. The local player lost one of AP-146's three refresh
edges, which matters beyond cosmetics: RuntimeSetPositionState
.IsAffectedCollisionResident reads FullCellId to pick which bodies a landblock
retirement parks, so a bot running A->B without teleporting would have retired A
while parking a body physically in B.

The fix, in three parts:

1. RuntimeEntityObjectLifetime.CommitWireCellRebucket — a new Runtime owner for
   the committed VALUE, extracted verbatim from LiveEntityRuntime
   .RebucketLiveEntity. This is also the root-cause fix for the layering
   inversion the review found: AD-60 was documenting its own correctness by
   naming an App class the Runtime assembly cannot reference. Behaviour on the
   graphical side is unchanged — record.FullCellId is a proxy for
   record.Canonical.FullCellId, which is the record the callee reads, and the
   commit is still CommitRebucket. Verified load-bearing for BOTH hosts:
   sabotaging the preserve branch reddens the graphical
   LiveEntityRuntimeTests.CanonicalOnlyRebucket_DoesNotOverwriteAuthoritativeFullCell
   as well as the new headless assertion.

2. RuntimeLiveEntitySessionController.TryCommitAcceptedWireCell — the no-window
   W2, under the same reachability rules the graphical route applies: Rejected
   writes nothing (the shape the App authority gate produces by returning false);
   a bound-projectile packet writes nothing (routed by the graphical host through
   the canonical projectile placement owner, which returns before W2); an active
   initial-create residence writes nothing (RebucketLiveEntity's own early
   return — while the lease is live the SetPosition conductor is the sole cell
   authority); a local ForcePosition writes only when the accepted-Position drive
   declined it (NotApplicable), because a handled force is
   placement-receipt-authoritative. W2/W3 themselves are untouched.

3. On the committed value (the landblock-vs-cell trap). RebucketLiveEntity's
   preserve branch fires on a LANDBLOCK-shaped id — low 16 bits 0xFFFF — and
   exists for LocalPlayerProjectionController.Project, the per-frame local
   movement caller that emits exactly that shape. An inbound wire objcell_id is
   never landblock-shaped, so on the accepted-Position route the branch is not
   taken and the exact wire cell is committed. That is what W2 commits today and
   what this now commits; the no-window host has no per-frame caller at all.

Ordering is matched, not improved on: the force drive submits its placement
before the commit, so its first submit still reads the pre-commit FullCellId —
AP-138's amended route-2 CurrentCellId measurement.

Bookkeeping in this commit:
- AD-60 corrected. Its surviving-channel enumeration presented "the local force
  path, the missile arm" as exhaustive; the entire no-window host belonged in it.
  23aa62f2's W2/W3-redundancy measurement is preserved verbatim.
- AP-146 and #320 amended the same way — their three-edge list was written from
  the graphical host and silently assumed both hosts shared it. The no-window
  host had two of three; it now has all three.
- AD-64 filed: the reachability decision is now expressed once per host. The
  value is single-sourced; the gate set is not.
- #324 filed: unifying the two session controllers is the genuinely correct fix
  and is campaign-sized (presentation recovery, hydration, the equipped-child
  renderer, and the remote/projectile routing arms only one host has). Not
  attempted here, per the fix brief.

Gates. Release build 0 errors. Complete suite 11,141 passed / 4 skipped /
0 failed, against the 11,134 / 4 / 0 baseline at 23aa62f2 — net +7, exactly the
7 tests added. Eight sabotages verified, each red on at least one discriminating
test and green when reverted: remote commit removed (2 Runtime + the end-to-end
Headless test); local ordinary commit removed; local NotApplicable-force commit
removed; force commit made unconditional; residence gate removed; missile gate
removed; Rejected gate removed; preserve branch broken (red on both hosts).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-05 22:36:31 +02:00

2306 lines
92 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Buffers.Binary;
using System.Collections.Immutable;
using System.Net;
using System.Numerics;
using System.Reflection;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Headless.Configuration;
using AcDream.Headless.Credentials;
using AcDream.Headless.Diagnostics;
using AcDream.Headless.Hosting;
using AcDream.Headless.Platform;
using AcDream.Runtime;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Gameplay;
using AcDream.Runtime.Physics;
using AcDream.Runtime.Session;
using AcDream.Runtime.World;
namespace AcDream.Headless.Tests;
public sealed class HeadlessSessionHostTests
{
[Fact]
public void SingleSessionStartsReconnectsAndConvergesWithoutPresentation()
{
var operations = new FixtureSessionOperations();
using var diagnosticsOutput = new StringWriter();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(diagnosticsOutput),
operations);
RuntimeSessionStartResult first = host.Start();
ulong firstGeneration = host.Runtime.Generation.Value;
host.Tick(0.015d);
RuntimeSessionStartResult second = host.Reconnect();
Assert.Equal(RuntimeSessionStartStatus.Connected, first.Status);
Assert.Equal(RuntimeSessionStartStatus.Connected, second.Status);
Assert.Equal(0x50000002u, first.CharacterId);
Assert.Equal("Headless", host.ActiveCharacterName);
Assert.True(host.Runtime.Session.IsInWorld);
Assert.True(host.Runtime.Generation.Value > firstGeneration);
Assert.Equal(2, operations.CreatedSessionCount);
Assert.Equal(1, operations.DisposedSessionCount);
host.Dispose();
Assert.Equal(2, operations.DisposedSessionCount);
Assert.True(host.Runtime.CaptureOwnership().IsConverged);
Assert.True(credential.IsDisposed);
string diagnostics = diagnosticsOutput.ToString();
Assert.Contains("\"state\":\"start-result\"", diagnostics);
Assert.DoesNotContain("password", diagnostics);
Assert.DoesNotContain("AcDream.App", diagnostics);
}
[Fact]
public async Task ProcessHostRunsUntilCancellationAndReturnsStableExitCode()
{
var configuration = new HeadlessConfiguration
{
Version = 1,
Sessions =
[
Descriptor(
HeadlessCredentialProviderKind.StandardInput,
"stdin-bot"),
],
};
HeadlessPathSet paths = HeadlessPathSet.Resolve(
new HeadlessPathOverrides());
using var diagnostics = new StringWriter();
var operations = new FixtureSessionOperations();
using var host = new HeadlessProcessHost(
configuration,
paths,
new System.IO.StringReader(
"process-password" + Environment.NewLine),
diagnostics,
operations);
using var cancellation = new CancellationTokenSource();
cancellation.Cancel();
HeadlessExitCode result =
await host.RunAsync(cancellation.Token);
Assert.Equal(HeadlessExitCode.Success, result);
Assert.True(host.Session.Runtime.Session.IsInWorld);
Assert.DoesNotContain(
"process-password",
diagnostics.ToString());
}
[Fact]
public async Task DirectCredentialsOverrideSingleConfiguredSession()
{
var configuration = new HeadlessConfiguration
{
Version = 1,
Sessions = [Descriptor()],
};
HeadlessPathSet paths = HeadlessPathSet.Resolve(
new HeadlessPathOverrides());
using var diagnostics = new StringWriter();
var operations = new FixtureSessionOperations();
using var host = new HeadlessProcessHost(
configuration,
paths,
TextReader.Null,
diagnostics,
operations,
directCredentials: new HeadlessDirectCredentials(
"direct-account",
"direct-secret"));
using var cancellation = new CancellationTokenSource();
cancellation.Cancel();
HeadlessExitCode result =
await host.RunAsync(cancellation.Token);
Assert.Equal(HeadlessExitCode.Success, result);
Assert.Equal("direct-account", operations.LastUser);
Assert.Equal("direct-secret", operations.LastPassword);
Assert.DoesNotContain(
"direct-secret",
diagnostics.ToString());
}
[Fact]
public void DirectFrameUsesSharedRetailOrderAndMovementCadence()
{
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations);
Assert.Equal(
RuntimeSessionStartStatus.Connected,
host.Start().Status);
HydrateGroundedPlayer(host.Runtime);
var sent = new List<(byte[] Body, double Time)>();
var trace = new RuntimeTraceRecorder();
using IDisposable subscription =
host.Runtime.Subscribe(trace);
operations.Sessions[^1].GameActionCapture =
body => sent.Add((
body,
host.Runtime.Clock.SimulationTimeSeconds));
RuntimeCommandResult intent = host.Commands.Movement.SetIntent(
host.Runtime.Generation,
new MovementInput(Forward: true, Run: true));
host.Tick(0.015d);
Assert.True(intent.Accepted);
Assert.Contains(
trace.Entries,
static entry =>
entry.Kind == RuntimeTraceKind.Movement);
Assert.Equal(
[
MoveToState.MoveToStateAction,
AutonomousPosition.AutonomousPositionAction,
],
sent.Select(static entry =>
ActionOpcode(entry.Body)).ToArray());
sent.Clear();
for (int index = 0; index < 70; index++)
host.Tick(0.015d);
(byte[] Body, double Time)[] positions = sent
.Where(static entry =>
ActionOpcode(entry.Body)
== AutonomousPosition.AutonomousPositionAction)
.ToArray();
Assert.InRange(positions.Length, 1, 2);
Assert.True(positions[^1].Time >= 1d);
if (positions.Length == 2)
{
Assert.True(
positions[1].Time - positions[0].Time >= 0.99d);
}
Assert.DoesNotContain(
sent,
entry => ActionOpcode(entry.Body)
== MoveToState.MoveToStateAction);
}
[Fact]
public void TeardownRetriesOnlyTheUnfinishedSuffix()
{
var operations = new FixtureSessionOperations();
var writer = new FailOnceTextWriter();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(writer),
operations);
Assert.Equal(
RuntimeSessionStartStatus.Connected,
host.Start().Status);
writer.FailNextWrite = true;
Assert.Throws<IOException>(host.Dispose);
Assert.Equal(1, operations.DisposedSessionCount);
host.Dispose();
Assert.Equal(1, operations.DisposedSessionCount);
Assert.True(host.Runtime.CaptureOwnership().IsConverged);
}
/// <summary>
/// B5(a) review fix: <see cref="HeadlessSessionEventRouteRetryPendingTests"/>
/// proved the underlying re-offer MECHANISM works, but hand-constructed
/// <see cref="HeadlessSessionEventRoute"/> directly and called
/// <c>route.RetryPending()</c> itself — it never touches
/// <see cref="HeadlessSessionHost.Tick"/>'s own
/// <c>_eventRoute?.RetryPending()</c> call. This test drives <c>Tick</c>
/// itself (via the <c>placementSinkOverride</c> test seam added for this
/// fix, mirroring the existing <c>policyOverride</c> parameter) so a
/// regression that deletes or reorders that exact line would fail HERE,
/// not just in the lower-level subscription test.
/// </summary>
[Fact]
public void TickRetriesAPreviouslyDeclinedPlacementThroughTheRealEventRoute()
{
const uint remote = 0x70004301u;
const uint landblock = 0xA9B40000u;
const uint cell = landblock | 0x0001u;
const float height = 6f;
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
var sink = new DecliningThenAcceptingPlacementSink();
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations,
placementSinkOverride: sink);
GameRuntime runtime = host.Runtime;
Assert.Equal(
RuntimeSessionStartStatus.Connected,
host.Start().Status);
runtime.EntityObjects.Physics.ObserveLocalWorldFrame(
cell, teleportAdvanced: false);
runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration(
landblock, 1UL);
AddFlatLandblock(runtime.EntityObjects.Physics.Engine);
runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration(
landblock, 1UL, ready: true);
RuntimeEntityRecord record = runtime.EntityObjects
.RegisterEntity(Spawn(remote, cell))
.Canonical!;
runtime.EntityObjects.Entities.SetFinalPhysicsState(
record, PhysicsStateFlags.Gravity);
runtime.EntityObjects.Entities.SetFullCell(
record, cell, landblock);
var body = new PhysicsBody
{
Position = new Vector3(10f, 10f, height),
Orientation = Quaternion.Identity,
LastUpdateTime = 1d,
State = PhysicsStateFlags.Gravity,
TransientState = TransientStateFlags.Active,
};
body.SnapToCell(cell, body.Position, body.Position);
runtime.EntityObjects.Entities.SetPhysicsBody(record, body);
record.ObjectClock.Activate();
runtime.EntityObjects.Physics.AcknowledgeSpatialProjection(
record, spatial: true);
RuntimeEntityPlacementToken token = runtime.EntityObjects.Physics
.SetPosition.TryBeginExclusiveAuthoredPlacement(
record,
record.PositionAuthorityVersion,
RuntimeSetPositionOperationKind.RemoteAuthoritative);
Assert.True(token.IsValid);
RuntimeSetPositionMoverPreparationStatus status = runtime.EntityObjects
.Physics.SetPosition.TryPrepareAndSubmitAuthoredPlacement(
record,
token,
RuntimeSetPositionOperationKind.RemoteAuthoritative,
PhysicsSetPositionFlags.Teleport | PhysicsSetPositionFlags.Slide,
new LoadedSetupCollisionSource(),
gameTime: runtime.Clock.SimulationTimeSeconds,
out RuntimeSetPositionOutcome outcome,
resolveWorldOffsetFromRuntimeFrame: true);
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared, status);
Assert.Equal(
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
outcome.Status);
// The production HeadlessSessionEventRoute's subscription attached
// during host.Start() already observed this Place synchronously —
// the fake sink is still declining, so it must remain unacknowledged.
Assert.Equal(1, sink.CallCount);
Assert.True(
runtime.EntityObjects.Physics.SetPosition.TryPeekProjection(
out _));
// The sink starts accepting (mirrors a landblock finishing streaming
// in) — driving ONE real host tick is what must re-offer the head,
// through Tick's own wiring, not a hand-built route.
sink.Accept = true;
host.Tick(0.015d);
Assert.Equal(2, sink.CallCount);
Assert.False(
runtime.EntityObjects.Physics.SetPosition.TryPeekProjection(
out _));
}
private sealed class DecliningThenAcceptingPlacementSink
: IRuntimePlacementProjectionSink
{
internal int CallCount { get; private set; }
internal bool Accept { get; set; }
public bool TryApply(in RuntimePlacementProjectionSnapshot projection)
{
CallCount++;
return Accept;
}
}
[Fact]
public void WorldProjectionHydratesCanonicalMovementAndTeleportState()
{
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations);
GameRuntime runtime = host.Runtime;
// C3c: conductor-driven flow — a live generation admits the initial
// residence, the flat landblock's collision generation commits, and
// the world projection's pump drives the local first-entry conductor
// to completion (the deleted SynchronizeLocalPlayer hand-copy's
// replacement).
Assert.Equal(
RuntimeSessionStartStatus.Connected,
host.Start().Status);
const uint player = 0x50000002u;
runtime.PlayerIdentity.ServerGuid = player;
runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration(
0xA9B40000u, 1UL);
AddFlatLandblock(runtime.EntityObjects.Physics.Engine);
runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration(
0xA9B40000u, 1UL, ready: true);
AcDream.Runtime.Session.RuntimeFirstEntryDriveController firstEntry =
CreateFirstEntryDrive(runtime);
RuntimeEntityRecord record = runtime.EntityObjects
.RegisterEntityWithInitialResidence(Spawn(player), isLocalPlayer: true)
.Canonical!;
Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn(
record,
record.CreateIntegrationVersion,
record.Snapshot,
replaceGeneration: false));
var collision = new FixtureCollisionNeighborhood();
// R5/A7 review fix (2026-08-05): the drive controller is now wired
// (was omitted in the first pass, leaving the canonical portal arm
// a no-op by construction here and dual-host parity with zero
// coverage). Mirrors production composition
// (HeadlessSessionHost.cs's own construction order): the SAME
// RuntimeAcceptedPositionDriveController drives both hosts through
// the identical TryExecuteAcceptedPortalArrival entry point.
RuntimeAcceptedPositionDriveController acceptedPositionDrive =
CreateAcceptedPositionDrive(runtime);
var projection = new HeadlessSessionWorldProjection(
runtime,
collision,
firstEntry,
acceptedPositionDrive);
projection.ProjectSpawn(record, isLocalPlayer: true);
PlayerMovementController controller =
Assert.IsType<PlayerMovementController>(
runtime.MovementOwner.Controller);
controller.SeedPlacementForTest(
new Vector3(48f, 49f, 50f),
0xA9B40001u, new Vector3(48f, 49f, 50f));
projection.ProjectPosition(
record,
isLocalPlayer: true,
PositionTimestampDisposition.Apply);
Assert.Same(controller, runtime.MovementOwner.Controller);
Assert.Equal(record.LocalEntityId, controller.LocalEntityId);
Assert.Equal(new Vector3(48f, 49f, 50f), controller.Position);
Assert.Equal(
0xA9B40000u,
controller.CellId & 0xFFFF0000u);
Assert.True((controller.CellId & 0xFFFFu) < 0x0100u);
projection.BeginTeleport();
Assert.Equal(PlayerState.PortalSpace, controller.State);
// R5/A7: the destination cell 0xA9B40001 is in the SAME landblock
// (0xA9B40000) whose collision generation the test already
// committed above, so the canonical portal arm resolves
// Committed synchronously - no DeferredCell park needed to exercise
// the real headless placement path. A1's headless retry loop
// (RuntimeLiveEntitySessionController.PumpPortalCompletion) is
// covered separately by
// HeadlessPortalDeferredCellCommitsOnPumpAfterCollisionGenerationWake
// below.
RuntimeDestinationReadiness readiness =
projection.PrepareDestination(
revealGeneration: 7,
new RuntimeTeleportDestination(
player,
InstanceSequence: 1,
PositionSequence: 2,
TeleportSequence: 1,
ForcePositionSequence: 0,
new Position(
0xA9B40001u,
new Vector3(96f, 97f, 50f),
Quaternion.Identity)),
// R5/A7: a real, valid token - `default` was fine for the
// old no-op arm but RuntimePortalPlacementAuthority.IsValid
// now genuinely gates TryExecuteAcceptedPortalArrival on it.
new RuntimeWorldHostProjectionToken(7, 0xA9B40001u));
Assert.True(readiness.IsCollisionReady);
Assert.False(readiness.IsUnhydratable);
Assert.Equal(PlayerState.InWorld, controller.State);
Assert.Equal(2, collision.CenterCount);
Assert.Equal(0xA9B40001u, collision.LastCell);
// A4/dual-host parity: the canonical placement actually committed -
// the body moved to the destination Position, not just the
// collision-neighborhood bookkeeping that CenterCount/LastCell
// alone would have proven even with the earlier no-op arm. Z
// settles 0.005 above the wire value (the foot sphere's bottom
// sits at origin + 0.475 - 0.48, LoadedSetupCollisionSource's own
// doc comment, ISSUES.md #285) - X/Y are exact, Z is asserted
// within that settle tolerance.
Assert.Equal(96f, controller.Position.X);
Assert.Equal(97f, controller.Position.Y);
Assert.Equal(50f, controller.Position.Z, 0.01f);
// The resolved outdoor sub-cell index is derived from X/Y within
// the landblock (not the wire placeholder 0xA9B40001), same as the
// FIRST ProjectPosition assertion above (":406-409") only checks
// landblock+indoor-vs-outdoor, not the exact sub-cell.
Assert.Equal(0xA9B40000u, controller.CellId & 0xFFFF0000u);
Assert.True((controller.CellId & 0xFFFFu) < 0x0100u);
}
/// <summary>
/// A1/R5/A7 review fix (2026-08-05): headless has no per-frame anim
/// sequencer the way the graphical host does, so its OWN equivalent of
/// A1's "hold until committed" mechanism is
/// <c>HeadlessSessionWorldProjection.PrepareDestination</c>'s
/// <c>_awaitingPortalWake</c> polling. This proves it end to end: a
/// destination in a landblock whose collision generation is NOT yet
/// committed parks (<c>IsCollisionReady: false</c>, body unmoved, no
/// throw — DeferredCell is a normal headless outcome per
/// <c>PrepareDestination</c>'s own doc), and once the destination
/// landblock's collision generation commits, the SAME park resolves on
/// a later attempt WITHOUT a second concurrent Begin (Runtime's own
/// Begin would refuse that with Contention if this class re-attempted
/// blindly instead of polling <c>PendingCount</c>).
/// </summary>
[Fact]
public void HeadlessPortalPrepareDestinationParksThenCommitsOnCollisionGenerationWake()
{
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations);
GameRuntime runtime = host.Runtime;
Assert.Equal(
RuntimeSessionStartStatus.Connected,
host.Start().Status);
const uint player = 0x50000009u;
const uint destinationLandblock = 0xAAB40000u;
runtime.PlayerIdentity.ServerGuid = player;
runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration(
0xA9B40000u, 1UL);
AddFlatLandblock(runtime.EntityObjects.Physics.Engine);
runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration(
0xA9B40000u, 1UL, ready: true);
AcDream.Runtime.Session.RuntimeFirstEntryDriveController firstEntry =
CreateFirstEntryDrive(runtime);
RuntimeEntityRecord record = runtime.EntityObjects
.RegisterEntityWithInitialResidence(Spawn(player), isLocalPlayer: true)
.Canonical!;
Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn(
record,
record.CreateIntegrationVersion,
record.Snapshot,
replaceGeneration: false));
var collision = new FixtureCollisionNeighborhood();
RuntimeAcceptedPositionDriveController acceptedPositionDrive =
CreateAcceptedPositionDrive(runtime);
var projection = new HeadlessSessionWorldProjection(
runtime,
collision,
firstEntry,
acceptedPositionDrive);
projection.ProjectSpawn(record, isLocalPlayer: true);
PlayerMovementController controller =
Assert.IsType<PlayerMovementController>(
runtime.MovementOwner.Controller);
controller.SeedPlacementForTest(
new Vector3(48f, 49f, 50f),
0xA9B40001u, new Vector3(48f, 49f, 50f));
projection.ProjectPosition(
record,
isLocalPlayer: true,
PositionTimestampDisposition.Apply);
projection.BeginTeleport();
var destination = new RuntimeTeleportDestination(
player,
InstanceSequence: 1,
PositionSequence: 2,
TeleportSequence: 1,
ForcePositionSequence: 0,
new Position(
destinationLandblock | 0x0001u,
new Vector3(10f, 10f, 50f),
Quaternion.Identity));
var projectionToken = new RuntimeWorldHostProjectionToken(
7, destinationLandblock | 0x0001u);
// Begin's own portal-vs-latest-cell gate (D-T5) requires the
// destination's landblock to match the record's LATEST MERGED
// Position, not just the transit's retained destination - mirror
// what the real inbound Position handler already does before
// TryCompletePortal ever runs (LiveEntityNetworkUpdateController's
// App-side equivalent).
Assert.True(runtime.EntityObjects.TryApplyPosition(
new WorldSession.EntityPositionUpdate(
player,
new CreateObject.ServerPosition(
destination.Position.ObjCellId,
destination.Position.Frame.Origin.X,
destination.Position.Frame.Origin.Y,
destination.Position.Frame.Origin.Z,
destination.Position.Frame.Orientation.W,
destination.Position.Frame.Orientation.X,
destination.Position.Frame.Orientation.Y,
destination.Position.Frame.Orientation.Z),
Velocity: null,
PlacementId: null,
IsGrounded: true,
InstanceSequence: 1,
PositionSequence: 3,
TeleportSequence: destination.TeleportSequence,
ForcePositionSequence: 0),
isLocalPlayer: true,
forcePositionRotation: Quaternion.Identity,
currentLocalVelocity: Vector3.Zero,
acknowledgeProjection: null,
out _,
out _,
out _));
// First attempt: destinationLandblock's collision generation was
// never begun/committed, so the canonical arm parks DeferredCell.
// Must NOT throw (a park is normal, not an error). The dormant
// stage (RuntimeSetPositionState's SubmitPreparedPlacementCore
// deferred-commit path) already stages the body's Position/CellId
// at the destination while it waits (StageDormantCellFrame,
// body.InWorld=false) - the reader-visible Position moving early is
// that mechanism, not evidence the placement committed; only
// PlayerState/IsCollisionReady distinguish "staged" from
// "committed" here.
RuntimeDestinationReadiness parked = projection.PrepareDestination(
revealGeneration: 7, destination, projectionToken);
Assert.False(parked.IsCollisionReady);
Assert.Equal(PlayerState.PortalSpace, controller.State);
// A SECOND attempt while still parked must not double-Begin -
// Runtime's own Begin would refuse a genuine second attempt with
// Contention, but PrepareDestination's _awaitingPortalWake polls
// PendingCount instead of re-attempting, so this must also report
// not-ready without throwing.
RuntimeDestinationReadiness stillParked =
projection.PrepareDestination(
revealGeneration: 7, destination, projectionToken);
Assert.False(stillParked.IsCollisionReady);
// Commit the destination landblock's collision generation and pump
// the drive's wake (mirrors HeadlessSessionHost.Tick's own
// PumpFirstEntry -> _acceptedPositionDrive.Advance() ordering).
runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration(
destinationLandblock, 1UL);
var heights = new byte[81];
Array.Fill(heights, (byte)50);
var heightTable = new float[256];
for (int index = 0; index < heightTable.Length; index++)
heightTable[index] = index;
runtime.EntityObjects.Physics.Engine.AddLandblock(
destinationLandblock,
new TerrainSurface(heights, heightTable),
[],
[],
worldOffsetX: 0f,
worldOffsetY: 0f);
runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration(
destinationLandblock, 1UL, ready: true);
// RuntimeAcceptedPositionDriveControllerTests.CommitLandblockCollision's
// exact proven-working shape: the wake path's
// resolveWorldOffsetFromRuntimeFrame requires the destination
// landblock's world-frame offset to already be resolvable.
runtime.EntityObjects.Physics.ObserveLocalWorldFrame(
destinationLandblock | 0x0001u,
teleportAdvanced: false);
// Drain the placement projection FIFO AFTER committing (exact order
// from RuntimeAcceptedPositionDriveControllerTests.DrainPlacementFifo's
// call site: commit collision -> drain FIFO -> Advance) - the
// deferred park's Withdraw notification is published as part of the
// collision-generation commit, not before it.
while (runtime.EntityObjects.Physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot head))
{
if (!runtime.EntityObjects.Physics.SetPosition
.AcknowledgeProjection(head.Token))
{
break;
}
}
acceptedPositionDrive.Advance();
RuntimeDestinationReadiness committed =
projection.PrepareDestination(
revealGeneration: 7, destination, projectionToken);
Assert.True(committed.IsCollisionReady);
Assert.Equal(PlayerState.InWorld, controller.State);
Assert.Equal(10f, controller.Position.X);
Assert.Equal(10f, controller.Position.Y);
Assert.Equal(destinationLandblock, controller.CellId & 0xFFFF0000u);
}
/// <summary>
/// B1 review fix (2026-08-05): headless's required test #2 — the same
/// unsound-commit-inference defect
/// <see cref="HeadlessPortalPrepareDestinationParksThenCommitsOnCollisionGenerationWake"/>
/// proves the HAPPY path for, exercised on the FORGOTTEN path instead. A
/// DeferredCell park killed by an ordinary, unrelated accepted Position
/// merge (exactly the ACE 5-10 Hz broadcast <c>RuntimeSetPositionState</c>'s
/// own doc names as the expected way a far-destination park resolves
/// without committing) must leave <c>PrepareDestination</c> reporting
/// NOT ready, <see cref="PlayerState.PortalSpace"/> unchanged, and the
/// body never moved to the destination - before the fix,
/// <c>PendingCount</c> hitting 0 made <c>PrepareDestination</c> infer
/// "committed" and run the full readiness/materialize/LoginComplete
/// sequence against an unmoved body.
/// </summary>
[Fact]
public void HeadlessPortalPrepareDestinationForgottenByOrdinaryMergeDoesNotLatchAsCommitted()
{
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations);
GameRuntime runtime = host.Runtime;
Assert.Equal(
RuntimeSessionStartStatus.Connected,
host.Start().Status);
const uint player = 0x50000009u;
const uint destinationLandblock = 0xAAB40000u;
runtime.PlayerIdentity.ServerGuid = player;
runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration(
0xA9B40000u, 1UL);
AddFlatLandblock(runtime.EntityObjects.Physics.Engine);
runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration(
0xA9B40000u, 1UL, ready: true);
AcDream.Runtime.Session.RuntimeFirstEntryDriveController firstEntry =
CreateFirstEntryDrive(runtime);
RuntimeEntityRecord record = runtime.EntityObjects
.RegisterEntityWithInitialResidence(Spawn(player), isLocalPlayer: true)
.Canonical!;
Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn(
record,
record.CreateIntegrationVersion,
record.Snapshot,
replaceGeneration: false));
var collision = new FixtureCollisionNeighborhood();
RuntimeAcceptedPositionDriveController acceptedPositionDrive =
CreateAcceptedPositionDrive(runtime);
var projection = new HeadlessSessionWorldProjection(
runtime,
collision,
firstEntry,
acceptedPositionDrive);
projection.ProjectSpawn(record, isLocalPlayer: true);
PlayerMovementController controller =
Assert.IsType<PlayerMovementController>(
runtime.MovementOwner.Controller);
controller.SeedPlacementForTest(
new Vector3(48f, 49f, 50f),
0xA9B40001u, new Vector3(48f, 49f, 50f));
projection.ProjectPosition(
record,
isLocalPlayer: true,
PositionTimestampDisposition.Apply);
projection.BeginTeleport();
var destination = new RuntimeTeleportDestination(
player,
InstanceSequence: 1,
PositionSequence: 2,
TeleportSequence: 1,
ForcePositionSequence: 0,
new Position(
destinationLandblock | 0x0001u,
new Vector3(10f, 10f, 50f),
Quaternion.Identity));
var projectionToken = new RuntimeWorldHostProjectionToken(
7, destinationLandblock | 0x0001u);
Assert.True(runtime.EntityObjects.TryApplyPosition(
new WorldSession.EntityPositionUpdate(
player,
new CreateObject.ServerPosition(
destination.Position.ObjCellId,
destination.Position.Frame.Origin.X,
destination.Position.Frame.Origin.Y,
destination.Position.Frame.Origin.Z,
destination.Position.Frame.Orientation.W,
destination.Position.Frame.Orientation.X,
destination.Position.Frame.Orientation.Y,
destination.Position.Frame.Orientation.Z),
Velocity: null,
PlacementId: null,
IsGrounded: true,
InstanceSequence: 1,
PositionSequence: 3,
TeleportSequence: destination.TeleportSequence,
ForcePositionSequence: 0),
isLocalPlayer: true,
forcePositionRotation: Quaternion.Identity,
currentLocalVelocity: Vector3.Zero,
acknowledgeProjection: null,
out _,
out _,
out _));
// First attempt: destinationLandblock's collision generation was
// never begun/committed, so the canonical arm parks DeferredCell.
RuntimeDestinationReadiness parked = projection.PrepareDestination(
revealGeneration: 7, destination, projectionToken);
Assert.False(parked.IsCollisionReady);
Assert.Equal(PlayerState.PortalSpace, controller.State);
Assert.Equal(1, acceptedPositionDrive.PendingCount);
// An ordinary, UNRELATED accepted Position for the same entity - no
// new teleport, just a normal broadcast at the SAME already-accepted
// teleport sequence - Forgets the parked operation the same way
// ACE's 5-10 Hz cadence would (RuntimeSetPositionState.Forget, called
// from TryApplyPosition for every accepted, non-Rejected Position).
Assert.True(runtime.EntityObjects.TryApplyPosition(
new WorldSession.EntityPositionUpdate(
player,
new CreateObject.ServerPosition(
0x20210001u, 48f, 49f, 50f, 1f, 0f, 0f, 0f),
Velocity: null,
PlacementId: null,
IsGrounded: true,
InstanceSequence: 1,
PositionSequence: 4,
TeleportSequence: destination.TeleportSequence,
ForcePositionSequence: 0),
isLocalPlayer: true,
forcePositionRotation: Quaternion.Identity,
currentLocalVelocity: Vector3.Zero,
acknowledgeProjection: null,
out _,
out _,
out _));
// Forget (inside TryApplyPosition) cancels the underlying
// RuntimeSetPositionState operation immediately, but the drive's OWN
// _pending cache only notices on its next Advance() pump - the real
// host does this every HeadlessSessionHost.Tick via PumpFirstEntry;
// the test drives it explicitly, same as the App-level equivalent.
acceptedPositionDrive.Advance();
Assert.Equal(0, acceptedPositionDrive.PendingCount);
// Drive well past where the pre-fix inference would have latched
// "committed" on the very next PrepareDestination call and then
// marched to the full readiness/materialize/LoginComplete sequence.
for (int i = 0; i < 10; i++)
{
RuntimeDestinationReadiness stillNotReady =
projection.PrepareDestination(
revealGeneration: 7, destination, projectionToken);
Assert.False(stillNotReady.IsCollisionReady);
}
Assert.Equal(PlayerState.PortalSpace, controller.State);
}
[Fact]
public void WorldProjectionIgnoresNormalEchoButBlipsForcePosition()
{
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations);
GameRuntime runtime = host.Runtime;
// C3c: conductor-driven flow — a live generation admits the initial
// residence, the flat landblock's collision generation commits, and
// the world projection's pump drives the local first-entry conductor
// to completion (the deleted SynchronizeLocalPlayer hand-copy's
// replacement).
Assert.Equal(
RuntimeSessionStartStatus.Connected,
host.Start().Status);
const uint player = 0x50000003u;
runtime.PlayerIdentity.ServerGuid = player;
runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration(
0xA9B40000u, 1UL);
AddFlatLandblock(runtime.EntityObjects.Physics.Engine);
runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration(
0xA9B40000u, 1UL, ready: true);
AcDream.Runtime.Session.RuntimeFirstEntryDriveController firstEntry =
CreateFirstEntryDrive(runtime);
RuntimeEntityRecord record = runtime.EntityObjects
.RegisterEntityWithInitialResidence(Spawn(player), isLocalPlayer: true)
.Canonical!;
Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn(
record,
record.CreateIntegrationVersion,
record.Snapshot,
replaceGeneration: false));
var collision = new FixtureCollisionNeighborhood();
var projection = new HeadlessSessionWorldProjection(
runtime,
collision,
firstEntry);
projection.ProjectSpawn(record, isLocalPlayer: true);
PlayerMovementController controller =
Assert.IsType<PlayerMovementController>(
runtime.MovementOwner.Controller);
controller.SeedPlacementForTest(
new Vector3(48f, 49f, 50f),
0xA9B40001u, new Vector3(48f, 49f, 50f));
projection.ProjectPosition(
record,
isLocalPlayer: true,
PositionTimestampDisposition.Apply);
Assert.Equal(new Vector3(48f, 49f, 50f), controller.Position);
var force = new WorldSession.EntityPositionUpdate(
player,
record.Snapshot.Position!.Value with
{
PositionX = 72f,
PositionY = 73f,
},
Velocity: null,
PlacementId: null,
IsGrounded: true,
InstanceSequence: 1,
PositionSequence: 2,
TeleportSequence: 0,
ForcePositionSequence: 1);
Assert.True(runtime.EntityObjects.TryApplyPosition(
force,
isLocalPlayer: true,
forcePositionRotation: Quaternion.Identity,
currentLocalVelocity: controller.BodyVelocity,
acknowledgeProjection: null,
out PositionTimestampDisposition disposition,
out _,
out AcceptedPhysicsTimestamps timestamps));
Assert.Equal(
PositionTimestampDisposition.ForcePosition,
disposition);
// C4 route 2 (2026-08-03): a ForcePosition on the local player no
// longer routes through HeadlessSessionWorldProjection.ProjectPosition
// at all — RuntimeLiveEntitySessionController.OnPositionUpdated
// dispatches it directly to RuntimeAcceptedPositionDriveController
// instead (the deleted BlipLocalPlayer's replacement), but R2 review
// fix (2026-08-03): it re-centers the collision neighborhood on the
// destination FIRST — the deleted BlipLocalPlayer's own CenterOn
// side effect, restored via CenterOnAcceptedForcePosition, because a
// DeferredCell park this neighborhood's window can never publish is
// a dead end, not a real park
// (RuntimeAcceptedPositionDriveController.Advance's R1 doc comment).
projection.CenterOnAcceptedForcePosition(record);
RuntimeAcceptedPositionDriveController acceptedPositionDrive =
CreateAcceptedPositionDrive(runtime);
RuntimeAcceptedPositionExecutionStatus forceStatus =
acceptedPositionDrive.TryExecuteAcceptedLocalPosition(
record,
force,
disposition,
timestamps,
timestamps.PreviousTeleport);
Assert.Equal(
RuntimeAcceptedPositionExecutionStatus.Committed,
forceStatus);
// R7 review fix (2026-08-03): Z is 50.005f — within 5 mm of the
// wire's bare 50f — NOT 50.48f. The dat-exact human Setup's foot
// sphere is (0,0,0.475) r=.48 (LoadedSetupCollisionSource above);
// its bottom sits at origin + 0.475 0.48 = origin 0.005, so a
// settled origin lands 0.005 m ABOVE the floor it rests on (measured
// empirically against this exact fixture), not a full sphere RADIUS
// above it. Retail's BlipPlayer (CPhysicsObj::SetPositionSimple
// @0x005162B0, called from SmartBox::BlipPlayer @0x00453940) has
// never lifted the origin by a sphere radius — the C4-route-2 FIRST
// implementation pass (uncommitted; this file asserts a bare 50f at
// HEAD, never 50.48f) had fitted a 50.48f assertion to a dummy
// fixture sphere whose offset happened to equal its own radius, not
// to retail behavior. The comment it carried described the sphere's
// CENTRE, then wrongly asserted that description about
// controller.Position, which is the body's ORIGIN
// (PlayerMovementController.cs -> PhysicsBody.cs Position), not the
// sphere centre.
Assert.Equal(new Vector3(72f, 73f, 50.005f), controller.Position);
// CenterCount is 2: ProjectSpawn's initial centering plus the
// ForcePosition's own re-centering above (R2's restored mechanism).
Assert.Equal(2, collision.CenterCount);
}
/// <summary>
/// C3c-R1 review F7: a remote Create whose landblock lies outside the
/// bounded collision neighborhood's service window can never see its
/// deferred placement's collision-generation wake — left alone it would
/// pin its residence (and the drive's pending entry) for the whole
/// session. The host converts it to the celless completion route before
/// the pump: the residence completes with FullCell 0, the accepted wire
/// frame stays on the canonical snapshot (the exact pre-flip
/// accepted-frame behavior for far remotes), and every ledger converges.
/// </summary>
[Fact]
public void FarRemoteCreateCompletesCelllessWithoutPinningItsResidence()
{
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations);
GameRuntime runtime = host.Runtime;
Assert.Equal(
RuntimeSessionStartStatus.Connected,
host.Start().Status);
const uint player = 0x5000000Bu;
runtime.PlayerIdentity.ServerGuid = player;
runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration(
0xA9B40000u, 1UL);
AddFlatLandblock(runtime.EntityObjects.Physics.Engine);
runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration(
0xA9B40000u, 1UL, ready: true);
AcDream.Runtime.Session.RuntimeFirstEntryDriveController firstEntry =
CreateFirstEntryDrive(runtime);
var collision = new FixtureCollisionNeighborhood();
var projection = new HeadlessSessionWorldProjection(
runtime,
collision,
firstEntry);
RuntimeEntityRecord playerRecord = runtime.EntityObjects
.RegisterEntityWithInitialResidence(Spawn(player), isLocalPlayer: true)
.Canonical!;
Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn(
playerRecord,
playerRecord.CreateIntegrationVersion,
playerRecord.Snapshot,
replaceGeneration: false));
projection.ProjectSpawn(playerRecord, isLocalPlayer: true);
const uint farRemote = 0x70000010u;
const uint farCell = 0x00010001u;
Assert.False(collision.IsWithinServiceWindow(farCell));
RuntimeEntityRecord remote = runtime.EntityObjects
.RegisterEntityWithInitialResidence(
Spawn(farRemote, farCell),
isLocalPlayer: false)
.Canonical!;
Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn(
remote,
remote.CreateIntegrationVersion,
remote.Snapshot,
replaceGeneration: false));
projection.ProjectSpawn(remote, isLocalPlayer: false);
// Celless completion: no pinned residence, FullCell stays 0, the
// accepted wire frame survives on the canonical snapshot.
Assert.False(runtime.EntityObjects.TryGetInitialCreateResidence(
remote,
out _));
Assert.Equal(0u, remote.FullCellId);
Assert.Equal(
farCell,
remote.Snapshot.Position!.Value.LandblockId);
RuntimeEntityObjectOwnershipSnapshot ownership =
runtime.EntityObjects.CaptureOwnership();
Assert.Equal(0, ownership.InitialCreateResidenceLeaseCount);
Assert.Equal(0, ownership.FirstEntryDrivePendingCount);
Assert.Equal(0, firstEntry.PendingCount);
// Draining the placement FIFO the way the host subscription would
// converges the completion-receipt ledger too.
while (runtime.EntityObjects.Physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot head))
{
if (!runtime.EntityObjects.Physics.SetPosition
.AcknowledgeProjection(head.Token))
{
break;
}
}
Assert.Equal(
0,
runtime.EntityObjects.CaptureOwnership()
.PendingCompletionReceiptCount);
}
[Fact]
public void PlacementReceiptValidationDoesNotRegainMovementOrPhysicsAuthority()
{
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations);
GameRuntime runtime = host.Runtime;
// C3c: conductor-driven flow — a live generation admits the initial
// residence, the flat landblock's collision generation commits, and
// the world projection's pump drives the local first-entry conductor
// to completion (the deleted SynchronizeLocalPlayer hand-copy's
// replacement).
Assert.Equal(
RuntimeSessionStartStatus.Connected,
host.Start().Status);
const uint player = 0x50000004u;
runtime.PlayerIdentity.ServerGuid = player;
runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration(
0xA9B40000u, 1UL);
AddFlatLandblock(runtime.EntityObjects.Physics.Engine);
runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration(
0xA9B40000u, 1UL, ready: true);
AcDream.Runtime.Session.RuntimeFirstEntryDriveController firstEntry =
CreateFirstEntryDrive(runtime);
RuntimeEntityRecord record = runtime.EntityObjects
.RegisterEntityWithInitialResidence(Spawn(player), isLocalPlayer: true)
.Canonical!;
Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn(
record,
record.CreateIntegrationVersion,
record.Snapshot,
replaceGeneration: false));
var directProjection = new HeadlessSessionWorldProjection(
runtime,
new FixtureCollisionNeighborhood(),
firstEntry);
directProjection.ProjectSpawn(record, isLocalPlayer: true);
PlayerMovementController controller = Assert.IsType<
PlayerMovementController>(runtime.MovementOwner.Controller);
Vector3 positionBefore = controller.Position;
Quaternion orientationBefore = controller.BodyOrientation;
RuntimePhysicsOwnershipSnapshot physicsBefore =
runtime.EntityObjects.Physics.CaptureOwnership();
RuntimePlacementProjectionSnapshot receipt = Placement(
runtime,
record,
RuntimePlacementProjectionKind.Place,
new Vector3(600f, 601f, 602f),
Quaternion.CreateFromAxisAngle(Vector3.UnitY, 1.2f));
var receiptSink = new HeadlessRuntimePlacementProjectionSink(runtime);
Assert.True(receiptSink.TryApply(in receipt));
Assert.Equal(positionBefore, controller.Position);
Assert.Equal(orientationBefore, controller.BodyOrientation);
Assert.Equal(
physicsBefore,
runtime.EntityObjects.Physics.CaptureOwnership());
}
[Fact]
public void PlacementReceiptUsesExactIncarnationAndDiscardIsAckOnly()
{
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations);
GameRuntime runtime = host.Runtime;
RuntimeEntityRecord record = runtime.EntityObjects
.RegisterEntity(Spawn(0x50000005u))
.Canonical!;
Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn(
record,
record.CreateIntegrationVersion,
record.Snapshot,
replaceGeneration: false));
var projection = new HeadlessRuntimePlacementProjectionSink(runtime);
RuntimePlacementProjectionSnapshot place = Placement(
runtime,
record,
RuntimePlacementProjectionKind.Place,
Vector3.One,
Quaternion.Identity);
RuntimePlacementProjectionSnapshot stale = place with
{
Token = place.Token with
{
Entity = place.Token.Entity with
{
Incarnation = unchecked((ushort)(
place.Token.Entity.Incarnation + 1)),
},
},
};
RuntimePlacementProjectionSnapshot discard = stale with
{
Kind = RuntimePlacementProjectionKind.Discard,
Token = stale.Token with
{
SessionLifetimeVersion = ulong.MaxValue,
},
};
Assert.False(projection.TryApply(in stale));
Assert.True(projection.TryApply(in discard));
}
[Fact]
public void ExecutorCompletedReceiptIsAcknowledgeOnlyRegardlessOfRecordValidity()
{
// F1: mirrors PlacementReceiptUsesExactIncarnationAndDiscardIsAckOnly's
// stale-token half - proves ExecutorCompleted is acknowledged
// unconditionally (never gated by the record-lookup/portal-shape
// checks Place/Withdraw depend on), so a genuinely stale/mismatched
// token can never wedge the FIFO behind it.
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations);
GameRuntime runtime = host.Runtime;
RuntimeEntityRecord record = runtime.EntityObjects
.RegisterEntity(Spawn(0x50000006u))
.Canonical!;
Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn(
record,
record.CreateIntegrationVersion,
record.Snapshot,
replaceGeneration: false));
var sink = new HeadlessRuntimePlacementProjectionSink(runtime);
RuntimePlacementProjectionSnapshot completion = Placement(
runtime,
record,
RuntimePlacementProjectionKind.ExecutorCompleted,
Vector3.One,
Quaternion.Identity);
RuntimePlacementProjectionSnapshot stale = completion with
{
Token = completion.Token with
{
Entity = completion.Token.Entity with
{
Incarnation = unchecked((ushort)(
completion.Token.Entity.Incarnation + 1)),
},
SessionLifetimeVersion = ulong.MaxValue,
},
};
Assert.True(sink.TryApply(in completion));
Assert.True(sink.TryApply(in stale));
}
[Fact]
public void SessionEventRouteOwnsOneObserverAndUnsubscribesBeforeNetworkDetach()
{
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations);
GameRuntime runtime = host.Runtime;
int subscriberCountDuringDetach = -1;
var inner = new FixtureEventRoute(
onDispose: () => subscriberCountDuringDetach =
runtime.EntityObjects.Events.PlacementSubscriberCount);
var placements = new HeadlessRuntimePlacementProjectionSink(runtime);
var route = new HeadlessSessionEventRoute(
inner,
runtime,
placements);
Assert.Equal(
0,
runtime.EntityObjects.Events.PlacementSubscriberCount);
route.Attach();
route.Attach();
Assert.Equal(
1,
runtime.EntityObjects.Events.PlacementSubscriberCount);
Assert.Equal(1, inner.AttachCount);
route.Dispose();
route.Dispose();
Assert.Equal(0, subscriberCountDuringDetach);
Assert.Equal(
0,
runtime.EntityObjects.Events.PlacementSubscriberCount);
Assert.Equal(1, inner.DisposeCount);
}
[Fact]
public void ReplacementSessionEventRouteGetsOneFreshObserver()
{
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations);
GameRuntime runtime = host.Runtime;
var placements = new HeadlessRuntimePlacementProjectionSink(runtime);
var first = new HeadlessSessionEventRoute(
new FixtureEventRoute(),
runtime,
placements);
first.Attach();
Assert.Equal(
1,
runtime.EntityObjects.Events.PlacementSubscriberCount);
first.Dispose();
Assert.Equal(
0,
runtime.EntityObjects.Events.PlacementSubscriberCount);
var replacement = new HeadlessSessionEventRoute(
new FixtureEventRoute(),
runtime,
placements);
replacement.Attach();
Assert.Equal(
1,
runtime.EntityObjects.Events.PlacementSubscriberCount);
replacement.Dispose();
Assert.Equal(
0,
runtime.EntityObjects.Events.PlacementSubscriberCount);
}
[Fact]
public void SessionEventRouteRetryDoesNotRestorePlacementObserver()
{
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations);
GameRuntime runtime = host.Runtime;
var inner = new FixtureEventRoute
{
DisposeFailuresRemaining = 1,
};
var route = new HeadlessSessionEventRoute(
inner,
runtime,
new HeadlessRuntimePlacementProjectionSink(runtime));
route.Attach();
Assert.Throws<IOException>(route.Dispose);
Assert.Equal(
0,
runtime.EntityObjects.Events.PlacementSubscriberCount);
Assert.Equal(1, inner.DisposeCount);
route.Dispose();
Assert.Equal(2, inner.DisposeCount);
Assert.Equal(
0,
runtime.EntityObjects.Events.PlacementSubscriberCount);
}
[Fact]
public void CollisionTransactionCancelsPostAdmissionFaultWithoutWithdrawingActiveWorld()
{
using var lifetime = new RuntimeEntityObjectLifetime();
RuntimePhysicsState physics = lifetime.Physics;
const uint landblockId = 0xA9B4FFFFu;
CompleteCollisionGeneration(
physics,
landblockId,
afterAdmission: null,
(admission, prepared) =>
physics.StageCollisionAssets(
admission,
prepared,
CollisionAssets(landblockId, 10f)));
Assert.Throws<FixtureCollisionPublicationException>(() =>
CompleteCollisionGeneration(
physics,
landblockId,
_ => throw new FixtureCollisionPublicationException(),
(_, _) => throw new InvalidOperationException(
"Staging must not run after the injected admission fault.")));
Assert.Equal(10f, physics.Engine.SampleTerrainZ(1f, 1f));
RuntimePhysicsOwnershipSnapshot ownership = physics.CaptureOwnership();
Assert.Equal(1, ownership.LandblockCount);
Assert.Equal(0, ownership.CollisionAdmissionCount);
}
[Fact]
public void CollisionTransactionYieldsTheFirstNonterminalRuntimePoll()
{
using var lifetime = new RuntimeEntityObjectLifetime();
RuntimePhysicsState physics = lifetime.Physics;
const uint landblockId = 0xA9B4FFFFu;
HeadlessCollisionGenerationTransaction transaction =
HeadlessCollisionGenerationTransaction.Begin(
physics,
landblockId,
afterAdmission: null,
(admission, prepared) =>
physics.StageCollisionAssets(
admission,
prepared,
CollisionAssets(landblockId, 10f)));
HeadlessCollisionGenerationAdvance advance;
do
{
advance = transaction.Advance();
Assert.True(advance.Progressed);
}
while (!advance.YieldToCaller);
Assert.False(advance.Completed);
Assert.False(advance.WaitingForProjectionAcknowledgement);
Assert.False(transaction.CompletionCommitted);
do
{
advance = transaction.Advance();
Assert.True(advance.Progressed);
}
while (!advance.Completed && !advance.YieldToCaller);
if (!advance.Completed)
advance = transaction.Advance();
Assert.True(advance.Completed);
Assert.True(transaction.EngineMutationCommitted);
Assert.True(transaction.CompletionCommitted);
Assert.Equal(0, physics.CaptureOwnership().CollisionAdmissionCount);
}
[Fact]
public void CollisionTransactionRetainsPostEngineCancellationUntilPlaceAck()
{
using var lifetime = new RuntimeEntityObjectLifetime();
RuntimePhysicsState physics = lifetime.Physics;
const uint landblockId = 0xA9B4FFFFu;
CompleteCollisionGeneration(
physics,
landblockId,
afterAdmission: null,
(admission, prepared) =>
physics.StageCollisionAssets(
admission,
prepared,
CollisionAssets(landblockId, 10f)));
const uint guid = 0x70004201u;
const uint cell = 0xA9B40001u;
Vector3 position = new(10f, 10f, 0f);
RuntimeEntityRecord record = lifetime.RegisterEntity(
Spawn(guid)).Canonical!;
lifetime.Entities.SetFinalPhysicsState(record, PhysicsStateFlags.Gravity);
lifetime.Entities.SetFullCell(record, cell, landblockId);
var body = new PhysicsBody
{
Position = position,
Orientation = Quaternion.Identity,
LastUpdateTime = 1d,
State = PhysicsStateFlags.Gravity,
TransientState = TransientStateFlags.Active,
};
body.SnapToCell(cell, position, position);
lifetime.Entities.SetPhysicsBody(record, body);
record.ObjectClock.Activate();
physics.AcknowledgeSpatialProjection(record, spatial: true);
RuntimePlacementProjectionToken seeded = SeedRuntimePlacement(
physics,
record,
cell,
position);
Assert.True(physics.SetPosition.AcknowledgeProjection(seeded));
HeadlessCollisionGenerationTransaction transaction =
HeadlessCollisionGenerationTransaction.Begin(
physics,
landblockId,
afterAdmission: null,
(admission, prepared) =>
physics.StageCollisionAssets(
admission,
prepared,
CollisionAssets(landblockId, 20f)));
HeadlessCollisionGenerationAdvance advance;
do
{
advance = transaction.Advance();
}
while (!advance.WaitingForProjectionAcknowledgement);
Assert.False(transaction.EngineMutationCommitted);
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot withdrawal));
Assert.True(physics.SetPosition.AcknowledgeProjection(withdrawal.Token));
do
{
advance = transaction.Advance();
}
while (!advance.WaitingForProjectionAcknowledgement);
Assert.True(transaction.EngineMutationCommitted);
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot placement));
Assert.False(transaction.TryCancel());
Assert.True(physics.SetPosition.AcknowledgeProjection(placement.Token));
Assert.True(transaction.TryCancel());
RuntimePhysicsOwnershipSnapshot ownership = physics.CaptureOwnership();
Assert.Equal(0, ownership.CollisionPrefixMutationCount);
Assert.Equal(0, ownership.CollisionAdmissionCount);
}
[Fact]
public void CollisionTransactionCancelsStagingFaultWithoutWithdrawingActiveWorld()
{
using var lifetime = new RuntimeEntityObjectLifetime();
RuntimePhysicsState physics = lifetime.Physics;
const uint landblockId = 0xA9B4FFFFu;
CompleteCollisionGeneration(
physics,
landblockId,
afterAdmission: null,
(admission, prepared) =>
physics.StageCollisionAssets(
admission,
prepared,
CollisionAssets(landblockId, 10f)));
Assert.Throws<FixtureCollisionPublicationException>(() =>
CompleteCollisionGeneration(
physics,
landblockId,
afterAdmission: null,
(admission, prepared) =>
{
physics.StageCollisionAssets(
admission,
prepared,
CollisionAssets(landblockId, 25f));
throw new FixtureCollisionPublicationException();
}));
Assert.Equal(10f, physics.Engine.SampleTerrainZ(1f, 1f));
RuntimePhysicsOwnershipSnapshot ownership = physics.CaptureOwnership();
Assert.Equal(1, ownership.LandblockCount);
Assert.Equal(0, ownership.CollisionAdmissionCount);
}
private static HeadlessSessionDescriptor Descriptor(
HeadlessCredentialProviderKind provider =
HeadlessCredentialProviderKind.Environment,
string credentialReference = "BOT_PASSWORD") => new()
{
Id = "bot",
Endpoint = new HeadlessEndpointDescriptor
{
Host = "127.0.0.1",
Port = 9000,
},
Account = "account",
Character = new HeadlessCharacterSelector
{
Name = "headless",
},
Policy = new HeadlessBotPolicyDescriptor
{
Id = "idle",
},
Credential = new HeadlessCredentialReference
{
Provider = provider,
Reference = credentialReference,
},
};
private static void HydrateGroundedPlayer(GameRuntime runtime)
{
const uint player = 0x50000002u;
PhysicsEngine engine = runtime.EntityObjects.Physics.Engine;
AddFlatLandblock(engine);
RuntimeEntityRecord record = runtime.EntityObjects
.RegisterEntity(Spawn(player))
.Canonical!;
Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn(
record,
record.CreateIntegrationVersion,
record.Snapshot,
replaceGeneration: false));
var controller = new PlayerMovementController(engine);
controller.SeedPlacementForTest(
new Vector3(96f, 97f, 50f),
0xA9B40001u,
new Vector3(96f, 97f, 50f));
runtime.MovementOwner.Controller = controller;
}
private static void CompleteCollisionGeneration(
RuntimePhysicsState physics,
uint landblockId,
Action<RuntimeCollisionAdmission>? afterAdmission,
Action<RuntimeCollisionAdmission,
PreparedLandblockCollisionGeneration> stage)
{
HeadlessCollisionGenerationTransaction transaction =
HeadlessCollisionGenerationTransaction.Begin(
physics,
landblockId,
afterAdmission,
stage);
while (true)
{
HeadlessCollisionGenerationAdvance advance = transaction.Advance();
if (advance.Completed)
return;
Assert.True(advance.Progressed);
Assert.False(advance.WaitingForProjectionAcknowledgement);
}
}
private static RuntimePlacementProjectionToken SeedRuntimePlacement(
RuntimePhysicsState physics,
RuntimeEntityRecord record,
uint cell,
Vector3 position)
{
Type coreMarker = typeof(PhysicsEngine);
Type requestType = coreMarker.Assembly.GetType(
"AcDream.Core.Physics.PhysicsSetPositionRequest",
throwOnError: true)!;
Type flagsType = coreMarker.Assembly.GetType(
"AcDream.Core.Physics.PhysicsSetPositionFlags",
throwOnError: true)!;
Type placementClassType = coreMarker.Assembly.GetType(
"AcDream.Core.Physics.PhysicsPlacementClass",
throwOnError: true)!;
object request = Activator.CreateInstance(
requestType,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
binder: null,
args:
[
position,
Quaternion.Identity,
cell,
position,
ImmutableArray<FlatCollisionSphere>.Empty,
1f,
0.4f,
0.4f,
PhysicsStateFlags.None,
ObjectInfoState.None,
0u,
Enum.ToObject(placementClassType, 0),
Enum.ToObject(flagsType, 0x011u),
Vector3.Zero,
0f,
0f,
0u,
cell,
],
culture: null)!;
Type runtimeMarker = typeof(RuntimePhysicsState);
Type commandType = runtimeMarker.Assembly.GetType(
"AcDream.Runtime.Physics.RuntimeSetPositionCommand",
throwOnError: true)!;
Type kindType = runtimeMarker.Assembly.GetType(
"AcDream.Runtime.Physics.RuntimeSetPositionOperationKind",
throwOnError: true)!;
object command = Activator.CreateInstance(
commandType,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
binder: null,
args:
[
request,
Enum.ToObject(kindType, 2),
10d,
0UL,
0f,
0f,
default(RuntimePortalPlacementAuthority),
],
culture: null)!;
MethodInfo apply = physics.SetPosition.GetType().GetMethod(
"Apply",
BindingFlags.Instance | BindingFlags.NonPublic)
?? throw new MissingMethodException("Runtime SetPosition.Apply");
object outcome = apply.Invoke(
physics.SetPosition,
[record, record.PositionAuthorityVersion, command])!;
return (RuntimePlacementProjectionToken)(outcome.GetType().GetProperty(
"Projection",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
?.GetValue(outcome)
?? throw new MissingMemberException("Runtime placement projection"));
}
private static RuntimeLandblockCollisionAssets CollisionAssets(
uint landblockId,
float terrainHeight)
{
var heights = new byte[81];
var table = new float[256];
table[0] = terrainHeight;
return new RuntimeLandblockCollisionAssets(
landblockId,
new TerrainSurface(heights, table),
Array.Empty<CellSurface>(),
Array.Empty<PortalPlane>(),
0f,
0f,
0u);
}
private sealed class FixtureCollisionPublicationException : Exception;
[Fact]
public void MissingPreparedCollisionYieldsTypedRetryAndCompletesWhenAvailable()
{
// C3c contract: the prepared-collision read failure is the
// conductor's typed AwaitingCollisionSource retry — no
// InvalidDataException (or any exception) escapes the host wiring,
// the entity stays a tracked, re-drivable first entry, and the same
// sequence completes once the source can serve the Setup.
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations);
GameRuntime runtime = host.Runtime;
Assert.Equal(
RuntimeSessionStartStatus.Connected,
host.Start().Status);
const uint player = 0x50000021u;
runtime.PlayerIdentity.ServerGuid = player;
runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration(
0xA9B40000u, 1UL);
AddFlatLandblock(runtime.EntityObjects.Physics.Engine);
runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration(
0xA9B40000u, 1UL, ready: true);
var source = new FlakySetupCollisionSource();
var firstEntry = new AcDream.Runtime.Session
.RuntimeFirstEntryDriveController(
runtime.EntityObjects,
runtime.Clock,
source,
() => PlayerMovementConstructionOptions.From(
runtime.CharacterOwner.MovementSkills.Snapshot),
static _ => new RuntimeLocalPlayerPhysicsActivationPreparation(
Radius: 0.48f,
Height: 1.835f,
RuntimeLocalPlayerShadowDisposition.ProvenShapeless));
RuntimeEntityRecord record = runtime.EntityObjects
.RegisterEntityWithInitialResidence(Spawn(player), isLocalPlayer: true)
.Canonical!;
Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn(
record,
record.CreateIntegrationVersion,
record.Snapshot,
replaceGeneration: false));
var collision = new FixtureCollisionNeighborhood();
var projection = new HeadlessSessionWorldProjection(
runtime,
collision,
firstEntry);
projection.ProjectSpawn(record, isLocalPlayer: true);
Assert.True(source.SetupReadAttempts >= 1);
Assert.Null(runtime.MovementOwner.Controller);
Assert.Equal(1, firstEntry.PendingCount);
Assert.Equal(0u, record.FullCellId);
source.Available = true;
// The session tick's retry pump.
firstEntry.DriveAll();
Assert.IsType<PlayerMovementController>(
runtime.MovementOwner.Controller);
Assert.Equal(0, firstEntry.PendingCount);
Assert.Equal(0xA9B40000u, record.FullCellId & 0xFFFF0000u);
Assert.NotEqual(0u, record.FullCellId);
}
private sealed class FlakySetupCollisionSource
: AcDream.Content.IPreparedCollisionSource
{
internal bool Available { get; set; }
internal int SetupReadAttempts { get; private set; }
public AcDream.Content.PreparedAssetPresence ProbeCollision(
AcDream.Content.Pak.PakAssetType type,
uint sourceFileId) =>
AcDream.Content.PreparedAssetPresence.Available;
public AcDream.Content.PreparedCollisionReadResult<FlatSetupCollision>
ReadSetupCollision(
uint sourceFileId,
CancellationToken cancellationToken = default)
{
SetupReadAttempts++;
if (!Available)
{
return AcDream.Content.PreparedCollisionReadResult<
FlatSetupCollision>.Missing;
}
return AcDream.Content.PreparedCollisionReadResult<
FlatSetupCollision>.Loaded(new FlatSetupCollision(
System.Collections.Immutable.ImmutableArray<
FlatCollisionCylinder>.Empty,
[new FlatCollisionSphere(Vector3.Zero, 0.48f)],
height: 0f,
radius: 0f,
stepUpHeight: 0.4f,
stepDownHeight: 0.4f));
}
public AcDream.Content.PreparedCollisionReadResult<
FlatGfxObjCollisionAsset> ReadGfxObjCollision(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException();
public AcDream.Content.PreparedCollisionReadResult<
FlatCellStructureCollisionAsset> ReadCellStructureCollision(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException();
public AcDream.Content.PreparedCollisionReadResult<FlatEnvCellTopology>
ReadEnvCellTopology(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException();
public AcDream.Content.PreparedCollisionSourceStats CollisionStats =>
default;
public void Dispose()
{
}
}
private static AcDream.Runtime.Session.RuntimeFirstEntryDriveController
CreateFirstEntryDrive(GameRuntime runtime) => new(
runtime.EntityObjects,
runtime.Clock,
new LoadedSetupCollisionSource(),
() => PlayerMovementConstructionOptions.From(
runtime.CharacterOwner.MovementSkills.Snapshot),
static _ => new RuntimeLocalPlayerPhysicsActivationPreparation(
Radius: 0.48f,
Height: 1.835f,
RuntimeLocalPlayerShadowDisposition.ProvenShapeless));
/// <summary>
/// C4 route 2 (2026-08-03): mirrors <see cref="CreateFirstEntryDrive"/>'s
/// construction pattern for the accepted-Position drive controller. No
/// real WorldSession is needed for these fixture tests —
/// LocalPlayerOutboundController.SendImmediatePosition no-ops on a null
/// session.
/// </summary>
private static RuntimeAcceptedPositionDriveController
CreateAcceptedPositionDrive(GameRuntime runtime) => new(
runtime.EntityObjects,
runtime.Clock,
new LoadedSetupCollisionSource(),
new LocalPlayerOutboundController((_, _, _, _, _, _) => { }),
() => runtime.Generation,
() => runtime.PlayerIdentity.ServerGuid,
() => runtime.MovementOwner.Controller,
() => runtime.CharacterOwner.UsePositionFromServer,
() => null);
private sealed class LoadedSetupCollisionSource
: AcDream.Content.IPreparedCollisionSource
{
public AcDream.Content.PreparedAssetPresence ProbeCollision(
AcDream.Content.Pak.PakAssetType type,
uint sourceFileId) =>
AcDream.Content.PreparedAssetPresence.Available;
public AcDream.Content.PreparedCollisionReadResult<FlatSetupCollision>
ReadSetupCollision(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
AcDream.Content.PreparedCollisionReadResult<FlatSetupCollision>
.Loaded(new FlatSetupCollision(
System.Collections.Immutable.ImmutableArray<
FlatCollisionCylinder>.Empty,
// R7 review fix (2026-08-03): the dat-exact human Setup
// 0x02000001 spheres (Ts46SphereListConformanceTests.cs
// :35-39) — foot (0,0,0.475) r=.48, head/torso
// (0,0,1.350) r=.48. The PREVIOUS single dummy sphere at
// (0,0,0) r=.48 (offset == radius) made a settled origin
// rest a FULL radius above the floor; the real foot
// sphere's bottom is origin + 0.475 0.48 = origin
// 0.005, so a settled origin lands ON the floor within
// 5 mm. Retail's BlipPlayer has never lifted the origin
// by a sphere radius — that was a fixture artifact, not
// a retail-fidelity gain (docs/ISSUES.md #285 correction).
[
new FlatCollisionSphere(
new Vector3(0f, 0f, 0.475f), 0.48f),
new FlatCollisionSphere(
new Vector3(0f, 0f, 1.350f), 0.48f),
],
height: 0f,
radius: 0f,
stepUpHeight: 0.4f,
stepDownHeight: 0.4f));
public AcDream.Content.PreparedCollisionReadResult<
FlatGfxObjCollisionAsset> ReadGfxObjCollision(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException();
public AcDream.Content.PreparedCollisionReadResult<
FlatCellStructureCollisionAsset> ReadCellStructureCollision(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException();
public AcDream.Content.PreparedCollisionReadResult<FlatEnvCellTopology>
ReadEnvCellTopology(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException();
public AcDream.Content.PreparedCollisionSourceStats CollisionStats =>
default;
public void Dispose()
{
}
}
/// <summary>
/// D1 (C5b architecture review), the local-player FORCE arm — both
/// directions of the reachability rule, driven through the real
/// <see cref="RuntimeLiveEntitySessionController"/> sink.
///
/// <para>
/// The graphical <c>OnPosition</c> route returns ahead of AD-60's W2
/// for every force status EXCEPT <c>NotApplicable</c>, and falls
/// through to it on that one. So: a force the drive HANDLED
/// (<c>Committed</c> here) is placement-receipt-authoritative and this
/// route must leave residency at the cell the placement RESOLVED, not
/// re-stamp the wire cell over it; a force the drive did NOT handle
/// (no drive at all — the login-window / route-1 shape) must still
/// refresh the cell, because nothing else will.
/// </para>
/// </summary>
[Theory]
[InlineData(true)]
[InlineData(false)]
public void LocalForcePosition_CommitsTheWireCellOnlyWhenTheDriveDeclined(
bool driveHandlesIt)
{
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations);
GameRuntime runtime = host.Runtime;
Assert.Equal(
RuntimeSessionStartStatus.Connected,
host.Start().Status);
const uint player = 0x50000004u;
runtime.PlayerIdentity.ServerGuid = player;
runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration(
0xA9B40000u, 1UL);
AddFlatLandblock(runtime.EntityObjects.Physics.Engine);
runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration(
0xA9B40000u, 1UL, ready: true);
RuntimeFirstEntryDriveController firstEntry =
CreateFirstEntryDrive(runtime);
RuntimeEntityRecord record = runtime.EntityObjects
.RegisterEntityWithInitialResidence(
Spawn(player),
isLocalPlayer: true)
.Canonical!;
Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn(
record,
record.CreateIntegrationVersion,
record.Snapshot,
replaceGeneration: false));
var collision = new FixtureCollisionNeighborhood();
var projection = new HeadlessSessionWorldProjection(
runtime,
collision,
firstEntry);
projection.ProjectSpawn(record, isLocalPlayer: true);
PlayerMovementController controller =
Assert.IsType<PlayerMovementController>(
runtime.MovementOwner.Controller);
controller.SeedPlacementForTest(
new Vector3(48f, 49f, 50f),
0xA9B40001u,
new Vector3(48f, 49f, 50f));
// The residence must be closed before the ordinary post-residence
// rules apply at all (the conductor is the sole cell authority
// while it is open — asserted separately in the Runtime suite).
Assert.False(runtime.EntityObjects.TryGetInitialCreateResidence(
record,
out _));
using var session = new WorldSession(
new IPEndPoint(IPAddress.Loopback, 9000));
var entities = new RuntimeLiveEntitySessionController(
runtime,
session,
log: null,
projection,
driveHandlesIt ? CreateAcceptedPositionDrive(runtime) : null);
LiveEntitySessionSink sink = entities.CreateSink();
// A wire cell that is NOT the cell a placement at (72,73) resolves
// to, so "committed the wire cell" and "kept the placement's own
// resolved cell" are distinguishable values rather than the same
// number arrived at two ways.
const uint wireCell = 0xA9B40002u;
sink.PositionUpdated(new WorldSession.EntityPositionUpdate(
player,
new CreateObject.ServerPosition(
wireCell, 72f, 73f, 50f, 1f, 0f, 0f, 0f),
Velocity: null,
PlacementId: null,
IsGrounded: true,
InstanceSequence: 1,
PositionSequence: 2,
TeleportSequence: 0,
ForcePositionSequence: 1));
if (driveHandlesIt)
{
// The placement committed and resolved its OWN cell — measured
// 0xA9B4001C, the outdoor landcell that actually contains
// (72, 73) in this flat landblock, which is neither the wire
// cell nor the spawn cell. This route added nothing on top of
// it.
Assert.Equal(new Vector3(72f, 73f, 50.005f), controller.Position);
Assert.Equal(0xA9B4001Cu, record.FullCellId);
Assert.NotEqual(wireCell, record.FullCellId);
}
else
{
Assert.Equal(wireCell, record.FullCellId);
Assert.Equal(0xA9B4FFFFu, record.CanonicalLandblockId);
}
}
private static void AddFlatLandblock(PhysicsEngine engine)
{
var heights = new byte[81];
Array.Fill(heights, (byte)50);
var heightTable = new float[256];
for (int index = 0; index < heightTable.Length; index++)
heightTable[index] = index;
engine.AddLandblock(
0xA9B4FFFFu,
new TerrainSurface(heights, heightTable),
[],
[],
worldOffsetX: 0f,
worldOffsetY: 0f);
}
private static WorldSession.EntitySpawn Spawn(
uint guid,
uint cellId = 0xA9B40001u)
{
var position = new CreateObject.ServerPosition(
cellId,
96f,
97f,
50f,
1f,
0f,
0f,
0f);
var timestamps = new PhysicsTimestamps(
Position: 1,
Movement: 1,
State: 1,
Vector: 1,
Teleport: 0,
ServerControlledMove: 1,
ForcePosition: 0,
ObjDesc: 1,
Instance: 1);
var physics = new PhysicsSpawnData(
RawState: (uint)PhysicsStateFlags.ReportCollisions,
Position: position,
Movement: null,
AnimationFrame: null,
SetupTableId: 0x02000001u,
MotionTableId: null,
SoundTableId: null,
PhysicsScriptTableId: null,
Parent: null,
Children: null,
Scale: null,
Friction: null,
Elasticity: null,
Translucency: null,
Velocity: null,
Acceleration: null,
AngularVelocity: null,
DefaultScriptType: null,
DefaultScriptIntensity: null,
Timestamps: timestamps);
return new WorldSession.EntitySpawn(
guid,
position,
0x02000001u,
[],
[],
[],
null,
null,
"Headless",
null,
null,
null,
PhysicsState: physics.RawState,
InstanceSequence: 1,
MovementSequence: 1,
ServerControlSequence: 1,
PositionSequence: 1,
Physics: physics);
}
private static RuntimePlacementProjectionSnapshot Placement(
GameRuntime runtime,
RuntimeEntityRecord record,
RuntimePlacementProjectionKind kind,
Vector3 position,
Quaternion orientation)
{
RuntimeEntityKey key = Assert.IsType<RuntimeEntityKey>(record.Key);
var token = new RuntimePlacementProjectionToken(
Sequence: 1,
Revision: 1,
Entity: key,
PositionAuthorityVersion: record.PositionAuthorityVersion,
SpatialAuthorityVersion: record.SpatialAuthorityVersion,
PlacementCommitVersion: record.PlacementCommitVersion,
SessionLifetimeVersion:
runtime.EntityObjects.Entities.SessionLifetimeVersion,
ExactCellId: record.FullCellId,
CollisionGeneration: 1,
Portal: default);
return new RuntimePlacementProjectionSnapshot(
token,
kind,
position,
orientation,
CellLocalPosition: position,
InContact: false,
OnWalkable: false);
}
private static uint ActionOpcode(byte[] body) =>
BinaryPrimitives.ReadUInt32LittleEndian(
body.AsSpan(8, sizeof(uint)));
private sealed class FixtureSessionOperations : ILiveSessionOperations
{
public List<WorldSession> Sessions { get; } = [];
public int CreatedSessionCount { get; private set; }
public int DisposedSessionCount { get; private set; }
public string? LastUser { get; private set; }
public string? LastPassword { get; private set; }
public IPEndPoint ResolveEndpoint(string host, int port) =>
new(IPAddress.Loopback, port);
public WorldSession CreateSession(IPEndPoint endpoint)
{
CreatedSessionCount++;
var session = new WorldSession(endpoint);
Sessions.Add(session);
return session;
}
public void Connect(
WorldSession session,
string user,
string password)
{
LastUser = user;
LastPassword = password;
}
public CharacterList.Parsed GetCharacters(
WorldSession session) =>
new(
0u,
[
new CharacterList.Character(
0x50000001u,
"Other",
0u),
new CharacterList.Character(
0x50000002u,
"Headless",
0u),
],
[],
11,
"account",
true,
true);
public void EnterWorld(
WorldSession session,
int activeCharacterIndex)
{
}
public void Tick(WorldSession session)
{
}
public void DisposeSession(WorldSession session)
{
DisposedSessionCount++;
session.Dispose();
}
}
private sealed class FailOnceTextWriter : StringWriter
{
public bool FailNextWrite { get; set; }
public override void WriteLine(string? value)
{
if (FailNextWrite)
{
FailNextWrite = false;
throw new IOException("fixture write failure");
}
base.WriteLine(value);
}
}
private sealed class FixtureCollisionNeighborhood
: IHeadlessCollisionNeighborhood
{
public int CenterCount { get; private set; }
public uint LastCell { get; private set; }
public void CenterOn(uint fullCellId)
{
CenterCount++;
LastCell = fullCellId;
}
public bool IsReady(uint fullCellId) =>
fullCellId == LastCell;
// C3c-R1 review F7: the fixture window mirrors production's 3x3
// membership around the last requested center; no center yet means
// "within" (never convert before the first CenterOn).
public bool IsWithinServiceWindow(uint fullCellId)
{
if (LastCell == 0u)
return true;
int dx = Math.Abs(
(int)((fullCellId >> 24) & 0xFFu)
- (int)((LastCell >> 24) & 0xFFu));
int dy = Math.Abs(
(int)((fullCellId >> 16) & 0xFFu)
- (int)((LastCell >> 16) & 0xFFu));
return dx <= 1 && dy <= 1;
}
}
private sealed class FixtureEventRoute(
Action? onDispose = null) : ILiveSessionEventRouting
{
public int AttachCount { get; private set; }
public int DisposeCount { get; private set; }
public int DisposeFailuresRemaining { get; set; }
public void Attach() => AttachCount++;
public void Dispose()
{
DisposeCount++;
onDispose?.Invoke();
if (DisposeFailuresRemaining > 0)
{
DisposeFailuresRemaining--;
throw new IOException("fixture route detach failure");
}
}
}
}