using NUnit.Framework; using ProjectM.Server; using ProjectM.Simulation; using Unity.Core; using Unity.Entities; using Unity.Mathematics; using Unity.NetCode; namespace ProjectM.Tests { /// /// EditMode coverage for the MC-0/MC-1 DevTelemetry counter WIRING (the fun-gate is measured, not argued): /// DashIFrameNegatedHits + DashState.NegatedCount (HealthApplyDamageSystem), DashesWasted (DashSystem /// window-close edge, server-gated on the DevTelemetry singleton). /// public class TelemetryCountersTests { static void SetServerTick(World world, uint tick) { var em = world.EntityManager; using var q = em.CreateEntityQuery(typeof(NetworkTime)); Entity e = q.IsEmpty ? em.CreateEntity(typeof(NetworkTime)) : q.GetSingletonEntity(); em.SetComponentData(e, new NetworkTime { ServerTick = new NetworkTick(tick) }); } static (World world, SimulationSystemGroup group) MakeWorld(string name, uint serverTick, bool withTelemetry) where T : unmanaged, ISystem { var world = new World(name); var group = world.GetOrCreateSystemManaged(); group.AddSystemToUpdateList(world.GetOrCreateSystem()); group.SortSystems(); world.SetTime(new TimeData(elapsedTime: 0f, deltaTime: 1f / 60f)); SetServerTick(world, serverTick); if (withTelemetry) world.EntityManager.CreateEntity(typeof(DevTelemetry)); return (world, group); } static DevTelemetry Telemetry(EntityManager em) { using var q = em.CreateEntityQuery(typeof(DevTelemetry)); return q.GetSingleton(); } static Entity MakeDashingPlayer(EntityManager em, DashState ds) { var e = em.CreateEntity(); em.AddComponentData(e, ds); em.AddComponentData(e, new DashCooldown { NextTick = 0 }); em.AddComponentData(e, new CharacterControl { MoveVelocity = float3.zero }); em.AddComponentData(e, CharacterComponent.GetDefault()); em.AddComponentData(e, new PlayerInput()); em.AddComponentData(e, new PlayerFacing { Direction = new float2(0, 1) }); em.AddComponent(e); em.AddComponent(e); em.SetComponentEnabled(e, false); return e; } [Test] public void Negation_Increments_DevTelemetry_And_DashState_NegatedCount() { var (world, group) = MakeWorld("TelemNegate", 113, withTelemetry: true); using (world) { var em = world.EntityManager; var e = em.CreateEntity(typeof(Health), typeof(DamageEvent), typeof(DashState)); em.SetComponentData(e, new Health { Current = 100f, Max = 100f }); em.SetComponentData(e, new DashState { StartTick = 100, IFrameUntilTick = 112, RecoverUntilTick = 121 }); var dmg = em.GetBuffer(e); dmg.Add(new DamageEvent { Amount = 5f, SourceNetworkId = -1, SourceTick = 101 }); // in-window -> negated dmg.Add(new DamageEvent { Amount = 6f, SourceNetworkId = -1, SourceTick = 111 }); // in-window -> negated dmg.Add(new DamageEvent { Amount = 7f, SourceNetworkId = -1, SourceTick = 113 }); // outside -> applies group.Update(); Assert.AreEqual(2u, Telemetry(em).DashIFrameNegatedHits, "Both in-window hits counted."); Assert.AreEqual(2u, em.GetComponentData(e).NegatedCount, "NegatedCount written back onto DashState (the wasted-dash signal source)."); Assert.AreEqual(93f, em.GetComponentData(e).Current, 1e-4f, "Only the outside hit applied."); } } [Test] public void Wasted_Dash_Counts_Once_On_Window_Close_With_Zero_Negations() { // Window fully elapsed at tick 120 (recover ended at 109) and nothing was negated. var (world, group) = MakeWorld("TelemWasted", 120, withTelemetry: true); using (world) { var em = world.EntityManager; var e = MakeDashingPlayer(em, new DashState { Dir = new float2(0, 1), StartTick = 80, IFrameUntilTick = 100, RecoverUntilTick = 109, NegatedCount = 0 }); group.Update(); Assert.AreEqual(1u, Telemetry(em).DashesWasted, "A dash whose window negated nothing scores wasted."); Assert.AreEqual(0u, em.GetComponentData(e).RecoverUntilTick, "Close edge clears the window (one-shot)."); group.Update(); Assert.AreEqual(1u, Telemetry(em).DashesWasted, "The close edge fires exactly once."); } } [Test] public void Effective_Dash_Is_Not_Wasted() { var (world, group) = MakeWorld("TelemEffective", 120, withTelemetry: true); using (world) { var em = world.EntityManager; MakeDashingPlayer(em, new DashState { Dir = new float2(0, 1), StartTick = 80, IFrameUntilTick = 100, RecoverUntilTick = 109, NegatedCount = 2 }); group.Update(); Assert.AreEqual(0u, Telemetry(em).DashesWasted, "A dash that negated hits is not wasted."); } } [Test] public void Without_Telemetry_Singleton_The_Close_Edge_Leaves_DashState_Intact() { // Client-world behavior: no DevTelemetry singleton -> no zeroing, so rollback re-simulation of the // tail window stays correct on the client (the server is the only world that clears). var (world, group) = MakeWorld("TelemClientNoZero", 120, withTelemetry: false); using (world) { var em = world.EntityManager; var e = MakeDashingPlayer(em, new DashState { Dir = new float2(0, 1), StartTick = 80, IFrameUntilTick = 100, RecoverUntilTick = 109 }); group.Update(); Assert.AreEqual(109u, em.GetComponentData(e).RecoverUntilTick, "No telemetry singleton (client world): the window is never zeroed by the close edge."); } } // 2026-08-07 audit purge: the three ChargerWhiffPunishesLanded tests are gone with the Charger. They // exercised HealthApplyDamageSystem's LungeState.StaggerUntilTick branch, which no baked prefab could // ever reach. The dash-window counters above still cover the telemetry plumbing itself. } }