Skip to content

NewNet fixes, perfomance tweaks and weapon bugs found. #12

Open
grahamslam wants to merge 25 commits intozenakuten:mainfrom
grahamslam:main
Open

NewNet fixes, perfomance tweaks and weapon bugs found. #12
grahamslam wants to merge 25 commits intozenakuten:mainfrom
grahamslam:main

Conversation

@grahamslam
Copy link
Copy Markdown

V22

NewNet ping estimator: median-of-5 sliding window, 0.5s polling, 150ms cap, spike rejection
Damage impulse replication: server sends momentum to client to prevent desync on taking damage
Rewind delta smoothing: cap rewind change per trace to prevent position jumps during ping fluctuation
Move error grace period: suppress corrections briefly after damage impulse
Fix NewNet_LinkAltFire TimeTravel referencing wrong weapon class (copy-paste bug)
Fix fake projectile timer clobbering on rapid fire (rockets, flak, bio, link alt)
Fix LockingPawns cleanup skipping consecutive None entries (forward-iteration removal bug)
Fix O(n^2) RemoveOutdatedHistory — batch removal instead of per-element shift

grahamslam and others added 25 commits March 28, 2026 13:13
Fix 1: Replace naive weighted-average ping with median-of-5 sliding window,
0.5s polling, 150ms cap, and spike rejection. Converges in ~2.5s vs 9-12s.

Fix 2: Server sends damage momentum to client via ClientDamageImpulse RPC
so client prediction stays in sync — prevents hiccup/snap on taking damage.

Fix 3: Smooth rewind delta per PCC (max 10ms change per trace) to prevent
visible position jumps when ping fluctuates between shots.

Fix 4: 200ms grace period on move error corrections after damage impulse,
letting stale in-flight moves expire without triggering rubber-banding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… history perf

- Fix copy-paste bug in NewNet_LinkAltFire: TimeTravel referenced
  NewNet_FlakCannon instead of NewNet_LinkGun for mutator lookup
- Fix fake projectile timer clobbering on rapid fire: track pending
  state with bFakeFirePending, fire pending effect before overwriting
  (NewNet_RocketFire, FlakFire, FlakAltFire, BioFire, LinkAltFire)
- Fix forward-iteration removal bug in NewNet_LinkFire and
  UTComp_LinkFire: LockingPawns cleanup skipped consecutive None
  entries, now iterates backwards
- Fix O(n^2) RemoveOutdatedHistory: count removals first, single
  Remove call instead of per-element loop shift
- Fix AddHistory: use Length++ instead of Insert at end for
  InterpCurve points
- Update build.bat and make.ini for local build layout with
  C:/UT2004 base asset paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix Y/Z axis assignment in DoTimedClientFireEffect: all three axes
were set to OldXAxis, breaking projectile spawn orientation for
delayed fake projectiles on higher ping (rockets, flak, bio, link alt).

Add None guard after FindFPM() in CheckForFakeProj to prevent crash
when FakeProjectileManager doesn't exist (rocket, link, shock,
seeking rocket projectiles).

Update make.ini paths for local build layout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reset PCC rewind smoothing state in TurnOffCollision so each flak
chunk in a salvo traces against the same rewound positions. Previously
the smoothing accumulated across 9 chunk traces, causing drift.

Add missing FPM None guard in NewNet_FlakChunk.CheckForFakeProj.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…traces

The per-PCC rewind smoothing (Fix 3) was clamping dt changes to 10ms
per TimeTravelPawn call. This broke:
- Shock beam correction loop: searches ±40ms offsets in 20ms steps,
  but smoothing prevented reaching the target offsets
- Flak multi-chunk traces: 9 chunks with independent TimeTravel
  cycles caused accumulated drift

The smoothing was misplaced — PCCs are invisible server-side trace
actors, not rendered. The ping estimator median filter (Fix 1)
already smooths at the right layer (client ping measurement).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-existing bug: AllowFakeProjectile/RegisterFakeProjectile called
without None check after FindFPM in DoClientFireEffect and
DoTimedClientFireEffect across multiple weapon fire classes.

