fix(phys): #112 - remove the non-retail escape-hatch demote from the pick; lateral stab-graph recovery + retail keep-curr
Root cause (oracle: CLandCell::point_in_cell :316941 = terrain-poly only;
find_cell_list null-result keep-curr pc:308788-308825; CEnvCell::
check_building_transit :309827 = sphere_intersects_cell per portal-adjacent
cell): retail KEEPS curr_cell when nothing contains the centre — including
inside a house's containment gaps. Our 6dbbf95 escape hatch instead demoted
any hydrated indoor claim the sphere no longer overlaps to the outdoor
column; at the A9B3 hill cottage's real interior gap this stranded the
player outdoor-classified deep indoors, where re-promotion is portal-
adjacent-only (retail-identical) -> the outdoor flood rendered the interior
transparent (the user's "sometimes transparent" walk).
The hatch's actual target - poisoned (cell, position) SAVES - has been
handled at the SNAP by PhysicsEngine.Resolve's AdjustPosition validation
since #107/#111, so the per-tick pick reverts to retail semantics:
1. lateral recovery first - when the sphere no longer overlaps the claim,
search the claim's stab list for a containing cell (retail
find_visible_child_cell :311444, the same recovery AdjustPosition uses);
the #111 adjacent-claim shape now self-heals laterally (dat-backed test:
pick(seed 0x172) at a 0x171-interior point -> 0x171);
2. else KEEP curr_cell (retail null-result).
Two old tests asserting the hatch demote rewritten to the retail semantics
(tests-can-codify-bugs); P1 retail-golden conformance gates explicitly green
(FindCellListConformance + ThresholdPortalCrossing + CottageDoorway +
CameraCornerSeal = 11/11). New Issue112MembershipTests: the lateral-recovery
fact + a DocumentsResidual fact pinning the remaining at-doorway gap demote
(via the NORMAL outdoor-candidate path; open oracle read = retail's
add_all_outside_cells gate in CEnvCell::find_transit_cells pc:317499 -
sphere-proximity vs graph-reachability). Core 1383 + 4 pre-existing #99
failures + 1 skip.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e9c8a925d2
commit
2d6954ee44
4 changed files with 133 additions and 35 deletions
|
|
@ -75,13 +75,15 @@ public sealed class Issue107SpawnDiagnosticTests
|
|||
_out.WriteLine($"FindCellList(seed=0x{ActualCell:X8}) -> 0x{pickGood:X8}");
|
||||
Assert.Equal(ActualCell, pickGood);
|
||||
|
||||
// Production pick with the POISONED seed demotes to the outdoor column —
|
||||
// retail-identical for a cross-building claim (0x171 is not in 0x162's
|
||||
// stab list). The login-side fix is AdjustPosition at the snap, which
|
||||
// demotes IMMEDIATELY instead of after the first movement.
|
||||
// Production pick with the POISONED seed KEEPS it — retail's
|
||||
// find_cell_list null-result behavior (pc:308788-308825; #112 removed
|
||||
// the non-retail escape-hatch demote that used to return the outdoor
|
||||
// column here). The poisoned-save case is handled at the SNAP:
|
||||
// PhysicsEngine.Resolve's AdjustPosition validation rejects the claim
|
||||
// and demotes via seen_outside BEFORE physics ever runs on it.
|
||||
uint pickBad = CellTransit.FindCellList(cache, footCenter, FootRadius, PoisonedClaim);
|
||||
_out.WriteLine($"FindCellList(seed=0x{PoisonedClaim:X8}) -> 0x{pickBad:X8}");
|
||||
Assert.Equal(0xA9B40031u, pickBad);
|
||||
Assert.Equal(PoisonedClaim, pickBad);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.Options;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace AcDream.Core.Tests.Conformance;
|
||||
|
||||
/// <summary>
|
||||
/// #112 (2026-06-10): the A9B3 hill cottage has a real containment GAP inside
|
||||
/// the house (world (184.9, −109.5, 116) / A9B3-local (184.9, 82.5) is in NO
|
||||
/// interior cell while points ~0.7 m away are inside 0x100/0x103). The
|
||||
/// 6dbbf95 escape hatch demoted the walker to the outdoor column there,
|
||||
/// stranding them outdoor-classified deep indoors (no containment-based
|
||||
/// re-promotion) → the outdoor flood rendered the interior transparent.
|
||||
/// Retail find_cell_list KEEPS curr_cell when nothing contains the centre
|
||||
/// (pc:308788-308825). These tests pin the replacement semantics against the
|
||||
/// real dats.
|
||||
/// </summary>
|
||||
public sealed class Issue112MembershipTests
|
||||
{
|
||||
private readonly ITestOutputHelper _out;
|
||||
public Issue112MembershipTests(ITestOutputHelper output) => _out = output;
|
||||
|
||||
private const float FootRadius = 0.48f;
|
||||
|
||||
private static PhysicsDataCache LoadLandblockInteriors(DatCollection dats, uint lbPrefix)
|
||||
{
|
||||
var cache = new PhysicsDataCache();
|
||||
for (uint low = 0x0100; low <= 0x01FF; low++)
|
||||
{
|
||||
try { ConformanceDats.LoadEnvCell(dats, cache, lbPrefix | low); }
|
||||
catch { }
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A9B3CottageGap_IndoorSeed_DemotesViaOutdoorCandidates_DocumentsResidual()
|
||||
{
|
||||
var datDir = ConformanceDats.ResolveDatDir();
|
||||
if (datDir is null) { _out.WriteLine("dats unavailable — skipped"); return; }
|
||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||
var cache = LoadLandblockInteriors(dats, 0xA9B30000u);
|
||||
|
||||
// The gap point (A9B3-local frame, footcenter height): contained by NO
|
||||
// interior cell (dat-scan fact from the live capture issue111-verify7).
|
||||
var gap = new Vector3(184.915f, 82.464f, 116.48f);
|
||||
|
||||
uint picked = CellTransit.FindCellList(cache, gap, FootRadius, 0xA9B30104u);
|
||||
_out.WriteLine($"pick(seed 0x104) at gap -> 0x{picked:X8}");
|
||||
|
||||
// DOCUMENTS THE #112 RESIDUAL (flips loudly when fixed): the gap sits
|
||||
// in the doorway region, so the BFS from 0x104 reaches the exterior
|
||||
// portal and outdoor cells enter the candidate array → the NORMAL
|
||||
// outdoorResult path demotes (not the removed escape hatch — its
|
||||
// removal fixed the deep-room stranding; re-promotion now happens at
|
||||
// the doorway cells on the way back in). Open question for the fix:
|
||||
// retail's CEnvCell::find_transit_cells gate for add_all_outside_cells
|
||||
// (pc:317499 region) — if it requires sphere proximity to the exterior
|
||||
// portal POLYGON (not just graph reachability), this demote disappears
|
||||
// and the assert below should become Assert.Equal(0xA9B30104u, ...).
|
||||
Assert.Equal(0xA9B3003Cu, picked);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThresholdCottage_AdjacentClaim_LaterallyRecovers_ViaStabGraph()
|
||||
{
|
||||
var datDir = ConformanceDats.ResolveDatDir();
|
||||
if (datDir is null) { _out.WriteLine("dats unavailable — skipped"); return; }
|
||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||
var cache = LoadLandblockInteriors(dats, 0xA9B40000u);
|
||||
|
||||
// The #111 gate shape: claim 0x172 (adjacent room), position deep
|
||||
// inside 0x171 (the captured spawn). The sphere does not overlap
|
||||
// 0x172's volume from there → the new lateral recovery searches the
|
||||
// claim's stab list (retail find_visible_child_cell :311444) and
|
||||
// self-heals to 0x171 — instead of the old outdoor demote.
|
||||
var spawnFootCenter = new Vector3(155.390f, 11.020f, 94.0f + FootRadius);
|
||||
|
||||
uint picked = CellTransit.FindCellList(cache, spawnFootCenter, FootRadius, 0xA9B40172u);
|
||||
_out.WriteLine($"pick(seed 0x172) at 0x171-interior -> 0x{picked:X8}");
|
||||
Assert.Equal(0xA9B40171u, picked);
|
||||
}
|
||||
}
|
||||
|
|
@ -265,15 +265,19 @@ public class CellTransitFindCellSetTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void IndoorSeed_SphereFullyOutsideHydratedCell_DemotesToOutdoorColumn()
|
||||
public void IndoorSeed_SphereFullyOutsideHydratedCell_KeepsCurrent_RetailNullResult()
|
||||
{
|
||||
// The gate-2 live wedge shape: claimed cell 0xA9B40150, sphere far
|
||||
// outside its volume (x = -10, fully behind the x≥0 half-space).
|
||||
// The BFS finds no portals (sphere nowhere near them), so no outdoor
|
||||
// candidates exist — pre-fix this returned the bogus claim forever.
|
||||
// Expected: the global outdoor column under the centre. x=-10 is one
|
||||
// cell WEST of the A9B4 block edge → block 0xA8B4, lcoord (1351,1440)
|
||||
// → cell (7,0) → low 0x39.
|
||||
// #112 (2026-06-10): REWRITTEN from the 6dbbf95 escape-hatch
|
||||
// assertion (was: demote to the outdoor column 0xA8B40039). Retail
|
||||
// find_cell_list leaves *result null when NOTHING contains the centre
|
||||
// and the caller KEEPS curr_cell (pc:308788-308825) — the hatch's
|
||||
// demote was a non-retail addition that also fired on legitimate
|
||||
// sub-meter containment gaps INSIDE houses (the A9B3 cottage),
|
||||
// stranding the player outdoor-classified deep indoors → transparent
|
||||
// interior. The hatch's actual target (poisoned saves) is handled at
|
||||
// the SNAP by PhysicsEngine.Resolve's AdjustPosition validation
|
||||
// (#107/#111). With no stab-list on this fixture cell, the lateral
|
||||
// recovery finds nothing → keep the claim.
|
||||
var cache = new PhysicsDataCache();
|
||||
cache.RegisterCellStructForTest(0xA9B40150u, MakeCellWithBoundedBsp(Matrix4x4.Identity));
|
||||
|
||||
|
|
@ -282,7 +286,7 @@ public class CellTransitFindCellSetTests
|
|||
currentCellId: 0xA9B40150u,
|
||||
out _);
|
||||
|
||||
Assert.Equal(0xA8B40039u, containing);
|
||||
Assert.Equal(0xA9B40150u, containing);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue