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:
@@ -118,7 +118,13 @@ namespace ProjectM.Client
|
||||
void UpdateEnemyDanger(float3 localPos)
|
||||
{
|
||||
if (_fxRoot == null || _dangerMat == null) return;
|
||||
Unity.NetCode.NetworkTick serverTick = SystemAPI.TryGetSingleton<NetworkTime>(out var nt) ? nt.ServerTick : default;
|
||||
// Enemies are ownerless INTERPOLATED ghosts, so their replicated AttackWindup arrives on the
|
||||
// INTERPOLATION timeline. Timing the danger cone against the PREDICTED ServerTick makes the cue
|
||||
// finish ~RTT/2 + interp-buffer ticks EARLY over a real connection — the dodge tell lies. Invisible on
|
||||
// loopback, which is exactly why it survived (audit finding M1). Same idiom as ZoneTelegraphSystem.
|
||||
Unity.NetCode.NetworkTick serverTick = default;
|
||||
if (SystemAPI.TryGetSingleton<NetworkTime>(out var nt))
|
||||
serverTick = nt.InterpolationTick.IsValid ? nt.InterpolationTick : nt.ServerTick;
|
||||
_dangerSeen.Clear();
|
||||
_enemySeen.Clear();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user