Fixed in: NewNet_RocketFire, NewNet_FlakFire (2 instances),
NewNet_FlakAltFire, NewNet_BioFire, NewNet_LinkAltFire,
NewNet_RocketMultiFire.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
High ping players rewinding low ping players 150-200ms+ back caused
"shot around corners" — the target had moved well past that position
on their screen. Cap rewind at 75ms (half of 150ms RTT target) in
TimeTravelPawn. Players at 150ms or below get full compensation.
Above that, they need to lead their shots for the excess.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
During lag spikes, the unreliable ClientDamageImpulse RPC could be
dropped. For shield jumps, this meant the client never learned about
the upward momentum — it predicted staying on the ground, then got
corrected to an airborne position, causing the "invisible ceiling"
snap. Making it reliable ensures the impulse always arrives, even
during packet loss.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a player fires, switches weapon before the fake timer fires,
then switches back and fires again, the bFakeFirePending check was
calling DoTimedClientFireEffect with stale aim/position data from the
previous shot. This spawned a ghost fake projectile from the old
position. Now just clears the flag — the timer delay (max 40ms) is
always shorter than any weapon's fire rate, so a valid pending effect
would have already fired before the next shot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ClientDamageImpulse now sends absolute post-damage velocity instead of
a delta. Previously the client added the delta on top of its predicted
velocity, causing a brief double-velocity spike on high-momentum hits
(shock combo, rockets). The normal server correction then snapped the
position back, producing a visible hitch.

With absolute velocity, the client sets its velocity to exactly what
the server has — no double-up, no delayed correction needed. Low-momentum
weapons were unaffected; this fixes shock combo, rocket, flak shell, and
shield gun knockback specifically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ocity

Previous fix sent absolute velocity but the client still had stale position
from frames predicted before the impulse arrived. After the grace period,
the server snapped the position to correct the accumulated error — causing
the hitch on high-momentum weapons.

Now sends Location, Velocity, and Physics together. Client syncs all three
via SetLocation + SetPhysics in one call. No position error accumulates,
no delayed correction needed. The grace period still suppresses redundant
corrections during the brief overlap window.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SetLocation causes a physics reset/collision recalc hitch on the frame
it's called. Reverted to velocity-only sync — the position error from
pre-impulse prediction frames is small (few units over 2-3 frames) and
converges naturally through continued prediction.

Extended grace period from 200ms to 500ms so the server doesn't snap-
correct during convergence. The velocity is correct immediately, so
forward prediction is accurate — the grace period just needs to outlast
the stale saved moves flushing from the queue.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bypass the impulse replication to isolate and test the other netcode
improvements (move accum fix, MaxResponseTime, MaxSavedMoves) without
interference. TakeDamage just calls super — engine handles corrections
natively.

The impulse approaches tried so far (delta, absolute vel, full state
with SetLocation) all had issues: double-velocity, physics reset hitch,
or delayed snap after grace period. Need to revisit with an interpolated
correction approach.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The correction pass runs when the client believes it hit but the server's
initial rewind trace missed. It searches nearby rewind times to find the
hit. At 20ms steps, fast-dodging targets could fall between search points
— the collision copy oscillated around the real position without landing
on it.

10ms steps halve the maximum position error during the search (~5 units
at dodge speed instead of ~10). The extra traces only run during unreg
attempts, not normal hits, so no performance impact on regular gameplay.

Changed in: ShockBeamFire, SniperFire, SuperShockBeamFire.
Other hitscan weapons (minigun, link, assault) don't have the correction
search and are unaffected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Broadcast() text chat with BroadcastLocalizedMessage() using new
UTComp_WinByTwoMessage (extends CriticalEventPlus). Renders centre-screen
in yellow at 40% height so players with chat disabled still see it.

Updated in both UTComp_GameRules (CTF/team games) and UTComp_ClanArena
(round-based). Wipeout inherits via GameRules — no change needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…figurable rewind cap

Root cause of unregs for 75-100ms players: PingDT calculates full RTT but
MAX_REWIND was 75ms, so any player above 75ms ping lost rewind. At 100ms
ping + dodge speed (1200 UU/s), the 25ms of lost rewind = 30 UU position
error, exceeding the 25 UU collision radius — guaranteed miss on moving targets.

Changes:
- PawnCollisionCopy: MAX_REWIND 0.075 -> 0.240 (now a var, not const)
- MutUTComp: new config var PawnCollisionMaxRewind (default 0.240),
  exposed in web admin under "UTComp NewNet"
- PawnCollisionCopy reads PawnCollisionMaxRewind from MutUTComp in PostBeginPlay
- MAX_PROJECTILE_FUDGE 0.075 -> 0.100 across 12 weapon fire classes
  (FlakCannon both FUDGE and FUDGE_ALT, RocketLauncher stays at 0.275)
- CHANGES.md updated with full RTT analysis and V24 bump

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UTComp_ServerReplicationInfo: add bWinByTwo, bEnableCorrectionSmoothing,
CorrectionHalfLife, CorrectionJumpThreshold, MaxVisualOffset,
bSmoothCameraOffset to replication block + defaultproperties.

ModernPawn: smoothed render offset changes (correction smoothing system).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ace8a8)

InitializeClient() calls SetMaxSavedMoves() once at startup, but RepInfo
may not have its replicated values yet at that point. Without an ongoing
re-sync, clients keep the default MaxSavedMoves=300 even when the server
configures a higher value, causing misleading "Exceeded max saved moves"
warnings in client logs.

Fix: override PostNetReceive() to re-check and re-sync MaxSavedMoves
whenever BS_xPlayer's replicated state updates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…o lookup

Multiple fixes targeting movement queue overflow rubberbanding:

1. GetFreeMoveEx: stop nuking the entire SavedMoves list on overflow.
   Previous behaviour destroyed all in-flight prediction history when the
   queue exceeded MaxSavedMoves, turning a survivable overflow into a
   catastrophic prediction reset (massive rubberband). Now drops only the
   single oldest move and reuses the slot, keeping the rest of the queue.

2. UTComp_ServerReplicationInfo: set NetUpdateFrequency=10.0
   Default ReplicationInfo NetUpdateFrequency is 1.0, meaning replicated
   state changes only push to clients once per second. 10Hz makes initial
   replication and any settings changes propagate quickly enough.

3. BS_xPlayer.MaxSavedMoves default: 300 -> 500
   Insurance for the case where replication fails entirely. High-FPS
   players (1000+ fps) at 200ms ping can hit 300 with normal jitter.

4. PostNetReceive: proactively look up RepInfo if None instead of just
   re-syncing from an existing reference. Closes the timing window where
   PostNetReceive fires before any function has run the DynamicActors
   lookup that populates RepInfo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…gated)

Adapts the DarkPlaces approach to UE2 movement: instead of allocating a new
SavedMove every render frame, share a single PendingMove between network
ticks. The PendingMove's Delta is extended and Acceleration is updated each
frame, while local prediction (ProcessMove + Pawn.AutonomousPhysics) still
runs every frame for visual smoothness.

Why: at high client framerate (1000+ fps), UE2 currently allocates ~1000
SavedMoves per second while only sending ~91, causing the queue to grow
~909/sec faster than it drains. This is the structural cause of the
"Exceeded max saved moves" overflow that we previously treated by bumping
MaxSavedMoves and fixing the destructive overflow logic. Coalescing fixes
it at the source — queue size becomes ~1/NetMoveDelta entries regardless
of fps.

Important input changes (jump press, duck toggle, dodge double-click,
acceleration direction change >17 degrees, non-walking physics) bypass
coalescing and force fresh allocation, preserving competitive responsiveness.

Gated behind bCoalesceMoves config (default false). Plumbing:
  - MutUTComp.uc: var config bool, FillPlayInfo entry, GetDescriptionText,
    push to RepInfo
  - UTComp_ServerReplicationInfo.uc: var, replication block, default
  - BS_xPlayer.uc: var, LastQueuedMoveTime tracking, coalesce branch in
    ReplicateMove (top), SetMaxSavedMoves syncs from RepInfo,
    PostNetReceive re-checks both MaxSavedMoves and bCoalesceMoves

Toggle on via web admin under "UTComp Movement" or in WSUTComp.ini under
[WSUTComp.MutUTComp]. Flip back instantly without recompile to revert.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…CK_None enum

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PlayerController.bDuck is var byte (0/1) while SavedMove.bDuck is var bool.
UE2 won't auto-convert across these types in ==. Use (bDuck != 0) to
explicitly cast to bool. Restored DoubleClickMove == DCLICK_None since
SavedMove.DoubleClickMove is the enum type (the existing combine logic at
line 4174 uses the same expression and compiles).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant