using System.Reflection; using AcDream.App.Input; using AcDream.App.Tests.Architecture; using AcDream.App.World; using AcDream.Core.Physics; using AcDream.Core.Physics.Motion; namespace AcDream.App.Tests.Input; /// /// C3c-F2 (2026-08-02): compiled architecture check for the production /// player-mode auto-entry precondition. PlayerModeAutoEntry is a /// ONE-SHOT that disarms before invoking its callback, and the production /// callback completes the world reveal, so an attempt made before the Runtime /// first-entry conductor has /// committed permanently seals the reveal with the player never in world — /// the second link of the connected-gate login wedge /// (logs/connected-world-gate-20260802-130455: one "not committed yet" line, /// then event=complete with materialized=False and 5,577 /// readiness-after-terminal rejections). The production context therefore has /// to report the same commit PlayerModeController.TryEnter requires. /// The guard's own one-shot/latch behavior is covered behaviorally by /// AcDream.Core.Tests.Input.AutoEnterPlayerModeTests; only the production /// context's dependency graph (a ~15-dependency PlayerModeController) has no /// focused harness, hence the metadata check. /// public sealed class C3cF2AutoEntryWiringTests { [Fact] public void ProductionAutoEntryRequiresTheRuntimePublishedController() { MethodInfo readiness = typeof(LivePlayerModeAutoEntryContext) .GetProperty( "IsPlayerControllerReady", BindingFlags.Instance | BindingFlags.Public)! .GetMethod!; IReadOnlyList calls = CompiledCallGraph.Read(readiness); Assert.Contains( calls, call => call.Target.DeclaringType == typeof(PlayerMovementController) && call.Target.Name == "get_IsRuntimePublished"); Assert.Contains( calls, call => call.Target.DeclaringType == typeof(LiveEntityRuntime) && call.Target.Name == nameof(LiveEntityRuntime.TryGetRecord)); Assert.Contains( calls, call => call.Target.DeclaringType == typeof(LiveEntityRecord) && call.Target.Name == "get_PhysicsHost"); Assert.Contains( typeof(EntityPhysicsHost), CompiledCallGraph.ReadTypeReferences(readiness)); } [Fact] public void PlayerPresentationAttach_DrainsMatchedAnimationsBeforeStartupMotionSuffix() { MethodInfo enter = typeof(PlayerModeController).GetMethod( "BuildControllerAndCamera", BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException( typeof(PlayerModeController).FullName, "BuildControllerAndCamera"); IReadOnlyList calls = CompiledCallGraph.Read(enter); int attach = CompiledCallGraph.IndexOf( calls, typeof(MotionInterpreter), "set_DefaultSink"); int animationDrain = CompiledCallGraph.IndexOf( calls, typeof(MotionTableManager), nameof(MotionTableManager.HandleEnterWorld), attach + 1); int unmatchedMotionDrain = CompiledCallGraph.IndexOf( calls, typeof(MotionInterpreter), nameof(MotionInterpreter.HandleExitWorld), animationDrain + 1); Assert.True(attach >= 0, "The player animation sink was not attached."); Assert.True( animationDrain > attach, "Matching PartArray animations must drain after attaching the sink."); Assert.True( unmatchedMotionDrain > animationDrain, "Only the unmatched pre-attach interpreter suffix may drain last."); } }