using NUnit.Framework; using ProjectM.Simulation; namespace ProjectM.Tests { /// /// Pure tests for the predicted-projectile SpawnId pack (). The /// load-bearing property (review NP-1/RS-1/DB-3): two ability sockets firing the same-prefab projectile on /// ONE tick with EQUAL per-socket fire counts must produce DISTINCT SpawnIds, so /// ProjectileClassificationSystem binds each predicted entity to its own server ghost instead of /// cross-adopting one and orphaning the other (the visible mis-prediction snap the old 32-bit key caused). /// public class ProjectileSpawnIdTests { [Test] public void DifferentSocket_SameOwnerCountFork_ProducesDistinctIds() { // The exact collision scenario: owner 1, every socket's first shot (count 1), fork 0. uint a = ProjectileSpawnId.Pack(netId: 1, socket: 0, fireCount: 1, fork: 0); uint b = ProjectileSpawnId.Pack(netId: 1, socket: 1, fireCount: 1, fork: 0); uint c = ProjectileSpawnId.Pack(netId: 1, socket: 2, fireCount: 1, fork: 0); uint d = ProjectileSpawnId.Pack(netId: 1, socket: 3, fireCount: 1, fork: 0); Assert.AreNotEqual(a, b); Assert.AreNotEqual(a, c); Assert.AreNotEqual(a, d); Assert.AreNotEqual(b, c); Assert.AreNotEqual(b, d); Assert.AreNotEqual(c, d); } [Test] public void DifferentFork_ProducesDistinctIds() { uint s0 = ProjectileSpawnId.Pack(2, 1, 5, 0); uint s1 = ProjectileSpawnId.Pack(2, 1, 5, 1); uint s2 = ProjectileSpawnId.Pack(2, 1, 5, 7); Assert.AreNotEqual(s0, s1); Assert.AreNotEqual(s0, s2); Assert.AreNotEqual(s1, s2); } [Test] public void DifferentOwner_ProducesDistinctIds() { Assert.AreNotEqual(ProjectileSpawnId.Pack(1, 0, 0, 0), ProjectileSpawnId.Pack(2, 0, 0, 0)); } [Test] public void FieldsDecodeToTheirBitRanges() { uint id = ProjectileSpawnId.Pack(netId: 3, socket: 2, fireCount: 7, fork: 5); Assert.AreEqual(5u, id & 0xFu, "fork in bits 0-3"); Assert.AreEqual(7u, (id >> 4) & 0x0FFFu, "fireCount in bits 4-15"); Assert.AreEqual(2u, (id >> 16) & 0x3u, "socket in bits 16-17"); Assert.AreEqual(3u, (id >> 18) & 0x3FFFu, "owner in bits 18-31"); } [Test] public void FireCountWrapsAt4096_WithoutBleedingIntoSocket() { // count 4096 wraps to 0 within its 12-bit field and must not flip the socket bits. uint wrapped = ProjectileSpawnId.Pack(1, 1, 4096, 0); uint zero = ProjectileSpawnId.Pack(1, 1, 0, 0); Assert.AreEqual(zero, wrapped, "count 4096 wraps to 0 within its 12 bits"); Assert.AreEqual(1u, (wrapped >> 16) & 0x3u, "socket bit intact across the wrap"); } } }