refactor(physics): hoist the live-entity collision builder to Runtime (#330 groundwork)
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
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 <noreply@anthropic.com>
This commit is contained in:
parent
5629e2cb12
commit
55b07f6a62
12 changed files with 255 additions and 35 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<LiveEntityCollisionRegistration>(
|
||||
builder.Build(entity, setup, Array.Empty<uint>(), spawn, record, new Vector3(192f, -192f, 0f)));
|
||||
builder.Build(
|
||||
entity,
|
||||
setup,
|
||||
Array.Empty<uint>(),
|
||||
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<uint>(),
|
||||
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<LiveEntityCollisionRegistration>(
|
||||
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<LiveEntityCollisionRegistration>(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<LiveEntityCollisionRegistration>(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<LiveEntityCollisionRegistration>(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<LiveEntityCollisionRegistration>(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<LiveEntityCollisionRegistration>(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<LiveEntityCollisionRegistration>(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<uint>(),
|
||||
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<uint>(),
|
||||
spawn,
|
||||
other,
|
||||
other.ServerGuid,
|
||||
other.Generation,
|
||||
other.WorldEntity!,
|
||||
other.FinalPhysicsState,
|
||||
Vector3.Zero));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<LiveEntityCollisionRegistration>(
|
||||
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<LiveEntityCollisionRegistration>(
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue