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:
parent
a9f8775110
commit
6c7e3ef1ab
3 changed files with 110 additions and 16 deletions
|
|
@ -98,7 +98,7 @@ accepted-divergence entries (#96, #49, #50).
|
|||
|
||||
---
|
||||
|
||||
## 3. Documented approximation (AP) — 44 rows
|
||||
## 3. Documented approximation (AP) — 45 rows
|
||||
|
||||
| # | Divergence | Where (file:line) | Why it is safe / justified | Risk if assumption breaks | Retail oracle |
|
||||
|---|---|---|---|---|---|
|
||||
|
|
@ -151,6 +151,7 @@ accepted-divergence entries (#96, #49, #50).
|
|||
| AP-47 | acdream keeps the 128 nearest-to-CAMERA point lights live (`MaxGlobalLights=128`, `BuildPointLightSnapshot`) and selects per cell CAMERA-INDEPENDENTLY (by the cell's own bounds), so a building interior stays lit at any distance within a town; retail keeps only the 40 nearest-to-PLAYER static lights (`Render::max_static_lights=0x28`, distance-sorted replace-farthest `insert_light`) and re-bakes a cell when its live light set changes, so distant interiors are baked dark and "light up" only as the player approaches and their torches enter the live 40. INTENTIONAL — acdream's always-lit interiors are the preferred behavior (no 1999-era light-budget pop-in); user-confirmed 2026-06-20. | `src/AcDream.Core/Lighting/LightManager.cs` (`MaxGlobalLights=128`, `BuildPointLightSnapshot`, `SelectForObject`) | The retail pop-in is a fixed-function light-budget artifact, not an intended aesthetic; revert to retail by clamping the global set to 40 + distance-to-player sort if ever desired | Distant town interiors are lit in acdream where retail's are dark until approached — a deliberate, user-preferred divergence | `Render::max_static_lights` 0x28; `insert_light` 0x0054d1b0 (distance-sorted, replace-farthest); bake re-trigger `SetStaticLightingVertexColors` cache `burnedInStaticLights != num_static_lights` |
|
||||
| AP-48 | On a landblock (re)load, acdream re-projects its retained server-object spawns (`GameWindow._lastSpawnByGuid` — our `weenie_object_table` for world objects, carrying position + Setup + appearance) into the render world via `LandblockEntityRehydrator`. The dungeon collapse (#133/#135) and Near→Far demote drop a landblock's RENDER entities for FPS but keep the parsed spawns; ACE will NOT re-broadcast objects whose guid is still in its per-player `KnownObjects` set (never cleared on a normal teleport — ACE relies on the client retaining its table + doing its own visibility cull). Re-projecting from our own table is the retail "client keeps its weenie_object_table and re-renders from it" model. DIVERGENCE: acdream has NO retail 25 s / 384 m visibility cull — the retained spawn table is pruned ONLY by an explicit server `DeleteObject` (0xF747) or a spawn de-dup, never by distance/time. (#138) | `src/AcDream.App/Streaming/LandblockEntityRehydrator.cs` + `GameWindow.RehydrateServerEntitiesForLandblock` + `StreamingController` (`onLandblockLoaded`, Loaded + Promoted) | Re-projection from our own table is retail-faithful AND the only reliable path (ACE won't re-send a known object); fixes the #138 "doors/NPCs/portals gone after a dungeon exit" symptom independent of any server re-broadcast | An object the server silently stopped tracking WITHOUT a `DeleteObject` (e.g. a creature that wandered out of PVS) is re-hydrated at its last-known position on reload, where retail's 25 s cull would have dropped it — a stale ghost until a real `DeleteObject` or the next session. Close it by porting holtburger's 25 s/384 m `ACE_DESTRUCTION_TIMEOUT` self-cull | ACE `ObjectMaint.KnownObjects` never cleared on teleport (`Physics/Common/ObjectMaint.cs`, `WorldObjects/Player_Tracking.cs`); holtburger client 25 s cull `liveness.rs` `ACE_DESTRUCTION_TIMEOUT_SECS`; retail client weenie_object_table re-render |
|
||||
| AP-49 | `TeleportAnimSequencer.ComputeFadeAlpha` uses a smoothstep curve for all fade states; retail's `gmSmartBoxUI::UseTime` drives fade alpha through a 1024-entry `GetAnimLevel` lookup table whose contents are unrecovered | `src/AcDream.Core/World/TeleportAnimSequencer.cs` (`ComputeFadeAlpha`) | `GetAnimLevel` table address and contents not yet extracted via cdb; smoothstep is a perceptually-reasonable S-curve that closely approximates a typical gamma-corrected fade; the visual difference is imperceptible under normal teleport conditions | Fade timing differs subtly from retail — ramp-in or ramp-out may be slightly too fast/slow at the black-fade edges; retire by reading the `GetAnimLevel` table via cdb and replacing smoothstep with a direct 1024-entry lookup (spec §8) | `gmSmartBoxUI::UseTime` 0x004d6e30; `GetAnimLevel` 1024-entry table (address unrecovered — spec §8 cdb trace) |
|
||||
| AP-50 | **`CEnvCell::find_env_collisions` omits the `CObjCell::check_entry_restrictions` gate** — retail's `CEnvCell::find_env_collisions` (pc:309576) calls `CObjCell::check_entry_restrictions` (pc:309576) FIRST and returns `COLLIDED` when an access-locked cell's `restriction_obj` entity rejects the mover (`CanMoveInto` fails AND `CanBypassMoveRestrictions` is false). acdream's `FindEnvCollisions` (`src/AcDream.Core/Physics/TransitionTypes.cs:2088`) goes straight to BSP collision. | `src/AcDream.Core/Physics/TransitionTypes.cs:2088` | **No access-restriction cell data exists in acdream.** `CellPhysics` (`src/AcDream.Core/Physics/PhysicsDataCache.cs:540`) has no `restriction_obj` field; `DatReaderWriter` does not model per-cell access locks; no weenie-object-table exists on the client for the gate's `CanMoveInto` / `CanBypassMoveRestrictions` dispatch. The gap is **inert** in all dev content (ACE starter area has no access-locked env cells). | If access-locked dungeon cells are ever modeled (dat + wire), the player will walk through the restriction barrier without being blocked — wrong PK/housing gating. | `CObjCell::check_entry_restrictions` pc:309576; `CEnvCell::find_env_collisions` pc:309573–309597 |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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:276808–276841</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:276808–276841.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal static bool PvpExempt() => false; // wire when PK ships (M2+) — pc:276808–276841
|
||||
|
||||
/// <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 + :276858–276861</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:276858–276861.
|
||||
/// </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 & 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 & 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
|
||||
|
|
|
|||
|
|
@ -75,4 +75,48 @@ public class A6P7DispatchRulesTests
|
|||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue