T6 (BR-7) C4: straddle-only outside-add (A6.P5 widening DELETED) + #90 stickiness removed
The two remaining flagged workarounds retired, per the BR-7 plan +
the WF1 [MEDIUM] correction (re-gate, do NOT delete the outside-add):
1. A6.P5 hasExitPortal topology widening DELETED. Outdoor cells enter the
collision cell array ONLY on the retail straddle gate - |dist| <
radius + F_EPSILON against an exterior portal plane
(CEnvCell::find_transit_cells Ghidra 0x0052c820, gate 0052c9d6,
live-binary verified) - the same flag that already gated the
membership pick (#112 rider). The widening existed so outdoor-
registered doors stayed findable from indoor cells under the old flat
registry query; with per-cell shadow lists the door is found in the
straddle-admitted outdoor cell's own list (tick-13558 pin holds).
The hasExitPortal out-param + plumbing deleted from
FindTransitCellsSphere; the AddAllOutsideCells call in
BuildCellSetAndPickContaining re-gated on exitOutsideStraddle
(once-per-walk = retail CELLARRAY.added_outside).
2. #90 ResolveCellId sphere-overlap stickiness REMOVED (the 4ca3596
workaround, deferred-to-A6.P4 in the physics digest). It was dead
code: the method's only caller is FindEnvCollisions' cache-null TEST
fallback, and the indoor branch (where the stickiness lived) required
a non-null DataCache. Production membership flows exclusively through
the collide-then-pick advance whose ordered-array hysteresis (current
cell at index 0, interior-wins-break) is the retail mechanism the
workaround approximated. ResolveCellId reduced to the bare
prefix-preserving outdoor re-derive, documented test-only.
Test updates (pins of the deleted behaviors inverted to retail):
- A6P5_BuildCellSetFromIndoorStart_ReachesDoorOutdoorCell (asserted the
topology widening verbatim) -> DeepInteriorSphere_NoStraddle_
AddsNoOutdoorCells: a deep-interior sphere admits NO outdoor cells.
- A6P5_BuildCellSetFromAlcove... -> AlcoveSphere_StraddlesExitPortal_
ReachesDoorOutdoorCell (the captured alcove position genuinely
straddles - the retail-positive half).
- Issue112MembershipTests straddle pin + the second-sphere straddle test
updated to the single-flag signature.
Suites: Core 1416/0/2, App 225, UI 420, Net 294 - green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
dbfbf8506c
commit
ca4b482f8b
5 changed files with 101 additions and 205 deletions
|
|
@ -101,21 +101,22 @@ public sealed class Issue112MembershipTests
|
|||
var cell102 = cache.GetCellStruct(0xA9B30102u)!;
|
||||
|
||||
// (a) Deep inside the room, 3 m from the door plane: the cell HAS an
|
||||
// exterior portal (topology flag) but no straddle → no outdoor
|
||||
// admission flag (retail: var_44 stays 0, add_all_outside skipped).
|
||||
// exterior portal but no straddle → no outdoor admission flag
|
||||
// (retail: var_44 stays 0, add_all_outside skipped). (BR-7 C4
|
||||
// deleted the non-retail hasExitPortal topology output — the
|
||||
// straddle flag is the only outdoor-admission signal, like
|
||||
// retail.)
|
||||
var farCandidates = new List<uint>();
|
||||
CellTransit.FindTransitCellsSphere(
|
||||
cache, cell102, 0xA9B30102u, new Vector3(183.0f, 86.5f, 117.0f),
|
||||
FootRadius, farCandidates, out bool farStraddle, out bool farHasExit);
|
||||
Assert.True(farHasExit);
|
||||
FootRadius, farCandidates, out bool farStraddle);
|
||||
Assert.False(farStraddle);
|
||||
|
||||
// (b) At the door plane (0.30 m away < 0.48 radius): straddle fires.
|
||||
var nearCandidates = new List<uint>();
|
||||
CellTransit.FindTransitCellsSphere(
|
||||
cache, cell102, 0xA9B30102u, new Vector3(185.70f, 85.5f, 117.0f),
|
||||
FootRadius, nearCandidates, out bool nearStraddle, out bool nearHasExit);
|
||||
Assert.True(nearHasExit);
|
||||
FootRadius, nearCandidates, out bool nearStraddle);
|
||||
Assert.True(nearStraddle);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ public class CellTransitFindTransitCellsSphereTests
|
|||
|
||||
CellTransit.FindTransitCellsSphere(
|
||||
cache, exitCell, currentCellId: 0xA9B40100u,
|
||||
spheres, spheres.Length, candidates, out bool exitOutside, out _);
|
||||
spheres, spheres.Length, candidates, out bool exitOutside);
|
||||
|
||||
Assert.True(exitOutside);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,37 +16,29 @@ namespace AcDream.Core.Tests.Physics;
|
|||
/// Tests for <see cref="CellTransit"/> portal-graph expansion.
|
||||
///
|
||||
/// <para>
|
||||
/// A6.P5 (2026-05-25) — unit-tests the cellSet build for the cottage door
|
||||
/// scenario, where pre-fix the <c>exitOutside</c> gate produced over-
|
||||
/// penetration ticks (sphere advanced 0.27 m INTO the door slab because
|
||||
/// the door's outdoor cell wasn't in the cellSet during a cell-crossing
|
||||
/// substep).
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Loads cells <c>0xA9B4013F</c> (player's starting indoor cell at the
|
||||
/// over-penetration tick) and <c>0xA9B40150</c> (alcove cell adjacent to
|
||||
/// the doorway) from the real dat. Asserts that BFS expansion from
|
||||
/// <c>0xA9B4013F</c> reaches the door's outdoor cell <c>0xA9B40029</c>
|
||||
/// via the portal chain. The fix makes this hold unconditionally;
|
||||
/// pre-fix it only held when the sphere physically straddled an exit
|
||||
/// portal.
|
||||
/// History: A6.P5 (2026-05-25) widened the collision cell set on exit-
|
||||
/// portal TOPOLOGY (outdoor cells added whenever an overlapped cell had a
|
||||
/// 0xFFFF portal) so outdoor-registered doors stayed findable from indoor
|
||||
/// cells under the flat registry query. BR-7 / A6.P4 C4 (2026-06-11)
|
||||
/// removed the widening: outdoor cells enter the array only on the retail
|
||||
/// STRADDLE gate (|dist| < radius + F_EPSILON vs the exterior portal
|
||||
/// plane — CEnvCell::find_transit_cells, Ghidra 0x0052c820, live-binary
|
||||
/// verified), and doors are found per-cell via registration-time
|
||||
/// membership instead. These tests pin BOTH halves of the retail
|
||||
/// semantics on real Holtburg dat geometry.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class CellTransitTests
|
||||
{
|
||||
[Fact]
|
||||
public void A6P5_BuildCellSetFromIndoorStart_ReachesDoorOutdoorCell()
|
||||
public void DeepInteriorSphere_NoStraddle_AddsNoOutdoorCells()
|
||||
{
|
||||
var datDir = ResolveDatDir();
|
||||
if (datDir is null) return;
|
||||
|
||||
// Hydrate the cells in the portal chain.
|
||||
// 0xA9B4013F — start cell at the over-penetration tick
|
||||
// 0xA9B40150 — alcove cell adjacent to doorway (has exit portal)
|
||||
// The door lives in outdoor cell 0xA9B40029 (registered as a
|
||||
// shadow entry on the landblock — not loaded here; this test
|
||||
// only asserts the cellSet membership, not collision).
|
||||
// 0xA9B4013F — deep interior cell (the old over-penetration tick)
|
||||
// 0xA9B40150 — alcove cell adjacent to the doorway (exit portal)
|
||||
var cache = new PhysicsDataCache();
|
||||
HydrateCell(cache, datDir, 0xA9B4013Fu);
|
||||
HydrateCell(cache, datDir, 0xA9B40150u);
|
||||
|
|
@ -54,16 +46,15 @@ public class CellTransitTests
|
|||
Assert.NotNull(cache.GetCellStruct(0xA9B4013Fu));
|
||||
Assert.NotNull(cache.GetCellStruct(0xA9B40150u));
|
||||
|
||||
// Sphere at the over-penetration tick start position: world
|
||||
// (132.594, 16.350, 94.48 sphere center). Foot Z = 94, sphere
|
||||
// radius 0.48 → center Z = 94 + radius? Actually the live
|
||||
// capture's sphere center is at Z = 94.48 since foot Z = 94
|
||||
// and sphere is foot-anchored. For this test the Z value
|
||||
// doesn't matter — only XY portal membership.
|
||||
// Sphere deep in 0x13F's volume — no path sphere straddles
|
||||
// 0x150's exit portal plane from here (the A6.P5 pin's own
|
||||
// message documented exactly that). Retail adds NO outdoor cells
|
||||
// in this state; the door (outdoor 0xA9B40029) is reached only
|
||||
// when the sphere actually straddles the threshold — pinned by
|
||||
// the alcove test below and the tick-13558 door pin.
|
||||
var sphereWorld = new Vector3(132.5935f, 16.350428f, 94.48f);
|
||||
const float sphereRadius = 0.48f;
|
||||
const uint startCellId = 0xA9B4013Fu;
|
||||
const uint doorOutdoorCell = 0xA9B40029u;
|
||||
|
||||
_ = CellTransit.FindCellSet(
|
||||
cache,
|
||||
|
|
@ -72,37 +63,25 @@ public class CellTransitTests
|
|||
startCellId,
|
||||
out var cellSet);
|
||||
|
||||
Assert.True(
|
||||
cellSet.Contains(doorOutdoorCell),
|
||||
$"A6.P5: BFS portal expansion from indoor start cell " +
|
||||
$"0x{startCellId:X8} (sphere world XY = " +
|
||||
$"({sphereWorld.X:F3}, {sphereWorld.Y:F3})) should include " +
|
||||
$"the door's outdoor cell 0x{doorOutdoorCell:X8} via the " +
|
||||
$"portal chain 0x{startCellId:X8} → 0xA9B40150 → outdoor. " +
|
||||
$"Pre-fix: the exitOutside gate required the sphere to " +
|
||||
$"straddle 0xA9B40150's exit portal — sphere is in " +
|
||||
$"0x{startCellId:X8}'s volume, so the gate didn't fire and " +
|
||||
$"the door's cell wasn't added. Post-fix: gate removed; " +
|
||||
$"exit-portal topology adds outdoor cells unconditionally. " +
|
||||
$"Actual cellSet: {string.Join(",", cellSet.Select(c => $"0x{c:X8}"))}");
|
||||
Assert.DoesNotContain(cellSet, c => (c & 0xFFFFu) < 0x0100u);
|
||||
Assert.Contains(startCellId, cellSet);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A6P5_BuildCellSetFromAlcove_AlsoReachesDoorOutdoorCell()
|
||||
public void AlcoveSphere_StraddlesExitPortal_ReachesDoorOutdoorCell()
|
||||
{
|
||||
var datDir = ResolveDatDir();
|
||||
if (datDir is null) return;
|
||||
|
||||
// The alcove cell 0xA9B40150 IS the cell with the exit portal.
|
||||
// Pre-fix this worked SOMETIMES (when the sphere straddled the
|
||||
// portal). Post-fix it works ALWAYS. This is a regression guard
|
||||
// for the previously-sometimes-working case.
|
||||
// The alcove cell 0xA9B40150 IS the cell with the exit portal, and
|
||||
// at this captured position the foot sphere straddles its plane
|
||||
// (|dist| < 0.48 + ε — same geometry the tick-13558 straddle pin
|
||||
// proves). Retail admits the outdoor cells exactly here, which is
|
||||
// how the indoor-side sphere reaches the outdoor-registered door's
|
||||
// per-cell shadow list.
|
||||
var cache = new PhysicsDataCache();
|
||||
HydrateCell(cache, datDir, 0xA9B40150u);
|
||||
|
||||
// Sphere at the stuck position — sphere center is NOT at the
|
||||
// exit portal plane (sphere is in the alcove volume, away from
|
||||
// the doorway threshold geometrically).
|
||||
var sphereWorld = new Vector3(132.4014f, 16.761757f, 94.48f);
|
||||
const float sphereRadius = 0.48f;
|
||||
|
||||
|
|
@ -115,8 +94,10 @@ public class CellTransitTests
|
|||
|
||||
Assert.True(
|
||||
cellSet.Contains(0xA9B40029u),
|
||||
$"A6.P5: BFS from alcove cell 0xA9B40150 should always " +
|
||||
$"include the door's outdoor cell 0xA9B40029. " +
|
||||
$"Straddle-gated outside-add: the alcove sphere straddles " +
|
||||
$"0xA9B40150's exit portal, so the door's outdoor cell " +
|
||||
$"0xA9B40029 must enter the set (retail " +
|
||||
$"CEnvCell::find_transit_cells straddle, Ghidra 0x0052c820). " +
|
||||
$"Actual: {string.Join(",", cellSet.Select(c => $"0x{c:X8}"))}");
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue