LANTERN P1 step 6: manual-aim windups (SoD readable telegraphs)
AbilityDefBlob/AbilityDefinition gain WindupTicks. AbilityFireSystem's socket loop resolves each socket at the historical command (serverTick - WindupTicks) via GetDataAtTick — a pure fn of replicated data on both worlds (Build Spec §4/ DB-6: no local fireAtTick that would diverge under reprediction; fireCount from that command, spawn uses the current tick's aim/pos; history gap = no-fire). WindupTicks set on the 4 non-Blink Sparks (Blink is movement, resolved by BlinkSystem). 497 EditMode green (no regression). Client telegraph VFX + the reprediction L3 test are operator-eyes / netcode-world follow-ups. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,4 +23,5 @@ MonoBehaviour:
|
|||||||
CooldownTicks: 360
|
CooldownTicks: 360
|
||||||
Reel: 0
|
Reel: 0
|
||||||
EffectDurationTicks: 480
|
EffectDurationTicks: 480
|
||||||
|
WindupTicks: 12
|
||||||
ProjectilePrefab: {fileID: 3885353946372160549, guid: a5b1532dc1acca045856fd653fe392b9, type: 3}
|
ProjectilePrefab: {fileID: 3885353946372160549, guid: a5b1532dc1acca045856fd653fe392b9, type: 3}
|
||||||
|
|||||||
@@ -23,4 +23,5 @@ MonoBehaviour:
|
|||||||
CooldownTicks: 120
|
CooldownTicks: 120
|
||||||
Reel: 1
|
Reel: 1
|
||||||
EffectDurationTicks: 0
|
EffectDurationTicks: 0
|
||||||
|
WindupTicks: 8
|
||||||
ProjectilePrefab: {fileID: 8857056134628386430, guid: 700fb9c3f7cf94830a0ee78c4e4f1290, type: 3}
|
ProjectilePrefab: {fileID: 8857056134628386430, guid: 700fb9c3f7cf94830a0ee78c4e4f1290, type: 3}
|
||||||
|
|||||||
@@ -23,4 +23,5 @@ MonoBehaviour:
|
|||||||
CooldownTicks: 480
|
CooldownTicks: 480
|
||||||
Reel: 0
|
Reel: 0
|
||||||
EffectDurationTicks: 420
|
EffectDurationTicks: 420
|
||||||
|
WindupTicks: 18
|
||||||
ProjectilePrefab: {fileID: 3885353946372160549, guid: e12b85c23fd562d49b27a3d2fdfd9cad, type: 3}
|
ProjectilePrefab: {fileID: 3885353946372160549, guid: e12b85c23fd562d49b27a3d2fdfd9cad, type: 3}
|
||||||
|
|||||||
@@ -23,4 +23,5 @@ MonoBehaviour:
|
|||||||
CooldownTicks: 420
|
CooldownTicks: 420
|
||||||
Reel: 0
|
Reel: 0
|
||||||
EffectDurationTicks: 300
|
EffectDurationTicks: 300
|
||||||
|
WindupTicks: 20
|
||||||
ProjectilePrefab: {fileID: 3885353946372160549, guid: 6e31b46e818d7f348931e3d0ea73bf23, type: 3}
|
ProjectilePrefab: {fileID: 3885353946372160549, guid: 6e31b46e818d7f348931e3d0ea73bf23, type: 3}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ namespace ProjectM.Authoring
|
|||||||
CooldownTicks = def.CooldownTicks,
|
CooldownTicks = def.CooldownTicks,
|
||||||
EffectFlags = (byte)(def.Reel ? ProjectileEffectFlag.Reel : (byte)0),
|
EffectFlags = (byte)(def.Reel ? ProjectileEffectFlag.Reel : (byte)0),
|
||||||
DurationTicks = def.EffectDurationTicks,
|
DurationTicks = def.EffectDurationTicks,
|
||||||
|
WindupTicks = def.WindupTicks,
|
||||||
Name = def.DisplayName,
|
Name = def.DisplayName,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ namespace ProjectM.Authoring
|
|||||||
public bool Reel = false;
|
public bool Reel = false;
|
||||||
[Min(0), Tooltip("Aoe/zone + decoy lifetime in ticks (0 = system default).")]
|
[Min(0), Tooltip("Aoe/zone + decoy lifetime in ticks (0 = system default).")]
|
||||||
public int EffectDurationTicks = 0;
|
public int EffectDurationTicks = 0;
|
||||||
|
[Header("Manual-aim windup")]
|
||||||
|
[Min(0), Tooltip("Telegraph delay (ticks, ~60/s) before the ability resolves. 0 = instant. The SoD 'readable windup'.")]
|
||||||
|
public int WindupTicks = 0;
|
||||||
|
|
||||||
[Header("Prefab (baked into the prefab buffer, not the blob)")]
|
[Header("Prefab (baked into the prefab buffer, not the blob)")]
|
||||||
public GameObject ProjectilePrefab;
|
public GameObject ProjectilePrefab;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ namespace ProjectM.Simulation
|
|||||||
public int CooldownTicks;
|
public int CooldownTicks;
|
||||||
public byte EffectFlags; // ProjectileEffectFlag bits stamped at spawn (the Reel bit ⇒ HookPull homes the hit target to the caster). Config, not replicated.
|
public byte EffectFlags; // ProjectileEffectFlag bits stamped at spawn (the Reel bit ⇒ HookPull homes the hit target to the caster). Config, not replicated.
|
||||||
public int DurationTicks; // Aoe/zone + decoy lifetime, ticks (0 = system default). Reel leash is a system const.
|
public int DurationTicks; // Aoe/zone + decoy lifetime, ticks (0 = system default). Reel leash is a system const.
|
||||||
|
public int WindupTicks; // manual-aim telegraph delay (ticks) before the ability resolves; 0 = instant (Build Spec §4/DB-6)
|
||||||
public FixedString64Bytes Name;
|
public FixedString64Bytes Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,9 +131,8 @@ namespace ProjectM.Simulation
|
|||||||
BoonEffects bfx = m_BoonEffectsLookup.HasComponent(entity) ? m_BoonEffectsLookup[entity] : default;
|
BoonEffects bfx = m_BoonEffectsLookup.HasComponent(entity) ? m_BoonEffectsLookup[entity] : default;
|
||||||
bool pull = (bfx.Flags & BoonFlag.KnockToPull) != 0;
|
bool pull = (bfx.Flags & BoonFlag.KnockToPull) != 0;
|
||||||
|
|
||||||
// Replicated command buffer at this tick (per-socket fire count for the SpawnId + scheme for aim assist).
|
// Replicated command buffer (windup resolve + per-socket fire count for the SpawnId + scheme for aim assist).
|
||||||
var inputBuffer = SystemAPI.GetBuffer<InputBufferData<PlayerInput>>(entity);
|
var inputBuffer = SystemAPI.GetBuffer<InputBufferData<PlayerInput>>(entity);
|
||||||
bool haveApplied = inputBuffer.GetDataAtTick(serverTick, out var applied);
|
|
||||||
|
|
||||||
bool cdDirty = false;
|
bool cdDirty = false;
|
||||||
int socketCount = math.min(SocketId.Count, sockets.Length);
|
int socketCount = math.min(SocketId.Count, sockets.Length);
|
||||||
@@ -141,7 +140,23 @@ namespace ProjectM.Simulation
|
|||||||
{
|
{
|
||||||
byte sparkId = sockets[sk].SparkId;
|
byte sparkId = sockets[sk].SparkId;
|
||||||
if (sparkId == 0) continue; // empty socket
|
if (sparkId == 0) continue; // empty socket
|
||||||
if (!input.ValueRO.GetSocket(sk).IsSet) continue; // this socket not fired this tick
|
|
||||||
|
EffectiveSocketStats es = sk < effSockets.Length ? effSockets[sk] : default;
|
||||||
|
byte archetype = adb.TryGetAbility(sparkId, out var adef) ? adef.Archetype : (byte)AbilityArchetype.Projectile;
|
||||||
|
|
||||||
|
// MANUAL-AIM WINDUP (Build Spec §4/DB-6): resolve when the command WindupTicks ago had this socket
|
||||||
|
// set, so the resolve tick is a pure fn of replicated data on both worlds (no local fireAtTick that
|
||||||
|
// diverges under reprediction). fireCount comes from THAT command; the spawn uses the CURRENT tick's
|
||||||
|
// aim/position. WindupTicks == 0 = instant. A history gap (older than the buffer window) = no-fire.
|
||||||
|
NetworkTick resolveTick = serverTick;
|
||||||
|
if (adef.WindupTicks > 0)
|
||||||
|
{
|
||||||
|
uint riNow = serverTick.TickIndexForValidTick;
|
||||||
|
if (riNow <= (uint)adef.WindupTicks) continue; // not enough history yet -> no-fire
|
||||||
|
resolveTick = new NetworkTick(riNow - (uint)adef.WindupTicks);
|
||||||
|
}
|
||||||
|
if (!inputBuffer.GetDataAtTick(resolveTick, out var applied)) continue; // history gap -> no-fire
|
||||||
|
if (!applied.InternalInput.GetSocket(sk).IsSet) continue; // socket not fired at the resolve tick
|
||||||
|
|
||||||
// Per-socket cooldown gate (0 = ready).
|
// Per-socket cooldown gate (0 = ready).
|
||||||
uint nextFireRaw = cd.Get(sk);
|
uint nextFireRaw = cd.Get(sk);
|
||||||
@@ -151,9 +166,6 @@ namespace ProjectM.Simulation
|
|||||||
if (nextTick.IsValid && nextTick.IsNewerThan(serverTick)) continue;
|
if (nextTick.IsValid && nextTick.IsNewerThan(serverTick)) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
EffectiveSocketStats es = sk < effSockets.Length ? effSockets[sk] : default;
|
|
||||||
byte archetype = adb.TryGetAbility(sparkId, out var adef) ? adef.Archetype : (byte)AbilityArchetype.Projectile;
|
|
||||||
|
|
||||||
// CONE: no projectile ghost. Predict the cooldown on both worlds; apply server-only cleave.
|
// CONE: no projectile ghost. Predict the cooldown on both worlds; apply server-only cleave.
|
||||||
if (archetype == (byte)AbilityArchetype.Cone)
|
if (archetype == (byte)AbilityArchetype.Cone)
|
||||||
{
|
{
|
||||||
@@ -261,7 +273,6 @@ namespace ProjectM.Simulation
|
|||||||
if (abilityPrefabs[i].Id == sparkId) { prefab = abilityPrefabs[i].Prefab; break; }
|
if (abilityPrefabs[i].Id == sparkId) { prefab = abilityPrefabs[i].Prefab; break; }
|
||||||
}
|
}
|
||||||
if (prefab == Entity.Null) continue;
|
if (prefab == Entity.Null) continue;
|
||||||
if (!haveApplied) continue;
|
|
||||||
|
|
||||||
uint socketFireCount = applied.InternalInput.GetSocket(sk).Count;
|
uint socketFireCount = applied.InternalInput.GetSocket(sk).Count;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user