extends Node ## Measures the gap between where the client *draws* the local player and where ## the server actually has it -- the number that decides whether bullets appear ## to leave the ship's nose or trail out of its back. ## ## godot --headless --path . res://tools/diag_prediction.tscn ## ## Runs the real listen-server path (ServerRuntime + ClientRuntime + the actual ## Net loopback), not a mock, so node process order is exactly production's. var _ticks: int = 0 var _worst: float = 0.0 var _sum: float = 0.0 var _samples: int = 0 func _ready() -> void: GameOpts.bot_client = true GameOpts.player_name = "diag" if Net.host(27300) != OK: push_error("could not host") get_tree().quit(1) return Net.start_local_client() func _physics_process(_delta: float) -> void: _ticks += 1 var srv := Net.server var cli := Net.client if srv == null or cli == null: return var inst := srv.instance_of(Net.LOCAL_PEER) if inst == null or not inst.world.players.has(Net.LOCAL_PEER): return var sp: SimPlayer = inst.world.players[Net.LOCAL_PEER] var delta_px := sp.pos.distance_to(cli.predicted_pos) # Skip the first few ticks while the handshake settles. if _ticks > 20: _worst = maxf(_worst, delta_px) _sum += delta_px _samples += 1 if _ticks % 20 == 0: print("tick=%4d srv_tick=%4d srv_pos=%-22s pred=%-22s gap=%6.2fpx acked=%4d cli_tick=%4d queue=%d" % [ _ticks, inst.world.tick, str(sp.pos.round()), str(cli.predicted_pos.round()), delta_px, sp.last_input_tick, cli.input_tick, sp.input_queue.size()]) if _ticks >= 300: print("---") print("GAP worst=%.2fpx mean=%.2fpx over %d samples" % [_worst, _sum / maxf(_samples, 1), _samples]) print("player speed is %.0f u/s = %.2f px per tick" % [ SimConfig.PLAYER_SPEED, SimConfig.PLAYER_SPEED * SimConfig.TICK_DELTA]) print("muzzle offset = %.1fpx, drawn ship radius = %.1fpx, hitbox = %.1fpx" % [ SimConfig.PLAYER_MUZZLE_OFFSET, SimConfig.PLAYER_VISUAL_RADIUS, SimConfig.PLAYER_RADIUS]) Net.shutdown() get_tree().quit(0)