diff --git a/Assets/_Project/Scripts/Client/Presentation/HudSystem.cs b/Assets/_Project/Scripts/Client/Presentation/HudSystem.cs index e6de440b1..d39dcfe52 100644 --- a/Assets/_Project/Scripts/Client/Presentation/HudSystem.cs +++ b/Assets/_Project/Scripts/Client/Presentation/HudSystem.cs @@ -72,9 +72,7 @@ namespace ProjectM.Client // END-2: terminal win/loss banner (observes the replicated RunOutcome; latched server-side). VisualElement _runBanner; Label _runBannerText, _runBannerSub; - // Demo polish: the clickable READY panel (Staging/Launching) + the drawn branching route map - // (RouteSelect) — the map is regenerated client-side from RunInfo.RunSeed for DISPLAY only; the - // clickable next-layer nodes bind to the authoritative RouteOpt* bytes (never the regen). + // Demo polish: the clickable READY panel (Staging/Launching). VisualElement _readyPanel, _readyPipRow; Button _readyBtn; Label _readyTitle; @@ -1566,23 +1564,5 @@ namespace ProjectM.Client bool _wentInRun; float _runFailedUntil; - - - - - - - - - - - - - - - - - - } } diff --git a/Assets/_Project/Tests/EditMode/BoonApplyTests.cs b/Assets/_Project/Tests/EditMode/BoonApplyTests.cs index b4bfdde9d..6ae2c6d4d 100644 --- a/Assets/_Project/Tests/EditMode/BoonApplyTests.cs +++ b/Assets/_Project/Tests/EditMode/BoonApplyTests.cs @@ -6,6 +6,7 @@ using Unity.Core; using Unity.Entities; using Unity.NetCode; using Unity.Transforms; +using System.Collections.Generic; namespace ProjectM.Tests { @@ -18,6 +19,14 @@ namespace ProjectM.Tests public class BoonApplyTests { const uint T0 = 3000; + readonly List _worlds = new(); + + [TearDown] + public void Cleanup() + { + foreach (var w in _worlds) if (w.IsCreated) w.Dispose(); + _worlds.Clear(); + } static (World world, SimulationSystemGroup group, Entity dir, Entity catalog) MakeWorld(byte lifecycle) { @@ -72,6 +81,8 @@ namespace ProjectM.Tests public void ValidPick_AppendsBoonBandRow_AndClearsPending() { var (world, group, dir, catalog) = MakeWorld(RunLifecycle.RoomReward); + using (world) + { var em = world.EntityManager; var player = MakePicker(em, 1); SendPick(em, 1, index: 1); // Option1 = id 4 (Fleet Foot, MoveSpeed +12%) @@ -85,7 +96,7 @@ namespace ProjectM.Tests Assert.AreEqual(0.12f, mods[0].Value, 1e-4f); Assert.AreEqual(0, em.GetComponentData(player).Pending, "pick consumed"); Assert.AreEqual(1u, em.GetComponentData(dir).BoonPickCounter, "band provenance advanced"); - world.Dispose(); + } } [Test] @@ -93,28 +104,30 @@ namespace ProjectM.Tests { // Not pending. var (w1, g1, d1, c1) = MakeWorld(RunLifecycle.RoomReward); + _worlds.Add(w1); var p1 = MakePicker(w1.EntityManager, 1); w1.EntityManager.SetComponentData(p1, new BoonOffer { Pending = 0, Option0 = 1 }); SendPick(w1.EntityManager, 1, 0); g1.Update(); Assert.AreEqual(0, BoonRows(w1.EntityManager, p1), "not-pending pick rejected"); - w1.Dispose(); // Lifecycle closed (Returning): the straggler pick dies BEFORE any strip could be out-run (D-F4). var (w2, g2, d2, c2) = MakeWorld(RunLifecycle.Returning); + _worlds.Add(w2); var p2 = MakePicker(w2.EntityManager, 1); SendPick(w2.EntityManager, 1, 0); g2.Update(); Assert.AreEqual(0, BoonRows(w2.EntityManager, p2), "closed-lifecycle pick rejected"); using (var q = w2.EntityManager.CreateEntityQuery(typeof(BoonPickRequest))) Assert.AreEqual(0, q.CalculateEntityCount(), "request still consumed"); - w2.Dispose(); } [Test] public void GraceElapsed_AutoPicksOption0_ForPendingExpeditionPlayers() { var (world, group, dir, catalog) = MakeWorld(RunLifecycle.RoomReward); + using (world) + { var em = world.EntityManager; var afk = MakePicker(em, 1, o0: 5); // Option0 = id 5 (Iron Constitution, +25 MaxHealth) var run = em.GetComponentData(dir); @@ -127,7 +140,7 @@ namespace ProjectM.Tests var mods = em.GetBuffer(afk); Assert.AreEqual((byte)StatTarget.MaxHealth, mods[0].Target); Assert.AreEqual(0, em.GetComponentData(afk).Pending, "gate released"); - world.Dispose(); + } } [Test] @@ -135,6 +148,8 @@ namespace ProjectM.Tests { // Drive the REAL RunDirectorSystem Returning edge over a player carrying all four bands. var world = new World("BoonStripTest"); + using (world) + { var group = world.GetOrCreateSystemManaged(); group.AddSystemToUpdateList(world.GetOrCreateSystem()); group.SortSystems(); @@ -168,7 +183,7 @@ namespace ProjectM.Tests && after[i].SourceId < Tuning.BoonSourceIdBase + Tuning.BoonSourceIdSpan, "no boon-band survivor"); Assert.AreEqual(0, em.GetComponentData(player).Pending, "straggler offer zeroed"); Assert.AreEqual(RunLifecycle.Staging, em.GetComponentData(dir).Lifecycle); - world.Dispose(); + } } } } diff --git a/Assets/_Project/Tests/EditMode/BoonOfferTests.cs b/Assets/_Project/Tests/EditMode/BoonOfferTests.cs index 50ea9d32e..7e3332752 100644 --- a/Assets/_Project/Tests/EditMode/BoonOfferTests.cs +++ b/Assets/_Project/Tests/EditMode/BoonOfferTests.cs @@ -55,6 +55,8 @@ namespace ProjectM.Tests public void OfferSystem_DealsOncePerRoom_ExpeditionOnly_PerPlayerSeeds() { var world = new World("BoonOfferTest"); + using (world) + { var group = world.GetOrCreateSystemManaged(); group.AddSystemToUpdateList(world.GetOrCreateSystem()); group.SortSystems(); @@ -98,7 +100,7 @@ namespace ProjectM.Tests group.Update(); Assert.AreEqual(0, em.GetComponentData(out1).Pending, "one deal per RoomEpoch (latch)"); - world.Dispose(); + } } } } diff --git a/Assets/_Project/Tests/EditMode/MetaSeedingTests.cs b/Assets/_Project/Tests/EditMode/MetaSeedingTests.cs index eb21d2f85..224fdb029 100644 --- a/Assets/_Project/Tests/EditMode/MetaSeedingTests.cs +++ b/Assets/_Project/Tests/EditMode/MetaSeedingTests.cs @@ -67,6 +67,8 @@ namespace ProjectM.Tests public void Spawn_SeedsClassTiers_SkipsOthers_ClampsOverMax() { var (world, group) = MakeWorld(); + using (world) + { var em = world.EntityManager; var prefab = MakePlayerPrefab(em); var spawnerE = em.CreateEntity(typeof(PlayerSpawner)); @@ -104,13 +106,15 @@ namespace ProjectM.Tests Assert.AreEqual(2, metaRows, "exactly the two legal Warrior tiers seeded (unknown/other-class/other-mask skipped)"); Assert.AreEqual(30f, frameValue, 1e-3f, "Reinforced Frame tier 2 = 15 * 2"); Assert.AreEqual(0.40f, mightValue, 1e-3f, "Warrior's Might CLAMPED to MaxTier 4 = 0.10 * 4 (D-F5)"); - world.Dispose(); + } } [Test] public void MissingCatalog_BlocksSpawn_PreservesRequest() { var (world, group) = MakeWorld(); + using (world) + { var em = world.EntityManager; var prefab = MakePlayerPrefab(em); var spawnerE = em.CreateEntity(typeof(PlayerSpawner)); @@ -125,7 +129,7 @@ namespace ProjectM.Tests var rq = em.CreateEntityQuery(typeof(GoInGameRequest)); Assert.AreEqual(1, rq.CalculateEntityCount(), "request PRESERVED — the spawn retries when the catalog streams in"); pq.Dispose(); rq.Dispose(); // BEFORE the world (a using-var here would outlive it)in"); - world.Dispose(); + } } } } diff --git a/Assets/_Project/Tests/EditMode/MetaSpendSystemTests.cs b/Assets/_Project/Tests/EditMode/MetaSpendSystemTests.cs index 50e11f54c..990ad4b08 100644 --- a/Assets/_Project/Tests/EditMode/MetaSpendSystemTests.cs +++ b/Assets/_Project/Tests/EditMode/MetaSpendSystemTests.cs @@ -84,6 +84,8 @@ namespace ProjectM.Tests public void Purchase_BumpsTier_Withdraws_UpsertsAllClassMembers_FlagsSave() { var (world, group) = MakeWorld(); + using (world) + { var em = world.EntityManager; var dir = MakeDirector(em, RunLifecycle.Staging, 25); var warriorA = MakePlayer(em, 1, ClassTraits.WarriorClass); @@ -104,13 +106,15 @@ namespace ProjectM.Tests Assert.AreEqual(0, rowsR); Assert.AreEqual(1, em.GetComponentData(dir).Pending, "purchase flags the autosave"); Assert.AreEqual(0, PendingRequests(em), "request consumed"); - world.Dispose(); + } } [Test] public void TwoSameTick_BarelyEnough_ExactlyOneSucceeds() { var (world, group) = MakeWorld(); + using (world) + { var em = world.EntityManager; var dir = MakeDirector(em, RunLifecycle.Staging, 10); // ids 1 and 4 BOTH cost 10 at tier 0 var warrior = MakePlayer(em, 1, ClassTraits.WarriorClass); @@ -126,13 +130,15 @@ namespace ProjectM.Tests Assert.AreEqual(0, StorageMath.TotalOf(em.GetBuffer(dir, true), ResourceId.Aether), "the single cost fully drained the ledger — and never went negative (TotalOf pre-check)"); Assert.AreEqual(0, PendingRequests(em), "both requests consumed"); - world.Dispose(); + } } [Test] public void SecondPurchase_AbsoluteUpsert_SingleRow_RampedCost() { var (world, group) = MakeWorld(); + using (world) + { var em = world.EntityManager; var dir = MakeDirector(em, RunLifecycle.Staging, 30); // tier1 = 10, tier2 = 10 + 1*5 = 15 var warrior = MakePlayer(em, 1, ClassTraits.WarriorClass); @@ -150,13 +156,15 @@ namespace ProjectM.Tests Assert.AreEqual(1, rows, "one modifier row — an incremental append would double-count in recompute"); Assert.AreEqual(5, StorageMath.TotalOf(em.GetBuffer(dir, true), ResourceId.Aether), "linear ramp: 30 - 10 - 15"); - world.Dispose(); + } } [Test] public void Rejects_WrongClassMask_MaxTierCap_NonStaging() { var (world, group) = MakeWorld(); + using (world) + { var em = world.EntityManager; var dir = MakeDirector(em, RunLifecycle.Staging, 999); var warrior = MakePlayer(em, 1, ClassTraits.WarriorClass); @@ -184,7 +192,7 @@ namespace ProjectM.Tests "non-Staging purchase dropped"); Assert.AreEqual(999, StorageMath.TotalOf(em.GetBuffer(dir, true), ResourceId.Aether)); Assert.AreEqual(0, PendingRequests(em), "every request consumed, accepted or not"); - world.Dispose(); + } } } } diff --git a/Assets/_Project/Tests/EditMode/ReadyCheckSystemTests.cs b/Assets/_Project/Tests/EditMode/ReadyCheckSystemTests.cs index 1b5927187..1095939b7 100644 --- a/Assets/_Project/Tests/EditMode/ReadyCheckSystemTests.cs +++ b/Assets/_Project/Tests/EditMode/ReadyCheckSystemTests.cs @@ -77,6 +77,8 @@ namespace ProjectM.Tests public void Toggle_SetsReady_AndSoloLaunches_SameTick() { var (world, group, dir) = MakeWorld(); + using (world) + { var em = world.EntityManager; var player = MakePlayer(em, 1); var conn = MakeConnection(em, 1); @@ -92,13 +94,15 @@ namespace ProjectM.Tests Assert.AreNotEqual(0u, info.RunSeed, "run seeded"); Assert.GreaterOrEqual(info.RoomCount, 6, "seed-varied length floor"); Assert.LessOrEqual(info.RoomCount, 10, "seed-varied length cap"); - world.Dispose(); + } } [Test] public void Toggle_Ignored_MidRun() { var (world, group, dir) = MakeWorld(); + using (world) + { var em = world.EntityManager; var player = MakePlayer(em, 1); var conn = MakeConnection(em, 1); @@ -111,13 +115,15 @@ namespace ProjectM.Tests Assert.AreEqual(0, em.GetComponentData(player).Value, "mid-run toggle dropped"); Assert.AreEqual(0, PendingRequests(em), "request still consumed"); - world.Dispose(); + } } [Test] public void PartialReady_DoesNotLaunch() { var (world, group, dir) = MakeWorld(); + using (world) + { var em = world.EntityManager; MakePlayer(em, 1); MakePlayer(em, 2); @@ -128,13 +134,15 @@ namespace ProjectM.Tests Assert.AreEqual(RunLifecycle.Staging, em.GetComponentData(dir).Lifecycle, "1/2 ready must NOT launch"); - world.Dispose(); + } } [Test] public void UnReady_DuringCountdown_Aborts() { var (world, group, dir) = MakeWorld(); + using (world) + { var em = world.EntityManager; MakePlayer(em, 1); var conn = MakeConnection(em, 1); @@ -149,13 +157,15 @@ namespace ProjectM.Tests var info = em.GetComponentData(dir); Assert.AreEqual(RunLifecycle.Staging, info.Lifecycle, "un-ready aborts the countdown"); Assert.AreEqual(0u, info.LaunchTick, "telegraph cleared"); - world.Dispose(); + } } [Test] public void Launch_TeleportsPartyOut_ThenHome_AndClearsReady() { var (world, group, dir) = MakeWorld(); + using (world) + { var em = world.EntityManager; var player = MakePlayer(em, 1); var conn = MakeConnection(em, 1); @@ -187,13 +197,15 @@ namespace ProjectM.Tests var run = em.GetComponentData(dir); Assert.AreEqual(run.RunEpoch, run.LastBankedRunEpoch, "terminal bank latch fired once"); Assert.AreEqual(RunLifecycle.Staging, em.GetComponentData(dir).Lifecycle); - world.Dispose(); + } } [Test] public void LaunchGuard_BlocksWhenOutcomeLatched() { var (world, group, dir) = MakeWorld(); + using (world) + { var em = world.EntityManager; MakePlayer(em, 1); var conn = MakeConnection(em, 1); @@ -204,7 +216,7 @@ namespace ProjectM.Tests Assert.AreEqual(RunLifecycle.Staging, em.GetComponentData(dir).Lifecycle, "a decided run must not launch (F2 guard)"); - world.Dispose(); + } } } } diff --git a/Assets/_Project/Tests/EditMode/RoomEnemyDirectorSystemTests.cs b/Assets/_Project/Tests/EditMode/RoomEnemyDirectorSystemTests.cs index d40328fcd..cd64c72b6 100644 --- a/Assets/_Project/Tests/EditMode/RoomEnemyDirectorSystemTests.cs +++ b/Assets/_Project/Tests/EditMode/RoomEnemyDirectorSystemTests.cs @@ -92,6 +92,8 @@ namespace ProjectM.Tests public void Seeds_ByRoomDifficulty_AndSpawnsTaggedAtActiveSlotOrigin() { var (world, group, runDir, zoneDir) = MakeWorld(currentRoom: 0, currentNodeId: RunMap.NodeId(0, 0), activeSubSlot: 0); + using (world) + { var em = world.EntityManager; var map = RunMapMath.Generate(Seed); var plan = RoomLayoutMath.Plan(map.NodeAt(RunMap.NodeId(0, 0)), 0, map.LayerCount); @@ -120,7 +122,7 @@ namespace ProjectM.Tests Assert.LessOrEqual(math.distance(xfs[0].Position.xz, origin.xz), 14f + 0.01f, "ring-spawned around the ACTIVE sub-slot origin"); xfs.Dispose(); regs.Dispose(); rooms.Dispose(); q.Dispose(); // dispose BEFORE the worldispose(); - world.Dispose(); + } } [Test] @@ -129,6 +131,8 @@ namespace ProjectM.Tests var map = RunMapMath.Generate(Seed); int bossLayer = map.LayerCount - 1; var (world, group, runDir, zoneDir) = MakeWorld(bossLayer, map.BossNodeId, activeSubSlot: (byte)(bossLayer & 1)); + using (world) + { var em = world.EntityManager; group.Update(); // seed tick (the landing grace holds the boss) @@ -145,13 +149,15 @@ namespace ProjectM.Tests Assert.AreEqual(50f * Tuning.BossHealthMultiplier, hps[0].Max, 1e-3f, "boss health scaled"); Assert.AreEqual(Tuning.BossScaleMultiplier, xfs[0].Scale, 1e-3f, "boss visual scale bumped"); hps.Dispose(); xfs.Dispose(); q.Dispose(); // dispose BEFORE the world (a using-var here outlives it); - world.Dispose(); + } } [Test] public void Objective_ClearedLatch_AndIdleOutsideRooms() { var (world, group, runDir, zoneDir) = MakeWorld(0, RunMap.NodeId(0, 0), 0); + using (world) + { var em = world.EntityManager; // Fabricate a fully-spawned, fully-dead wave for the CURRENT room epoch. @@ -166,13 +172,15 @@ namespace ProjectM.Tests group.Update(); Assert.AreEqual(ExpeditionObjectiveState.Idle, em.GetComponentData(runDir).State, "no active room -> Idle (objective still written above the early-return)"); - world.Dispose(); + } } [Test] public void MaxAlive_PackFitWaits_WithoutConsumingTheSlot() { var (world, group, runDir, zoneDir) = MakeWorld(0, RunMap.NodeId(0, 0), 0, maxAlive: 1); + using (world) + { var em = world.EntityManager; // One zone enemy already alive fills the cap. var blocker = em.CreateEntity(typeof(ZoneEnemyTag)); @@ -184,7 +192,7 @@ namespace ProjectM.Tests var zs = em.GetComponentData(zoneDir); Assert.AreEqual(1, Alive(em), "cap full -> nothing spawned"); Assert.AreEqual(2, zs.RemainingToSpawn, "the slot WAITS (not consumed) until the pack fits"); - world.Dispose(); + } } } } diff --git a/Assets/_Project/Tests/EditMode/RoomFieldSystemTests.cs b/Assets/_Project/Tests/EditMode/RoomFieldSystemTests.cs index a845ea927..00d96236f 100644 --- a/Assets/_Project/Tests/EditMode/RoomFieldSystemTests.cs +++ b/Assets/_Project/Tests/EditMode/RoomFieldSystemTests.cs @@ -70,6 +70,8 @@ namespace ProjectM.Tests public void Spawns_OncePerRoomEpoch_PlanCount_TaggedInShape_ScalePreserved() { var (world, group, dir, spawnerE, prefab) = MakeWorld(nodeBudget: 12); + using (world) + { var em = world.EntityManager; var map = RunMapMath.Generate(Seed); var plan = RoomLayoutMath.Plan(map.NodeAt(RunMap.NodeId(0, 0)), 0, map.LayerCount); @@ -100,13 +102,15 @@ namespace ProjectM.Tests group.Update(); // same RoomEpoch — must NOT scatter again Assert.AreEqual(expected, LiveNodes(em), "int-equality reseed: one scatter per RoomEpoch"); - world.Dispose(); + } } [Test] public void Budget_FloorsSpawnCount_AndExhausts() { var (world, group, dir, spawnerE, prefab) = MakeWorld(nodeBudget: 1); + using (world) + { var em = world.EntityManager; group.Update(); @@ -125,13 +129,15 @@ namespace ProjectM.Tests group.Update(); Assert.AreEqual(1, LiveNodes(em), "a dry budget spawns nothing (scarcity holds run-wide)"); - world.Dispose(); + } } [Test] public void StagingSweep_KillsRoomGhosts_SparesUntagged() { var (world, group, dir, spawnerE, prefab) = MakeWorld(nodeBudget: 12); + using (world) + { var em = world.EntityManager; group.Update(); Assert.Greater(LiveNodes(em), 0, "room content exists"); @@ -147,7 +153,7 @@ namespace ProjectM.Tests group.Update(); Assert.AreEqual(0, LiveNodes(em), "Staging sweep cleared every room ghost"); Assert.IsTrue(em.Exists(baseNode), "untagged entities survive (the old base-field regression, structural now)"); - world.Dispose(); + } } } } diff --git a/Assets/_Project/Tests/EditMode/RouteSelectSystemTests.cs b/Assets/_Project/Tests/EditMode/RouteSelectSystemTests.cs index 00e048fa1..ba0b18c4c 100644 --- a/Assets/_Project/Tests/EditMode/RouteSelectSystemTests.cs +++ b/Assets/_Project/Tests/EditMode/RouteSelectSystemTests.cs @@ -4,6 +4,7 @@ using ProjectM.Simulation; using Unity.Core; using Unity.Entities; using Unity.NetCode; +using System.Collections.Generic; namespace ProjectM.Tests { @@ -18,6 +19,14 @@ namespace ProjectM.Tests { const uint Seed = 999u; const int GateLayer = 2; + readonly List _worlds = new(); + + [TearDown] + public void Cleanup() + { + foreach (var w in _worlds) if (w.IsCreated) w.Dispose(); + _worlds.Clear(); + } static (World world, SimulationSystemGroup group, Entity dir) MakeGateWorld(byte lifecycle = RunLifecycle.RouteSelect) { @@ -69,6 +78,8 @@ namespace ProjectM.Tests public void ValidPick_Latches_WithTrueServerEpoch() { var (world, group, dir) = MakeGateWorld(); + using (world) + { var em = world.EntityManager; MakePlayer(em, 1, RegionId.Expedition); SendPick(em, 1, optionIndex: 1, forSeed: (int)Seed, forLayer: GateLayer); @@ -80,13 +91,15 @@ namespace ProjectM.Tests Assert.AreEqual(1, cmd.OptionIndex, "the picked option"); Assert.AreEqual(3, cmd.ForRunEpoch, "stamped from the TRUE server epoch, never the client echo"); Assert.AreEqual(0, PendingRequests(em), "request consumed"); - world.Dispose(); + } } [Test] public void TwoSameTickPicks_FirstWins() { var (world, group, dir) = MakeGateWorld(); + using (world) + { var em = world.EntityManager; MakePlayer(em, 1, RegionId.Expedition); MakePlayer(em, 2, RegionId.Expedition); @@ -99,7 +112,7 @@ namespace ProjectM.Tests Assert.AreEqual(1, cmd.HasPick, "exactly one commit"); Assert.AreEqual(0, cmd.OptionIndex, "the FIRST accepted pick wins (in-place latch, DR-014)"); Assert.AreEqual(0, PendingRequests(em), "both requests consumed"); - world.Dispose(); + } } [Test] @@ -107,51 +120,53 @@ namespace ProjectM.Tests { // Wrong run-identity token (a stale pick from the previous run). var (w1, g1, d1) = MakeGateWorld(); + _worlds.Add(w1); MakePlayer(w1.EntityManager, 1, RegionId.Expedition); SendPick(w1.EntityManager, 1, 0, forSeed: (int)Seed + 1, forLayer: GateLayer); g1.Update(); Assert.AreEqual(0, w1.EntityManager.GetComponentData(d1).HasPick, "wrong seed rejected"); Assert.AreEqual(0, PendingRequests(w1.EntityManager)); - w1.Dispose(); // Wrong layer (a pick from the previous gate of the SAME run). var (w2, g2, d2) = MakeGateWorld(); + _worlds.Add(w2); MakePlayer(w2.EntityManager, 1, RegionId.Expedition); SendPick(w2.EntityManager, 1, 0, (int)Seed, forLayer: GateLayer - 1); g2.Update(); Assert.AreEqual(0, w2.EntityManager.GetComponentData(d2).HasPick, "stale layer rejected"); - w2.Dispose(); // Option index out of the published range. var (w3, g3, d3) = MakeGateWorld(); + _worlds.Add(w3); MakePlayer(w3.EntityManager, 1, RegionId.Expedition); SendPick(w3.EntityManager, 1, optionIndex: 2, (int)Seed, GateLayer); // count is 2 -> max index 1 g3.Update(); Assert.AreEqual(0, w3.EntityManager.GetComponentData(d3).HasPick, "out-of-range rejected"); - w3.Dispose(); // Base-region sender (N3): a home-bound joiner cannot commit the party's route. var (w4, g4, d4) = MakeGateWorld(); + _worlds.Add(w4); MakePlayer(w4.EntityManager, 1, RegionId.Base); SendPick(w4.EntityManager, 1, 0, (int)Seed, GateLayer); g4.Update(); Assert.AreEqual(0, w4.EntityManager.GetComponentData(d4).HasPick, "base sender rejected (N3)"); - w4.Dispose(); // Gate closed (mid-room): the pick is dropped, never queued. var (w5, g5, d5) = MakeGateWorld(lifecycle: RunLifecycle.InRoom); + _worlds.Add(w5); MakePlayer(w5.EntityManager, 1, RegionId.Expedition); SendPick(w5.EntityManager, 1, 0, (int)Seed, GateLayer); g5.Update(); Assert.AreEqual(0, w5.EntityManager.GetComponentData(d5).HasPick, "closed gate rejected"); Assert.AreEqual(0, PendingRequests(w5.EntityManager), "request still consumed"); - w5.Dispose(); } [Test] public void AlreadyLatched_LaterPickIgnored() { var (world, group, dir) = MakeGateWorld(); + using (world) + { var em = world.EntityManager; MakePlayer(em, 1, RegionId.Expedition); em.SetComponentData(dir, new RouteCommand { HasPick = 1, OptionIndex = 0, ForRunEpoch = 3, ForLayer = GateLayer }); @@ -161,7 +176,7 @@ namespace ProjectM.Tests var cmd = em.GetComponentData(dir); Assert.AreEqual(0, cmd.OptionIndex, "an already-latched gate ignores later picks"); - world.Dispose(); + } } } } diff --git a/Assets/_Project/Tests/EditMode/RunDirectorTraversalTests.cs b/Assets/_Project/Tests/EditMode/RunDirectorTraversalTests.cs index 06908006f..ca4436b64 100644 --- a/Assets/_Project/Tests/EditMode/RunDirectorTraversalTests.cs +++ b/Assets/_Project/Tests/EditMode/RunDirectorTraversalTests.cs @@ -93,6 +93,8 @@ static int RoomEntities(EntityManager em) public void Cleared_LootWindowThenPortalAdvances_WithSlotFlipAndEpochBump() { var (world, group, dir, player) = MakeMidRunWorld(0, out var map); + using (world) + { var em = world.EntityManager; var node0 = em.CreateEntity(typeof(RoomTag)); em.SetComponentData(node0, new RoomTag { Room = 0 }); @@ -132,7 +134,7 @@ static int RoomEntities(EntityManager em) Assert.AreEqual(0, em.GetComponentData(dir).HasPick, "latch consumed"); Assert.AreEqual(0, (int)info.RouteOptionCount, "gate closed on advance"); Assert.GreaterOrEqual(em.GetComponentData(player).Position.x, 1499f, "party teleported (+1500)"); - world.Dispose(); + } } [Test] @@ -140,6 +142,8 @@ static int RoomEntities(EntityManager em) { RunMap map0; var (world, group, dir, player) = MakeMidRunWorld(0, out map0); + using (world) + { var em = world.EntityManager; int bossLayer = map0.LayerCount - 1; var info0 = em.GetComponentData(dir); @@ -174,13 +178,15 @@ static int RoomEntities(EntityManager em) group.Update(); Assert.AreEqual(1, em.GetComponentData(dir).Charge, "no double credit (F7)"); Assert.AreEqual(1, em.GetComponentData(dir).RunsCompleted); - world.Dispose(); + } } [Test] public void Abort_BanksDepthOnly_NoWinCredit() { var (world, group, dir, player) = MakeMidRunWorld(2, out var map); + using (world) + { var em = world.EntityManager; // All expedition players gone mid-room-2 (rooms 0-1 cleared) -> abort. em.SetComponentData(player, new RegionTag { Region = RegionId.Base }); @@ -196,13 +202,15 @@ static int RoomEntities(EntityManager em) var threat = em.GetComponentData(dir); Assert.AreEqual(0, threat.PendingReturns, "no retaliation provoked by an abort"); Assert.AreEqual(0, em.GetComponentData(dir).Pending, "no save spam on abort"); - world.Dispose(); + } } [Test] public void RouteGate_PickBeatsSameTickGrace() { var (world, group, dir, player) = MakeMidRunWorld(0, out var map); + using (world) + { var em = world.EntityManager; MarkCleared(em, dir); group.Update(); @@ -221,13 +229,15 @@ static int RoomEntities(EntityManager em) var info = em.GetComponentData(dir); Assert.AreEqual(RunLifecycle.InRoom, info.Lifecycle); Assert.AreEqual(pickedCol, info.CurrentCol, "the accepted pick beats the same-tick grace expiry"); - world.Dispose(); + } } [Test] public void RouteGate_GraceAutoPicksLowestOption() { var (world, group, dir, player) = MakeMidRunWorld(0, out var map); + using (world) + { var em = world.EntityManager; MarkCleared(em, dir); group.Update(); @@ -242,13 +252,15 @@ static int RoomEntities(EntityManager em) var info = em.GetComponentData(dir); Assert.AreEqual(RunLifecycle.InRoom, info.Lifecycle, "the AFK backstop advances the run"); Assert.AreEqual(lowestCol, info.CurrentCol, "deterministic lowest-index reachable auto-pick"); - world.Dispose(); + } } [Test] public void RouteGate_Abort_ClosesGateOnTheEdge() { var (world, group, dir, player) = MakeMidRunWorld(0, out var map); + using (world) + { var em = world.EntityManager; MarkCleared(em, dir); group.Update(); @@ -262,7 +274,7 @@ static int RoomEntities(EntityManager em) var info = em.GetComponentData(dir); Assert.AreEqual(RunLifecycle.Returning, info.Lifecycle); Assert.AreEqual(0, (int)info.RouteOptionCount, "gate closed ON the abort edge (review F3)"); - world.Dispose(); + } } } } diff --git a/Assets/_Project/Tests/EditMode/RunRosterRegressionTests.cs b/Assets/_Project/Tests/EditMode/RunRosterRegressionTests.cs index 753e96b11..f357ac833 100644 --- a/Assets/_Project/Tests/EditMode/RunRosterRegressionTests.cs +++ b/Assets/_Project/Tests/EditMode/RunRosterRegressionTests.cs @@ -55,6 +55,8 @@ namespace ProjectM.Tests public void StalePendingAtBase_DoesNotHoldGate_AndIsStrippedOnExit() { var (world, group, dir) = MakeWorld(out var map); + using (world) + { var em = world.EntityManager; em.SetComponentData(dir, new RunInfo { @@ -86,13 +88,15 @@ namespace ProjectM.Tests Assert.AreEqual(0, em.GetComponentData(alive).Pending, "no offer survives the gate"); - world.Dispose(); + } } [Test] public void Advance_TeleportsOnlyParticipants_LateJoinerStaysAtBase() { var (world, group, dir) = MakeWorld(out var map); + using (world) + { var em = world.EntityManager; em.SetComponentData(dir, new RunInfo { @@ -130,7 +134,7 @@ namespace ProjectM.Tests Assert.AreEqual(RegionId.Base, em.GetComponentData(lateJoiner).Region, "the late joiner stays in the Base relevancy bucket"); - world.Dispose(); + } } } }