acdream/tests/AcDream.Core.Tests/Physics/A6P7DispatchRulesTests.cs
Erik 6c7e3ef1ab feat(physics): D4+W1 — entry-restrictions register row + named PvP/missile dispatch terms
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>
2026-06-24 19:45:39 +02:00

122 lines
4.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 &amp; 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:276808276841.
/// </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:276858276861.
/// </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));
}
}