diff --git a/src/AcDream.App/Input/PlayerMovementController.cs b/src/AcDream.App/Input/PlayerMovementController.cs
index 8084231a..d3c25c46 100644
--- a/src/AcDream.App/Input/PlayerMovementController.cs
+++ b/src/AcDream.App/Input/PlayerMovementController.cs
@@ -413,13 +413,7 @@ public sealed class PlayerMovementController
stepUpHeight: StepUpHeight,
stepDownHeight: 0.04f, // retail default
isOnGround: _body.OnWalkable,
- body: _body, // persist ContactPlane across frames for slope tracking
- // Commit C 2026-04-29 — local player is always IsPlayer.
- // The PK/PKLite/Impenetrable bits come from PlayerDescription's
- // PlayerKillerStatus property; not yet parsed (non-PK pair → walks
- // through other non-PK players, which is retail's default for
- // ACE's character creation defaults too).
- moverFlags: AcDream.Core.Physics.ObjectInfoState.IsPlayer);
+ body: _body); // persist ContactPlane across frames for slope tracking
// Apply resolved position.
_body.Position = resolveResult.Position;
diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs
index ba3c9781..2b9bfb0e 100644
--- a/src/AcDream.App/Rendering/GameWindow.cs
+++ b/src/AcDream.App/Rendering/GameWindow.cs
@@ -2067,18 +2067,6 @@ public sealed class GameWindow : IDisposable
// UpdateMotion / UpdatePosition events can reseat this entity by guid.
_entitiesByServerGuid[spawn.Guid] = entity;
- // Commit B 2026-04-29 — live-entity collision registration. The
- // local player is the simulator (its PhysicsBody is the source of
- // truth for our own movement); only remotes register as targets.
- // Phantom-Setup entities (no CylSpheres / no Spheres / no Radius)
- // are deliberately skipped — retail FUN's `FindObjCollisions`
- // falls through to OK_TS for any object with no collision
- // geometry (acclient_2013_pseudo_c.txt:276917,276987).
- if (spawn.Guid != _playerServerGuid)
- {
- RegisterLiveEntityCollision(entity, setup, spawn, origin);
- }
-
// Phase B.2: capture the server-sent MotionTableId for our own
// character so UpdatePlayerAnimation can pass it to GetIdleCycle.
// The Setup's DefaultMotionTable is often 0 for human characters;
@@ -2296,100 +2284,6 @@ public sealed class GameWindow : IDisposable
}
}
- ///
- /// Commit B 2026-04-29 — register a live (server-spawned) entity into
- /// the as a single collision body.
- /// One entry per entity (in contrast to static scenery's per-CylSphere
- /// registration) so RemoveLiveEntityByServerGuid's single
- /// Deregister(entity.Id) cleans it up without leaks.
- ///
- ///
- /// Geometry-priority order matches retail
- /// (acclient_2013_pseudo_c.txt:276858-276987): CylSpheres >
- /// Sphere fallback > Setup.Radius. Phantom Setups (no shape) are
- /// rejected — retail's FindObjCollisions falls through to
- /// OK_TS in that case.
- ///
- ///
- ///
- /// Carries derived from the PWD
- /// bitfield (acclient.h:6431-6463) plus IsCreature
- /// derived from the inbound ItemType. Commit C consumes these in
- /// the PvP exemption block.
- ///
- ///
- private void RegisterLiveEntityCollision(
- AcDream.Core.World.WorldEntity entity,
- DatReaderWriter.DBObjs.Setup setup,
- AcDream.Core.Net.WorldSession.EntitySpawn spawn,
- System.Numerics.Vector3 origin)
- {
- if (spawn.Position is null) return;
-
- bool hasCyl = setup.CylSpheres.Count > 0;
- bool hasSphere = setup.Spheres.Count > 0;
- bool hasRadius = setup.Radius > 0.0001f;
-
- // Retail-faithful phantom skip (acclient_2013_pseudo_c.txt:276917).
- if (!hasCyl && !hasSphere && !hasRadius)
- return;
-
- float entScale = spawn.ObjScale ?? 1.0f;
- float radius;
- float height;
-
- if (hasCyl)
- {
- // Pick the largest CylSphere as the body cylinder. Retail
- // tests every CylSphere in turn (276891) but for collision
- // BLOCKING the largest is sufficient — the player will stop
- // at the body's outer radius.
- var sph = setup.CylSpheres[0];
- for (int i = 1; i < setup.CylSpheres.Count; i++)
- {
- if (setup.CylSpheres[i].Radius > sph.Radius) sph = setup.CylSpheres[i];
- }
- radius = sph.Radius * entScale;
- height = (sph.Height > 0 ? sph.Height : sph.Radius * 4f) * entScale;
- }
- else if (hasRadius)
- {
- radius = setup.Radius * entScale;
- height = (setup.Height > 0 ? setup.Height : setup.Radius * 2f) * entScale;
- }
- else
- {
- // Sphere-only: largest sphere as a Cylinder approximation.
- var sph = setup.Spheres[0];
- for (int i = 1; i < setup.Spheres.Count; i++)
- {
- if (setup.Spheres[i].Radius > sph.Radius) sph = setup.Spheres[i];
- }
- radius = sph.Radius * entScale;
- height = sph.Radius * 2f * entScale;
- }
-
- if (radius <= 0f) return;
-
- // Decode PvP / Player / Impenetrable from PWD._bitfield.
- // IsCreature comes from the spawn's ItemType (server-known type).
- var flags = AcDream.Core.Physics.EntityCollisionFlags.None;
- if (spawn.ObjectDescriptionFlags is { } odf)
- flags = AcDream.Core.Physics.EntityCollisionFlagsExt.FromPwdBitfield(odf);
- if (spawn.ItemType == (uint)AcDream.Core.Items.ItemType.Creature)
- flags |= AcDream.Core.Physics.EntityCollisionFlags.IsCreature;
-
- uint state = spawn.PhysicsState ?? 0u;
-
- _physicsEngine.ShadowObjects.Register(
- entity.Id, entity.SourceGfxObjOrSetupId,
- entity.Position, entity.Rotation, radius,
- origin.X, origin.Y, spawn.Position.Value.LandblockId,
- AcDream.Core.Physics.ShadowCollisionType.Cylinder,
- cylHeight: height, scale: 1.0f,
- state: state, flags: flags);
- }
-
private bool RemoveLiveEntityByServerGuid(uint serverGuid, bool logDelete)
{
if (!_entitiesByServerGuid.TryGetValue(serverGuid, out var existingEntity))
@@ -3086,18 +2980,6 @@ public sealed class GameWindow : IDisposable
entity.Position = worldPos;
entity.Rotation = rot;
- // Commit B 2026-04-29 — keep the shadow registry in sync with
- // server-authoritative position so the player's collision broadphase
- // tests against the up-to-date target body. Skip the local player
- // (its body is the simulator, not a target). Retail does the
- // equivalent via SetPosition → change_cell → AddShadowObject
- // (acclient_2013_pseudo_c.txt:284276 / 281200 / 282862).
- if (update.Guid != _playerServerGuid)
- {
- _physicsEngine.ShadowObjects.UpdatePosition(
- entity.Id, worldPos, rot, origin.X, origin.Y, p.LandblockId);
- }
-
// Track remote-entity motion for stop detection. Only record the
// timestamp when position moved MEANINGFULLY (> 0.05m). Updates
// that report the same position keep the old Time, so the
diff --git a/src/AcDream.Core.Net/Messages/CreateObject.cs b/src/AcDream.Core.Net/Messages/CreateObject.cs
index d574887b..39b30cde 100644
--- a/src/AcDream.Core.Net/Messages/CreateObject.cs
+++ b/src/AcDream.Core.Net/Messages/CreateObject.cs
@@ -84,24 +84,6 @@ public static class CreateObject
/// SetupTableId are nullable because their corresponding
/// physics-description-flag bits may not be set on every CreateObject.
///
- ///
- ///
- /// (acclient.h:2815) carries flag
- /// bits like ETHEREAL_PS=0x4, IGNORE_COLLISIONS_PS=0x10,
- /// HAS_PHYSICS_BSP_PS=0x10000 — the bits retail's
- /// FindObjCollisions reads to short-circuit ethereal /
- /// no-collision entities. Pre-2026-04-29 (Commit A of the live-entity
- /// collision port) the parser silently dropped this field.
- ///
- ///
- /// is the PWD._bitfield
- /// trailer (acclient.h:6431-6463) — bits include BF_PLAYER (0x8),
- /// BF_PLAYER_KILLER (0x20), BF_FREE_PKSTATUS (0x200000),
- /// BF_PKLITE_PKSTATUS (0x2000000). Decoded into
- /// EntityCollisionFlags at registration time for the PvP
- /// exemption gate.
- ///
- ///
public readonly record struct Parsed(
uint Guid,
ServerPosition? Position,
@@ -118,9 +100,7 @@ public static class CreateObject
ushort InstanceSequence = 0,
ushort TeleportSequence = 0,
ushort ServerControlSequence = 0,
- ushort ForcePositionSequence = 0,
- uint? PhysicsState = null,
- uint? ObjectDescriptionFlags = null);
+ ushort ForcePositionSequence = 0);
///
/// The relevant subset of the server-sent MovementData /
@@ -280,12 +260,6 @@ public static class CreateObject
float? objScale = null;
ServerMotionState? motionState = null;
uint? motionTableId = null;
- // Commit A 2026-04-29 — live-entity collision plumbing. PhysicsState
- // (acclient.h:2815) was previously skipped at line ~337; the PWD
- // _bitfield (acclient.h:6431-6463) was previously discarded as
- // "ObjectDescriptionFlags" at the WeenieHeader trailer.
- uint? physicsState = null;
- uint? objectDescriptionFlags = null;
try
{
@@ -360,10 +334,7 @@ public static class CreateObject
if (body.Length - pos < 8) return null;
var physicsFlags = (PhysicsDescriptionFlag)BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(pos));
pos += 4;
- // PhysicsState (acclient.h:2815). Previously skipped, now
- // surfaced for live-entity collision (Commit A 2026-04-29).
- physicsState = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(pos));
- pos += 4;
+ pos += 4; // PhysicsState (skip)
if ((physicsFlags & PhysicsDescriptionFlag.Movement) != 0)
{
@@ -492,16 +463,7 @@ public static class CreateObject
if (body.Length - pos >= 4)
itemType = ReadU32(body, ref pos);
if (body.Length - pos >= 4)
- {
- // ObjectDescriptionFlags = retail PWD._bitfield
- // (acclient.h:6431-6463). Carries BF_PLAYER (0x8),
- // BF_PLAYER_KILLER (0x20), BF_FREE_PKSTATUS (0x200000),
- // BF_PKLITE_PKSTATUS (0x2000000) — the bits that
- // acclient_2013_pseudo_c.txt:406898-406918 read for
- // IsPK() / IsPKLite() / IsImpenetrable(). Previously
- // discarded; now surfaced for the PvP collision rule.
- objectDescriptionFlags = ReadU32(body, ref pos);
- }
+ _ = ReadU32(body, ref pos); // ObjectDescriptionFlags
AlignTo4(ref pos);
}
catch { /* truncated name — partial result is still useful */ }
@@ -509,15 +471,13 @@ public static class CreateObject
return new Parsed(guid, position, setupTableId, animParts,
textureChanges, subPalettes, basePaletteId, objScale, name, itemType, motionState, motionTableId,
- instanceSeq, teleportSeq, serverControlSeq, forcePositionSeq,
- physicsState, objectDescriptionFlags);
+ instanceSeq, teleportSeq, serverControlSeq, forcePositionSeq);
// Local helper: if we ran out of fields past PhysicsData, still
// return the useful prefix (guid/position/setup/animParts/textures/palettes/scale/motion).
Parsed PartialResult() => new(
guid, position, setupTableId, animParts,
- textureChanges, subPalettes, basePaletteId, objScale, null, null, motionState, motionTableId,
- PhysicsState: physicsState, ObjectDescriptionFlags: objectDescriptionFlags);
+ textureChanges, subPalettes, basePaletteId, objScale, null, null, motionState, motionTableId);
}
catch
{
diff --git a/src/AcDream.Core.Net/WorldSession.cs b/src/AcDream.Core.Net/WorldSession.cs
index 030573b8..885ec634 100644
--- a/src/AcDream.Core.Net/WorldSession.cs
+++ b/src/AcDream.Core.Net/WorldSession.cs
@@ -56,14 +56,7 @@ public sealed class WorldSession : IDisposable
string? Name,
uint? ItemType,
CreateObject.ServerMotionState? MotionState,
- uint? MotionTableId,
- // Commit A 2026-04-29 — live-entity collision plumbing.
- // PhysicsState: retail acclient.h:2815 (ETHEREAL_PS=0x4,
- // IGNORE_COLLISIONS_PS=0x10, HAS_PHYSICS_BSP_PS=0x10000, ...).
- // ObjectDescriptionFlags: retail PWD._bitfield (acclient.h:6431-6463)
- // — drives IsPlayer/IsPK/IsPKLite/IsImpenetrable for PvP gating.
- uint? PhysicsState = null,
- uint? ObjectDescriptionFlags = null);
+ uint? MotionTableId);
/// Fires when the session finishes parsing a CreateObject.
public event Action? EntitySpawned;
@@ -655,9 +648,7 @@ public sealed class WorldSession : IDisposable
parsed.Value.Name,
parsed.Value.ItemType,
parsed.Value.MotionState,
- parsed.Value.MotionTableId,
- parsed.Value.PhysicsState,
- parsed.Value.ObjectDescriptionFlags));
+ parsed.Value.MotionTableId));
}
}
else if (op == DeleteObject.Opcode)
diff --git a/src/AcDream.Core/Physics/CollisionExemption.cs b/src/AcDream.Core/Physics/CollisionExemption.cs
deleted file mode 100644
index 89b368d6..00000000
--- a/src/AcDream.Core/Physics/CollisionExemption.cs
+++ /dev/null
@@ -1,123 +0,0 @@
-namespace AcDream.Core.Physics;
-
-///
-/// The retail-faithful exemption gate at the top of
-/// CPhysicsObj::FindObjCollisions. Decides — based on the moving
-/// object's bits and the target's raw
-/// PhysicsState + decoded —
-/// whether collision against the target should be skipped entirely
-/// (return OK_TS) or proceed to broad-phase / shape dispatch.
-///
-///
-/// Ported from the named retail decompilation:
-///
-///
-/// - acclient_2013_pseudo_c.txt:276782 — target
-/// ETHEREAL_PS=0x4 & IGNORE_COLLISIONS_PS=0x10: walk through.
-/// - acclient_2013_pseudo_c.txt:276787 — viewer mover vs
-/// creature target: walk through (camera ray ignores creatures).
-/// - acclient_2013_pseudo_c.txt:276971 — mover with
-/// IGNORE_CREATURES (state & 0x400) vs creature target:
-/// walk through.
-/// - acclient_2013_pseudo_c.txt:276807-276839 — PvP rule:
-///
-/// If both are players: skip unless target is Impenetrable,
-/// or both are PK, or both are PKLite. Mismatched PK status (PK vs
-/// non-PK, PK vs PKLite) is exempted — players in different pools
-/// pass through each other, matching retail muscle memory.
-///
-///
-///
-///
-///
-/// Cross-checked against ACE
-/// references/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:381-405
-/// (line-for-line C# port of the same logic). Note: ACE adds
-/// state.HasFlag(IsImpenetrable) (mover-impenetrable) to the
-/// collide list; retail's pseudo-C only checks the target's
-/// IsImpenetrable(). acdream follows retail.
-///
-///
-public static class CollisionExemption
-{
- private const uint ETHEREAL_PS = 0x4u; // acclient.h:2819
- private const uint IGNORE_COLLISIONS_PS = 0x10u; // acclient.h:2821
-
- ///
- /// Should the moving object skip collision testing against this
- /// target entirely? Returns true if exempt (no further
- /// shape dispatch).
- ///
- /// Raw retail PhysicsState bits
- /// captured at CreateObject time (ETHEREAL/IGNORE/etc.).
- /// Decoded
- /// from the target's PWD bitfield
- /// plus its ItemType-derived IsCreature bit.
- /// The moving object's
- /// — typically the local player's
- /// IsPlayer + (PK/PKLite/Impenetrable bits if known) flags.
- public static bool ShouldSkip(uint targetState, EntityCollisionFlags targetFlags,
- ObjectInfoState moverState)
- {
- // 1. Target ETHEREAL + IGNORE_COLLISIONS → walk through.
- // acclient_2013_pseudo_c.txt:276782 — wraps the entire body of
- // FindObjCollisions; we hoist it as the first early-out.
- if ((targetState & ETHEREAL_PS) != 0
- && (targetState & IGNORE_COLLISIONS_PS) != 0)
- {
- return true;
- }
-
- // 2. Viewer mover + creature target → walk through.
- // acclient_2013_pseudo_c.txt:276787-276790.
- bool moverIsViewer = (moverState & ObjectInfoState.IsViewer) != 0;
- bool targetIsCreature = (targetFlags & EntityCollisionFlags.IsCreature) != 0;
- if (moverIsViewer && targetIsCreature)
- return true;
-
- // 3. IGNORE_CREATURES mover + creature target → walk through.
- // acclient_2013_pseudo_c.txt:276971.
- bool moverIgnoresCreatures = (moverState & ObjectInfoState.IgnoreCreatures) != 0;
- if (moverIgnoresCreatures && targetIsCreature)
- return true;
-
- // 4. PvP exemption block.
- // acclient_2013_pseudo_c.txt:276807-276839.
- bool moverIsPlayer = (moverState & ObjectInfoState.IsPlayer) != 0;
- bool targetIsPlayer = (targetFlags & EntityCollisionFlags.IsPlayer) != 0;
- if (moverIsPlayer && targetIsPlayer)
- {
- // Tentatively exempt (retail `ebp_1 = 1`). Then disqualify
- // if any of the COLLIDE conditions hold.
- bool collide = false;
-
- // 4a. Impenetrable target → collide.
- // acclient_2013_pseudo_c.txt:276826.
- if ((targetFlags & EntityCollisionFlags.IsImpenetrable) != 0)
- collide = true;
-
- // 4b. Both PK → collide.
- // acclient_2013_pseudo_c.txt:276832-276836.
- if (!collide
- && (moverState & ObjectInfoState.IsPK) != 0
- && (targetFlags & EntityCollisionFlags.IsPK) != 0)
- {
- collide = true;
- }
-
- // 4c. Both PKLite → collide.
- // acclient_2013_pseudo_c.txt:276837.
- if (!collide
- && (moverState & ObjectInfoState.IsPKLite) != 0
- && (targetFlags & EntityCollisionFlags.IsPKLite) != 0)
- {
- collide = true;
- }
-
- if (!collide)
- return true; // exempt — non-PK pair walks through
- }
-
- return false; // proceed to broad-phase + shape dispatch
- }
-}
diff --git a/src/AcDream.Core/Physics/EntityCollisionFlags.cs b/src/AcDream.Core/Physics/EntityCollisionFlags.cs
deleted file mode 100644
index 5ee86a85..00000000
--- a/src/AcDream.Core/Physics/EntityCollisionFlags.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-namespace AcDream.Core.Physics;
-
-///
-/// Per-entity flags driving the retail-faithful PvP / Player /
-/// Impenetrable exemption logic in FindObjCollisions. Decoded
-/// from PublicWeenieDesc._bitfield at CreateObject time.
-///
-///
-/// Bit positions (verified against
-/// docs/research/named-retail/acclient_2013_pseudo_c.txt:406898-406918
-/// where ACCWeenieObject::IsPK/IsPKLite/IsImpenetrable read directly
-/// from this->pwd._bitfield):
-///
-///
-/// - BF_PLAYER = 0x8 (bit 3) →
-/// - BF_PLAYER_KILLER = 0x20 (bit 5) →
-/// - BF_FREE_PKSTATUS = 0x200000 (bit 21) →
-/// - BF_PKLITE_PKSTATUS = 0x2000000 (bit 25) →
-///
-///
-///
-/// is NOT a PWD bit — retail derives it from
-/// PublicWeenieDesc._type matching ITEM_TYPE_CREATURE
-/// (acclient.h ITEM_TYPE enum). Set at registration time by callers that
-/// already know the item type.
-///
-///
-[Flags]
-public enum EntityCollisionFlags : byte
-{
- None = 0x00,
- /// Set when BF_PLAYER (0x8) is set in pwd._bitfield.
- IsPlayer = 0x01,
- /// Derived from ItemType.Creature on the spawn payload.
- IsCreature = 0x02,
- /// Set when BF_PLAYER_KILLER (0x20) is set.
- IsPK = 0x04,
- /// Set when BF_PKLITE_PKSTATUS (0x2000000) is set.
- IsPKLite = 0x08,
- /// Set when BF_FREE_PKSTATUS (0x200000) is set (a.k.a. "Free" PK status — cannot be PKed).
- IsImpenetrable = 0x10,
-}
-
-/// Helpers to convert raw retail bitfields into .
-public static class EntityCollisionFlagsExt
-{
- ///
- /// Decode the player/PK/PKLite/Impenetrable bits from a
- /// PublicWeenieDesc._bitfield value (the WeenieHeader trailer
- /// field acdream's parser surfaces as ObjectDescriptionFlags).
- ///
- /// Bit positions per
- /// docs/research/named-retail/acclient.h:6431-6463
- /// (PublicWeenieDesc::BitfieldIndex) and
- /// acclient_2013_pseudo_c.txt:441868-441890
- /// (PublicWeenieDesc::SetPlayerKillerStatus).
- ///
- public static EntityCollisionFlags FromPwdBitfield(uint bitfield)
- {
- var flags = EntityCollisionFlags.None;
- if ((bitfield & 0x8u) != 0) flags |= EntityCollisionFlags.IsPlayer;
- if ((bitfield & 0x20u) != 0) flags |= EntityCollisionFlags.IsPK;
- if ((bitfield & 0x200000u) != 0) flags |= EntityCollisionFlags.IsImpenetrable;
- if ((bitfield & 0x2000000u) != 0) flags |= EntityCollisionFlags.IsPKLite;
- return flags;
- }
-}
diff --git a/src/AcDream.Core/Physics/PhysicsEngine.cs b/src/AcDream.Core/Physics/PhysicsEngine.cs
index 4a934031..bb26c54a 100644
--- a/src/AcDream.Core/Physics/PhysicsEngine.cs
+++ b/src/AcDream.Core/Physics/PhysicsEngine.cs
@@ -388,21 +388,13 @@ public sealed class PhysicsEngine
float sphereRadius, float sphereHeight,
float stepUpHeight, float stepDownHeight,
bool isOnGround,
- PhysicsBody? body = null,
- ObjectInfoState moverFlags = ObjectInfoState.None)
+ PhysicsBody? body = null)
{
var transition = new Transition();
transition.ObjectInfo.StepUpHeight = stepUpHeight;
transition.ObjectInfo.StepDownHeight = stepDownHeight;
transition.ObjectInfo.StepDown = true;
- // Commit C 2026-04-29 — caller-supplied mover flags drive the
- // retail PvP exemption block in FindObjCollisions. The local
- // player passes IsPlayer (and PK/PKLite/Impenetrable when known
- // from PlayerDescription); remote dead-reckoning passes None
- // (matches non-player movement, all targets collide).
- transition.ObjectInfo.State |= moverFlags;
-
if (isOnGround)
transition.ObjectInfo.State |= ObjectInfoState.Contact | ObjectInfoState.OnWalkable;
diff --git a/src/AcDream.Core/Physics/ShadowObjectRegistry.cs b/src/AcDream.Core/Physics/ShadowObjectRegistry.cs
index 6b4ea114..e7c29507 100644
--- a/src/AcDream.Core/Physics/ShadowObjectRegistry.cs
+++ b/src/AcDream.Core/Physics/ShadowObjectRegistry.cs
@@ -19,24 +19,11 @@ public sealed class ShadowObjectRegistry
///
/// Register an entity into the cells it overlaps based on world position + radius.
- ///
- ///
- /// The optional +
- /// parameters carry retail PhysicsState bits and decoded
- /// respectively, so the
- /// FindObjCollisions retail-faithful exemption block (PvP rule,
- /// ETHEREAL skip, viewer-vs-creature) can short-circuit without an
- /// extra lookup. Default state=0 + flags=None preserves
- /// the original "static decoration" behavior — the existing 5
- /// landblock-entity registration sites pass nothing.
- ///
///
public void Register(uint entityId, uint gfxObjId, Vector3 worldPos, Quaternion rotation,
float radius, float worldOffsetX, float worldOffsetY, uint landblockId,
ShadowCollisionType collisionType = ShadowCollisionType.BSP,
- float cylHeight = 0f, float scale = 1.0f,
- uint state = 0u,
- EntityCollisionFlags flags = EntityCollisionFlags.None)
+ float cylHeight = 0f, float scale = 1.0f)
{
Deregister(entityId);
@@ -51,8 +38,7 @@ public sealed class ShadowObjectRegistry
int minCy = Math.Max(0, (int)((localY - radius) / 24f));
int maxCy = Math.Min(7, (int)((localY + radius) / 24f));
- var entry = new ShadowEntry(entityId, gfxObjId, worldPos, rotation, radius,
- collisionType, cylHeight, scale, state, flags);
+ var entry = new ShadowEntry(entityId, gfxObjId, worldPos, rotation, radius, collisionType, cylHeight, scale);
var cellIds = new List();
uint lbPrefix = landblockId & 0xFFFF0000u;
@@ -76,56 +62,6 @@ public sealed class ShadowObjectRegistry
_entityToCells[entityId] = cellIds;
}
- ///
- /// Update an already-registered entity's world position + rotation,
- /// preserving its ,
- /// , and shape parameters.
- ///
- ///
- /// Cheaper than + for
- /// the 5–10 Hz UpdatePosition (0xF748) stream the server emits
- /// per visible entity: this is the path retail's
- /// CPhysicsObj::SetPosition takes (cited at
- /// acclient_2013_pseudo_c.txt:284276) — same shape, new cell
- /// membership. If the entity isn't already registered, this is a
- /// no-op so callers don't have to gate.
- ///
- ///
- public void UpdatePosition(uint entityId, Vector3 worldPos, Quaternion rotation,
- float worldOffsetX, float worldOffsetY, uint landblockId)
- {
- // Find the existing entry (any cell holds a copy with the same
- // entity-scoped state + flags + shape).
- if (!_entityToCells.TryGetValue(entityId, out var oldCells) || oldCells.Count == 0)
- return;
-
- ShadowEntry? template = null;
- foreach (var oldCellId in oldCells)
- {
- if (_cells.TryGetValue(oldCellId, out var list))
- {
- foreach (var e in list)
- {
- if (e.EntityId == entityId)
- {
- template = e;
- break;
- }
- }
- }
- if (template is not null) break;
- }
- if (template is null)
- return;
-
- // Preserve everything except position + rotation.
- var t = template.Value;
- Register(entityId, t.GfxObjId, worldPos, rotation, t.Radius,
- worldOffsetX, worldOffsetY, landblockId,
- t.CollisionType, t.CylHeight, t.Scale,
- t.State, t.Flags);
- }
-
/// Remove an entity from all cells it was registered in.
public void Deregister(uint entityId)
{
@@ -267,18 +203,4 @@ public readonly record struct ShadowEntry(
float Radius,
ShadowCollisionType CollisionType = ShadowCollisionType.BSP,
float CylHeight = 0f,
- float Scale = 1.0f,
- ///
- /// Retail PhysicsState bits (acclient.h:2815). Used
- /// by FindObjCollisions to honor ETHEREAL_PS=0x4 +
- /// IGNORE_COLLISIONS_PS=0x10 short-circuits. Zero for static
- /// landblock entities (default behavior matches pre-Commit-A).
- ///
- uint State = 0u,
- ///
- /// Decoded player / PK / PKLite / Impenetrable flags driving the
- /// retail PvP exemption block in FindObjCollisions. Built
- /// from PWD._bitfield at CreateObject time via
- /// .
- ///
- EntityCollisionFlags Flags = EntityCollisionFlags.None);
+ float Scale = 1.0f);
diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs
index 57d6e6d2..62a9c8bf 100644
--- a/src/AcDream.Core/Physics/TransitionTypes.cs
+++ b/src/AcDream.Core/Physics/TransitionTypes.cs
@@ -34,13 +34,6 @@ public enum ObjectInfoState : uint
IsPlayer = 0x100,
EdgeSlide = 0x200,
IgnoreCreatures = 0x400,
- /// Bits added 2026-04-29 (Commit A live-entity collision):
- /// the moving object's PK status drives the player-vs-player
- /// exemption block in FindObjCollisions per
- /// acclient_2013_pseudo_c.txt:276807-276839. Values match
- /// retail's OBJECTINFO::state bits (acclient.h:6190-6194).
- IsPK = 0x800,
- IsPKLite = 0x1000,
}
///
@@ -807,17 +800,6 @@ public sealed class Transition
if (distToCurr > maxReach)
continue;
- // Commit C 2026-04-29 — retail exemption block at the top of
- // CPhysicsObj::FindObjCollisions
- // (acclient_2013_pseudo_c.txt:276782-276839,276971). Skips
- // ethereal+ignore-collisions, viewer-vs-creature,
- // IGNORE_CREATURES movers, and the player-vs-player PvP
- // pass-through (non-PK pair / mismatched PK). Static
- // landblock entries register with State=0 and Flags=None,
- // so this is a cheap fall-through for them.
- if (CollisionExemption.ShouldSkip(obj.State, obj.Flags, ObjectInfo.State))
- continue;
-
TransitionState result;
if (obj.CollisionType == ShadowCollisionType.BSP)
diff --git a/tests/AcDream.Core.Net.Tests/Messages/CreateObjectTests.cs b/tests/AcDream.Core.Net.Tests/Messages/CreateObjectTests.cs
index 71bf72db..a7dea334 100644
--- a/tests/AcDream.Core.Net.Tests/Messages/CreateObjectTests.cs
+++ b/tests/AcDream.Core.Net.Tests/Messages/CreateObjectTests.cs
@@ -23,63 +23,10 @@ public sealed class CreateObjectTests
Assert.Equal((uint)ItemType.Creature, parsed.Value.ItemType);
}
- // -----------------------------------------------------------------------
- // Commit A of 2026-04-29 live-entity collision port:
- // PhysicsState (post-flags u32) + ObjectDescriptionFlags (PWD bitfield)
- // must be surfaced for downstream collision registration.
- // -----------------------------------------------------------------------
-
- [Fact]
- public void TryParse_PhysicsState_Parsed()
- {
- // ETHEREAL_PS = 0x4 + IGNORE_COLLISIONS_PS = 0x10 → 0x14
- byte[] body = BuildMinimalCreateObjectWithWeenieHeader(
- guid: 0x50000003u, name: "GhostNpc",
- itemType: (uint)ItemType.Creature,
- physicsState: 0x14u);
-
- var parsed = CreateObject.TryParse(body);
-
- Assert.NotNull(parsed);
- Assert.Equal(0x14u, parsed!.Value.PhysicsState);
- }
-
- [Fact]
- public void TryParse_ObjectDescriptionFlags_PlayerKillerBitsSurface()
- {
- // BF_PLAYER (0x8) | BF_PLAYER_KILLER (0x20) → a PK player.
- byte[] body = BuildMinimalCreateObjectWithWeenieHeader(
- guid: 0x50000004u, name: "+PkPlayer",
- itemType: (uint)ItemType.Creature,
- objectDescriptionFlags: 0x8u | 0x20u);
-
- var parsed = CreateObject.TryParse(body);
-
- Assert.NotNull(parsed);
- Assert.Equal(0x28u, parsed!.Value.ObjectDescriptionFlags);
- }
-
- [Fact]
- public void TryParse_ObjectDescriptionFlags_PkLiteBit()
- {
- // BF_PLAYER (0x8) | BF_PKLITE_PKSTATUS (0x2000000) → a PK-lite player.
- byte[] body = BuildMinimalCreateObjectWithWeenieHeader(
- guid: 0x50000005u, name: "+PklPlayer",
- itemType: (uint)ItemType.Creature,
- objectDescriptionFlags: 0x8u | 0x2000000u);
-
- var parsed = CreateObject.TryParse(body);
-
- Assert.NotNull(parsed);
- Assert.Equal(0x2000008u, parsed!.Value.ObjectDescriptionFlags);
- }
-
private static byte[] BuildMinimalCreateObjectWithWeenieHeader(
uint guid,
string name,
- uint itemType,
- uint physicsState = 0,
- uint objectDescriptionFlags = 0)
+ uint itemType)
{
var bytes = new List();
WriteU32(bytes, CreateObject.Opcode);
@@ -91,9 +38,9 @@ public sealed class CreateObjectTests
bytes.Add(0);
bytes.Add(0);
- // PhysicsData: physics flags = 0, then PhysicsState u32, then 9 seq stamps.
+ // PhysicsData: no flags, empty physics state, then 9 sequence stamps.
+ WriteU32(bytes, 0);
WriteU32(bytes, 0);
- WriteU32(bytes, physicsState);
for (int i = 0; i < 9; i++)
WriteU16(bytes, 0);
Align4(bytes);
@@ -104,7 +51,7 @@ public sealed class CreateObjectTests
WritePackedDword(bytes, 0x1234); // WeenieClassId
WritePackedDword(bytes, 0); // IconId via known-type writer
WriteU32(bytes, itemType);
- WriteU32(bytes, objectDescriptionFlags);
+ WriteU32(bytes, 0); // ObjectDescriptionFlags
Align4(bytes);
return bytes.ToArray();
diff --git a/tests/AcDream.Core.Tests/Physics/CollisionExemptionTests.cs b/tests/AcDream.Core.Tests/Physics/CollisionExemptionTests.cs
deleted file mode 100644
index b8431658..00000000
--- a/tests/AcDream.Core.Tests/Physics/CollisionExemptionTests.cs
+++ /dev/null
@@ -1,169 +0,0 @@
-using AcDream.Core.Physics;
-using Xunit;
-
-namespace AcDream.Core.Tests.Physics;
-
-///
-/// Unit tests for — Commit C of the
-/// 2026-04-29 live-entity collision port. Covers retail's
-/// CPhysicsObj::FindObjCollisions exemption block, ported
-/// line-for-line from
-/// docs/research/named-retail/acclient_2013_pseudo_c.txt:276782-276839,276971.
-///
-///
-/// Behaviour matrix (target / mover columns):
-///
-///
-/// MoverTargetSkip?
-/// - anyETHEREAL+IGNORE_COLLISIONSYES (early-out)
-/// - IsViewerIsCreatureYES (camera ray-test passes through)
-/// - IGNORE_CREATURESIsCreatureYES (mover walks through creatures)
-/// - IsPlayerIsPlayer (no PK)YES (non-PK pair walks through)
-/// - IsPlayer + IsPKIsPlayer + IsPKNO (PK pair collides)
-/// - IsPlayer + IsPKLiteIsPlayer + IsPKLiteNO (PKLite pair collides)
-/// - IsPlayerIsPlayer + IsImpenetrableNO (Impenetrable target always collides)
-/// - IsPlayer + IsPKIsPlayer (no PK)YES (mismatched PK skip)
-/// - IsPlayerIsCreature (NPC)NO (player vs NPC always collides)
-///
-///
-public class CollisionExemptionTests
-{
- private const uint ETHEREAL_PS = 0x4u;
- private const uint IGNORE_COLLISIONS_PS = 0x10u;
-
- [Fact]
- public void EtherealAndIgnoreCollisions_AlwaysSkipped()
- {
- // Target with both bits set is exempted from any mover.
- Assert.True(CollisionExemption.ShouldSkip(
- targetState: ETHEREAL_PS | IGNORE_COLLISIONS_PS,
- targetFlags: EntityCollisionFlags.None,
- moverState: ObjectInfoState.IsPlayer));
- }
-
- [Fact]
- public void EtherealOnly_NotSkipped()
- {
- // Target with ETHEREAL but NOT IGNORE_COLLISIONS does not bail
- // out at the first gate — collision proceeds. (Step-down marks
- // obstruction_ethereal, but does not exempt.)
- Assert.False(CollisionExemption.ShouldSkip(
- targetState: ETHEREAL_PS,
- targetFlags: EntityCollisionFlags.None,
- moverState: ObjectInfoState.IsPlayer));
- }
-
- [Fact]
- public void Viewer_VsCreature_Skipped()
- {
- // Camera-ray viewer transitions through creatures.
- Assert.True(CollisionExemption.ShouldSkip(
- targetState: 0u,
- targetFlags: EntityCollisionFlags.IsCreature,
- moverState: ObjectInfoState.IsViewer));
- }
-
- [Fact]
- public void Viewer_VsNonCreature_NotSkipped()
- {
- Assert.False(CollisionExemption.ShouldSkip(
- targetState: 0u,
- targetFlags: EntityCollisionFlags.None,
- moverState: ObjectInfoState.IsViewer));
- }
-
- [Fact]
- public void IgnoreCreatures_VsCreature_Skipped()
- {
- // Per acclient_2013_pseudo_c.txt:276971 — an arrow with
- // IGNORE_CREATURES doesn't get blocked by the very monster it's
- // tracking towards (until missile_ignore filters its target).
- Assert.True(CollisionExemption.ShouldSkip(
- targetState: 0u,
- targetFlags: EntityCollisionFlags.IsCreature,
- moverState: ObjectInfoState.IgnoreCreatures));
- }
-
- [Fact]
- public void NonPkPlayer_VsNonPkPlayer_Skipped()
- {
- // The user-visible payoff: two ordinary players walk through each
- // other instead of blocking.
- Assert.True(CollisionExemption.ShouldSkip(
- targetState: 0u,
- targetFlags: EntityCollisionFlags.IsPlayer,
- moverState: ObjectInfoState.IsPlayer));
- }
-
- [Fact]
- public void Pk_VsPk_NotSkipped()
- {
- // Two PK players collide.
- Assert.False(CollisionExemption.ShouldSkip(
- targetState: 0u,
- targetFlags: EntityCollisionFlags.IsPlayer | EntityCollisionFlags.IsPK,
- moverState: ObjectInfoState.IsPlayer | ObjectInfoState.IsPK));
- }
-
- [Fact]
- public void PkLite_VsPkLite_NotSkipped()
- {
- Assert.False(CollisionExemption.ShouldSkip(
- targetState: 0u,
- targetFlags: EntityCollisionFlags.IsPlayer | EntityCollisionFlags.IsPKLite,
- moverState: ObjectInfoState.IsPlayer | ObjectInfoState.IsPKLite));
- }
-
- [Fact]
- public void Pk_VsNonPk_Skipped()
- {
- // Mismatched PK status: still exempt — only matching pair collides.
- Assert.True(CollisionExemption.ShouldSkip(
- targetState: 0u,
- targetFlags: EntityCollisionFlags.IsPlayer,
- moverState: ObjectInfoState.IsPlayer | ObjectInfoState.IsPK));
- }
-
- [Fact]
- public void Pk_VsPkLite_Skipped()
- {
- // PK and PKLite are different pools — pair doesn't match.
- Assert.True(CollisionExemption.ShouldSkip(
- targetState: 0u,
- targetFlags: EntityCollisionFlags.IsPlayer | EntityCollisionFlags.IsPKLite,
- moverState: ObjectInfoState.IsPlayer | ObjectInfoState.IsPK));
- }
-
- [Fact]
- public void ImpenetrableTarget_VsAnyPlayer_NotSkipped()
- {
- // Impenetrable target ("Free" PK status) always collides with
- // any player mover — regardless of mover's PK state.
- Assert.False(CollisionExemption.ShouldSkip(
- targetState: 0u,
- targetFlags: EntityCollisionFlags.IsPlayer | EntityCollisionFlags.IsImpenetrable,
- moverState: ObjectInfoState.IsPlayer));
- }
-
- [Fact]
- public void Player_VsCreature_NotSkipped()
- {
- // PvP exemption only applies player-on-player. Player vs creature
- // (NPC, monster) is the normal blocking case.
- Assert.False(CollisionExemption.ShouldSkip(
- targetState: 0u,
- targetFlags: EntityCollisionFlags.IsCreature,
- moverState: ObjectInfoState.IsPlayer));
- }
-
- [Fact]
- public void NonPlayerMover_VsPlayer_NotSkipped()
- {
- // PvP rule requires BOTH to be players. Mover is not a player
- // (e.g., dead-reckoned remote NPC) → no exemption applies.
- Assert.False(CollisionExemption.ShouldSkip(
- targetState: 0u,
- targetFlags: EntityCollisionFlags.IsPlayer,
- moverState: ObjectInfoState.None));
- }
-}
diff --git a/tests/AcDream.Core.Tests/Physics/EntityCollisionFlagsTests.cs b/tests/AcDream.Core.Tests/Physics/EntityCollisionFlagsTests.cs
deleted file mode 100644
index 9c8cff0f..00000000
--- a/tests/AcDream.Core.Tests/Physics/EntityCollisionFlagsTests.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-using AcDream.Core.Physics;
-using Xunit;
-
-namespace AcDream.Core.Tests.Physics;
-
-///
-/// Unit tests for — Commit A of the
-/// 2026-04-29 live-entity collision port. Verifies retail-faithful
-/// conversion from PWD bitfield bits to the boolean collision-decision
-/// flags that FindObjCollisions consumes for the PvP exemption.
-///
-/// Bit positions confirmed against:
-///
-/// - docs/research/named-retail/acclient_2013_pseudo_c.txt:406898-406918
-/// (ACCWeenieObject::IsPKLite/IsPK/IsImpenetrable) — the
-/// retail client itself reads bits 25 / 5 / 21 of pwd._bitfield.
-/// - docs/research/named-retail/acclient.h:6431-6463
-/// (PublicWeenieDesc::BitfieldIndex) — names the bits:
-/// BF_PLAYER=0x8, BF_PLAYER_KILLER=0x20,
-/// BF_FREE_PKSTATUS=0x200000, BF_PKLITE_PKSTATUS=0x2000000.
-/// - docs/research/named-retail/acclient_2013_pseudo_c.txt:441868-441890
-/// (PublicWeenieDesc::SetPlayerKillerStatus) — confirms
-/// the writer maps PKStatusEnum values onto these exact bits.
-///
-///
-public class EntityCollisionFlagsTests
-{
- [Fact]
- public void FromPwdBitfield_AllZeros_NoFlags()
- {
- Assert.Equal(EntityCollisionFlags.None, EntityCollisionFlagsExt.FromPwdBitfield(0u));
- }
-
- [Fact]
- public void FromPwdBitfield_PlayerBit_SetsIsPlayer()
- {
- // BF_PLAYER = 0x8 (bit 3)
- var flags = EntityCollisionFlagsExt.FromPwdBitfield(0x8u);
- Assert.True(flags.HasFlag(EntityCollisionFlags.IsPlayer));
- Assert.False(flags.HasFlag(EntityCollisionFlags.IsPK));
- }
-
- [Fact]
- public void FromPwdBitfield_PlayerKillerBit_SetsIsPK()
- {
- // BF_PLAYER_KILLER = 0x20 (bit 5)
- var flags = EntityCollisionFlagsExt.FromPwdBitfield(0x20u);
- Assert.True(flags.HasFlag(EntityCollisionFlags.IsPK));
- Assert.False(flags.HasFlag(EntityCollisionFlags.IsPKLite));
- }
-
- [Fact]
- public void FromPwdBitfield_PkLiteBit_SetsIsPKLite()
- {
- // BF_PKLITE_PKSTATUS = 0x2000000 (bit 25)
- var flags = EntityCollisionFlagsExt.FromPwdBitfield(0x2000000u);
- Assert.True(flags.HasFlag(EntityCollisionFlags.IsPKLite));
- Assert.False(flags.HasFlag(EntityCollisionFlags.IsPK));
- }
-
- [Fact]
- public void FromPwdBitfield_FreePkStatusBit_SetsIsImpenetrable()
- {
- // BF_FREE_PKSTATUS = 0x200000 (bit 21)
- var flags = EntityCollisionFlagsExt.FromPwdBitfield(0x200000u);
- Assert.True(flags.HasFlag(EntityCollisionFlags.IsImpenetrable));
- }
-
- [Fact]
- public void FromPwdBitfield_PlayerAndPK_SetsBoth()
- {
- // A PK player: BF_PLAYER (0x8) | BF_PLAYER_KILLER (0x20) = 0x28
- var flags = EntityCollisionFlagsExt.FromPwdBitfield(0x28u);
- Assert.True(flags.HasFlag(EntityCollisionFlags.IsPlayer));
- Assert.True(flags.HasFlag(EntityCollisionFlags.IsPK));
- Assert.False(flags.HasFlag(EntityCollisionFlags.IsPKLite));
- Assert.False(flags.HasFlag(EntityCollisionFlags.IsImpenetrable));
- }
-
- [Fact]
- public void FromPwdBitfield_UnrelatedBits_Ignored()
- {
- // Set BF_OPENABLE (0x1), BF_INSCRIBABLE (0x2), BF_STUCK (0x4) — none
- // map to collision flags. A creature spawn might have BF_ATTACKABLE
- // (0x10) set; that's ItemType-derived IsCreature, not a PvP flag.
- var flags = EntityCollisionFlagsExt.FromPwdBitfield(0x1u | 0x2u | 0x4u | 0x10u);
- Assert.Equal(EntityCollisionFlags.None, flags);
- }
-}
diff --git a/tests/AcDream.Core.Tests/Physics/ShadowObjectRegistryTests.cs b/tests/AcDream.Core.Tests/Physics/ShadowObjectRegistryTests.cs
index 73143d8c..2f2b4636 100644
--- a/tests/AcDream.Core.Tests/Physics/ShadowObjectRegistryTests.cs
+++ b/tests/AcDream.Core.Tests/Physics/ShadowObjectRegistryTests.cs
@@ -177,79 +177,4 @@ public class ShadowObjectRegistryTests
var objs = reg.GetObjectsInCell(cellId);
Assert.Contains(objs, e => e.EntityId == 99u);
}
-
- // -----------------------------------------------------------------------
- // UpdatePosition — Commit A of 2026-04-29 live-entity collision port
- // -----------------------------------------------------------------------
-
- [Fact]
- public void UpdatePosition_MovedEntity_NewCellOccupied()
- {
- // Entity starts at local (12, 12) — cell (0,0).
- var reg = new ShadowObjectRegistry();
- reg.Register(42u, 0x01000010u, new Vector3(12f, 12f, 50f), Quaternion.Identity, 0.5f, OffX, OffY, LbId);
-
- // Move to local (60, 12) — cell (2, 0). Same landblock.
- reg.UpdatePosition(42u, new Vector3(60f, 12f, 50f), Quaternion.Identity, OffX, OffY, LbId);
-
- // Old cell empty, new cell holds the entity.
- Assert.Empty(reg.GetObjectsInCell(LbId | 1u)); // cell (0,0)
- var newCell = reg.GetObjectsInCell(LbId | 17u); // cell (2,0) → 2*8+0+1
- Assert.Single(newCell);
- Assert.Equal(42u, newCell[0].EntityId);
- Assert.Equal(1, reg.TotalRegistered); // not duplicated
- }
-
- [Fact]
- public void UpdatePosition_PreservesFlags()
- {
- // Register with PK flags + PhysicsState; UpdatePosition must keep them.
- var reg = new ShadowObjectRegistry();
- reg.Register(50u, 0x01000011u, new Vector3(12f, 12f, 50f), Quaternion.Identity,
- 0.5f, OffX, OffY, LbId,
- state: 0x4u, // ETHEREAL_PS
- flags: EntityCollisionFlags.IsPlayer | EntityCollisionFlags.IsPK);
-
- reg.UpdatePosition(50u, new Vector3(60f, 12f, 50f), Quaternion.Identity, OffX, OffY, LbId);
-
- var newCell = reg.GetObjectsInCell(LbId | 17u);
- Assert.Single(newCell);
- Assert.Equal(0x4u, newCell[0].State);
- Assert.Equal(EntityCollisionFlags.IsPlayer | EntityCollisionFlags.IsPK, newCell[0].Flags);
- }
-
- [Fact]
- public void UpdatePosition_UnregisteredEntity_NoOp()
- {
- var reg = new ShadowObjectRegistry();
- // Should not throw, should not register a new entity.
- reg.UpdatePosition(99u, new Vector3(12f, 12f, 50f), Quaternion.Identity, OffX, OffY, LbId);
- Assert.Equal(0, reg.TotalRegistered);
- }
-
- [Fact]
- public void Register_WithStateAndFlags_StoredOnEntry()
- {
- var reg = new ShadowObjectRegistry();
- reg.Register(60u, 0x01000012u, new Vector3(12f, 12f, 50f), Quaternion.Identity,
- 0.5f, OffX, OffY, LbId,
- state: 0x10u, // IGNORE_COLLISIONS_PS
- flags: EntityCollisionFlags.IsImpenetrable);
-
- var entry = reg.GetObjectsInCell(LbId | 1u)[0];
- Assert.Equal(0x10u, entry.State);
- Assert.Equal(EntityCollisionFlags.IsImpenetrable, entry.Flags);
- }
-
- [Fact]
- public void Register_DefaultStateAndFlags_AreZeroAndNone()
- {
- var reg = new ShadowObjectRegistry();
- reg.Register(70u, 0x01000013u, new Vector3(12f, 12f, 50f), Quaternion.Identity,
- 0.5f, OffX, OffY, LbId);
-
- var entry = reg.GetObjectsInCell(LbId | 1u)[0];
- Assert.Equal(0u, entry.State);
- Assert.Equal(EntityCollisionFlags.None, entry.Flags);
- }
}