feat(core): UCG W2 Task 3 — stab-list doorway hysteresis in ResolveCellId
Port retail CObjCell::find_cell_list do_not_load_cells prune (acclient_2013_pseudo_c.txt:308829-308867) as indoor->outdoor doorway hysteresis: hold the previous indoor cell when the outdoor candidate is not in its stab list AND the foot-sphere still overlaps the cell's containment BSP expanded by DoorwayHoldMargin. Kills the front-door 0170<->0031 ping-pong (handoff §5) the #98 saga never addressed. Fires only at the front-door seam; the cellar has no exit portal so it never falls through here (#98 cellar-up untouched). Three TDD tests in CellGraphMembershipTests: HOLD (the RED->GREEN case, Y=3.9 inside the 0.2 m margin), RELEASE when fully outside (Y=4.5 exceeds expanded margin), and stab-list gate (outdoor candidate in stab list releases even near the boundary). Adds using System.Linq for IReadOnlyList.Contains at the prune site. SphereOverlapsEnvCell helper mirrors BSPQuery.SphereIntersectsCellBsp via EnvCell.InverseWorldTransform + ContainmentBsp. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3622a658fd
commit
2acd8f9e1d
2 changed files with 246 additions and 0 deletions
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace AcDream.Core.Physics;
|
||||
|
|
@ -51,6 +52,27 @@ public sealed class PhysicsEngine
|
|||
float WorldOffsetX,
|
||||
float WorldOffsetY);
|
||||
|
||||
/// <summary>
|
||||
/// UCG W2b doorway-hysteresis band (metres) added to the foot-sphere radius when deciding
|
||||
/// whether to HOLD the previous indoor cell at the indoor→outdoor seam. Must exceed the
|
||||
/// ~10 cm push-back oscillation at the threshold (handoff §5: front-door 0170↔0031) while
|
||||
/// still releasing on a genuine step-out. The single value the visual gate may tune.
|
||||
/// </summary>
|
||||
private const float DoorwayHoldMargin = 0.2f;
|
||||
|
||||
/// <summary>
|
||||
/// UCG W2b: does the foot-sphere (at <paramref name="worldPos"/>, inflated to
|
||||
/// <paramref name="radius"/>) overlap <paramref name="cell"/>'s containment BSP? Returns false
|
||||
/// when the cell has no containment BSP (can't test ⇒ release, never hold forever).
|
||||
/// Retail: CCellStruct::sphere_intersects_cell (acclient_2013_pseudo_c.txt:317666).
|
||||
/// </summary>
|
||||
private static bool SphereOverlapsEnvCell(World.Cells.EnvCell cell, Vector3 worldPos, float radius)
|
||||
{
|
||||
if (cell.ContainmentBsp?.Root is null) return false;
|
||||
var local = Vector3.Transform(worldPos, cell.InverseWorldTransform);
|
||||
return BSPQuery.SphereIntersectsCellBsp(cell.ContainmentBsp.Root, local, radius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a landblock with its terrain surface, indoor cells, portal
|
||||
/// planes, and world-space origin offset.
|
||||
|
|
@ -377,6 +399,27 @@ public sealed class PhysicsEngine
|
|||
}
|
||||
}
|
||||
|
||||
// UCG W2b — retail find_cell_list do_not_load_cells prune
|
||||
// (acclient_2013_pseudo_c.txt:308829-308867). Retail drops from the candidate
|
||||
// cell-array any cell that is neither the current cell nor in its stab list
|
||||
// (VisibleCells). Adapted here as doorway hysteresis at the indoor→outdoor seam:
|
||||
// if the previous membership answer is an indoor cell and the outdoor candidate
|
||||
// is NOT in its stab list (outdoor landcells never are), HOLD the indoor cell
|
||||
// while the foot-sphere still overlaps that cell's containment BSP expanded by
|
||||
// DoorwayHoldMargin. The strict overlap check at the indoor branch (~line 340)
|
||||
// just released (the center was pushed past the wall), but the expanded test holds
|
||||
// us across the ~10 cm push-back oscillation at the threshold (handoff §5:
|
||||
// 0170↔0031). Releases on a genuine step-out — by then the bare overlap is also
|
||||
// false, so CheckBuildingTransit won't re-grab the cell next tick. Anti-ping-pong
|
||||
// the #98 saga never had; fires only at the front-door seam (the cellar has no
|
||||
// exit portal → it never falls through here → #98 cellar-up is untouched).
|
||||
if (DataCache?.CellGraph.CurrCell is World.Cells.EnvCell prevCell
|
||||
&& !prevCell.StabList.Contains(outdoorCellId)
|
||||
&& SphereOverlapsEnvCell(prevCell, worldPos, sphereRadius + DoorwayHoldMargin))
|
||||
{
|
||||
return SetCurrAndReturn(prevCell.Id);
|
||||
}
|
||||
|
||||
return SetCurrAndReturn(outdoorCellId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue