refactor(physics): delete legacy PhysicsEngine.Resolve/ResolvePlacement/HasCellSurface (C5a, AP-1/AD-1)

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>
This commit is contained in:
Erik 2026-08-05 14:11:31 +02:00
parent f8e55ba5e4
commit 6921a02744
34 changed files with 508 additions and 946 deletions

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Numerics;
using System.Reflection;
using AcDream.Core.Physics;
@ -176,57 +177,95 @@ public sealed class TransitionScratchDifferentialTests
Coverage: CoverageKind.Failure));
}
/// <summary>
/// C5a (2026-08-05) re-point: the legacy <c>PhysicsEngine.ResolvePlacement</c>
/// this arm originally drove is deleted (zero production callers).
/// Canonical <c>PhysicsEngine.SetPosition</c> reaches the exact same
/// <c>FindPlacementPos</c> ring search — <c>SetPositionInternal</c> sets
/// <c>InsertType.Placement</c> before <c>Transition.FindValidPosition</c>,
/// which dispatches to <c>FindPlacementPosition</c>, which calls
/// <c>FindPlacementPos</c> — so this is the same scratch-reuse surface
/// under a new entry point, not a new code path. The bitwise
/// fresh-vs-reused comparison and the second-identity leak check are both
/// preserved verbatim, now via <see cref="AssertSetPositionBitwise"/>
/// over <see cref="PhysicsSetPositionResult"/>.
/// </summary>
[Fact]
public void ReusedScratch_MatchesFreshPlacementSearch()
{
PhysicsEngine fresh = BuildPlacementEngine(reuse: false);
PhysicsEngine reused = BuildPlacementEngine(reuse: true);
ResolveResult expected = fresh.ResolvePlacement(
new Vector3(10f, 10f, 0f),
Cell,
0.48f,
1.835f,
0.40f,
0.40f,
ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide,
0x50000101u);
ResolveResult actual = reused.ResolvePlacement(
new Vector3(10f, 10f, 0f),
Cell,
0.48f,
1.835f,
0.40f,
0.40f,
ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide,
0x50000101u);
PhysicsSetPositionResult expected = fresh.SetPosition(
PlacementRequest(
new Vector3(10f, 10f, 0f),
ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide,
0x50000101u));
PhysicsSetPositionResult actual = reused.SetPosition(
PlacementRequest(
new Vector3(10f, 10f, 0f),
ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide,
0x50000101u));
AssertResolveBitwise(expected, actual, "placement");
AssertSetPositionBitwise(expected, actual, "placement");
// A6 (architecture review, 2026-08-05): the bitwise comparison alone
// proves fresh and reused AGREE, not that either actually placed
// anything — a future regression that makes SetPosition fail
// IDENTICALLY on both engines would leave this differential green
// while the scratch-reuse surface it exists to guard goes
// unexercised. Assert the positive fact the comparison itself can't:
// this placement committed.
Assert.True(expected.IsCommitted, "fresh engine did not commit the placement");
Assert.True(actual.IsCommitted, "reused engine did not commit the placement");
// A second placement with a different self identity proves that the
// previous mover and collision-GUID list cannot leak through the lease.
expected = fresh.ResolvePlacement(
new Vector3(11f, 10f, 0f),
Cell,
0.48f,
1.835f,
0.40f,
0.40f,
ObjectInfoState.EdgeSlide,
0x80000102u);
actual = reused.ResolvePlacement(
new Vector3(11f, 10f, 0f),
Cell,
0.48f,
1.835f,
0.40f,
0.40f,
ObjectInfoState.EdgeSlide,
0x80000102u);
expected = fresh.SetPosition(
PlacementRequest(
new Vector3(11f, 10f, 0f),
ObjectInfoState.EdgeSlide,
0x80000102u));
actual = reused.SetPosition(
PlacementRequest(
new Vector3(11f, 10f, 0f),
ObjectInfoState.EdgeSlide,
0x80000102u));
AssertResolveBitwise(expected, actual, "placement after hostile identity");
AssertSetPositionBitwise(expected, actual, "placement after hostile identity");
Assert.True(
expected.IsCommitted,
"fresh engine did not commit the second placement");
Assert.True(
actual.IsCommitted,
"reused engine did not commit the second placement");
}
/// <summary>
/// Builds the two-sphere capsule request the legacy scalar
/// <c>InitPath(sphereRadius: 0.48, sphereHeight: 1.835)</c> reconstructed
/// internally (origin (0,0,radius) + (0,0,height-radius), both
/// radius-sized), so canonical <c>SetPosition</c> sees the identical
/// mover shape the legacy <c>ResolvePlacement</c> call did.
/// </summary>
private static PhysicsSetPositionRequest PlacementRequest(
Vector3 position,
ObjectInfoState moverFlags,
uint movingEntityId) => new(
Position: position,
Orientation: Quaternion.Identity,
CellId: Cell,
CellLocalPosition: position,
Spheres: ImmutableArray.Create(
new FlatCollisionSphere(new Vector3(0f, 0f, 0.48f), 0.48f),
new FlatCollisionSphere(new Vector3(0f, 0f, 1.835f - 0.48f), 0.48f)),
Scale: 1f,
StepUpHeight: 0.40f,
StepDownHeight: 0.40f,
MoverFlags: moverFlags,
MovingEntityId: movingEntityId,
Flags: PhysicsSetPositionFlags.Placement
| PhysicsSetPositionFlags.Slide);
private static void RunSequence(
Func<bool, PhysicsEngine> buildEngine,
params ResolveSpec[] specs)
@ -441,6 +480,66 @@ public sealed class TransitionScratchDifferentialTests
Assert.Equal(expected.ContactPlaneIsWater, actual.ContactPlaneIsWater);
}
/// <summary>
/// Bitwise sibling of <see cref="AssertResolveBitwise"/> for
/// <see cref="PhysicsSetPositionResult"/>, used by the re-pointed
/// <see cref="ReusedScratch_MatchesFreshPlacementSearch"/> (C5a). Every
/// field is compared — float-bearing members bitwise (a stale scratch
/// leaking a different -0.0/+0.0 sign bit would otherwise pass a
/// value-equality check), enum/bool/id members by value, and the
/// immutable id arrays by ordered sequence.
/// </summary>
private static void AssertSetPositionBitwise(
PhysicsSetPositionResult expected,
PhysicsSetPositionResult actual,
string context)
{
Assert.Equal(expected.Error, actual.Error);
Assert.Equal(expected.Residence, actual.Residence);
AssertVectorBitwise(expected.Position, actual.Position, context);
AssertQuaternionBitwise(expected.Orientation, actual.Orientation, context);
Assert.Equal(expected.CellId, actual.CellId);
AssertVectorBitwise(
expected.CellLocalPosition,
actual.CellLocalPosition,
$"{context}.CellLocalPosition");
Assert.Equal(expected.InContact, actual.InContact);
Assert.Equal(expected.OnWalkable, actual.OnWalkable);
AssertPlaneBitwise(expected.ContactPlane, actual.ContactPlane, context);
Assert.Equal(expected.ContactPlaneCellId, actual.ContactPlaneCellId);
Assert.Equal(expected.ContactPlaneIsWater, actual.ContactPlaneIsWater);
Assert.Equal(expected.SlidingNormalValid, actual.SlidingNormalValid);
AssertVectorBitwise(
expected.SlidingNormal,
actual.SlidingNormal,
$"{context}.SlidingNormal");
Assert.Equal(expected.CollisionNormalValid, actual.CollisionNormalValid);
AssertVectorBitwise(
expected.CollisionNormal,
actual.CollisionNormal,
$"{context}.CollisionNormal");
Assert.Equal(expected.FramesStationaryFall, actual.FramesStationaryFall);
Assert.Equal(expected.CollidedWithEnvironment, actual.CollidedWithEnvironment);
Assert.Equal(expected.CollisionHandlerResult, actual.CollisionHandlerResult);
Assert.Equal(expected.CellChanged, actual.CellChanged);
Assert.Equal(expected.ShadowAction, actual.ShadowAction);
// ImmutableArray<T>.Equals(ImmutableArray<T>) compares the BACKING
// ARRAY REFERENCE, not the elements — the fresh and reused engines
// never share a backing array even when the contents match, so
// Assert.Equal on the bare ImmutableArray would false-fail. Compare
// as plain arrays (regular array Equals/sequence comparison) instead.
Assert.Equal(ToArrayOrEmpty(expected.CrossCellIds), ToArrayOrEmpty(actual.CrossCellIds));
Assert.Equal(
ToArrayOrEmpty(expected.CollidedObjectIds),
ToArrayOrEmpty(actual.CollidedObjectIds));
Assert.Equal(
ToArrayOrEmpty(expected.QueriedCellIds),
ToArrayOrEmpty(actual.QueriedCellIds));
}
private static uint[] ToArrayOrEmpty(ImmutableArray<uint> array) =>
array.IsDefault ? Array.Empty<uint>() : array.ToArray();
private static void AssertCoverage(ResolveSpec spec, ResolveResult result)
{
switch (spec.Coverage)