using System; using System.Collections.Generic; using System.Numerics; using AcDream.Core.Physics; using AcDream.Core.Tests.Conformance; using DatReaderWriter; using DatReaderWriter.Options; using Xunit; using Xunit.Abstractions; namespace AcDream.Core.Tests.Physics; /// /// #180 residual — the camera-sweep SAWTOOTH in the Facility Hub corridor /// (0x8A020164), root-caused to a dead BSPQuery.AdjustToPlane: /// acdream (via ACE's misdecoded port) had the PerfectClip exact-contact /// machinery structurally inverted so it ALWAYS failed, and every PathClipped /// camera stop reverted to the previous transition-step boundary instead of /// the contact point. Live signature (launch-180-verify.log): the stateful /// sought re-extends ~3 mm/frame, the sweep passes silently until the step /// containing the contact, then clips a whole step (~0.27 m) back — a ~19 Hz /// sawtooth; the pre-stateful camera flipped 1-step vs 2-step backoffs /// (pulledIn 0.27 ↔ 0.53) — the ORIGINAL #176 stripe strobe. /// /// This replay walks sweep targets along the exact captured ray /// (pivot → the [flap-sweep] in= of idx 28882) across the wall behind the /// spawn camera, against the REAL cell BSP. It pins the retail-faithful /// stop behavior (BSPTREE::adjust_to_plane 0x00539bf0, pseudocode /// docs/research/2026-07-06-adjust-to-plane-pseudocode.md): /// /// 1. targets short of first touch pass unclipped (the wall plane is /// genuinely ~1.61 m out along this ray: dist(center,plane) > r); /// 2. every target past first touch CONTACTS (no silent pass-through /// band — pre-fix, targets embedded up to ~0.25 m passed); /// 3. the clipped eye is the CONSTANT surface-contact point (pre-fix it /// tracked the target one step back: eyeBack = s − ~0.27). /// public class Issue180CorridorSweepHysteresisReplayTests { private readonly ITestOutputHelper _out; public Issue180CorridorSweepHysteresisReplayTests(ITestOutputHelper output) => _out = output; private const float ViewerSphereRadius = 0.3f; // retail viewer_sphere (acclient :93314) private const uint FacilityHubLandblock = 0x8A020000u; private const uint CorridorCell = 0x8A020164u; // [flap-cam] player=(70.58,-40.16,-5.90) (parked spawn) + PivotHeight 1.5. private static readonly Vector3 Pivot = new(70.58f, -40.16f, -4.40f); // [flap-sweep] idx 28882: the captured in= that clipped (the sawtooth's deep edge). private static readonly Vector3 THit = new(70.366051f, -38.628315f, -3.935829f); private static (PhysicsEngine, PhysicsDataCache) BuildCorridorEngine(DatCollection dats) { var cache = new PhysicsDataCache(); var engine = new PhysicsEngine { DataCache = cache }; for (uint low = 0x0100u; low <= 0x01FFu; low++) { uint id = FacilityHubLandblock | low; try { ConformanceDats.LoadEnvCell(dats, cache, id); } catch (InvalidOperationException) { /* cell id not present in this dungeon */ } } var heights = new byte[81]; var heightTable = new float[256]; for (int i = 0; i < 256; i++) heightTable[i] = -1000f; engine.AddLandblock(FacilityHubLandblock, new TerrainSurface(heights, heightTable), Array.Empty(), Array.Empty(), 0f, 0f); return (engine, cache); } /// Mirror of PhysicsCameraCollisionProbe.SweepEye's transition call. private static ResolveResult SweepViewer(PhysicsEngine engine, Vector3 pivot, Vector3 desiredEye, uint cellId) { uint startCell = cellId; if ((cellId & 0xFFFFu) >= 0x0100u) { var (pivotCell, found) = engine.AdjustPosition(cellId, pivot); if (found) startCell = pivotCell; } Vector3 begin = pivot - new Vector3(0f, 0f, ViewerSphereRadius); Vector3 end = desiredEye - new Vector3(0f, 0f, ViewerSphereRadius); return engine.ResolveWithTransition( currentPos: begin, targetPos: end, cellId: startCell, sphereRadius: ViewerSphereRadius, sphereHeight: 0f, stepUpHeight: 0f, stepDownHeight: 0f, isOnGround: false, body: null, moverFlags: ObjectInfoState.IsViewer | ObjectInfoState.PathClipped | ObjectInfoState.FreeRotate | ObjectInfoState.PerfectClip, movingEntityId: 0); } [Fact] public void ClippedStop_IsTheContactPoint_NotAStepBoundary() { var datDir = ConformanceDats.ResolveDatDir(); if (datDir is null) { _out.WriteLine("SKIP: dats unavailable"); return; } using var dats = new DatCollection(datDir, DatAccessType.Read); var (engine, _) = BuildCorridorEngine(dats); Vector3 dir = Vector3.Normalize(THit - Pivot); var clippedEyeBacks = new List(); for (float s = 1.20f; s <= 1.75f; s += 0.025f) { Vector3 target = Pivot + dir * s; var r = SweepViewer(engine, Pivot, target, CorridorCell); Vector3 eye = r.Position + new Vector3(0f, 0f, ViewerSphereRadius); float eyeBack = Vector3.Distance(Pivot, eye); _out.WriteLine(FormattableString.Invariant( $"s={s:F3} eyeBack={eyeBack:F3} collNorm={r.CollisionNormalValid}")); if (s <= 1.55f) { // (1) Short of first touch (~1.61 m): the sweep must reach the target. Assert.False(r.CollisionNormalValid, $"s={s:F3}: no wall within reach, sweep must not clip"); Assert.True(MathF.Abs(eyeBack - s) < 0.01f, $"s={s:F3}: unclipped sweep must reach the target, got eyeBack={eyeBack:F3}"); } else if (s >= 1.65f) { // (2) Past first touch: every target must contact — the pre-fix // defect passed targets embedded up to ~0.25 m unclipped. Assert.True(r.CollisionNormalValid, $"s={s:F3}: target past the wall must clip"); clippedEyeBacks.Add(eyeBack); } } // (3) The clipped stop is the constant surface-contact point. Pre-fix the // stop tracked the target one step back (eyeBack = s − ~0.27, spread // ≈ 0.10 m over this range); retail's adjust_to_plane commits the // last-clear time within a 0.02 window of the contact. Assert.True(clippedEyeBacks.Count >= 4, "expected several clipped samples"); float min = float.MaxValue, max = float.MinValue; foreach (var e in clippedEyeBacks) { min = MathF.Min(min, e); max = MathF.Max(max, e); } Assert.True(max - min < 0.03f, $"clipped stops must be one contact point, got spread {max - min:F3} m ({min:F3}..{max:F3})"); } }