feat(physics): A4 — Transition.CheckOtherCells + ApplyOtherCellResult
Port of retail's CTransition::check_other_cells at
acclient_2013_pseudo_c.txt:272717-272798. Iterates every non-primary
cell in a candidate set, runs BSPQuery.FindCollisions per cell with
that cell's WorldTransform-derived rotation + origin, halts on first
Collided/Adjusted/Slid.
ApplyOtherCellResult is the combine-semantics helper extracted for
unit testability — it pins the retail switch:
- Collided/Adjusted → CollidedWithEnvironment = true (gated on
!Contact), halt.
- Slid → ContactPlaneValid + ContactPlaneIsWater = false,
halt.
- OK → continue.
Not yet wired into FindEnvCollisions — see next commit. Probe gated
on PhysicsDiagnostics.ProbeIndoorBspEnabled (ACDREAM_PROBE_INDOOR_BSP).
Six new unit tests: five against the pure combine helper for each halt
case + one direct CheckOtherCells call exercising the null-BSP guard.
Spec: docs/superpowers/specs/2026-05-20-phase-a4-multi-cell-bsp-design.md
Plan: docs/superpowers/plans/2026-05-20-phase-a4-multi-cell-bsp.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e6369e266f
commit
493c5e5ff6
2 changed files with 257 additions and 0 deletions
|
|
@ -1387,6 +1387,121 @@ public sealed class Transition
|
|||
/// Ported from pseudocode section 4 (LandCell.FindEnvCollisions + ValidateWalkable).
|
||||
/// ACE: LandCell.FindEnvCollisions / ObjectInfo.ValidateWalkable.
|
||||
/// </summary>
|
||||
|
||||
/// <summary>
|
||||
/// Phase A4 (2026-05-20). Port of retail's
|
||||
/// <c>CTransition::check_other_cells</c> at
|
||||
/// <c>acclient_2013_pseudo_c.txt:272717-272798</c>.
|
||||
///
|
||||
/// <para>
|
||||
/// After the primary cell's BSP collision returns OK, iterate every
|
||||
/// other cell in the sphere's overlap set and run BSP collision
|
||||
/// against each. Halt on the first Collided/Adjusted/Slid; OK
|
||||
/// continues. Mirrors retail's behaviour exactly — no save/restore
|
||||
/// of <see cref="Transition"/> state between cells.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal TransitionState CheckOtherCells(
|
||||
PhysicsEngine engine,
|
||||
Vector3 footCenter,
|
||||
float sphereRadius,
|
||||
System.Collections.Generic.IReadOnlyCollection<uint> cellSet)
|
||||
{
|
||||
if (engine.DataCache is null) return TransitionState.OK;
|
||||
var sp = SpherePath;
|
||||
|
||||
// Deterministic order for greppable probe logs. Skip the primary
|
||||
// cell — caller has already run its BSP.
|
||||
var ordered = new System.Collections.Generic.List<uint>(cellSet);
|
||||
ordered.Sort();
|
||||
|
||||
foreach (uint cellId in ordered)
|
||||
{
|
||||
if (cellId == sp.CheckCellId) continue;
|
||||
|
||||
var cell = engine.DataCache.GetCellStruct(cellId);
|
||||
// R2 guard: stale CellPhysics loaded for render but not physics.
|
||||
if (cell?.BSP?.Root is null) continue;
|
||||
|
||||
// Transform sphere into THIS cell's local space. Mirrors the
|
||||
// primary-cell pattern at TransitionTypes.cs (FindEnvCollisions,
|
||||
// ~line 1413) AND the Bug B world-origin fix that decomposes
|
||||
// WorldTransform per cell so BSP Path-3 + Path-4 land write
|
||||
// world-space ContactPlanes.
|
||||
var localCenter = Vector3.Transform(footCenter, cell.InverseWorldTransform);
|
||||
var localCurrCenter = Vector3.Transform(sp.GlobalCurrCenter[0].Origin, cell.InverseWorldTransform);
|
||||
|
||||
var localSphere = new DatReaderWriter.Types.Sphere
|
||||
{
|
||||
Origin = localCenter,
|
||||
Radius = sphereRadius,
|
||||
};
|
||||
DatReaderWriter.Types.Sphere? localSphere1 = null;
|
||||
if (sp.NumSphere > 1)
|
||||
{
|
||||
localSphere1 = new DatReaderWriter.Types.Sphere
|
||||
{
|
||||
Origin = Vector3.Transform(sp.GlobalSphere[1].Origin, cell.InverseWorldTransform),
|
||||
Radius = sp.GlobalSphere[1].Radius,
|
||||
};
|
||||
}
|
||||
|
||||
System.Numerics.Quaternion cellRotation;
|
||||
Vector3 cellOrigin;
|
||||
if (!System.Numerics.Matrix4x4.Decompose(cell.WorldTransform, out _,
|
||||
out cellRotation, out cellOrigin))
|
||||
{
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[other-cells] WARN cell 0x{cellId:X8} WorldTransform did not decompose — falling back to identity rotation"));
|
||||
cellRotation = System.Numerics.Quaternion.Identity;
|
||||
cellOrigin = cell.WorldTransform.Translation;
|
||||
}
|
||||
|
||||
var result = BSPQuery.FindCollisions(
|
||||
cell.BSP.Root, cell.Resolved, this,
|
||||
localSphere, localSphere1, localCurrCenter,
|
||||
Vector3.UnitZ, 1.0f, cellRotation, engine,
|
||||
worldOrigin: cellOrigin);
|
||||
|
||||
if (PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||
{
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[other-cells] primary=0x{sp.CheckCellId:X8} iter=0x{cellId:X8} result={result}"));
|
||||
}
|
||||
|
||||
if (ApplyOtherCellResult(result, out var halted))
|
||||
return halted;
|
||||
}
|
||||
|
||||
return TransitionState.OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase A4 (2026-05-20). Combine helper for
|
||||
/// <see cref="CheckOtherCells"/>. Mirrors retail's switch at
|
||||
/// <c>acclient_2013_pseudo_c.txt:272739-272752</c>:
|
||||
/// Collided/Adjusted halt with <c>CollidedWithEnvironment</c>; Slid
|
||||
/// halts AND clears the contact-plane fields; OK continues.
|
||||
/// </summary>
|
||||
internal bool ApplyOtherCellResult(TransitionState result, out TransitionState finalState)
|
||||
{
|
||||
finalState = result;
|
||||
switch (result)
|
||||
{
|
||||
case TransitionState.Collided:
|
||||
case TransitionState.Adjusted:
|
||||
if (!ObjectInfo.State.HasFlag(ObjectInfoState.Contact))
|
||||
CollisionInfo.CollidedWithEnvironment = true;
|
||||
return true;
|
||||
case TransitionState.Slid:
|
||||
CollisionInfo.ContactPlaneValid = false;
|
||||
CollisionInfo.ContactPlaneIsWater = false;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private TransitionState FindEnvCollisions(PhysicsEngine engine)
|
||||
{
|
||||
var sp = SpherePath;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue