Netcode: fix interpolated-tick cues + add a terminal RPC reaper (audit M1/M4/M12)
M1 — two systems timed INTERPOLATED ghosts against the PREDICTED tick, the exact hazard CLAUDE.md documents and the one that is invisible on loopback: - EnemyDangerTelegraphSystem timed the red danger cone off nt.ServerTick, so over a real connection the dodge tell finished ~RTT/2 + interp buffer EARLY. The cue lied. - PlayerAnimationDriveSystem fed the same predicted tick to RemoteDriveJob ([WithDisabled(GhostOwnerIsLocal)] — i.e. interpolated teammates), so a teammate's swing animation desynced from their damage. Both now use the ZoneTelegraphSystem idiom. The LOCAL drive job keeps ServerTick: the owning player really is predicted. M12 — the RPC leak I reproduced live during the audit. Every receiver in this project gates on RequireForUpdate over a scene-baked singleton; in a scene without it the receiver never runs and the request entity is never destroyed. Netcode's WarnAboutStaleRpcSystem Consume()s but never destroys, and is compiled out of player builds — so these accumulated silently, and worst in a shipped build. New StaleRpcReaperSystem (server, OrderLast, no RequireForUpdate) destroys any unconsumed request that outlived its receiving frame. Consumed requests are left to their owner. Verified live: a planted unconsumed request is gone within a few frames. Three regression tests pin both halves of the contract. Also: HealthApplyDamageSystem and ProjectileDamageSystem now filter .WithAll<Simulate>(). They are ServerSimulation-only so it was not a bug, but the audit's "all predicted systems filter Simulate" reassurance was false until now — the rule is unconditional again. The five undisposed Allocator.Temp ECBs the audit flagged all lived in systems the purge deleted; none remain. 298/298 green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -83,7 +83,12 @@ static readonly FastAnimatorParameter k_ComboStep = new FastAnimatorParameter(
|
||||
if (dt < 1e-5f) dt = 1e-5f;
|
||||
|
||||
// Current authoritative tick for the swing-window check (default = invalid -> IsAttacking stays false).
|
||||
NetworkTick serverTick = SystemAPI.TryGetSingleton<NetworkTime>(out var nt) ? nt.ServerTick : default;
|
||||
NetworkTick serverTick = default, interpTick = default;
|
||||
if (SystemAPI.TryGetSingleton<NetworkTime>(out var nt))
|
||||
{
|
||||
serverTick = nt.ServerTick; // predicted: local owner
|
||||
interpTick = nt.InterpolationTick.IsValid ? nt.InterpolationTick : nt.ServerTick; // interpolated: remotes
|
||||
}
|
||||
// Ability blob for the per-class special read (Cone -> slam anim); default(BlobAssetReference) if absent.
|
||||
var abilityBlob = SystemAPI.TryGetSingleton<AbilityDatabase>(out var adbSingleton) ? adbSingleton.Value : default;
|
||||
// 07-18 combat idle: swing-recency window (the same wrap-safe SwingActive predicate as the anim pulse, wider). 60Hz sim ticks.
|
||||
@@ -125,13 +130,18 @@ static readonly FastAnimatorParameter k_ComboStep = new FastAnimatorParameter(
|
||||
Dependency = localJob.ScheduleParallel(Dependency);
|
||||
|
||||
// --- REMOTE players (position-delta). Single-threaded write to the shared prevPos cache. ---
|
||||
// Remote teammates are INTERPOLATED ghosts: their replicated MeleeCombo / SocketCooldown arrive on the
|
||||
// interpolation timeline, so their swing windows must be timed against InterpolationTick. Using the
|
||||
// predicted ServerTick here desynced a teammate's swing animation from their damage by ~RTT/2 +
|
||||
// interp buffer — invisible on loopback (audit finding M1). The LOCAL job above stays on ServerTick:
|
||||
// the owning player IS predicted.
|
||||
var seen = new NativeParallelHashSet<Entity>(16, Allocator.TempJob);
|
||||
var remoteJob = new RemoteDriveJob
|
||||
{
|
||||
moveX = k_MoveX, moveZ = k_MoveZ, speed = k_Speed, isDead = k_IsDead,
|
||||
isAttacking = k_IsAttacking, isFiring = k_IsFiring, isDashing = k_IsDashing,
|
||||
comboStep = k_ComboStep, isCone = k_IsCone, abilityDb = abilityBlob,
|
||||
serverTick = serverTick, attackTicks = k_AttackAnimTicks,
|
||||
serverTick = interpTick, attackTicks = k_AttackAnimTicks,
|
||||
dt = dt,
|
||||
strideScale = k_StrideScale, trudgeNaturalSpeed = math.max(0.5f, FeelConfig.TrudgeNaturalSpeed),
|
||||
runNaturalSpeed = math.max(0.5f, FeelConfig.RunNaturalSpeed),
|
||||
|
||||
Reference in New Issue
Block a user