acdream/tests/AcDream.App.Tests/World/GameWindowLiveEntityCompositionTests.cs
Erik 55b07f6a62
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
refactor(physics): hoist the live-entity collision builder to Runtime (#330 groundwork)
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>
2026-08-07 01:49:13 +02:00

270 lines
11 KiB
C#

using System.Collections;
using System.Reflection;
using AcDream.App.Physics;
using AcDream.App.Rendering;
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;
public sealed class GameWindowLiveEntityCompositionTests
{
private const BindingFlags PrivateImplementation =
BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic;
[Theory]
[InlineData("RegisterLiveEntityCollision")]
[InlineData("MotionTableDefaultPose")]
[InlineData("LeaveWorldLiveEntityRuntimeComponents")]
[InlineData("WithdrawLiveEntityWorldProjection")]
[InlineData("RebindAnimatedEntityForAppearance")]
[InlineData("OnLiveEntitySpawned")]
[InlineData("OnLiveEntitySpawnedLocked")]
[InlineData("RehydrateServerEntitiesForLandblock")]
[InlineData("TryInitializeLiveCenter")]
[InlineData("CompleteLiveEntityReady")]
[InlineData("OnLiveAppearanceUpdated")]
[InlineData("OnLiveEntityPickedUp")]
[InlineData("OnLiveParentUpdated")]
[InlineData("TryAcceptParentForRender")]
[InlineData("OnLiveEntityDeleted")]
[InlineData("OnLiveEntityPruned")]
[InlineData("TearDownLiveEntityRuntimeComponents")]
[InlineData("CreateLiveEntityRuntimeTeardownPlan")]
[InlineData("RouteSameGenerationCreateObject")]
[InlineData("OnLiveMotionUpdated")]
[InlineData("OnLivePositionUpdated")]
[InlineData("OnLiveVectorUpdated")]
[InlineData("OnLiveStateUpdated")]
[InlineData("EnsureRemoteMotionBindings")]
[InlineData("ResolvePhysicsHost")]
[InlineData("GetSetupCylinder")]
[InlineData("RouteServerMoveTo")]
[InlineData("StickToObjectFromWire")]
[InlineData("ClearTargetForHiddenEntity")]
[InlineData("ApplyServerControlledVelocityCycle")]
[InlineData("RunRemoteTeleportHook")]
[InlineData("SendImmediateLocalPositionEvent")]
[InlineData("DispatchRemoteInboundMotion")]
[InlineData("CreateRemoteMotion")]
[InlineData("WillAdvanceRemoteMotion")]
[InlineData("AdvanceLiveObjectRuntime")]
[InlineData("AdvanceLiveObjectRuntimeCore")]
[InlineData("ReconcileLiveObjectSpatialPresentation")]
[InlineData("CaptureAnimationHooks")]
public void GameWindow_DoesNotReacquireExtractedLiveEntityBodies(string methodName)
{
Assert.Null(typeof(GameWindow).GetMethod(methodName, PrivateImplementation));
}
[Fact]
public void GameWindow_DoesNotOwnAnAppearanceUpdateStateType()
{
Assert.Null(typeof(GameWindow).GetNestedType(
"AppearanceUpdateState",
BindingFlags.NonPublic));
}
[Fact]
public void RetainedButWithdrawnWorldEntity_RequiresPositionProjectionRecovery()
{
LiveEntityRecord record =
LiveEntityTestFixture.CreateExactProjectionRecord(
new WorldSession.EntitySpawn(
Guid: 0x7000_00D0u,
Position: null,
SetupTableId: null,
AnimPartChanges: [],
TextureChanges: [],
SubPalettes: [],
BasePaletteId: null,
ObjScale: null,
Name: null,
ItemType: null,
MotionState: null,
MotionTableId: null,
InstanceSequence: 1));
record.WorldEntity = new WorldEntity
{
Id = record.LocalEntityId!.Value,
ServerGuid = record.ServerGuid,
SourceGfxObjOrSetupId = 0x0200_0001u,
Position = System.Numerics.Vector3.Zero,
Rotation = System.Numerics.Quaternion.Identity,
MeshRefs = [],
};
Assert.NotNull(record.WorldEntity);
Assert.False(record.IsSpatiallyProjected);
Assert.True(
LiveEntityNetworkUpdateController
.RequiresSpatialProjectionRecovery(record));
}
[Theory]
[InlineData(typeof(LiveEntityCollisionBuilder))]
[InlineData(typeof(LiveEntityDefaultPoseResolver))]
[InlineData(typeof(LiveEntityProjectionWithdrawalController))]
[InlineData(typeof(LocalPlayerShadowState))]
[InlineData(typeof(LiveEntityAppearanceBinding))]
[InlineData(typeof(LiveEntityHydrationController))]
[InlineData(typeof(DatLiveEntityProjectionMaterializer))]
[InlineData(typeof(LiveEntityRuntimeTeardownController))]
[InlineData(typeof(LiveEntityNetworkUpdateController))]
[InlineData(typeof(LiveEntityMotionRuntimeController))]
[InlineData(typeof(LiveEntityInboundAuthorityGate))]
[InlineData(typeof(DeferredLiveEntityMotionRuntimeBindings))]
[InlineData(typeof(AcDream.App.Update.LiveObjectFrameController))]
[InlineData(typeof(AcDream.App.Update.LiveEffectFrameController))]
[InlineData(typeof(AcDream.App.Update.LiveSpatialPresentationReconciler))]
[InlineData(typeof(AcDream.App.Streaming.StreamingFrameController))]
[InlineData(typeof(LiveEntityAnimationPresenter))]
[InlineData(typeof(LiveAnimationPresentationContext))]
[InlineData(typeof(StaticLiveRootCommitter))]
[InlineData(typeof(RetailLiveFrameCoordinator))]
public void ExtractedHelpers_DoNotOwnGuidIndexesOrBackendState(Type helperType)
{
foreach (FieldInfo field in helperType.GetFields(
BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic))
{
bool isExactSynchronousProjectionContext =
helperType == typeof(LiveEntityHydrationController)
&& field.Name == "_projectionOperations"
&& field.FieldType.IsGenericType
&& field.FieldType.GetGenericArguments()[0]
== typeof(AcDream.Runtime.Entities.RuntimeEntityRecord);
if (!isExactSynchronousProjectionContext)
{
Assert.False(
typeof(IDictionary).IsAssignableFrom(field.FieldType),
$"{helperType.Name}.{field.Name} must resolve identity through LiveEntityRuntime.");
}
string typeName = field.FieldType.FullName ?? field.FieldType.Name;
Assert.DoesNotContain("Silk.NET", typeName, StringComparison.Ordinal);
Assert.DoesNotContain("OpenGL", typeName, StringComparison.OrdinalIgnoreCase);
Assert.NotEqual(typeof(GameWindow), field.FieldType);
}
}
[Fact]
public void ExtractedUpdateOwners_DoNotRetainAnonymousCallbacks()
{
Type[] owners =
[
typeof(AcDream.App.Update.LiveObjectFrameController),
typeof(AcDream.App.Update.LiveEffectFrameController),
typeof(AcDream.App.Update.LiveSpatialPresentationReconciler),
typeof(AcDream.App.Streaming.StreamingFrameController),
typeof(LiveEntityAnimationScheduler),
typeof(LiveEntityAnimationPresenter),
typeof(LiveAnimationPresentationContext),
typeof(StaticLiveRootCommitter),
typeof(RetailLiveFrameCoordinator),
];
foreach (Type owner in owners)
{
Assert.DoesNotContain(
owner.GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
}
Assert.False(typeof(ILiveAnimationPresentationContext).IsAssignableFrom(
typeof(GameWindow)));
}
[Fact]
public void RuntimeViewsAndProjectileController_RetainTypedSourcesNotWindowClosures()
{
FieldInfo animationRuntime = Assert.Single(
typeof(LiveEntityAnimationRuntimeView<LiveEntityAnimationState>)
.GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_runtime");
Assert.Equal(typeof(ILiveEntityRuntimeSource), animationRuntime.FieldType);
FieldInfo[] projectileFields = typeof(ProjectileController).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.Equal(
typeof(IProjectileSetupResolver),
Assert.Single(projectileFields, field => field.Name == "_setupResolver").FieldType);
Assert.Equal(
typeof(IEntityRootPosePublisher),
Assert.Single(projectileFields, field => field.Name == "_rootPoses").FieldType);
Assert.Equal(
typeof(LiveWorldOriginState),
Assert.Single(projectileFields, field => field.Name == "_origin").FieldType);
Assert.Equal(
typeof(RuntimeProjectilePhysicsUpdater),
Assert.Single(
projectileFields,
field => field.Name == "_runtimeUpdater").FieldType);
Assert.DoesNotContain(
projectileFields,
field => field.FieldType == typeof(PhysicsEngine)
|| field.FieldType == typeof(ProjectilePhysicsStepper)
|| field.FieldType.IsGenericType
&& field.FieldType.GetGenericTypeDefinition()
== typeof(Dictionary<,>));
Assert.DoesNotContain(
projectileFields,
field => typeof(Delegate).IsAssignableFrom(field.FieldType)
&& !field.Name.Contains("DiagnosticSink", StringComparison.Ordinal));
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(), "src", "AcDream.App", "Rendering", "GameWindow.cs"));
string livePresentation = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"LivePresentationComposition.cs"));
Assert.DoesNotContain(
"new LiveEntityAnimationRuntimeView<LiveEntityAnimationState>(() =>",
source,
StringComparison.Ordinal);
Assert.DoesNotContain(
"LiveEntityRemoteMotionRuntimeView",
source,
StringComparison.Ordinal);
Assert.Contains("new DatProjectileSetupResolver", livePresentation);
}
[Fact]
public void SessionReset_ClosesEveryStreamingReadinessOwner()
{
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(), "src", "AcDream.App", "Net", "LiveSessionRuntimeFactory.cs"));
Assert.Contains("_interaction.PlayerMode.ResetSession();", source,
StringComparison.Ordinal);
string playerModeSource = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Input",
"PlayerModeController.cs"));
Assert.Contains("_mode.ResetSession();", playerModeSource,
StringComparison.Ordinal);
Assert.Contains(
"_world.NetworkUpdates.ResetSessionState();",
source,
StringComparison.Ordinal);
Assert.Contains("_player.WorldOrigin.Reset();", source, StringComparison.Ordinal);
}
private static string FindRepoRoot()
{
DirectoryInfo? directory = new(AppContext.BaseDirectory);
while (directory is not null)
{
if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
return directory.FullName;
directory = directory.Parent;
}
throw new DirectoryNotFoundException("Could not find AcDream.slnx.");
}
}