Part A (D4): CObjCell::check_entry_restrictions (pc:309576) gate is omitted from FindEnvCollisions. Investigation confirmed the gate requires restriction_obj (per-cell access-lock entity id) + weenie-object-table dispatch (CanMoveInto / CanBypassMoveRestrictions) — neither exist in acdream. CellPhysics has no restriction_obj field; DatReaderWriter models no per-cell access locks. Gap is inert in all dev content (ACE starter area has no access-locked env cells). Documented as AP-50 in the retail divergence register. Part B (W1): Replace the hardcoded-false smell in BspOnlyDispatch with named internal helpers PvpExempt() and MissileIgnore() that return false with retail oracle citations (pc:276808-276841 and pc:274385 respectively). BspOnlyDispatch now folds all three terms in retail's exact predicate structure. Behavior is byte-identical in M1.5 scope (both stubs false ⇒ reduces to HAS_PHYSICS_BSP_PS check alone, same as before). A6.P7 door dispatch unchanged. Tests: 3 new guard tests in A6P7DispatchRulesTests — W1_PvpExempt_ReturnsFalseInM15Scope, W1_MissileIgnore_ReturnsFalseInM15Scope, W1_BspOnlyDispatch_DoorStateStillDispatchesBspOnly. Suite: 1590 pass / 0 fail / 2 skip (was 1587 + 3 = 1590). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
122 lines
4.9 KiB
C#
122 lines
4.9 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));
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// W1 (2026-06-24) — named PvP/missile terms are false in M1.5
|
||
// -----------------------------------------------------------------------
|
||
|
||
/// <summary>
|
||
/// W1: <see cref="Transition.PvpExempt"/> must return false in M1.5.
|
||
/// When PK ships (M2+) this stub will accept mover + target state;
|
||
/// the test pins the current named-false value as a guard.
|
||
/// Retail oracle: <c>CPhysicsObj::FindObjCollisions</c> pc:276808–276841.
|
||
/// </summary>
|
||
[Fact]
|
||
public void W1_PvpExempt_ReturnsFalseInM15Scope()
|
||
{
|
||
// PvP hasn't shipped (M1.5); the stub always returns false.
|
||
Assert.False(Transition.PvpExempt());
|
||
}
|
||
|
||
/// <summary>
|
||
/// W1: <see cref="Transition.MissileIgnore"/> must return false in M1.5.
|
||
/// When missiles ship (F.3) this stub will accept mover OBJECTINFO +
|
||
/// target state; the test pins the current named-false value as a guard.
|
||
/// Retail oracle: <c>OBJECTINFO::missile_ignore</c> pc:274385;
|
||
/// dispatch use pc:276858–276861.
|
||
/// </summary>
|
||
[Fact]
|
||
public void W1_MissileIgnore_ReturnsFalseInM15Scope()
|
||
{
|
||
// Missiles haven't shipped (M1.5); the stub always returns false.
|
||
Assert.False(Transition.MissileIgnore());
|
||
}
|
||
|
||
/// <summary>
|
||
/// Guard: with both W1 terms false, a BSP-flagged entity still
|
||
/// dispatches BSP-only — A6.P7 door behavior is unchanged after W1.
|
||
/// This protects the A6.P7 cottage-door / dungeon-wall fix.
|
||
/// </summary>
|
||
[Fact]
|
||
public void W1_BspOnlyDispatch_DoorStateStillDispatchesBspOnly()
|
||
{
|
||
// Cottage door state from A6.P7 investigation: 0x10008 = STATIC | REPORT | HAS_BSP
|
||
const uint doorState = 0x00010008u;
|
||
Assert.True(Transition.BspOnlyDispatch(doorState));
|
||
}
|
||
}
|