Files
CosmicClash/Game
Josh Creek 14698d4ccb fix(multiplayer): adversarial review fixes for Phase 2
An Opus subagent's adversarial review of Phase 2 found real bugs the
smoke tests couldn't catch, since constant-velocity dead reckoning still
moves a ship far enough to pass a "moved > 1.0" check:

- The interpolator never actually interpolated. NetInterpolator.to_tick()
  assumes physics_frame * TICK_MS == Time.get_ticks_msec() on the server,
  which is off by a steady ~45-55ms in practice (real startup work before
  the first physics step, widened by any dropped tick). Every sample_at()
  call took the extrapolation branch, 100% of the time, defeating the
  interpolation buffer entirely. Fixed with a shared, min-filtered rolling
  bias estimate in networked_match.gd, applied before every to_tick() call.

- Goals caused a ~27m visual slide: _reset_gen was bumped before the
  queued teleport actually landed, so the client's buffer-clear kept
  exactly the stale in-goal sample and lerped a slide to the next, real
  one. Fixed by tracking the tick the goal was detected on and only
  bumping the generation once strictly later ticks confirm the teleport
  has landed - a naive "next _physics_process" boolean flag doesn't
  work, since a goal Area's body_entered fires before that same tick's
  _physics_process runs, not on the next one.

- _local_input_sampler (a Node, never added to the tree) was never freed
  - this was the unexplained "3 resources still in use at exit" warning
  on every Phase 2 test run.

- Ball angular velocity decoded 8x too small (rescale_avel was never
  called); get_server_time_estimate_ms() was used before the clock had
  synced; net_sim.gd's delayed-send timer stopped ticking while the tree
  was paused and didn't check connection status before firing;
  _broadcast_snapshot's ball index could silently break if a ship were
  ever despawned; declared-but-unemitted HUD lifecycle signals showed a
  permanently frozen timer widget.

Also confirmed, empirically, several things the review checked and found
fine: a hostile client sending malformed input cannot crash the server,
skipping GameMode's super() drops nothing load-bearing, deterministic
slot assignment is correct with 2 real simultaneous clients, and RPC
authority enforcement genuinely rejects a forging client.

All fixes verified with real two-process runs (including forcing an
actual goal and reading the server's own broadcast stream) and temporary
instrumentation, removed once each fix was confirmed. Full Phase 1 +
Phase 2 regression suite, including the net-sim-latency milestone gate,
re-run clean after every fix.
2026-08-20 12:43:33 +01:00
..