using System.Collections.Immutable; using System.Reflection; using AcDream.Content; using AcDream.Core.Combat; using AcDream.Core.Items; using AcDream.Core.Physics; using AcDream.Core.Spells; using AcDream.Headless.Configuration; using AcDream.Headless.Hosting; using AcDream.Runtime; using AcDream.Runtime.Gameplay; using AcDream.Runtime.Session; namespace AcDream.Headless.Tests; /// /// B1 review fix: implements TWO /// interfaces that share the identical bool IsWithinServiceWindow(uint) /// signature but ask different questions — /// is a /// pure geometry test ("can this landblock EVER collision-publish", true /// outright with no center requested), while /// /// must answer "is it collision-published RIGHT NOW". This is the focused /// proof that the two answers genuinely diverge — before this fix a single /// method satisfied both interfaces, so both answers were identical (and /// wrong for the new interface's contract). /// public sealed class HeadlessCollisionNeighborhoodServiceWindowTests { [Fact] public void ServiceWindowIsResidencyNotGeometry_UnpublishedLandblockIsRefusedDespiteGeometricMembership() { var factory = new FixtureContentFactory(); using var owner = new HeadlessProcessContentOwner( ContentDescriptor(), _ => { }, factory); using HeadlessProcessContentOwner.HeadlessProcessContentLease lease = owner.AcquireLease("fixture"); var operations = new FixtureGameplayOperations(); using var runtime = new GameRuntime(new GameRuntimeDependencies( operations, operations, operations, operations)); var neighborhood = new HeadlessCollisionNeighborhood(runtime, lease); // CenterOn was never called, so the geometry interface's own // documented contract applies: "no center requested yet" => true // (this landblock could theoretically EVER be served). Nothing has // published ANY collision for it, though — the residency-based // interface must say false. const uint cell = 0xA9B40001u; Assert.True( ((IHeadlessCollisionNeighborhood)neighborhood) .IsWithinServiceWindow(cell)); Assert.False( ((IRuntimeRemotePlacementServiceWindow)neighborhood) .IsWithinServiceWindow(cell)); } /// /// C2-3 review fix (delta round): BuildPublicationPlan publishes /// the requested center's FULL 3x3 window, not just the exact center — /// so a landblock this host HAS published but which is not the exact /// _centerLandblock (a remote sitting one landblock off-center, /// exactly the boundary population this route exists to serve) must /// still read as currently published. Before this fix the predicate /// inherited 's own /// _centerLandblock != center restriction — correct for /// IsReady's narrower question, wrong here — so only ONE of the /// nine published landblocks would ever read true. /// /// Seeds _resident directly via reflection (no lightweight DAT /// fixture in this test project can drive real 3x3 publication through /// CenterOn — its dummy proxy makes /// LandblockLoader.Load fail for every landblock, including a /// REQUIRED center) — mirrors the existing reflection precedent /// HeadlessSessionHostTests.SeedRuntimePlacement already uses for /// otherwise-unreachable internal state. _centerLandblock is /// deliberately left at its default (never set) — the whole point is /// that this predicate no longer depends on it. /// /// [Fact] public void ServiceWindowCoversAPublishedNeighborLandblockNotOnlyTheExactCenter() { var factory = new FixtureContentFactory(); using var owner = new HeadlessProcessContentOwner( ContentDescriptor(), _ => { }, factory); using HeadlessProcessContentOwner.HeadlessProcessContentLease lease = owner.AcquireLease("fixture"); var operations = new FixtureGameplayOperations(); using var runtime = new GameRuntime(new GameRuntimeDependencies( operations, operations, operations, operations)); var neighborhood = new HeadlessCollisionNeighborhood(runtime, lease); const uint neighborLandblock = 0xA9B5FFFFu; const uint neighborCell = 0xA9B50001u; runtime.EntityObjects.Physics.Engine.AddLandblock( neighborLandblock, new TerrainSurface(new byte[81], new float[256]), Array.Empty(), Array.Empty(), worldOffsetX: 0f, worldOffsetY: 0f); SeedResident(neighborhood, neighborLandblock); Assert.True( ((IRuntimeRemotePlacementServiceWindow)neighborhood) .IsWithinServiceWindow(neighborCell)); } private static void SeedResident( HeadlessCollisionNeighborhood neighborhood, uint landblockId) { FieldInfo field = typeof(HeadlessCollisionNeighborhood).GetField( "_resident", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new MissingFieldException( nameof(HeadlessCollisionNeighborhood), "_resident"); var resident = (HashSet)field.GetValue(neighborhood)!; resident.Add(landblockId); } private static HeadlessContentDescriptor ContentDescriptor() => new() { DatDirectory = "fixture-dats", PreparedAssetPath = "fixture.pak", }; private sealed class FixtureContentFactory : IHeadlessProcessContentFactory { internal FixtureContentFactory() { DatsResource = DispatchProxy.Create(); PreparedResource = DispatchProxy.Create(); } internal IDatReaderWriter DatsResource { get; } internal ITestPreparedSource PreparedResource { get; } public HeadlessOpenedProcessContent Open( HeadlessContentDescriptor descriptor, Action diagnostic) => new( DatsResource, PreparedResource, MagicCatalog.Empty, ImmutableArray.CreateRange(new float[256])); } private sealed class FixtureGameplayOperations : IRuntimeCombatAttackOperations, IRuntimeCombatTargetOperations, IRuntimeCombatModeOperations, IRuntimeSpellCastOperations { public bool CanStartAttack() => false; public void PrepareAttackRequest() { } public bool SendAttack(AttackHeight height, float power) => false; public void SendCancelAttack() { } public bool IsDualWield => false; public bool PlayerReadyForAttack => false; public bool AutoRepeatAttack => false; public bool AutoTarget => false; public uint? SelectClosestTarget() => null; public bool IsInWorld => false; public IReadOnlyList GetOrderedEquipment() => []; public void NotifyExplicitCombatModeRequest() { } public void SendChangeCombatMode(CombatMode mode) { } public uint LocalPlayerId => 0u; public bool CanSend => false; public bool HasRequiredComponents(uint spellId) => false; public bool IsTargetCompatible( uint targetId, SpellMetadata spell, bool showMessage) => false; public void StopCompletely() { } public void SendUntargeted(uint spellId) { } public void SendTargeted(uint targetId, uint spellId) { } public void DisplayMessage(string message) { } public void IncrementBusy() { } } }