acdream/tests/AcDream.Core.Tests/Physics/A6P7DispatchRulesTests.cs
Erik f02b995e4f feat(physics): port retail projectile integration
Add a pure Core projectile driver for retail clock quanta, final-state acceleration, 50-unit velocity clamping, world-space angular integration, full-3D AlignPath, and oriented Setup-local sphere sweeps. Preserve exact success versus set_frame-only failure semantics, independent Contact/OnWalkable state, and full-cell identity across all landblock directions.

Port OBJECTINFO::missile_ignore with explicit weenie/creature metadata, remove the invented transition-step cap, retain PathClipped without arming PerfectClip, and keep authoritative target identity optional. Add malformed-shape/clock, thin-wall, terrain, target-exclusion, frame-spike, failure-state, and boundary conformance coverage.

Retire TS-2 and synchronize the architecture, milestones, roadmap, research oracle, and durable physics memory.

Co-Authored-By: Codex <noreply@openai.com>
2026-07-14 11:43:11 +02:00

106 lines
4.1 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>
/// After the whole-object PvP and missile exemption gates, 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>
/// The PvP and missile terms are whole-object early exits in the port.
/// Once shape dispatch is reached, the predicate is therefore:
/// "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));
}
/// <summary>
/// A non-missile mover does not trigger the retail missile exemption.
/// Retail oracle: <c>OBJECTINFO::missile_ignore</c> pc:274385;
/// dispatch use pc:276858276861.
/// </summary>
[Fact]
public void NonMissileMover_DoesNotIgnoreOrdinaryTarget()
{
var mover = new ObjectInfo();
Assert.False(mover.MissileIgnore(
targetEntityId: 1u,
targetPhysicsState: 0u,
targetFlags: EntityCollisionFlags.HasWeenie));
}
/// <summary>
/// Guard: a BSP-flagged entity that reaches shape dispatch still uses
/// only its BSP — A6.P7 door behavior remains unchanged.
/// This protects the A6.P7 cottage-door / dungeon-wall fix.
/// </summary>
[Fact]
public void BspOnlyDispatch_DoorStateStillDispatchesBspOnly()
{
// Cottage door state from A6.P7 investigation: 0x10008 = STATIC | REPORT | HAS_BSP
const uint doorState = 0x00010008u;
Assert.True(Transition.BspOnlyDispatch(doorState));
}
}