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:
@@ -91,6 +91,15 @@ var _input_history: Array[ShipAction] = []
|
||||
var _last_received_snapshot_tick := 0 # client only: echoed back as ack_snapshot_tick
|
||||
var _input_lead_controller := InputLeadController.new() # client only (§3.3)
|
||||
var _last_known_input_buffer_depth := -1 # client only: -1 = no snapshot with this field yet
|
||||
# Loss estimate (task 3.7's debug overlay), client only: snapshots go out
|
||||
# at a steady one-tick cadence, so a server_tick that jumps by more than 1
|
||||
# since the last received one is direct evidence of a dropped or reordered
|
||||
# snapshot on the unreliable channel. EWMA over each reception's own
|
||||
# "missed / (missed + 1)" fraction rather than a flat drop-count, so it
|
||||
# reads as a live percentage and decays naturally once loss stops.
|
||||
const SNAPSHOT_LOSS_EWMA_ALPHA := 1.0 / 16.0
|
||||
var _snapshot_loss_ewma := 0.0
|
||||
var _expected_next_snapshot_tick := -1
|
||||
# §3.1 step 4. Not 120: InputLeadController.LEAD_MAX is 12, so anything
|
||||
# claiming to be further ahead of the current server tick than this is
|
||||
# broken or hostile, not just an honest client running a legitimately fast
|
||||
@@ -405,6 +414,11 @@ func _on_snapshot_received(decoded: Dictionary) -> void:
|
||||
var server_tick: int = decoded["server_tick"]
|
||||
var reset_gen: int = decoded["reset_gen"]
|
||||
var bodies: Array = decoded["bodies"]
|
||||
if _expected_next_snapshot_tick >= 0:
|
||||
var missed := maxi(0, server_tick - _expected_next_snapshot_tick)
|
||||
var sample := float(missed) / float(missed + 1)
|
||||
_snapshot_loss_ewma += (sample - _snapshot_loss_ewma) * SNAPSHOT_LOSS_EWMA_ALPHA
|
||||
_expected_next_snapshot_tick = server_tick + 1
|
||||
_last_received_snapshot_tick = server_tick
|
||||
# Per-client header (§2.4): unlike the shared body segment, this is
|
||||
# genuinely this recipient's own — input_buffer_depth is THIS client's
|
||||
@@ -468,6 +482,23 @@ func _current_interp_delay_ms() -> float:
|
||||
return clampf(one_way + SNAPSHOT_INTERVAL_MS * 1.5, INTERP_DELAY_MIN_MS, INTERP_DELAY_MAX_MS)
|
||||
|
||||
|
||||
# Client-only stats for task 3.7's debug overlay, discovered via the "game"
|
||||
# group the same way HUDController finds this node — no direct reference
|
||||
# needed, and the overlay degrades gracefully (has_method check) against
|
||||
# any mode that doesn't implement this at all.
|
||||
func get_net_debug_stats() -> Dictionary:
|
||||
var snapshot_age_ms := 0.0
|
||||
if NetworkManager.rtt_ms >= 0.0:
|
||||
var estimated_now_tick := _estimated_tick(NetworkManager.get_server_time_estimate_ms())
|
||||
snapshot_age_ms = (estimated_now_tick - float(_last_received_snapshot_tick)) * NetInterpolator.TICK_MS
|
||||
return {
|
||||
"input_buffer_depth": _last_known_input_buffer_depth,
|
||||
"input_lead": _input_lead_controller.lead,
|
||||
"snapshot_age_ms": snapshot_age_ms,
|
||||
"snapshot_loss_pct": _snapshot_loss_ewma * 100.0,
|
||||
}
|
||||
|
||||
|
||||
# Collider time: present-time estimate, applied once per physics tick.
|
||||
func _physics_process(_delta: float) -> void:
|
||||
# Automatic multiplayer polling is disabled project-wide (task 1.3) —
|
||||
|
||||
Reference in New Issue
Block a user