From 55b07f6a6276152035d24f19c79308b8fec71cee Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 7 Aug 2026 01:49:13 +0200 Subject: [PATCH] refactor(physics): hoist the live-entity collision builder to Runtime (#330 groundwork) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LiveEntityCollisionBuilder and LiveEntityDefaultPoseResolver move from AcDream.App.Physics to AcDream.Runtime.Physics with no behaviour change — diff-verified byte-identical shape math by both review lenses. The Build signature's App-record parameter is replaced by presentation-free primitives with identical guard semantics, INCLUDING the FinalPhysicsState read the contract had missed and the implementer surfaced rather than dropped. Visibility stays internal: Runtime's existing InternalsVisibleTo grants already cover every consumer, so the implementation's public widening is reverted per the architecture review's finding 11. The registration WIRING is deliberately WITHHELD. Both Opus lenses failed it, converging: a shadow registered at spawn freezes there (RuntimeRemotePhysicsUpdater is Runtime-homed but App-driven — nothing headless ticks it), so a walking NPC becomes a phantom obstacle at its spawn point while the real NPC still passes through the bot; three of five shadow-lifetime edges leaked (pickup leaves a permanent invisible collider, supersession orphans a duplicate, generation reset never unregisters and the K-ledger convergence oracle only checks retained shadows AFTER disposal clears them); and headless cannot resolve BSP collision assets at all, so doors and chests would still be walk-through. The frozen-shadow root was the SESSION LEAD's contract error (fact 3), not the implementer's. #330 stays OPEN, rewritten as the seven-point scope map the reviews produced — the honest overnight deliverable is that map, not a half-mechanism carrying new divergences. Suite 11,235 passed / 4 skipped / 0 failed (the withheld seam's two tests account for the delta from the implementation run's 11,237). Co-Authored-By: Claude Opus 5 --- docs/ISSUES.md | 57 +++++++- .../ContentEffectsAudioComposition.cs | 1 + .../DatLiveEntityProjectionMaterializer.cs | 6 +- src/AcDream.App/Rendering/GameWindow.cs | 4 +- .../Rendering/LiveEntityAppearanceBinding.cs | 7 +- .../Physics/LiveEntityCollisionBuilder.cs | 55 ++++++-- .../Physics/LiveEntityDefaultPoseResolver.cs | 8 +- .../ContentEffectsAudioCompositionTests.cs | 1 + .../LiveEntityCollisionBuilderTests.cs | 124 +++++++++++++++--- ...pBitfieldSurvivesAppearanceRebuildTests.cs | 24 +++- .../Rendering/LiveAppearanceAnimationTests.cs | 2 +- .../GameWindowLiveEntityCompositionTests.cs | 1 + 12 files changed, 255 insertions(+), 35 deletions(-) rename src/{AcDream.App => AcDream.Runtime}/Physics/LiveEntityCollisionBuilder.cs (77%) rename src/{AcDream.App => AcDream.Runtime}/Physics/LiveEntityDefaultPoseResolver.cs (85%) diff --git a/docs/ISSUES.md b/docs/ISSUES.md index 1fe5c09e..9c7882ce 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -803,7 +803,62 @@ If the one-frame deletion is ever judged unacceptable, the lever is the ## #330 — The headless host registers no live-entity collision at all: a bot walks through every NPC and every server-spawned object -**Status:** OPEN +**Status:** OPEN — **SCOPE MAPPED 2026-08-07 by a dual Opus review of a +withheld implementation; the builder hoist landed, the wiring did not.** + +An overnight implementation attempt wired spawn-time registration into the +no-window route. Both review lenses failed it, converging (two reviewers +converging = near-proof, per the project's own rule), and the findings ARE +the issue's true scope — recorded here so the next attempt starts from the +map instead of rediscovering it: + +1. **A headless remote-motion tick does not exist.** `RuntimeRemotePhysicsUpdater` + is Runtime-HOMED but App-DRIVEN (constructed only at + `RemotePhysicsUpdater.cs:46`, ticked only from + `LiveEntityAnimationScheduler.cs:351`), and `GetOrCreateRemoteMotion` has + one production caller, in App. A shadow registered at spawn therefore + FREEZES at the unsettled wire pose: a walking NPC becomes a phantom + obstacle at its spawn point while the real NPC still passes through the + bot — strictly worse than the honest gap. This is the load-bearing + prerequisite. +2. **Live collision-asset publication does not exist headless-side.** The + headless `PhysicsDataCache` holds only the per-landblock static closure; + there is no `IPreparedCollisionSource` on-demand pull + (graphical: `LiveCollisionAssetPublisher`). Without it, + `_physicsBspBounds` is null for live-entity parts, BSP dispatch never + fires, and doors/chests/statues/portals get the wrong shape or none. +3. **Degrade resolution:** the graphical route resolves collision part ids + through `GfxObjDegradeResolver` slot-0 walking; raw `setup.Parts` is the + wrong id for every humanoid part. +4. **Shadow lifetime hangs off FIVE edges, not two:** wire delete, pickup + (`TryApplyPickup` leaves a permanent invisible collider from a looted + item), same-guid generation supersession (`RetireCanonicalOnly` path has + no unregister → duplicate phantom), GENERATION RESET + (`HeadlessGenerationResetHost.RetireEntityProjection` is an empty no-op + and `ResetSessionPhysics` does not clear `ShadowObjects` — registrations + leak across reconnects), and hidden/withdrawn suspension. +5. **The K-ledger cannot see any of this:** `RuntimePhysicsOwnershipSnapshot.IsConverged` + checks retained shadow count only AFTER disposal, and disposal clears the + registry. Extending the convergence oracle to pre-disposal retained + counts is part of this issue's test work, or every leak above ships green. +6. **Ordering + retry:** registration must follow `ProjectSpawn` (a throw + after `ApplyAcceptedSpawn` leaves a committed-but-unprojected entity), + and a spawn arriving before the world frame publishes needs a retry pump + — the withheld code silently dropped it for the incarnation's lifetime. +7. **The appearance route EXISTS** (`OnAppearanceUpdated`) and must rebuild + collision, as the graphical binding does. + +**What DID land 2026-08-07:** the builder hoist — +`LiveEntityCollisionBuilder` + `LiveEntityDefaultPoseResolver` moved to +`AcDream.Runtime.Physics` (internal + existing IVT), `Build(...)`'s +App-record parameter replaced by presentation-free primitives including the +`FinalPhysicsState` the contract had missed. Both reviewers passed the hoist +explicitly; the graphical host is diff-verified unchanged. The wiring +attempt itself is preserved in the review transcripts, not in the tree. + +**Original entry below.** + +**Status (original):** OPEN **Severity:** HIGH for headless gameplay fidelity; zero impact on the graphical client. **Filed:** 2026-08-06, from the AP-22 deletion's blast-radius survey (§5 of [`docs/research/2026-08-06-ap22-contract.md`](research/2026-08-06-ap22-contract.md)). diff --git a/src/AcDream.App/Composition/ContentEffectsAudioComposition.cs b/src/AcDream.App/Composition/ContentEffectsAudioComposition.cs index ab545721..b92a76c8 100644 --- a/src/AcDream.App/Composition/ContentEffectsAudioComposition.cs +++ b/src/AcDream.App/Composition/ContentEffectsAudioComposition.cs @@ -14,6 +14,7 @@ using AcDream.Core.Spells; using AcDream.Core.Vfx; using AcDream.Runtime; using AcDream.Runtime.Gameplay; +using AcDream.Runtime.Physics; using DatReaderWriter; using Silk.NET.Input; diff --git a/src/AcDream.App/Rendering/DatLiveEntityProjectionMaterializer.cs b/src/AcDream.App/Rendering/DatLiveEntityProjectionMaterializer.cs index 8887903f..1a78c234 100644 --- a/src/AcDream.App/Rendering/DatLiveEntityProjectionMaterializer.cs +++ b/src/AcDream.App/Rendering/DatLiveEntityProjectionMaterializer.cs @@ -12,6 +12,7 @@ using AcDream.Core.Physics; using AcDream.Core.Plugins; using AcDream.Core.World; using AcDream.Runtime.Entities; +using AcDream.Runtime.Physics; using DatReaderWriter; using DatReaderWriter.DBObjs; using DatReaderWriter.Types; @@ -834,7 +835,10 @@ internal sealed class DatLiveEntityProjectionMaterializer setup, collisionPartGfxObjIds, spawn, - expectedRecord, + expectedRecord.ServerGuid, + expectedRecord.Generation, + expectedRecord.WorldEntity!, + expectedRecord.FinalPhysicsState, worldOrigin) is { } collision) { LiveEntityCollisionBuilder.Register(_shadows, collision); diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 9b2996bf..b3b472b9 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -267,7 +267,7 @@ public sealed class GameWindow : private readonly AcDream.App.Rendering.Wb.EntityClassificationCache _classificationCache = new(); private AcDream.Core.Physics.IAnimationLoader? _animLoader; - private AcDream.App.Physics.LiveEntityCollisionBuilder? _liveEntityCollisionBuilder; + private AcDream.Runtime.Physics.LiveEntityCollisionBuilder? _liveEntityCollisionBuilder; // Phase E.1: central fan-out for animation hooks. Audio (E.2), // particles (E.3), combat (E.4), and renderer state mutators all @@ -818,7 +818,7 @@ public sealed class GameWindow : PublishCompositionOwner(ref _animLoader, value, "animation loader"); void IGameWindowContentEffectsAudioPublication.PublishLiveEntityCollisionBuilder( - AcDream.App.Physics.LiveEntityCollisionBuilder value) => + AcDream.Runtime.Physics.LiveEntityCollisionBuilder value) => PublishCompositionOwner( ref _liveEntityCollisionBuilder, value, diff --git a/src/AcDream.App/Rendering/LiveEntityAppearanceBinding.cs b/src/AcDream.App/Rendering/LiveEntityAppearanceBinding.cs index 96e156bf..dce6618f 100644 --- a/src/AcDream.App/Rendering/LiveEntityAppearanceBinding.cs +++ b/src/AcDream.App/Rendering/LiveEntityAppearanceBinding.cs @@ -1,9 +1,9 @@ using System.Numerics; -using AcDream.App.Physics; using AcDream.App.World; using AcDream.Core.Net; using AcDream.Core.Physics; using AcDream.Core.World; +using AcDream.Runtime.Physics; using DatReaderWriter.DBObjs; namespace AcDream.App.Rendering; @@ -95,7 +95,10 @@ internal static class LiveEntityAppearanceBinding setup, effectivePartGfxObjIds, spawn, - record, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, worldOrigin, retainEmptyPayload: true)); } diff --git a/src/AcDream.App/Physics/LiveEntityCollisionBuilder.cs b/src/AcDream.Runtime/Physics/LiveEntityCollisionBuilder.cs similarity index 77% rename from src/AcDream.App/Physics/LiveEntityCollisionBuilder.cs rename to src/AcDream.Runtime/Physics/LiveEntityCollisionBuilder.cs index 59929e9c..9299b13a 100644 --- a/src/AcDream.App/Physics/LiveEntityCollisionBuilder.cs +++ b/src/AcDream.Runtime/Physics/LiveEntityCollisionBuilder.cs @@ -1,5 +1,4 @@ using System.Numerics; -using AcDream.App.World; using AcDream.Core.Items; using AcDream.Core.Net; using AcDream.Core.Physics; @@ -7,7 +6,7 @@ using AcDream.Core.World; using DatReaderWriter.DBObjs; using DatReaderWriter.Types; -namespace AcDream.App.Physics; +namespace AcDream.Runtime.Physics; /// /// Immutable input to the canonical shadow registry for one live object. @@ -45,6 +44,23 @@ internal sealed record LiveEntityCollisionRegistration( /// set — Setup.Radius/Height serve attack cones, /// cylinder_distance, and MoveTo, never collision geometry. /// +/// +/// #330 hoist (2026-08-07): moved from AcDream.App.Physics to +/// AcDream.Runtime.Physics so a no-window host can build the same +/// collision shapes a graphical host does +/// (src/AcDream.Headless/Hosting/HeadlessLiveEntityCollisionRegistrar.cs). +/// 's identity guard used to take the App-only +/// LiveEntityRecord exactRecord parameter; it now takes the four +/// presentation-free primitives that record exposed +/// ('s expectedServerGuid/ +/// expectedGeneration/expectedEntity/ +/// expectedFinalPhysicsState below) with IDENTICAL guard and +/// registration-state semantics — no behaviour change. Both graphical call +/// sites (DatLiveEntityProjectionMaterializer, +/// LiveEntityAppearanceBinding.PrepareCollision) pass +/// record.ServerGuid, record.Generation, record.WorldEntity!, +/// record.FinalPhysicsState for these four parameters. +/// internal sealed class LiveEntityCollisionBuilder { private readonly Func _physicsBspBounds; @@ -110,24 +126,47 @@ internal sealed class LiveEntityCollisionBuilder return result; } + /// Identity guard term 1: must equal + /// 's wire guid. Presentation-free replacement for + /// the App-only LiveEntityRecord.ServerGuid this parameter used to + /// read (#330 hoist). + /// Identity guard term 2: must equal + /// 's wire instance sequence. Replaces + /// LiveEntityRecord.Generation () — + /// widened to so a Runtime canonical caller can + /// pass its own generation counter without a narrowing cast; the + /// comparison below is exact either way. + /// Identity guard term 3: must be reference- + /// equal to . Replaces + /// LiveEntityRecord.WorldEntity. + /// Replaces + /// LiveEntityRecord.FinalPhysicsState, which the original + /// exactRecord parameter also supplied for the emitted + /// registration's — + /// this is NOT part of the identity guard, only of the registration + /// payload, but it was only reachable through the same record and so + /// travels alongside the guard's three primitives. public LiveEntityCollisionRegistration? Build( WorldEntity entity, Setup setup, IReadOnlyList effectivePartGfxObjIds, WorldSession.EntitySpawn spawn, - LiveEntityRecord exactRecord, + uint expectedServerGuid, + ulong expectedGeneration, + WorldEntity expectedEntity, + PhysicsStateFlags expectedFinalPhysicsState, Vector3 worldOrigin, bool retainEmptyPayload = false) { ArgumentNullException.ThrowIfNull(entity); ArgumentNullException.ThrowIfNull(setup); ArgumentNullException.ThrowIfNull(effectivePartGfxObjIds); - ArgumentNullException.ThrowIfNull(exactRecord); + ArgumentNullException.ThrowIfNull(expectedEntity); if (spawn.Position is not { } position) return null; - if (spawn.Guid != exactRecord.ServerGuid - || spawn.InstanceSequence != exactRecord.Generation - || !ReferenceEquals(exactRecord.WorldEntity, entity)) + if (spawn.Guid != expectedServerGuid + || spawn.InstanceSequence != expectedGeneration + || !ReferenceEquals(expectedEntity, entity)) { throw new InvalidOperationException( "Live collision construction requires the exact materialized record."); @@ -164,7 +203,7 @@ internal sealed class LiveEntityCollisionBuilder entity.Position, entity.Rotation, shapes, - (uint)exactRecord.FinalPhysicsState, + (uint)expectedFinalPhysicsState, flags, worldOrigin.X, worldOrigin.Y, diff --git a/src/AcDream.App/Physics/LiveEntityDefaultPoseResolver.cs b/src/AcDream.Runtime/Physics/LiveEntityDefaultPoseResolver.cs similarity index 85% rename from src/AcDream.App/Physics/LiveEntityDefaultPoseResolver.cs rename to src/AcDream.Runtime/Physics/LiveEntityDefaultPoseResolver.cs index 40d9a74f..32a31926 100644 --- a/src/AcDream.App/Physics/LiveEntityDefaultPoseResolver.cs +++ b/src/AcDream.Runtime/Physics/LiveEntityDefaultPoseResolver.cs @@ -3,7 +3,7 @@ using AcDream.Core.Physics.Motion; using DatReaderWriter.DBObjs; using DatReaderWriter.Types; -namespace AcDream.App.Physics; +namespace AcDream.Runtime.Physics; /// /// Resolves the motion table's authored default-state part pose used by a @@ -13,6 +13,12 @@ namespace AcDream.App.Physics; /// The retained default-pose snapshot approximation is tracked as AP-84 in /// the retail divergence register until live per-frame collision poses land. /// +/// +/// #330 hoist (2026-08-07): moved from AcDream.App.Physics to +/// AcDream.Runtime.Physics so a no-window host can build the same +/// collision shapes a graphical host does. No behaviour change - this class +/// was already presentation-free (DAT/physics-only dependencies). +/// internal sealed class LiveEntityDefaultPoseResolver { private readonly Func _loadMotionTable; diff --git a/tests/AcDream.App.Tests/Composition/ContentEffectsAudioCompositionTests.cs b/tests/AcDream.App.Tests/Composition/ContentEffectsAudioCompositionTests.cs index ff2c6b0f..0b5aaede 100644 --- a/tests/AcDream.App.Tests/Composition/ContentEffectsAudioCompositionTests.cs +++ b/tests/AcDream.App.Tests/Composition/ContentEffectsAudioCompositionTests.cs @@ -16,6 +16,7 @@ using AcDream.Core.Rendering; using AcDream.Core.Spells; using AcDream.Core.Vfx; using AcDream.Runtime.Gameplay; +using AcDream.Runtime.Physics; using DatReaderWriter.DBObjs; using Silk.NET.Input; using Silk.NET.OpenAL; diff --git a/tests/AcDream.App.Tests/Physics/LiveEntityCollisionBuilderTests.cs b/tests/AcDream.App.Tests/Physics/LiveEntityCollisionBuilderTests.cs index 320034d5..b104f751 100644 --- a/tests/AcDream.App.Tests/Physics/LiveEntityCollisionBuilderTests.cs +++ b/tests/AcDream.App.Tests/Physics/LiveEntityCollisionBuilderTests.cs @@ -1,11 +1,11 @@ using System.Numerics; -using AcDream.App.Physics; using AcDream.App.World; using AcDream.Core.Items; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.Physics; using AcDream.Core.World; +using AcDream.Runtime.Physics; using DatReaderWriter.DBObjs; using DatReaderWriter.Types; @@ -55,7 +55,16 @@ public sealed class LiveEntityCollisionBuilderTests var builder = Builder(); LiveEntityCollisionRegistration registration = Assert.IsType( - builder.Build(entity, setup, Array.Empty(), spawn, record, new Vector3(192f, -192f, 0f))); + builder.Build( + entity, + setup, + Array.Empty(), + spawn, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, + new Vector3(192f, -192f, 0f))); ShadowShape shape = Assert.Single(registration.Shapes); Assert.Equal(ShadowCollisionType.Cylinder, shape.CollisionType); @@ -94,7 +103,10 @@ public sealed class LiveEntityCollisionBuilderTests setup, Array.Empty(), spawn, - record, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, new Vector3(192f, -192f, 0f))); } @@ -125,7 +137,16 @@ public sealed class LiveEntityCollisionBuilderTests PoseResolver()); LiveEntityCollisionRegistration registration = Assert.IsType( - builder.Build(entity, setup, [part], spawn, record, Vector3.Zero)); + builder.Build( + entity, + setup, + [part], + spawn, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, + Vector3.Zero)); ShadowShape shape = Assert.Single(registration.Shapes); Assert.Equal(ShadowCollisionType.BSP, shape.CollisionType); @@ -168,7 +189,15 @@ public sealed class LiveEntityCollisionBuilderTests LiveEntityCollisionRegistration registration = Assert.IsType(builder.Build( - entity, setup, [part], spawn, record, Vector3.Zero)); + entity, + setup, + [part], + spawn, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, + Vector3.Zero)); ShadowShape shape = Assert.Single(registration.Shapes); Assert.Equal(ShadowCollisionType.BSP, shape.CollisionType); @@ -197,7 +226,10 @@ public sealed class LiveEntityCollisionBuilderTests setup, [replacement], spawn, - record, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, Vector3.Zero)); } @@ -222,7 +254,10 @@ public sealed class LiveEntityCollisionBuilderTests setup, [replacement], spawn, - record, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, Vector3.Zero)); ShadowShape shape = Assert.Single(registration.Shapes); @@ -246,12 +281,28 @@ public sealed class LiveEntityCollisionBuilderTests PoseResolver()); LiveEntityCollisionRegistration initial = Assert.IsType(builder.Build( - entity, setup, [basePart], spawn, record, Vector3.Zero)); + entity, + setup, + [basePart], + spawn, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, + Vector3.Zero)); var registry = new ShadowObjectRegistry(); LiveEntityCollisionBuilder.Register(registry, initial); LiveEntityCollisionRegistration? changed = builder.Build( - entity, setup, [replacement], spawn, record, Vector3.Zero, + entity, + setup, + [replacement], + spawn, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, + Vector3.Zero, retainEmptyPayload: true); LiveEntityCollisionBuilder.ReconcileAppearance( registry, entity.Id, changed, suspendIfNew: false); @@ -278,13 +329,29 @@ public sealed class LiveEntityCollisionBuilderTests PoseResolver()); LiveEntityCollisionRegistration initial = Assert.IsType(builder.Build( - entity, setup, [basePart], spawn, record, Vector3.Zero)); + entity, + setup, + [basePart], + spawn, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, + Vector3.Zero)); var registry = new ShadowObjectRegistry(); LiveEntityCollisionBuilder.Register(registry, initial); Assert.True(registry.Suspend(entity.Id)); LiveEntityCollisionRegistration changed = Assert.IsType(builder.Build( - entity, setup, [replacement], spawn, record, Vector3.Zero)); + entity, + setup, + [replacement], + spawn, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, + Vector3.Zero)); LiveEntityCollisionBuilder.ReconcileAppearance( registry, entity.Id, changed, suspendIfNew: true); @@ -301,7 +368,10 @@ public sealed class LiveEntityCollisionBuilderTests setup, [0x0100AC13u], spawn, - record, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, Vector3.Zero, retainEmptyPayload: true)); LiveEntityCollisionBuilder.ReconcileAppearance( @@ -342,12 +412,28 @@ public sealed class LiveEntityCollisionBuilderTests PoseResolver()); LiveEntityCollisionRegistration initial = Assert.IsType(builder.Build( - entity, setup, [basePart], spawn, record, Vector3.Zero)); + entity, + setup, + [basePart], + spawn, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, + Vector3.Zero)); var registry = new ShadowObjectRegistry(); LiveEntityCollisionBuilder.Register(registry, initial); LiveEntityCollisionRegistration changed = Assert.IsType(builder.Build( - entity, setup, [replacement], spawn, record, Vector3.Zero)) + entity, + setup, + [replacement], + spawn, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, + Vector3.Zero)) with { LandblockId = 0u, @@ -376,7 +462,10 @@ public sealed class LiveEntityCollisionBuilderTests new Setup(), Array.Empty(), spawn, - record, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, Vector3.Zero)); } @@ -396,7 +485,10 @@ public sealed class LiveEntityCollisionBuilderTests new Setup { Radius = 1f }, Array.Empty(), spawn, - other, + other.ServerGuid, + other.Generation, + other.WorldEntity!, + other.FinalPhysicsState, Vector3.Zero)); } diff --git a/tests/AcDream.App.Tests/Physics/PvpBitfieldSurvivesAppearanceRebuildTests.cs b/tests/AcDream.App.Tests/Physics/PvpBitfieldSurvivesAppearanceRebuildTests.cs index 40136a30..ca00278b 100644 --- a/tests/AcDream.App.Tests/Physics/PvpBitfieldSurvivesAppearanceRebuildTests.cs +++ b/tests/AcDream.App.Tests/Physics/PvpBitfieldSurvivesAppearanceRebuildTests.cs @@ -1,5 +1,4 @@ using System.Numerics; -using AcDream.App.Physics; using AcDream.App.Streaming; using AcDream.App.World; using AcDream.Core.Items; @@ -9,6 +8,7 @@ using AcDream.Core.Physics; using AcDream.Core.World; using AcDream.Runtime; using AcDream.Runtime.Entities; +using AcDream.Runtime.Physics; using DatReaderWriter.DBObjs; namespace AcDream.App.Tests.Physics; @@ -155,7 +155,16 @@ public sealed class PvpBitfieldSurvivesAppearanceRebuildTests // Initial shadow registration, mirroring CreateObject-time behavior. LiveEntityCollisionRegistration initial = Assert.IsType( - builder.Build(entity, setup, [], record.Snapshot, record, Vector3.Zero)); + builder.Build( + entity, + setup, + [], + record.Snapshot, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, + Vector3.Zero)); LiveEntityCollisionBuilder.Register(registry, initial); ShadowEntry beforeObjDesc = Assert.Single(registry.GetObjectsInCell(Cell)); Assert.True(beforeObjDesc.Flags.HasFlag(EntityCollisionFlags.IsPKLite)); @@ -184,7 +193,16 @@ public sealed class PvpBitfieldSurvivesAppearanceRebuildTests // LiveEntityAppearanceBinding.PrepareCollision/CommitCollision -> // LiveEntityCollisionBuilder.ReconcileAppearance). LiveEntityCollisionRegistration rebuilt = Assert.IsType( - builder.Build(entity, setup, [], accepted, record, Vector3.Zero)); + builder.Build( + entity, + setup, + [], + accepted, + record.ServerGuid, + record.Generation, + record.WorldEntity!, + record.FinalPhysicsState, + Vector3.Zero)); LiveEntityCollisionBuilder.ReconcileAppearance( registry, entity.Id, rebuilt, suspendIfNew: false); diff --git a/tests/AcDream.App.Tests/Rendering/LiveAppearanceAnimationTests.cs b/tests/AcDream.App.Tests/Rendering/LiveAppearanceAnimationTests.cs index f0d53ba3..99187874 100644 --- a/tests/AcDream.App.Tests/Rendering/LiveAppearanceAnimationTests.cs +++ b/tests/AcDream.App.Tests/Rendering/LiveAppearanceAnimationTests.cs @@ -1,12 +1,12 @@ using System.Numerics; using AcDream.App.Rendering; -using AcDream.App.Physics; using AcDream.App.Streaming; using AcDream.App.World; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.Physics; using AcDream.Core.World; +using AcDream.Runtime.Physics; using DatReaderWriter.DBObjs; namespace AcDream.App.Tests.Rendering; diff --git a/tests/AcDream.App.Tests/World/GameWindowLiveEntityCompositionTests.cs b/tests/AcDream.App.Tests/World/GameWindowLiveEntityCompositionTests.cs index 46cae8f9..066d7ab5 100644 --- a/tests/AcDream.App.Tests/World/GameWindowLiveEntityCompositionTests.cs +++ b/tests/AcDream.App.Tests/World/GameWindowLiveEntityCompositionTests.cs @@ -6,6 +6,7 @@ using AcDream.App.World; using AcDream.Core.Net; using AcDream.Core.Physics; using AcDream.Core.World; +using AcDream.Runtime.Physics; namespace AcDream.App.Tests.World;