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()
{
}
}
}