acdream/tests/AcDream.Core.Tests/Physics/EdgeSlideBackProbePrecipiceSlideTests.cs
Erik 325fee7cbb docs+test(physics): retire stale TS-1 row; file AD-53/AD-54 for its two acdream-only branches
Campaign P Slice P2 step 1 (docs/research/2026-07-30-response-layer-edge-family-pseudocode.md
§2, §6 Step 1/2). The TS-1 register row (retail-divergence-register.md:238)
described work that is already done: SpherePath.PrecipiceSlide,
Transition.CliffSlide, and Transition.EdgeSlideAfterStepDownFailed are real,
tested ports of retail's edge_slide -> precipice_slide/cliff_slide chain
(pc:274316, pc:272397, pc:273001-273090). Its cited :1254 line was stale
stepping-loop code the file moved past.

The one real remaining gap (the back-probe fallback skipping retail's
walkable_check_pos/localspace_sphere recache before its second
precipice_slide call, pc:274318-274326 / 0050b4e0-0050b507) needed no
production code change: a fresh read of SPHEREPATH::get_walkable_pos
(0050a8f0), cache_localspace_sphere (0050c9d0), and set_walkable_check_pos
(00509ce0) shows that machinery exists to re-project a sphere across
retail's PER-CELL local coordinate frames. acdream's SpherePath.WalkableVertices
and GlobalSphere are populated in UNIFIED WORLD SPACE at assignment time
(SetWalkable/SetWalkableTransformed, SetCheckPos/RestoreCheckPos), so both
operands BSPQuery.FindCrossedEdge compares are already commensurable --
retail's recache is a no-op correction under this architecture, and
FindCrossedEdge never reads a sphere radius, so retail's walkable_scale
radius correction has no acdream counterpart either. Documented in-code at
the back-probe site with full citations, and pinned with
EdgeSlideBackProbePrecipiceSlideTests: a walkable polygon rediscovered near
GlobalCurrCenter, tested against GlobalSphere[0] restored to the original
failed target, crosses the edge and slides -- it does not wedge into
Collided (and the inverse case, standing inside the polygon with no edge
crossed, correctly still returns Collided matching retail's own
precipice_slide on a false find_crossed_edge).

TS-1's other two flagged gaps are real acdream-only compensating branches,
not retail reads, and get their own rows rather than being silently
retired alongside it:
- AD-53: CliffSlide's three-source reference-normal fallback chain
  (LastWalkablePlane -> LastKnownContactPlane -> world-up) vs retail's
  direct last_known_contact_plane.N use. A fresh read of
  last_known_contact_plane's maintenance (pc:272659-272668) confirms retail
  overwrites it unconditionally every validate_transition pass, including
  with a steep plane -- so the fallback chain compensates for AP-4's
  incomplete OnWalkable bookkeeping, not a retail-matching read.
- AD-54: the walkable-steepness reroute to CliffSlide before PrecipiceSlide
  when the stored walkable polygon itself is steeper than FloorZ. Retail's
  raw edge_slide has no such branch; the permissive LandingZ acceptance
  that makes this state reachable IS retail-faithful (TS-4's own
  BSPTREE::find_collisions citation), but whether retail's outer
  transitional_insert retry loop absorbs the resulting COLLIDED_TS some
  other way is not yet independently verified -- flagged open in the row.

Physics test suite: 1836 passed, 1 skipped (D4, unrelated to this change).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 08:13:20 +02:00

151 lines
6.7 KiB
C#

