Files
tactical-shooter/scripts/network/snapshot.gd
T
shawn f6d69545c9 feat: implement client-side prediction and reconciliation system
Add client-side prediction (t_p1_pred) with:

- scripts/network/snapshot.gd — lightweight snapshot resource with
  to_dict/from_dict serialization for RPC. Stores position (Vector3),
  rotation (Quaternion), velocity (Vector3), grounded (bool).

- scripts/network/client_prediction.gd — prediction/reconciliation
  controller. 64-entry ring buffer of local snapshots, sends inputs to
  server each physics tick (128Hz, ENet channel 0), detects misprediction
  on server state arrival, rewinds and re-applies unconfirmed inputs.
  Also supports remote player interpolation.

- scripts/network/network_manager.gd — new RPC endpoints for client
  prediction: send_client_input (client->server, ch 0) and
  send_server_state (server->client, ch 1). New signals for routing.

- client/characters/character/fps_character_controller.gd — prediction
  hooks in _physics_process: on_before_tick() captures pre-input
  snapshot, on_after_tick() sends input to server. Client prediction
  path uses local movement (instant feedback) instead of reading from
  SimulationServer entity.

Architecture:
  Each tick: client applies input → predicts new state locally
  → sends input to server → server returns authoritative state
  → client compares and reconciles if mismatch.
2026-07-01 20:21:16 -04:00

61 lines
2.1 KiB
GDScript

## Snapshot — Lightweight world-state snapshot for client prediction & reconciliation.
##
## Stores a point-in-time record of a player's physical state.
## Used by ClientPrediction to build its ring buffer, send inputs,
## and reconcile against authoritative server state.
##
## Serialization: to_dict() / from_dict() for RPC transport over ENet.
class_name Snapshot
extends RefCounted
# ---------------------------------------------------------------------------
# Fields
# ---------------------------------------------------------------------------
## Server tick / local tick number this snapshot corresponds to.
var timestamp: int = 0
## World-space position of the character's feet / body origin.
var position: Vector3 = Vector3.ZERO
## Character body rotation as a quaternion (avoids gimbal lock in interpolation).
var rotation: Quaternion = Quaternion.IDENTITY
## Linear velocity in world space.
var velocity: Vector3 = Vector3.ZERO
## Whether the character was on the ground (is_on_floor()) at capture time.
var grounded: bool = false
# ---------------------------------------------------------------------------
# Serialization
# ---------------------------------------------------------------------------
## Convert to a Dictionary suitable for RPC transfer.
## Arrays are used instead of Vector3/Quaternion because Godot's RPC
## serialization handles basic types more reliably.
func to_dict() -> Dictionary:
return {
"timestamp": timestamp,
"position": [position.x, position.y, position.z],
"rotation": [rotation.x, rotation.y, rotation.z, rotation.w],
"velocity": [velocity.x, velocity.y, velocity.z],
"grounded": grounded,
}
## Deserialize from a Dictionary produced by to_dict().
static func from_dict(data: Dictionary) -> Snapshot:
var s = Snapshot.new()
s.timestamp = data.get("timestamp", 0)
var p: Array = data.get("position", [0.0, 0.0, 0.0])
s.position = Vector3(p[0], p[1], p[2])
var r: Array = data.get("rotation", [0.0, 0.0, 0.0, 1.0])
s.rotation = Quaternion(r[0], r[1], r[2], r[3])
var v: Array = data.get("velocity", [0.0, 0.0, 0.0])
s.velocity = Vector3(v[0], v[1], v[2])
s.grounded = data.get("grounded", false)
return s