Member-wise deletion of the three legacy resolver members named in docs/research/2026-08-05-c5a-contract.md: PhysicsEngine.Resolve, PhysicsEngine.HasCellSurface, and PhysicsEngine.ResolvePlacement. An exhaustive receiver census over src/ found zero production callers of any of the three — every production placement writer already reaches the canonical PhysicsEngine.SetPosition transaction exclusively through RuntimeSetPositionState (three call sites total). The deletion is purely member-wise: IsSpawnCellReady and AdjustPosition, which shared the same source region as the deleted members, are preserved byte-identical — every remaining production caller of either (including PhysicsCameraCollisionProbe, AdjustPosition's sole surviving production caller) is unaffected. Companion changes: - PlayerMovementController's 3-argument SetPosition test overload is renamed to SeedPlacementForTest (internal) and CommitPreparedPosition is deleted; 83 call sites across 19 test files were mechanically renamed to match. - Seven pinned test dispositions from the contract are executed: 3.1 (PhysicsEngineTests.cs: 11 legacy-resolver tests deleted, 6 ResolveWithTransition tests kept), 3.2/3.3/3.4 (re-point to canonical SetPosition, with TransitionScratchDifferentialTests.cs additionally gaining positive IsCommitted assertions after each bitwise comparison so the differential proves a placement actually committed, not just that two possibly-uncommitted results match), 3.5 (Runtime rename), and 3.6 (PlayerMovementPlacementTransactionTests.cs rewritten — its xmldoc now states plainly that the render-root publish moved to RuntimeSetPositionState.cs, but the sticky-release relocation claim was false and is retracted; this disposition's coverage loss is the sticky release path, not silently absorbed elsewhere). - Stale `PhysicsEngine.Resolve`/`Resolve` doc citations in CellTransit.cs, PlayerMovementController.cs, and HeadlessSessionWorldProjection.cs are corrected to name the surviving canonical entry points by symbol (SetPosition, AdjustSetPosition/AdjustPosition, ResolveWithTransition) rather than fragile line numbers. Retires AP-1 and AD-1 in docs/architecture/retail-divergence-register.md: both rows described production zero-delta placement routing remaining on the legacy resolver pending the Slice 4B2/4B route cutover; that resolver no longer exists, so the condition each row tracked is now structurally false rather than merely narrowed. AP-145 (routed through the prior commit) and this commit's AP-1/AD-1 together bring the section counts to 101 AP / 47 AD active rows. Builds on the AP-145 fix (previous commit) — this commit's staged tree was independently rebuilt and its four suites independently rerun on top of that commit before this commit was created, in addition to the combined rebuild/rerun below. Full-solution build: 0 errors (21 pre-existing warnings, all unrelated). Suite results (combined tree): Core 4270/4271 passed (1 skip; the single DatSoundCacheTests concurrent-decode-dedup failure is a known load-sensitive race, confirmed passing standalone and unrelated to this change), Runtime 1176/1176, Headless 86/86, App 4132/4135 (3 skips). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
147 lines
6.8 KiB
C#
147 lines
6.8 KiB
C#
using System.Numerics;
|
|
using AcDream.App.Input;
|
|
using AcDream.App.Physics;
|
|
using AcDream.Core.Physics;
|
|
using AcDream.Core.Physics.Motion;
|
|
using DatReaderWriter.Types;
|
|
using DatEnvCell = DatReaderWriter.DBObjs.EnvCell;
|
|
|
|
namespace AcDream.App.Tests.Input;
|
|
|
|
public sealed class PlayerMovementPlacementTransactionTests
|
|
{
|
|
private const uint PlayerGuid = 0x5000_0001u;
|
|
private const uint TargetGuid = 0x7000_0001u;
|
|
private const uint PriorCell = 0x0101_0101u;
|
|
private const uint DestinationCell = 0x0101_0102u;
|
|
|
|
/// <summary>
|
|
/// C5a (2026-08-05) re-point: <c>PlayerMovementController.CommitPreparedPosition</c>
|
|
/// is deleted (zero production callers — production placement now arms
|
|
/// the login-entry leash via <c>ArmConstraintLeashAtCommittedPlacement</c>,
|
|
/// called from <c>RuntimeLocalPlayerPhysicsPublicationState.ArmFirstEntryConstraintLeash</c>
|
|
/// AFTER <c>RuntimeSetPositionState.TryApplyDormantLocalActivationFinalCommit</c>
|
|
/// has already published the shared current cell — see that method's
|
|
/// doc comment). Architecture-review correction (A5, 2026-08-05): the two
|
|
/// deleted invariants did NOT relocate symmetrically.
|
|
/// <b>Render-root publish DID move</b> — it happens inside
|
|
/// <c>RuntimeSetPositionState.TryApplyDormantLocalActivationFinalCommit</c>
|
|
/// itself, via <c>_physics.Engine.UpdatePlayerCurrCell(result.CellId)</c>
|
|
/// (<c>RuntimeSetPositionState.cs:2774</c>).
|
|
/// <b>Sticky-target release did NOT move anywhere</b> — <c>grep -rn
|
|
/// "UnStick" src/AcDream.Runtime/</c> returns zero call sites on this
|
|
/// path; the only local-player <c>UnStick</c> left is
|
|
/// <c>PlayerMovementController.SetPositionCore</c>'s
|
|
/// <c>if (publishSharedState) PositionManager?.UnStick();</c>, and
|
|
/// <c>PreparePositionForCommit</c> passes <c>publishSharedState: false</c>.
|
|
/// The unstick-at-first-entry-commit behaviour <c>CommitPreparedPosition</c>
|
|
/// used to perform is simply gone — it was dead code (the method had zero
|
|
/// production callers before this slice deleted it), so nothing regresses
|
|
/// today, but no layer pins the invariant "first-entry commit releases any
|
|
/// sticky target" any more. That is disposition 3.6's one real coverage
|
|
/// loss (see the C5a commit message).
|
|
/// This App-layer fixture (a bare <see cref="PlayerMovementController"/> +
|
|
/// <see cref="EntityPhysicsHost"/> pair, no Runtime activation pipeline)
|
|
/// cannot drive the Runtime final-commit transaction without
|
|
/// disproportionate fixture growth — recorded here as the explicit C5a
|
|
/// scope decision rather than silently dropped.
|
|
/// What THIS test still proves, unchanged: <see cref="PlayerMovementController.PreparePositionForCommit"/>
|
|
/// defers the render-root publish exactly as before (<c>controller.CellId</c>
|
|
/// updates immediately; <c>physics.DataCache.CellGraph.CurrCell</c> and
|
|
/// the sticky target stay untouched), and the surviving production
|
|
/// commit-arm, <see cref="PlayerMovementController.ArmConstraintLeashAtCommittedPlacement"/>,
|
|
/// arms the constraint leash without independently touching either —
|
|
/// neither the render-root publish (a DIFFERENT layer's job now) nor the
|
|
/// sticky release (nobody's job any more).
|
|
/// </summary>
|
|
[Fact]
|
|
public void PreparedPosition_DefersRenderRootPublish_CommitArmsLeashOnly()
|
|
{
|
|
PhysicsEngine physics = PhysicsWithCells(PriorCell, DestinationCell);
|
|
physics.UpdatePlayerCurrCell(PriorCell);
|
|
var controller = new PlayerMovementController(physics);
|
|
var hosts = new Dictionary<uint, IPhysicsObjHost>();
|
|
IPhysicsObjHost? Resolve(uint id) => hosts.GetValueOrDefault(id);
|
|
EntityPhysicsHost player = Host(PlayerGuid, Resolve);
|
|
EntityPhysicsHost target = Host(TargetGuid, Resolve);
|
|
hosts.Add(PlayerGuid, player);
|
|
hosts.Add(TargetGuid, target);
|
|
controller.PositionManager = player.PositionManager;
|
|
player.PositionManager.StickTo(TargetGuid, 0.5f, 1.8f);
|
|
|
|
controller.PreparePositionForCommit(
|
|
new Vector3(2f, 3f, 4f),
|
|
DestinationCell,
|
|
new Vector3(2f, 3f, 4f));
|
|
|
|
Assert.Equal(DestinationCell, controller.CellId);
|
|
Assert.Equal(PriorCell, physics.DataCache!.CellGraph.CurrCell!.Id);
|
|
Assert.Equal(TargetGuid, player.PositionManager.GetStickyObjectId());
|
|
Assert.False(player.PositionManager.IsFullyConstrained());
|
|
Assert.Null(player.PositionManager.Constraint);
|
|
|
|
controller.ArmConstraintLeashAtCommittedPlacement();
|
|
|
|
// The leash is armed...
|
|
Assert.NotNull(player.PositionManager.Constraint);
|
|
Assert.True(player.PositionManager.Constraint!.IsConstrained);
|
|
// ...but the surviving App-layer commit does NOT publish the render
|
|
// root or release the sticky target — those now happen inside the
|
|
// Runtime dormant-activation final commit, before this method runs.
|
|
Assert.Equal(PriorCell, physics.DataCache.CellGraph.CurrCell!.Id);
|
|
Assert.Equal(TargetGuid, player.PositionManager.GetStickyObjectId());
|
|
}
|
|
|
|
private static PhysicsEngine PhysicsWithCells(params uint[] cellIds)
|
|
{
|
|
var physics = new PhysicsEngine { DataCache = new PhysicsDataCache() };
|
|
foreach (uint cellId in cellIds)
|
|
{
|
|
var cellStruct = new CellStruct
|
|
{
|
|
VertexArray = new VertexArray
|
|
{
|
|
Vertices = new Dictionary<ushort, SWVertex>(),
|
|
},
|
|
Polygons = new Dictionary<ushort, Polygon>(),
|
|
CellBSP = new CellBSPTree
|
|
{
|
|
Root = new CellBSPNode
|
|
{
|
|
Type = DatReaderWriter.Enums.BSPNodeType.Leaf,
|
|
},
|
|
},
|
|
};
|
|
var envCell = new DatEnvCell
|
|
{
|
|
CellPortals = [],
|
|
VisibleCells = [],
|
|
};
|
|
physics.DataCache.CacheCellStruct(
|
|
cellId,
|
|
envCell,
|
|
cellStruct,
|
|
Matrix4x4.Identity);
|
|
}
|
|
return physics;
|
|
}
|
|
|
|
private static EntityPhysicsHost Host(
|
|
uint guid,
|
|
Func<uint, IPhysicsObjHost?> resolve) =>
|
|
new(
|
|
guid,
|
|
getPosition: () => new AcDream.Core.Physics.Position(
|
|
PriorCell,
|
|
Vector3.Zero,
|
|
Quaternion.Identity),
|
|
getVelocity: () => Vector3.Zero,
|
|
getRadius: () => 0.5f,
|
|
inContact: () => true,
|
|
minterpMaxSpeed: () => 1f,
|
|
curTime: () => 0d,
|
|
physicsTimerTime: () => 0d,
|
|
getObjectA: resolve,
|
|
handleUpdateTarget: _ => { },
|
|
interruptCurrentMovement: () => { });
|
|
}
|