using System.Numerics;
using AcDream.Core.Physics;
using Xunit;
using Plane = System.Numerics.Plane;
namespace AcDream.Core.Tests.Physics;
/// <summary>
/// Campaign P Slice P2, TS-1 gap #1 (2026-07-30 research pass,
/// <c>docs/research/2026-07-30-response-layer-edge-family-pseudocode.md</c>
/// §2). Pins the specific state <c>Transition.EdgeSlideAfterStepDownFailed</c>'s
/// back-probe fallback (<c>TransitionTypes.cs:2015-2031</c>) hands to
/// <see cref="SpherePath.PrecipiceSlide"/>: a walkable polygon rediscovered
/// near <c>GlobalCurrCenter</c> (the last-known-good grounded position),
/// tested against <c>GlobalSphere[0]</c> restored to the ORIGINAL failed
/// move target (off the polygon's edge) after <c>RestoreCheckPos()</c>.
///
/// <para>
/// Retail's <c>SPHEREPATH::edge_slide</c> back-probe branch
/// (<c>acclient_2013_pseudo_c.txt:274316-274326</c>, <c>0050b4e0-0050b507</c>)
/// re-caches <c>walkable_check_pos</c>/<c>localspace_sphere</c> from
/// <c>SPHEREPATH::get_walkable_pos</c> (<c>0050a8f0</c>) via
/// <c>SPHEREPATH::cache_localspace_sphere</c> (<c>0050c9d0</c>) and
/// <c>SPHEREPATH::set_walkable_check_pos</c> (<c>00509ce0</c>) before calling
/// <c>precipice_slide</c> a second time. That machinery exists to solve a
/// coordinate-FRAME problem: retail's <c>walkable</c> polygon and
/// <c>check_pos</c> are each expressed relative to a PER-CELL local frame
/// (<c>cache_localspace_sphere</c> re-projects one into the other's frame via
/// <c>Position::localtolocal</c>, and <c>precipice_slide</c> itself applies a
/// <c>LandDefs::get_block_offset</c> landblock correction, pc:274341).
/// </para>
///
/// <para>
/// acdream's <see cref="SpherePath.WalkableVertices"/>/<see cref="SpherePath.WalkablePlane"/>
/// are populated in UNIFIED WORLD SPACE at assignment time (see
/// <c>SpherePath.SetWalkable</c>/<c>SetWalkableTransformed</c>,
/// <c>TransitionTypes.cs:667-739</c>, which bake <c>worldOrigin</c> and
/// <c>scale</c> in immediately), and <see cref="SpherePath.GlobalSphere"/> is
/// likewise always world-space (<c>SpherePath.SetCheckPos</c>/
/// <c>RestoreCheckPos</c>, <c>TransitionTypes.cs:621-650</c>). Both operands
/// <see cref="BSPQuery.FindCrossedEdge"/> compares are therefore ALREADY
/// commensurable without any recache step — retail's local-frame
/// re-projection is a no-op correction in acdream's flat-world-space design.
/// <see cref="BSPQuery.FindCrossedEdge"/> also never reads a sphere radius
/// (only <c>sphereCenter</c>), so retail's radius/<c>walkable_scale</c>
/// correction has no acdream counterpart to begin with.
/// </para>
///
/// <para>
/// This test does not add a recache step (there is nothing for it to
/// correct in this architecture); it instead PINS the claim: given exactly
/// the field values the back-probe fallback produces (a world-space walkable
/// polygon near <c>GlobalCurrCenter</c>, a restored <c>GlobalSphere[0]</c> at
/// the original off-edge target), <c>PrecipiceSlide</c> must find the
/// crossed edge and slide — not wedge into <c>Collided</c>.
/// </para>
/// </summary>
public class EdgeSlideBackProbePrecipiceSlideTests
{
/// <summary>
/// Back-probe re-discovers a flat platform's edge polygon near the last
/// known good center; the restored (failed) target sphere sits just past
/// the +X edge. PrecipiceSlide must cross that edge and slide, matching
/// retail's post-recache precipice_slide result (ADJUSTED_TS/SLID_TS,
/// never a stuck COLLIDED_TS).
/// </summary>
[Fact]
public void PrecipiceSlide_BackProbeState_CrossesEdge_DoesNotWedge()
{
var transition = new Transition();
var sp = transition.SpherePath;
// Last known good grounded center — where the back-probe offset
// points back toward (retail: global_curr_center).
sp.GlobalCurrCenter[0].Origin = new Vector3(0f, 0f, 1f);
sp.GlobalCurrCenter[0].Radius = 0.5f;
// The walkable polygon the back-probe's DoStepDown rediscovered near
// that center: a flat 2x2 platform, top face at Z=1.
var plane = new Plane(Vector3.UnitZ, -1f);
sp.SetWalkable(
plane,
new[]
{
new Vector3(-1f, -1f, 1f),
new Vector3(1f, -1f, 1f),
new Vector3(1f, 1f, 1f),
new Vector3(-1f, 1f, 1f),
},
Vector3.UnitZ);
// GlobalSphere[0] after RestoreCheckPos(): the ORIGINAL failed move
// target, just past the platform's +X edge (off the polygon, over
// open air) — exactly what the back-probe fallback hands to
// PrecipiceSlide once retail's edge_slide restores check_pos.
sp.CheckPos = new Vector3(1.2f, 0f, 1f);
sp.GlobalSphere[0].Origin = sp.CheckPos;
sp.GlobalSphere[0].Radius = 0.5f;
var result = sp.PrecipiceSlide(transition);
Assert.NotEqual(TransitionState.Collided, result);
Assert.True(
result is TransitionState.Slid or TransitionState.Adjusted or TransitionState.OK,
$"Back-probe PrecipiceSlide must slide/adjust across the found edge, not wedge; got {result}.");
// The walkable context is consumed (retail: this->walkable = nullptr
// inside precipice_slide before slide_sphere runs).
Assert.False(sp.WalkableValid);
}
/// <summary>
/// Sanity inverse: when the restored target sphere is still WELL INSIDE
/// the rediscovered polygon (no edge crossed — e.g. the back-probe found
/// the same ground the mover is standing on), retail's raw
/// <c>precipice_slide</c> returns COLLIDED_TS
/// (<c>acclient_2013_pseudo_c.txt:274322-274326</c>: <c>eax==0 → walkable
/// = nullptr; return 2</c>). Confirms the "no wedge" claim above is about
/// the edge-crossing case specifically, not a blanket "never Collided."
/// </summary>
[Fact]
public void PrecipiceSlide_NoEdgeCrossed_ReturnsCollided_MatchingRetail()
{
var transition = new Transition();
var sp = transition.SpherePath;
sp.GlobalCurrCenter[0].Origin = new Vector3(0f, 0f, 1f);
sp.GlobalCurrCenter[0].Radius = 0.5f;
var plane = new Plane(Vector3.UnitZ, -1f);
sp.SetWalkable(
plane,
new[]
{
new Vector3(-1f, -1f, 1f),
new Vector3(1f, -1f, 1f),
new Vector3(1f, 1f, 1f),
new Vector3(-1f, 1f, 1f),
},
Vector3.UnitZ);
// Restored target well inside the polygon — no edge crossed.
sp.CheckPos = new Vector3(0.1f, 0f, 1f);
sp.GlobalSphere[0].Origin = sp.CheckPos;
sp.GlobalSphere[0].Radius = 0.5f;
var result = sp.PrecipiceSlide(transition);
Assert.Equal(TransitionState.Collided, result);
}
}