LANTERN P1 step 1: 4-socket kit data model (player-ghost re-bake)
Additive socket data model per Phase1_Combat_Gym_Build_Spec §3 (no behaviour; legacy AbilityRef/AbilityCooldown left intact for the step-2 migration): - AbilitySocket: cold per-socket loadout buffer (EquipmentSlot-modelled, [GhostField] byte SparkId, SendToOwnerType.All, 4 rows baked empty). - SocketCooldown: hot owner-predicted per-socket cooldown (4x [GhostField] uint + Burst-safe switch indexer; SendToOwnerType.All for remote ability-feel parity with AbilityCooldown). - FrameId: replicated [GhostField] byte frame/class signal (SendToOwner; the class source the socket model needs since no single ability id maps 1:1 to a class). Baked onto the player ghost in PlayerAuthoring. L1 clean compile + domain reload. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -109,6 +109,16 @@ namespace ProjectM.Authoring
|
||||
// the Returning edge) + the server-only Blade-Dash per-dash dedup accumulator (non-replicated).
|
||||
AddComponent<BoonEffects>(entity);
|
||||
AddComponent<DashTrailState>(entity);
|
||||
// LANTERN Phase 1 (Step 1): 4-socket kit data model - parallels AbilityRef/AbilityCooldown
|
||||
// (both kept until the AbilityFireSystem migration in Step 2). AbilitySocket = cold per-socket
|
||||
// loadout (EquipmentSlot-modelled, 4 empty rows); SocketCooldown = hot owner-predicted per-socket
|
||||
// cooldown; FrameId = replicated frame/class signal (baked 0, written server-side at frame select).
|
||||
var sockets = AddBuffer<AbilitySocket>(entity);
|
||||
for (int sk = 0; sk < SocketId.Count; sk++)
|
||||
sockets.Add(new AbilitySocket { SparkId = 0 });
|
||||
AddComponent<SocketCooldown>(entity);
|
||||
AddComponent<FrameId>(entity);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>Socket count / index constants for the LANTERN 4-socket kit.</summary>
|
||||
public static class SocketId
|
||||
{
|
||||
public const int Count = 4;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-player ability socket loadout — one replicated row per socket (buffer index = socket 0..3),
|
||||
/// each holding the <see cref="AbilityId"/> (Spark) occupying that socket (0 = empty). The LANTERN
|
||||
/// 4-socket-kit analog of the single <see cref="AbilityRef"/>; modelled on <see cref="EquipmentSlot"/>:
|
||||
/// COLD (mutated only when socketing in the hub, server-sole-writer — NOT predicted). Read by
|
||||
/// <c>AbilityFireSystem</c> via a <c>BufferLookup</c>, never as a SystemAPI.Query type arg (the 7-arg cap).
|
||||
/// </summary>
|
||||
[InternalBufferCapacity(4)]
|
||||
[GhostComponent(OwnerSendType = SendToOwnerType.All)]
|
||||
public struct AbilitySocket : IBufferElementData
|
||||
{
|
||||
/// <summary>The <see cref="AbilityId"/> (Spark) occupying this socket; 0 = empty.</summary>
|
||||
[GhostField] public byte SparkId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HOT, owner-predicted per-socket ability cooldown for the 4-socket kit — the LANTERN analog of the
|
||||
/// single scalar <see cref="AbilityCooldown"/> (deliberately NOT the cold <see cref="EquipmentSlot"/>
|
||||
/// buffer: a scalar-shaped, index-addressable cooldown whose per-tick rollback restore is proven, unlike
|
||||
/// a per-tick-mutating <c>[GhostField]</c> buffer). Four raw next-fire ticks, one per socket, each a
|
||||
/// <see cref="GhostField"/> so the gate survives the frame→tick→rollback boundary and converges without
|
||||
/// double-firing. <c>0</c> = ready. Route stored ticks through <see cref="TickUtil.NonZero"/> and compare
|
||||
/// by wrapping into a <see cref="NetworkTick"/> + <see cref="NetworkTick.IsNewerThan"/> (raw subtraction
|
||||
/// is unsafe across wraparound). Explicit <c>SendToOwnerType.All</c> (parity with today's implicit-send-all
|
||||
/// <see cref="AbilityCooldown"/>, so remote clients still drive teammates' ability feel).
|
||||
/// </summary>
|
||||
[GhostComponent(OwnerSendType = SendToOwnerType.All)]
|
||||
public struct SocketCooldown : IComponentData
|
||||
{
|
||||
[GhostField] public uint Next0;
|
||||
[GhostField] public uint Next1;
|
||||
[GhostField] public uint Next2;
|
||||
[GhostField] public uint Next3;
|
||||
|
||||
/// <summary>Next-fire tick for socket <paramref name="i"/> (0..3). Burst-safe (switch, not a field index).</summary>
|
||||
public uint Get(int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return Next0;
|
||||
case 1: return Next1;
|
||||
case 2: return Next2;
|
||||
default: return Next3;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Set the next-fire tick for socket <paramref name="i"/> (0..3).</summary>
|
||||
public void Set(int i, uint v)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: Next0 = v; break;
|
||||
case 1: Next1 = v; break;
|
||||
case 2: Next2 = v; break;
|
||||
default: Next3 = v; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replicated per-player frame/class signal (a LANTERN suit-frame IS the class). Baked on the player
|
||||
/// ghost, written server-side when the frame/class is chosen. Exists because, under the 4-socket kit, no
|
||||
/// single ability id maps 1:1 to a class the way the old single <see cref="AbilityRef"/> did — the HUD/
|
||||
/// feel systems read this instead of <c>ClassTraits.ClassForAbility(AbilityRef.Id)</c>. <c>SendToOwner</c>:
|
||||
/// only the owning client's HUD needs its own frame. (Distinct from the server-only <c>PlayerClass</c>,
|
||||
/// which is runtime-added and cannot carry a GhostField.)
|
||||
/// </summary>
|
||||
[GhostComponent(OwnerSendType = SendToOwnerType.SendToOwner)]
|
||||
public struct FrameId : IComponentData
|
||||
{
|
||||
/// <summary>The frame/class id (see the <c>CharacterId</c> / <c>ClassId</c> convention).</summary>
|
||||
[GhostField] public byte Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e1ff5b506cb3c84c9913c58d64c1975
|
||||
Reference in New Issue
Block a user