Files
Project-M/Assets/_Project/Scripts/Simulation/Combat/ProjectileSpawnId.cs
T
kronic 12f86c86de LANTERN P1 step 2: AbilityFireSystem socket restructure + SpawnId repack
The netcode core of the 4-socket kit (Phase1_Combat_Gym_Build_Spec §1,§2,§5; review NP-1/
RS-1/DB-1/DB-3):
- PlayerInput: +4 Socket0..3 InputEvents + Burst-safe GetSocket(i); legacy Fire kept vestigial.
- PlayerInputGatherSystem: gather sockets from keyboard 1..4 (+gamepad RT/LB/RB); socket 0 also
  fires on the legacy primary (right-click / pad LT) as a bridge.
- AbilityFireSystem: query dropped to 4 type args (PlayerInput/PlayerFacing/LocalTransform/
  GhostOwner) + BufferLookup<AbilitySocket>/ComponentLookup<SocketCooldown>/BufferLookup<
  EffectiveSocketStats> (the 7-arg-cap fix); loops 4 sockets; per-socket cooldown + per-socket
  effective stats; Cone + Projectile dispatch per socket (predict-spawn Projectile-only).
- ProjectileSpawnId.Pack: pure Burst-safe key owner14|socket2|fireCount12|fork4 so same-tick
  multi-socket projectiles never collide; AbilityFireSystem uses it.
L1 clean; L2 494/494 (+5 ProjectileSpawnId tests: distinct-per-socket, fork, owner, bit-ranges,
count-wrap). L3 two-player + rollback is the group-A gate (after step 2.5). Note: the client
feel layer (muzzle/anim) still reads the legacy cooldown until step 2.5; sockets bake empty
until content (step 4), so nothing fires in-game yet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:59:45 -07:00

30 lines
1.8 KiB
C#

namespace ProjectM.Simulation
{
/// <summary>
/// Pure, Burst-safe packing of the predicted-projectile classification key
/// (<see cref="Projectile.SpawnId"/>). Layout: <c>owner(14) | socket(2) | fireCount(12) | fork(4)</c> = 32
/// bits exact. Reserving 2 socket bits is the review's NP-1/RS-1/DB-3 fix: under the LANTERN 4-socket kit,
/// two sockets firing the same-prefab projectile on ONE tick carry independent per-socket fire counts that
/// are near-guaranteed equal early (both 0->1); without a socket discriminator their SpawnIds collide and
/// <c>ProjectileClassificationSystem</c> cross-adopts one predicted entity and orphans the other. Fork keeps
/// all 4 low bits (Phase-4 Fork mutation fans up to 8 shots). Extracted so the collision-free property is
/// unit-tested independently of the netcode prediction context.
/// </summary>
public static class ProjectileSpawnId
{
public const int OwnerBits = 14;
public const int SocketBits = 2;
public const int FireCountBits = 12;
public const int ForkBits = 4;
/// <summary>Pack the classification key. Inputs are masked to their bit widths (wrap, not overflow).</summary>
public static uint Pack(int netId, int socket, uint fireCount, int fork)
{
return ((((uint)netId) & 0x3FFFu) << (SocketBits + FireCountBits + ForkBits)) // owner -> bits 18..31
| (((uint)socket & 0x3u) << (FireCountBits + ForkBits)) // socket -> bits 16..17
| ((fireCount & 0x0FFFu) << ForkBits) // fireCount -> bits 4..15
| ((uint)fork & 0xFu); // fork -> bits 0..3
}
}
}