Closes the door-cyl phantom slide where a sphere approaching a closed cottage door at NE/SE headings could be blocked by the cyl's radial normal contaminating the slide tangent into the slab face (live evidence in door-a6p6-v2.utf8.log: 12 resolves with cn=(0.86,0.51,0) attributed to door entity 0x000F4245). Retail anchor: CPhysicsObj::FindObjCollisions at acclient_2013_pseudo_c.txt:276861 dispatches BINARILY between BSP-only and cyl+sphere based on HAS_PHYSICS_BSP_PS (0x10000 in acclient.h:2833). For non-PvP, non-missile movers — every M1.5 scope walking-vs-static scenario — an entity with the flag set tests its BSP exclusively; the foot cyl is never tested. ACE confirms the truth table at PhysicsObj.cs:412-450 (HasPhysicsBSP, missileIgnore, exemption). Our dispatcher iterated every ShadowEntry independently and tested both the cyl AND the BSP for a closed door. Cyl was registered first (FromSetup walk order), and its diagonal radial slide normal "won" attribution at the early-return on first non-OK. Result was out=in for tangential motion along the door face. Changes (~15 LOC + 7 unit tests): - PhysicsStateFlags.HasPhysicsBsp = 0x00010000 (PhysicsBody.cs) - Transition.BspOnlyDispatch(uint state) static predicate (TransitionTypes.cs) — mirrors retail's branch with M1.5 scope defaults (ebp_1 and eax_12 treated as false; wire PvP / missile refinements when those scopes ship) - Per-entry guard in FindObjCollisions cyl/sphere branch (TransitionTypes.cs:2433) — continue when BspOnlyDispatch fires, with [cyl-skip-bsp] diagnostic line gated on ProbeBuildingEnabled - A6P7DispatchRulesTests (7 tests, all GREEN): flag value + 6 parameterized predicate cases Verification: 14-test keep-green list from the 2026-05-25 handoff passes (5 BSPQueryTests.FindCollisions_Path5_*, 2 CellTransitTests.A6P5_*, 2 DoorCollisionApparatusTests.Apparatus_DeadCenter_*, 5 DoorBugTrajectoryReplayTests, 1 CellarUpTrajectoryReplayTests.LiveCompare_FirstCap_FixClosesCottageFloorCap). Total: 20/20 pass including the new 7-test predicate suite. The DocumentsBug test (Apparatus_Grounded_50cmOffCenter) fails post-fix BUT was already failing pre-fix in the worktree baseline (verified by stashing the fix and re-running — same failure mode: sphere blocks at start with floor normal (0,0,1)). Not in the keep-green list, so this is a known pre-existing condition; the test's own header comment instructs flipping the assertion when the fix lands. Investigation: docs/research/2026-05-25-a6-door-cyl-retail-dispatch-investigation.md Needs visual verification at Holtburg cottage door (NE/SE approach should now slide smoothly along the door face — zero [cyl-test] log lines attributed to door entity, replaced by [cyl-skip-bsp]). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
78 lines
3.1 KiB
C#
78 lines
3.1 KiB
C#
using AcDream.Core.Physics;
|
|
using Xunit;
|
|
|
|
namespace AcDream.Core.Tests.Physics;
|
|
|
|
/// <summary>
|
|
/// A6.P7 (2026-05-25) — retail-binary dispatch rule. Retail's
|
|
/// <c>CPhysicsObj::FindObjCollisions</c> at
|
|
/// <c>docs/research/named-retail/acclient_2013_pseudo_c.txt:276861</c>
|
|
/// dispatches BINARILY between "BSP-only" and "cyl + sphere" based on
|
|
/// the <c>HAS_PHYSICS_BSP_PS</c> flag (bit 16, hex 0x10000) in
|
|
/// <c>PhysicsState</c>. The flag is defined in
|
|
/// <c>docs/research/named-retail/acclient.h:2833</c> and mirrored in
|
|
/// ACE at <c>references/ACE/Source/ACE.Entity/Enum/PhysicsState.cs:24</c>
|
|
/// as <c>HasPhysicsBSP = 0x00010000</c>.
|
|
///
|
|
/// <para>
|
|
/// For non-PvP, non-missile movers (M1.5 scope — walking-vs-static), an
|
|
/// entity with HAS_PHYSICS_BSP_PS in its state tests its BSP exclusively
|
|
/// — the foot cyl is NEVER tested. The closed cottage door (state
|
|
/// <c>0x10008</c> = <c>STATIC | REPORT_COLLISIONS | HAS_PHYSICS_BSP</c>)
|
|
/// hits this branch.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Pre-A6.P7: our dispatcher iterates every <c>ShadowEntry</c>
|
|
/// independently and tests both the cyl AND the BSP for a door. The
|
|
/// cyl's radial normal contaminates the slide direction at NE/SE
|
|
/// approach headings (see
|
|
/// <c>docs/research/2026-05-25-a6-door-cyl-retail-dispatch-investigation.md</c>).
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Post-A6.P7: the dispatcher consults
|
|
/// <see cref="Transition.BspOnlyDispatch(uint)"/> on each cyl/sphere
|
|
/// entry and skips it when the entity has BSP, matching retail.
|
|
/// </para>
|
|
/// </summary>
|
|
public class A6P7DispatchRulesTests
|
|
{
|
|
/// <summary>
|
|
/// Retail bit constant <c>HAS_PHYSICS_BSP_PS = 0x10000</c>
|
|
/// (acclient.h:2833) and ACE's <c>HasPhysicsBSP = 0x00010000</c>
|
|
/// (PhysicsState.cs:24) must match the value we expose on
|
|
/// <see cref="PhysicsStateFlags"/>.
|
|
/// </summary>
|
|
[Fact]
|
|
public void PhysicsStateFlags_HasPhysicsBsp_Is_Bit_16()
|
|
{
|
|
Assert.Equal(0x00010000u, (uint)PhysicsStateFlags.HasPhysicsBsp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The dispatch predicate maps directly from retail's branch at
|
|
/// acclient_2013_pseudo_c.txt:276861:
|
|
/// <code>
|
|
/// ((state & 0x10000) == 0 || ebp_1 != 0 || eax_12 != 0)
|
|
/// → cyl + sphere path
|
|
/// else
|
|
/// → BSP-only path
|
|
/// </code>
|
|
/// For M1.5 scope <c>ebp_1</c> (PvP-target-player) and <c>eax_12</c>
|
|
/// (missile_ignore) are treated as false. The predicate reduces to:
|
|
/// "BSP-only iff <c>HAS_PHYSICS_BSP_PS</c> is set".
|
|
/// </summary>
|
|
[Theory]
|
|
[InlineData(0x00010008u, true)] // closed cottage door — STATIC | REPORT | HAS_BSP
|
|
[InlineData(0x00010000u, true)] // bare HAS_BSP
|
|
[InlineData(0x00110000u, true)] // HAS_BSP | CLOAKED
|
|
[InlineData(0x00000008u, false)] // creature — REPORT_COLLISIONS only
|
|
[InlineData(0x00000000u, false)] // empty state
|
|
[InlineData(0x0000FFFFu, false)] // every bit BELOW 0x10000 set
|
|
public void BspOnlyDispatch_RespectsHasPhysicsBspFlag(
|
|
uint entityState, bool expected)
|
|
{
|
|
Assert.Equal(expected, Transition.BspOnlyDispatch(entityState));
|
|
}
|
|
}
|