LANTERN purge B6: delete the legacy single-ability path (sockets are THE ability model)
AbilityRef, AbilityCooldown, EffectiveAbilityStats, DefaultAbility deleted. GoInGameServerSystem seeds the per-frame 4-socket Spark loadout UNCONDITIONALLY (was gym-only); ClassSelectReceiveSystem + DebugOp.SetClass swap FrameId + re-seed sockets + zero SocketCooldown; ClassSwapUtil.Apply drops newAbilityId; EquipSystem weapons become stat-sticks (GrantedAbilityId removed from the item blob/authoring); StatRecomputeSystem folds CharacterStatsRef + sockets only; HUD cooldown bar reads socket 0 of SocketCooldown/EffectiveSocketStats; class HUD readers are FrameId-only (ClassForAbility/AbilityFor deleted); DebugModifierInjectionSystem drops CycleAbility. 390 tests green; Play-verified: a non-gym spawn gets frame=2 with Sparks [7,8,6,9] and no console errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,30 +0,0 @@
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Predicted per-player ability cooldown gate. Holds the earliest server tick at which the
|
||||
/// owning player may fire again, so <see cref="AbilityFireSystem"/> can throttle shots
|
||||
/// deterministically across client prediction and server simulation.
|
||||
/// <para>
|
||||
/// Replicated as a <see cref="GhostField"/> so the cooldown survives the frame→tick→rollback
|
||||
/// boundary: when the client re-predicts ticks after a snapshot, it sees the same authoritative
|
||||
/// gate the server applied and converges without double-firing. Stored as a raw <c>uint</c>
|
||||
/// rather than a <see cref="NetworkTick"/> for simple, quantization-free serialization; compare
|
||||
/// by wrapping it back into a <see cref="NetworkTick"/> and using
|
||||
/// <see cref="NetworkTick.IsNewerThan"/> (raw subtraction is unsafe across tick wraparound).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public struct AbilityCooldown : IComponentData
|
||||
{
|
||||
/// <summary>
|
||||
/// Raw tick value of the earliest tick the player may fire again. <c>0</c> = ready (no
|
||||
/// cooldown pending). Set by <see cref="AbilityFireSystem"/> to
|
||||
/// <c>serverTick + max(1, CooldownTicks)</c> on fire; treat as "still cooling down" only
|
||||
/// while a valid <see cref="NetworkTick"/> built from it is newer than the current
|
||||
/// <c>ServerTick</c>.
|
||||
/// </summary>
|
||||
[GhostField] public uint NextFireTick;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b7a2b67b22b2a4abaa8efd84759445c0
|
||||
@@ -1,15 +0,0 @@
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Which authored ability definition occupies this entity's primary slot - a light replicated key
|
||||
/// into the AbilityDatabase blob, replacing M2's inlined AbilityStats values. Replicated so an
|
||||
/// ability swap is server-authoritative and prediction-correct. <c>Id</c> stores an <see cref="AbilityId"/>.
|
||||
/// </summary>
|
||||
public struct AbilityRef : IComponentData
|
||||
{
|
||||
[GhostField] public byte Id;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6ea08a11ef3d4afdb722b735ca3ed03
|
||||
@@ -18,13 +18,16 @@ namespace ProjectM.Simulation
|
||||
/// <summary>Re-seed the class band + re-sync the permanent-meta band for <paramref name="rawClass"/> on
|
||||
/// <paramref name="mods"/>. Returns the normalized class + its Fire ability id (the caller sets AbilityRef).
|
||||
/// <paramref name="haveMeta"/> false (no catalog/record) skips the meta replay (the strip still runs).</summary>
|
||||
/// <summary>Re-seed the class band + re-sync the permanent-meta band for <paramref name="rawClass"/> on
|
||||
/// <paramref name="mods"/>. Returns the normalized class (the caller writes FrameId/PlayerClass + re-seeds
|
||||
/// the socket loadout). <paramref name="haveMeta"/> false (no catalog/record) skips the meta replay (the
|
||||
/// strip still runs).</summary>
|
||||
public static void Apply(byte rawClass, DynamicBuffer<StatModifier> mods,
|
||||
bool haveMeta, in MetaUpgradeCatalog metaCat, DynamicBuffer<MetaTierState> metaRecord,
|
||||
out byte newClass, out byte newAbilityId)
|
||||
out byte newClass)
|
||||
{
|
||||
newClass = ClassTraits.Normalize(rawClass);
|
||||
ClassTraits.Reapply(newClass, mods);
|
||||
newAbilityId = ClassTraits.AbilityFor(newClass);
|
||||
|
||||
// Strip the OLD class's meta rows (Reapply only touched the class-seed band), then replay the NEW class's
|
||||
// persisted tiers (the GoInGame skip/clamp rules) so the permanent channel stays correct across the swap.
|
||||
|
||||
@@ -36,14 +36,12 @@ namespace ProjectM.Simulation
|
||||
public static byte Normalize(byte classId) => classId == RangerClass ? RangerClass : WarriorClass;
|
||||
|
||||
/// <summary>The Fire-slot ability id for a class (Warrior = cone, Ranger = the default projectile).</summary>
|
||||
public static byte AbilityFor(byte classId)
|
||||
=> classId == RangerClass ? (byte)AbilityId.Primary : (byte)AbilityId.WarriorCone;
|
||||
|
||||
|
||||
/// <summary>The class a Fire-slot ability id implies — the exact inverse of <see cref="AbilityFor"/> (Ranger
|
||||
/// iff Primary). Lets the CLIENT derive the local class from the replicated <see cref="AbilityRef"/> (tracks
|
||||
/// the dev class-switch, unlike the menu's ClassSelection static; PlayerClass itself is server-only).</summary>
|
||||
public static byte ClassForAbility(byte abilityId)
|
||||
=> abilityId == (byte)AbilityId.Primary ? RangerClass : WarriorClass;
|
||||
|
||||
|
||||
/// <summary>Default 4-socket Spark loadout per frame (Harpooner = the Ranger slot, line-and-iron; Bathynaut =
|
||||
/// the Warrior slot, anchor-and-crash). Tunable; drives the gym's per-frame default sockets (Build Spec step 5).</summary>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Per-entity effective ability stats: the authored base (from the AbilityDatabase blob keyed by
|
||||
/// AbilityRef) folded with the entity's StatModifier buffer by StatRecomputeSystem each predicted
|
||||
/// tick. Derived/local, NOT replicated - both worlds recompute it deterministically from the
|
||||
/// replicated modifier buffer, so it matches under prediction without being in the snapshot.
|
||||
/// </summary>
|
||||
public struct EffectiveAbilityStats : IComponentData
|
||||
{
|
||||
public float Damage;
|
||||
public float ProjectileSpeed;
|
||||
public float Range;
|
||||
public float AutoTargetRange;
|
||||
public float AutoTargetConeRadians;
|
||||
public int CooldownTicks;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8bb3a5c343e74e7fb249e96c0c55fdc
|
||||
@@ -7,8 +7,9 @@ namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Folds each modifiable entity's authored base stats (from the AbilityDatabase blob, keyed by
|
||||
/// AbilityRef / CharacterStatsRef) with its replicated StatModifier buffer into the
|
||||
/// EffectiveAbilityStats / EffectiveCharacterStats components - every predicted tick, on both worlds.
|
||||
/// CharacterStatsRef / the AbilitySocket loadout) with its replicated StatModifier buffer into the
|
||||
/// EffectiveCharacterStats / EffectiveSocketStats components - every predicted tick, on both worlds.
|
||||
/// (The legacy single AbilityRef -> EffectiveAbilityStats fold is deleted — LANTERN purge.)
|
||||
///
|
||||
/// Runs at the head of the predicted group (UpdateBefore PlayerAimSystem;
|
||||
/// AbilityFireSystem runs after PlayerAimSystem, so it sees fresh values too). Recompute is
|
||||
@@ -34,24 +35,11 @@ namespace ProjectM.Simulation
|
||||
var database = SystemAPI.GetSingleton<AbilityDatabase>();
|
||||
ref var db = ref database.Value.Value;
|
||||
|
||||
foreach (var (abilityRef, charRef, mods, effAbility, effChar) in
|
||||
SystemAPI.Query<RefRO<AbilityRef>, RefRO<CharacterStatsRef>, DynamicBuffer<StatModifier>,
|
||||
RefRW<EffectiveAbilityStats>, RefRW<EffectiveCharacterStats>>()
|
||||
foreach (var (charRef, mods, effChar) in
|
||||
SystemAPI.Query<RefRO<CharacterStatsRef>, DynamicBuffer<StatModifier>,
|
||||
RefRW<EffectiveCharacterStats>>()
|
||||
.WithAll<Simulate>())
|
||||
{
|
||||
if (db.TryGetAbility(abilityRef.ValueRO.Id, out var a))
|
||||
{
|
||||
effAbility.ValueRW = new EffectiveAbilityStats
|
||||
{
|
||||
Damage = StatMath.Apply(a.Damage, StatTarget.Damage, mods),
|
||||
ProjectileSpeed = StatMath.Apply(a.ProjectileSpeed, StatTarget.ProjectileSpeed, mods),
|
||||
Range = StatMath.Apply(a.Range, StatTarget.Range, mods),
|
||||
AutoTargetRange = StatMath.Apply(a.AutoTargetRange, StatTarget.AutoTargetRange, mods),
|
||||
AutoTargetConeRadians = StatMath.Apply(a.AutoTargetConeRadians, StatTarget.AutoTargetConeRadians, mods),
|
||||
CooldownTicks = (int)math.round(StatMath.Apply(a.CooldownTicks, StatTarget.CooldownTicks, mods)),
|
||||
};
|
||||
}
|
||||
|
||||
if (db.TryGetCharacter(charRef.ValueRO.Id, out var c))
|
||||
{
|
||||
effChar.ValueRW = new EffectiveCharacterStats
|
||||
@@ -65,8 +53,7 @@ namespace ProjectM.Simulation
|
||||
|
||||
// LANTERN Phase 1 (Step 1b): per-socket fold - each socket's Spark base folded with the SHARED
|
||||
// StatModifier band into its EffectiveSocketStats row (uniform band; per-Spark warping is Phase 4).
|
||||
// Separate query so this system stays under the 7-arg cap; the legacy single fold above stays
|
||||
// until AbilityFireSystem + the feel layer migrate to the buffer (steps 2/2.5).
|
||||
// Separate query so this system stays under the 7-arg cap.
|
||||
foreach (var (sockets, socketMods, effSockets) in
|
||||
SystemAPI.Query<DynamicBuffer<AbilitySocket>, DynamicBuffer<StatModifier>, DynamicBuffer<EffectiveSocketStats>>()
|
||||
.WithAll<Simulate>())
|
||||
|
||||
Reference in New Issue
Block a user