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>
This commit is contained in:
Erik 2026-06-24 19:45:39 +02:00
parent a9f8775110
commit 6c7e3ef1ab
3 changed files with 110 additions and 16 deletions

View file

@ -642,9 +642,54 @@ public sealed class Transition
Environment.GetEnvironmentVariable("ACDREAM_DUMP_EDGE_SLIDE") == "1";
// -----------------------------------------------------------------------
// A6.P7 (2026-05-25) — retail-binary dispatch rule
// A6.P7 (2026-05-25) + W1 (2026-06-24) — retail-binary dispatch rule
// -----------------------------------------------------------------------
/// <summary>
/// W1: Named predicate for the PvP-exemption term in retail's
/// <c>CPhysicsObj::FindObjCollisions</c> dispatch (pc:276861,
/// <c>acclient_2013_pseudo_c.txt:276808276841</c>).
///
/// <para>
/// In retail <c>ebp_1</c> is non-null when the mover is a player AND
/// the target is not impenetrable AND PK/PKLite flags indicate a
/// PvP-exempt pairing — i.e. the mover should pass through rather than
/// collide. While this is non-null the dispatch takes the cyl+sphere
/// path regardless of <c>HAS_PHYSICS_BSP_PS</c>, effectively making
/// PvP-exempt players transparent to BSP-only objects.
/// </para>
///
/// <para>
/// M1.5 scope has no PK. Returns <c>false</c> always.
/// Wire through mover + target state when PK ships (M2+ / phase TBD).
/// Retail oracle: pc:276808276841.
/// </para>
/// </summary>
internal static bool PvpExempt() => false; // wire when PK ships (M2+) — pc:276808276841
/// <summary>
/// W1: Named predicate for the <c>OBJECTINFO::missile_ignore</c> term
/// in retail's <c>CPhysicsObj::FindObjCollisions</c> dispatch
/// (pc:276861, <c>acclient_2013_pseudo_c.txt:274385 + :276858276861</c>).
///
/// <para>
/// In retail <c>eax_12 = OBJECTINFO::missile_ignore(ebx, this)</c>
/// returns non-zero when either the TARGET has <c>MISSILE_PS (0x40)</c>
/// set, or the mover has <c>MISSILE_PS</c> set and the target is a
/// creature that is not the mover's designated target. When non-zero the
/// dispatch takes the cyl+sphere path regardless of
/// <c>HAS_PHYSICS_BSP_PS</c> — missiles pass through BSP-only objects.
/// </para>
///
/// <para>
/// M1.5 scope has no missiles. Returns <c>false</c> always.
/// Wire through mover OBJECTINFO + target state when missiles ship (F.3).
/// Retail oracle: <c>OBJECTINFO::missile_ignore</c> pc:274385;
/// dispatch pc:276858276861.
/// </para>
/// </summary>
internal static bool MissileIgnore() => false; // wire when missiles ship (F.3) — pc:274385
/// <summary>
/// A6.P7 retail-binary dispatch predicate. Returns true when an
/// entity's collision queries should go to its BSP exclusively,
@ -660,23 +705,24 @@ public sealed class Transition
/// else
/// // BSP-only via CPartArray::FindObjCollisions
/// </code>
/// where <c>ebp_1</c> is the PvP-target-player flag (lines 276808-
/// 276841) and <c>eax_12</c> is the <c>OBJECTINFO::missile_ignore</c>
/// result (line 274385). The flag is named
/// where <c>ebp_1</c> is the PvP-target-player flag (lines 276808
/// 276841, see <see cref="PvpExempt"/>) and <c>eax_12</c> is the
/// <c>OBJECTINFO::missile_ignore</c> result (line 274385, see
/// <see cref="MissileIgnore"/>). The flag is named
/// <c>HAS_PHYSICS_BSP_PS = 0x10000</c> in acclient.h:2833 and
/// <c>PhysicsState.HasPhysicsBSP</c> in ACE
/// (references/ACE/Source/ACE.Entity/Enum/PhysicsState.cs:24).
/// </para>
///
/// <para>
/// M1.5 scope (walking-vs-static, no PK, no missiles) treats both
/// <c>ebp_1</c> and <c>eax_12</c> as <c>false</c>. The predicate
/// reduces to <c>(state &amp; HAS_PHYSICS_BSP_PS) != 0</c>. When
/// PK ships (M2+ phase) and missiles ship (F.3), wire the
/// PvP-exemption and missile_ignore checks through as additional
/// parameters following retail's
/// <c>references/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:412</c>
/// dispatch shape.
/// BSP-only iff:
/// <c>HAS_PHYSICS_BSP_PS</c> is set on the entity
/// AND the mover is not PvP-exempt for this target (<see cref="PvpExempt"/> = false)
/// AND the entity is not missile-ignore exempt (<see cref="MissileIgnore"/> = false).
/// In M1.5 <see cref="PvpExempt"/> and <see cref="MissileIgnore"/> both
/// return false, so the predicate reduces to
/// <c>(state &amp; HAS_PHYSICS_BSP_PS) != 0</c> — no behavior change
/// from before W1.
/// </para>
///
/// <para>
@ -688,10 +734,13 @@ public sealed class Transition
/// <c>PhysicsState</c> value (as stored on
/// <c>ShadowEntry.State</c>).</param>
/// <returns>True when retail would dispatch BSP-only — i.e. when
/// the entity has <c>HAS_PHYSICS_BSP_PS</c> set; cyl/sphere shapes
/// must be skipped at the per-entry dispatch site.</returns>
/// the entity has <c>HAS_PHYSICS_BSP_PS</c> set AND neither the PvP
/// exemption nor the missile-ignore exemption applies; cyl/sphere
/// shapes must be skipped at the per-entry dispatch site.</returns>
public static bool BspOnlyDispatch(uint entityState)
=> (entityState & (uint)PhysicsStateFlags.HasPhysicsBsp) != 0;
=> (entityState & (uint)PhysicsStateFlags.HasPhysicsBsp) != 0
&& !PvpExempt()
&& !MissileIgnore();
// -----------------------------------------------------------------------
// Public entry point