using System.Collections.Generic;
using AcDream.App.Streaming;
using Xunit;
namespace AcDream.Core.Tests.Streaming;
///
/// #138 — selection logic for re-hydrating server objects when a landblock
/// reloads after a dungeon collapse (or a Near→Far demote). See
/// for the retail/ACE rationale.
///
public class LandblockEntityRehydratorTests
{
private const uint PlayerGuid = 0x50000001u;
// A door spawned in cell 0x00070123 of dungeon landblock 0x0007; its
// cell-resolved spawn id must match the streamed canonical id 0x0007FFFF.
private const uint DungeonLb = 0x0007FFFFu;
private const uint DoorGuid = 0x7A9B4001u;
private const uint DoorSpawnId = 0x00070123u;
private static LandblockEntityRehydrator.RetainedSpawn Spawn(
uint guid, uint spawnLbId, bool hasMesh = true)
=> new(guid, spawnLbId, hasMesh);
[Fact]
public void RetainedSpawnInLoadedLandblock_NotPresent_IsSelected()
{
var result = LandblockEntityRehydrator.SelectGuidsToRehydrate(
DungeonLb,
new[] { Spawn(DoorGuid, DoorSpawnId) },
new HashSet(),
PlayerGuid);
Assert.Equal(new[] { DoorGuid }, result);
}
[Fact]
public void CellResolvedSpawnId_MatchesCanonicalLoadedId()
{
// The streamed landblock id is canonical (0xAAAAFFFF); the spawn id is
// cell-resolved (0xAAAA00CC). They name the same landblock, so the door
// must be selected — the canonicalization is the load-bearing step.
var result = LandblockEntityRehydrator.SelectGuidsToRehydrate(
DungeonLb, // 0x0007FFFF
new[] { Spawn(DoorGuid, 0x000701A9u) }, // a different cell, same landblock
new HashSet(),
PlayerGuid);
Assert.Equal(new[] { DoorGuid }, result);
}
[Fact]
public void SpawnInDifferentLandblock_IsNotSelected()
{
var result = LandblockEntityRehydrator.SelectGuidsToRehydrate(
DungeonLb,
new[] { Spawn(DoorGuid, 0xA9B30123u) }, // Holtburg, not the loaded dungeon
new HashSet(),
PlayerGuid);
Assert.Empty(result);
}
[Fact]
public void AlreadyPresentGuid_IsNotSelected()
{
// Already rendered (server re-sent it, or it was never dropped) — a
// re-hydrate would be a redundant mesh rebuild.
var result = LandblockEntityRehydrator.SelectGuidsToRehydrate(
DungeonLb,
new[] { Spawn(DoorGuid, DoorSpawnId) },
new HashSet { DoorGuid },
PlayerGuid);
Assert.Empty(result);
}
[Fact]
public void PlayerGuid_IsNeverSelected()
{
// The player has a retained spawn too, but the persistent-rescue path
// owns it (preserves its live pose). Re-hydrating would reset it.
var result = LandblockEntityRehydrator.SelectGuidsToRehydrate(
DungeonLb,
new[] { Spawn(PlayerGuid, DoorSpawnId) },
new HashSet(),
PlayerGuid);
Assert.Empty(result);
}
[Fact]
public void SpawnWithoutWorldMesh_IsNotSelected()
{
// Inventory items / setup-less spawns build no visible entity.
var result = LandblockEntityRehydrator.SelectGuidsToRehydrate(
DungeonLb,
new[] { Spawn(DoorGuid, DoorSpawnId, hasMesh: false) },
new HashSet(),
PlayerGuid);
Assert.Empty(result);
}
[Fact]
public void MixedSet_SelectsOnlyAbsentWorldObjectsInLoadedLandblock()
{
uint npcGuid = 0x7A9B4002u; // same dungeon landblock, absent → select
uint chestGuid = 0x7A9B4003u; // same landblock but already present → skip
uint holtburgGuid = 0x7A9B4004u; // different landblock → skip
uint heldItemGuid = 0x7A9B4005u; // no world mesh → skip
var spawns = new[]
{
Spawn(DoorGuid, DoorSpawnId), // select
Spawn(npcGuid, 0x00070177u), // select (same lb, different cell)
Spawn(chestGuid, 0x00070123u), // skip (present)
Spawn(holtburgGuid, 0xA9B30100u), // skip (other lb)
Spawn(heldItemGuid, 0x00070123u, hasMesh: false), // skip (no mesh)
Spawn(PlayerGuid, 0x00070100u), // skip (player)
};
var result = LandblockEntityRehydrator.SelectGuidsToRehydrate(
DungeonLb, spawns, new HashSet { chestGuid }, PlayerGuid);
Assert.Equal(new HashSet { DoorGuid, npcGuid }, new HashSet(result));
}
}