mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
feat(multiplayer): Phase 3 task 3.7 - debug net overlay extension
Extends net_debug_overlay.gd (Phase 1's RTT/offset display) with the rest of task 3.7's list: jitter (new RFC3550-style EWMA in NetworkManager, computed from raw per-sample RTT before Phase 1's own min-filtering, since that filter is deliberately jitter-insensitive by design), input buffer depth and input_lead (both already tracked client-side for task 3.3), snapshot loss (a new EWMA in networked_match.gd over each received snapshot's own server_tick gap - snapshots go out at a steady one-tick cadence, so a gap is direct evidence of a drop or reorder), snapshot age (computed on demand from the same bias-corrected tick estimate the interpolator itself uses), and bandwidth (new rolling per-second byte counters in MatchSim, on the two 60Hz hot-path channels only). Prediction error is deliberately omitted with a comment explaining why: there's no client-side prediction to measure until Phase 4. Verified values are live and plausible, not just present, by calling get_net_debug_stats() directly in a real two-process test and checking the numbers make sense: bandwidth matched the wire format's own byte math almost exactly (measured ~2400 B/s sent against a computed 40B x 60Hz, ~3540 B/s received against 59B x 60Hz), and buffer depth/lead/loss all moved in the correct direction between a clean LAN run and one under simulated 60ms latency + 10% loss. Full regression suite re-run clean.
This commit is contained in:
@@ -52,10 +52,42 @@ class _PeerInputState:
|
||||
|
||||
var _peer_input_state: Dictionary = {} # peer_id -> _PeerInputState, server only
|
||||
|
||||
# Bandwidth (task 3.7's debug overlay): only the two 60Hz hot-path channels
|
||||
# (input, snapshot) — match_config/score_update are low-frequency control
|
||||
# messages, not what §2's byte-budget analysis or a live overlay cares
|
||||
# about. Rolling per-second counters, recomputed opportunistically on each
|
||||
# send/receive rather than on a timer — nothing needs the rate outside of
|
||||
# an on-demand overlay read anyway.
|
||||
const BANDWIDTH_WINDOW_MS := 1000
|
||||
var bytes_sent_per_sec := 0.0
|
||||
var bytes_received_per_sec := 0.0
|
||||
var _sent_window_start_ms := 0
|
||||
var _sent_window_bytes := 0
|
||||
var _received_window_start_ms := 0
|
||||
var _received_window_bytes := 0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
NetworkManager.client_disconnected.connect(func(peer_id: int) -> void: _peer_input_state.erase(peer_id))
|
||||
|
||||
|
||||
func _track_sent(n: int) -> void:
|
||||
var now := Time.get_ticks_msec()
|
||||
if now - _sent_window_start_ms >= BANDWIDTH_WINDOW_MS:
|
||||
bytes_sent_per_sec = _sent_window_bytes * 1000.0 / maxf(1.0, float(now - _sent_window_start_ms))
|
||||
_sent_window_start_ms = now
|
||||
_sent_window_bytes = 0
|
||||
_sent_window_bytes += n
|
||||
|
||||
|
||||
func _track_received(n: int) -> void:
|
||||
var now := Time.get_ticks_msec()
|
||||
if now - _received_window_start_ms >= BANDWIDTH_WINDOW_MS:
|
||||
bytes_received_per_sec = _received_window_bytes * 1000.0 / maxf(1.0, float(now - _received_window_start_ms))
|
||||
_received_window_start_ms = now
|
||||
_received_window_bytes = 0
|
||||
_received_window_bytes += n
|
||||
|
||||
# Server only: the last match_config actually sent, so a client whose own
|
||||
# scene load (and therefore its match_config_received listener) finishes
|
||||
# AFTER the server already broadcast can still get it — a one-shot
|
||||
@@ -80,12 +112,14 @@ func request_match_config() -> void:
|
||||
|
||||
|
||||
func send_input(bytes: PackedByteArray) -> void:
|
||||
_track_sent(bytes.size())
|
||||
# bytes is already fully packed (any timestamps it carries are already
|
||||
# fixed), so wrapping the dispatch itself is enough — task 2.8.
|
||||
NetSim.send(func() -> void: _recv_input.rpc_id(1, bytes), 1)
|
||||
|
||||
|
||||
func send_snapshot(peer_id: int, bytes: PackedByteArray) -> void:
|
||||
_track_sent(bytes.size())
|
||||
NetSim.send(func() -> void: _snapshot.rpc_id(peer_id, bytes), peer_id)
|
||||
|
||||
|
||||
@@ -113,6 +147,7 @@ func _request_match_config() -> void:
|
||||
func _recv_input(bytes: PackedByteArray) -> void:
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
_track_received(bytes.size())
|
||||
var peer_id := multiplayer.get_remote_sender_id()
|
||||
|
||||
var state: _PeerInputState = _peer_input_state.get(peer_id)
|
||||
@@ -173,6 +208,7 @@ func _disconnect_abusive_peer(peer_id: int, reason: String) -> void:
|
||||
|
||||
@rpc("authority", "call_remote", "unreliable_ordered", 2)
|
||||
func _snapshot(bytes: PackedByteArray) -> void:
|
||||
_track_received(bytes.size())
|
||||
var decoded := NetCodec.unpack_snapshot(bytes)
|
||||
snapshot_received.emit(decoded)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user