Game Scene Split up
This commit is contained in:
@@ -8,10 +8,13 @@ using Unity.NetCode;
|
||||
namespace ProjectM.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Plain-Entities EditMode tests for the server-only <see cref="CyclePhaseSystem"/> — the macro-loop director
|
||||
/// (Expedition → Defend → Build → next cycle). A bare world is seeded with a NetworkTime singleton and a cycle
|
||||
/// entity carrying CycleState + CycleRuntime (and optionally WaveState / GoalProgress). All timing is wrap-safe
|
||||
/// NetworkTick math; these tests pin each phase transition and the per-cycle goal-charge increment.
|
||||
/// Plain-Entities EditMode tests for the server-only <see cref="CyclePhaseSystem"/> — the PLAYER-DRIVEN
|
||||
/// run-state director (Calm ↔ Siege). A bare world is seeded with a NetworkTime singleton and a cycle entity
|
||||
/// carrying CycleState + CycleRuntime (+ optionally ThreatState / WaveState / GoalProgress). The global phase
|
||||
/// is only ever Calm or Siege — being out on an expedition is per-player presence, NOT a global phase — so
|
||||
/// these pin: Calm holds with no pending siege; an armed ThreatState.PendingSiegeSize enters Siege and seeds
|
||||
/// WaveState's Spawning entry at the EXACT size; a cleared Siege returns to Calm and charges the goal once;
|
||||
/// and split co-op presence never produces a non-Calm phase. All timing is wrap-safe NetworkTick math.
|
||||
/// </summary>
|
||||
public class CyclePhaseSystemTests
|
||||
{
|
||||
@@ -28,111 +31,132 @@ namespace ProjectM.Tests
|
||||
return (world, group);
|
||||
}
|
||||
|
||||
static Entity MakeCycle(EntityManager em, byte phase, uint phaseEndTick, int cycleNumber, int defendStartWave)
|
||||
static Entity MakeCycle(EntityManager em, byte phase, int defendStartWave)
|
||||
{
|
||||
var e = em.CreateEntity(typeof(CycleState), typeof(CycleRuntime));
|
||||
em.SetComponentData(e, new CycleState { Phase = phase, PhaseEndTick = phaseEndTick, CycleNumber = cycleNumber });
|
||||
em.SetComponentData(e, new CycleState { Phase = phase, PhaseEndTick = 0u, CycleNumber = 1 });
|
||||
em.SetComponentData(e, new CycleRuntime { DefendStartWave = defendStartWave });
|
||||
return e;
|
||||
}
|
||||
|
||||
static void MakeWaveState(EntityManager em, int waveNumber, int remainingToSpawn)
|
||||
static void AddThreat(EntityManager em, Entity cycle, int pendingSiegeSize, uint armTick)
|
||||
{
|
||||
em.AddComponentData(cycle, new ThreatState { PendingSiegeSize = pendingSiegeSize, ArmTick = armTick });
|
||||
}
|
||||
|
||||
static Entity MakeWaveState(EntityManager em, int waveNumber, byte phase, int remainingToSpawn)
|
||||
{
|
||||
var e = em.CreateEntity(typeof(WaveState));
|
||||
em.SetComponentData(e, new WaveState { WaveNumber = waveNumber, RemainingToSpawn = remainingToSpawn });
|
||||
em.SetComponentData(e, new WaveState { WaveNumber = waveNumber, Phase = phase, RemainingToSpawn = remainingToSpawn });
|
||||
return e;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Expedition_Enters_Defend_When_Timer_Due_Capturing_StartWave()
|
||||
public void Calm_Holds_When_No_PendingSiege()
|
||||
{
|
||||
var (world, group) = MakeWorld("CycleExpToDefend", serverTick: 200);
|
||||
var (world, group) = MakeWorld("CalmHolds", serverTick: 200);
|
||||
using (world)
|
||||
{
|
||||
var em = world.EntityManager;
|
||||
var cycle = MakeCycle(em, CyclePhase.Expedition, phaseEndTick: 100, cycleNumber: 1, defendStartWave: 0);
|
||||
MakeWaveState(em, waveNumber: 5, remainingToSpawn: 0);
|
||||
var cycle = MakeCycle(em, CyclePhase.Calm, defendStartWave: 0);
|
||||
AddThreat(em, cycle, pendingSiegeSize: 0, armTick: 0);
|
||||
|
||||
group.Update();
|
||||
|
||||
var cs = em.GetComponentData<CycleState>(cycle);
|
||||
Assert.AreEqual(CyclePhase.Defend, cs.Phase, "An expired Expedition timer enters Defend.");
|
||||
Assert.AreEqual(0u, cs.PhaseEndTick, "Defend is wave-driven, so PhaseEndTick is cleared.");
|
||||
Assert.AreEqual(CyclePhase.Calm, em.GetComponentData<CycleState>(cycle).Phase,
|
||||
"With no pending siege the base stays Calm — no forced timer.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PendingSiege_Enters_Siege_And_Seeds_WaveState_Spawning_With_Exact_Size()
|
||||
{
|
||||
var (world, group) = MakeWorld("PendingSiege", serverTick: 200);
|
||||
using (world)
|
||||
{
|
||||
var em = world.EntityManager;
|
||||
var cycle = MakeCycle(em, CyclePhase.Calm, defendStartWave: 0);
|
||||
AddThreat(em, cycle, pendingSiegeSize: 7, armTick: 0); // armTick 0 => fire immediately
|
||||
var wave = MakeWaveState(em, waveNumber: 5, phase: WavePhase.Lull, remainingToSpawn: 0);
|
||||
|
||||
group.Update();
|
||||
|
||||
Assert.AreEqual(CyclePhase.Siege, em.GetComponentData<CycleState>(cycle).Phase,
|
||||
"An armed pending siege enters Siege.");
|
||||
|
||||
var w = em.GetComponentData<WaveState>(wave);
|
||||
Assert.AreEqual(WavePhase.Spawning, w.Phase,
|
||||
"WaveState is driven into Spawning (bypassing the Lull escalation recompute).");
|
||||
Assert.AreEqual(7, w.RemainingToSpawn,
|
||||
"RemainingToSpawn is the EXACT director-chosen siege size (not the escalation curve).");
|
||||
Assert.AreEqual(6, w.WaveNumber, "WaveNumber advances by one for the siege.");
|
||||
|
||||
Assert.AreEqual(5, em.GetComponentData<CycleRuntime>(cycle).DefendStartWave,
|
||||
"DefendStartWave captures the current WaveState.WaveNumber.");
|
||||
"DefendStartWave captures the pre-bump wave number.");
|
||||
Assert.AreEqual(0, em.GetComponentData<ThreatState>(cycle).PendingSiegeSize,
|
||||
"The pending siege is consumed (zeroed) so it fires exactly once.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Expedition_Holds_While_Timer_Pending()
|
||||
public void Siege_Exits_To_Calm_On_DefendCleared_And_Charges_Goal_Once()
|
||||
{
|
||||
var (world, group) = MakeWorld("CycleExpHold", serverTick: 200);
|
||||
var (world, group) = MakeWorld("SiegeClears", serverTick: 200);
|
||||
using (world)
|
||||
{
|
||||
var em = world.EntityManager;
|
||||
var cycle = MakeCycle(em, CyclePhase.Expedition, phaseEndTick: 5000, cycleNumber: 1, defendStartWave: 0);
|
||||
|
||||
group.Update();
|
||||
|
||||
Assert.AreEqual(CyclePhase.Expedition, em.GetComponentData<CycleState>(cycle).Phase,
|
||||
"Expedition holds until its timer is due.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Defend_Enters_Build_When_Wave_Cleared()
|
||||
{
|
||||
var (world, group) = MakeWorld("CycleDefendToBuild", serverTick: 200);
|
||||
using (world)
|
||||
{
|
||||
var em = world.EntityManager;
|
||||
var cycle = MakeCycle(em, CyclePhase.Defend, phaseEndTick: 0, cycleNumber: 1, defendStartWave: 1);
|
||||
// Wave advanced past the captured start, fully spawned, and no Husks alive (none created).
|
||||
MakeWaveState(em, waveNumber: 2, remainingToSpawn: 0);
|
||||
|
||||
group.Update();
|
||||
|
||||
var cs = em.GetComponentData<CycleState>(cycle);
|
||||
Assert.AreEqual(CyclePhase.Build, cs.Phase, "A cleared Defend wave enters Build.");
|
||||
Assert.AreNotEqual(0u, cs.PhaseEndTick, "Build is timed, so a PhaseEndTick is stamped.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Build_Enters_Expedition_Incrementing_Cycle_And_Goal()
|
||||
{
|
||||
var (world, group) = MakeWorld("CycleBuildToExp", serverTick: 200);
|
||||
using (world)
|
||||
{
|
||||
var em = world.EntityManager;
|
||||
var cycle = MakeCycle(em, CyclePhase.Build, phaseEndTick: 100, cycleNumber: 1, defendStartWave: 0);
|
||||
var cycle = MakeCycle(em, CyclePhase.Siege, defendStartWave: 5);
|
||||
em.AddComponentData(cycle, new GoalProgress { Charge = 0, Target = 10 });
|
||||
// Wave advanced past the captured start, fully spawned, no Husks alive (none created).
|
||||
MakeWaveState(em, waveNumber: 6, phase: WavePhase.Spawning, remainingToSpawn: 0);
|
||||
|
||||
group.Update();
|
||||
|
||||
var cs = em.GetComponentData<CycleState>(cycle);
|
||||
Assert.AreEqual(CyclePhase.Expedition, cs.Phase, "An expired Build timer starts the next Expedition.");
|
||||
Assert.AreEqual(2, cs.CycleNumber, "CycleNumber increments on the new cycle.");
|
||||
Assert.AreEqual(CyclePhase.Calm, em.GetComponentData<CycleState>(cycle).Phase,
|
||||
"A cleared siege returns to Calm.");
|
||||
Assert.AreEqual(1, em.GetComponentData<GoalProgress>(cycle).Charge,
|
||||
"One goal charge accrues per completed cycle (single writer).");
|
||||
"One goal charge accrues per siege survived (single writer).");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
[Test]
|
||||
public void Coop_Split_Presence_Keeps_Global_Phase_Calm()
|
||||
{
|
||||
var (world, group) = MakeWorld("CoopSplit", serverTick: 200);
|
||||
using (world)
|
||||
{
|
||||
var em = world.EntityManager;
|
||||
var cycle = MakeCycle(em, CyclePhase.Calm, defendStartWave: 0);
|
||||
AddThreat(em, cycle, pendingSiegeSize: 0, armTick: 0);
|
||||
|
||||
// One player out on expedition, one home — the GLOBAL phase machine must ignore presence.
|
||||
var pOut = em.CreateEntity(typeof(RegionTag), typeof(PlayerTag));
|
||||
em.SetComponentData(pOut, new RegionTag { Region = RegionId.Expedition });
|
||||
var pHome = em.CreateEntity(typeof(RegionTag), typeof(PlayerTag));
|
||||
em.SetComponentData(pHome, new RegionTag { Region = RegionId.Base });
|
||||
|
||||
group.Update();
|
||||
|
||||
Assert.AreEqual(CyclePhase.Calm, em.GetComponentData<CycleState>(cycle).Phase,
|
||||
"Split presence (one out, one home) never drives the single global phase — Expedition is per-player.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WaveNumber_Is_Synced_From_WaveState_For_The_Hud()
|
||||
{
|
||||
var (world, group) = MakeWorld("CycleWaveSync", serverTick: 200);
|
||||
var (world, group) = MakeWorld("WaveSync", serverTick: 200);
|
||||
using (world)
|
||||
{
|
||||
var em = world.EntityManager;
|
||||
var cycle = MakeCycle(em, CyclePhase.Defend, phaseEndTick: 0, cycleNumber: 1, defendStartWave: 1);
|
||||
MakeWaveState(em, waveNumber: 4, remainingToSpawn: 2);
|
||||
var cycle = MakeCycle(em, CyclePhase.Siege, defendStartWave: 5);
|
||||
MakeWaveState(em, waveNumber: 4, phase: WavePhase.Spawning, remainingToSpawn: 2);
|
||||
|
||||
group.Update();
|
||||
|
||||
Assert.AreEqual(4, em.GetComponentData<CycleState>(cycle).WaveNumber,
|
||||
"CycleState.WaveNumber mirrors the server-only WaveState.WaveNumber so the replicated-state-only HUD can show it.");
|
||||
"CycleState.WaveNumber mirrors the server-only WaveState.WaveNumber for the replicated-state-only HUD.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user