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)); } }