Files
Project-M/Assets/_Project/Tests/EditMode/ExpeditionFieldTeardownTests.cs
kronic 096c62862f Tests: base mining field, expedition teardown, schedule siege, melee harvest
8 new EditMode tests (302 total, all green): BaseFieldSpawnSystem (target count + Base/Ore + cadence + top-up), ExpeditionFieldSystem teardown preserves the base field, ThreatDirector schedule arming, and melee harvest routing (base->ledger, expedition->personal inventory) which guards the cross-region leak the post-impl review caught. See DR-031.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 15:00:03 -07:00

58 lines
3.1 KiB
C#

using NUnit.Framework;
using ProjectM.Server;
using ProjectM.Simulation;
using Unity.Core;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
namespace ProjectM.Tests
{
/// <summary>
/// Regression test for the <see cref="ExpeditionFieldSystem"/> teardown region-filter. When the last player
/// leaves the expedition the field is cleared — but that teardown must destroy ONLY RegionTag{Expedition}
/// nodes, never the permanent RegionTag{Base} home-base mining field. Before the fix the unfiltered teardown
/// wiped every ResourceNode on the empty edge (a despawn storm beside base players that broke the core loop).
/// </summary>
public class ExpeditionFieldTeardownTests
{
[Test]
public void Expedition_Empty_Edge_Destroys_Only_Expedition_Nodes_Base_Field_Survives()
{
var world = new World("ExpeditionTeardown");
using (world)
{
var group = world.GetOrCreateSystemManaged<SimulationSystemGroup>();
group.AddSystemToUpdateList(world.GetOrCreateSystem<ExpeditionFieldSystem>());
group.SortSystems();
world.SetTime(new TimeData(elapsedTime: 0f, deltaTime: 1f / 60f));
var em = world.EntityManager;
// Cycle director: was occupied last tick, nobody out there now => the occupied->empty edge fires.
var cycle = em.CreateEntity(typeof(CycleState), typeof(CycleRuntime));
em.SetComponentData(cycle, new CycleState { Phase = CyclePhase.Calm, CycleNumber = 1 });
em.SetComponentData(cycle, new CycleRuntime { PrevExpeditionOccupied = 1 });
// Spawner singleton (required); null prefab so the spawn branch is inert.
var spawnerE = em.CreateEntity(typeof(ResourceFieldSpawner));
em.SetComponentData(spawnerE, new ResourceFieldSpawner { Prefab = Entity.Null, Count = 5, Radius = 10f });
var baseNode = em.CreateEntity(typeof(LocalTransform), typeof(ResourceNode), typeof(RegionTag));
em.SetComponentData(baseNode, LocalTransform.FromPosition(new float3(20, 0, 0)));
em.SetComponentData(baseNode, new ResourceNode { ResourceId = ResourceId.Ore, Remaining = 30, HarvestPerHit = 5f });
em.SetComponentData(baseNode, new RegionTag { Region = RegionId.Base });
var expNode = em.CreateEntity(typeof(LocalTransform), typeof(ResourceNode), typeof(RegionTag));
em.SetComponentData(expNode, LocalTransform.FromPosition(new float3(1020, 0, 0)));
em.SetComponentData(expNode, new ResourceNode { ResourceId = ResourceId.Aether, Remaining = 30, HarvestPerHit = 5f });
em.SetComponentData(expNode, new RegionTag { Region = RegionId.Expedition });
group.Update();
Assert.IsTrue(em.Exists(baseNode), "The permanent base mining field survives the expedition teardown.");
Assert.IsFalse(em.Exists(expNode), "Only the expedition node is cleared when the last player leaves.");
}
}
}
}