extends RefCounted # Plain data holder for one body's snapshot state (§2.4 of MULTIPLAYER_SPEC.md). # Deliberately not Ship/Ball themselves, and deliberately not a scene-tree # node — NetCodec's pack/unpack must stay callable from pure-function tests # with no live scene. Phase 2's snapshot writer fills one of these per body # per tick from the real RigidBody3D state; Phase 2's interpolator does the # reverse. # # avel_range must match what the sender quantised with (SHIP_AVEL_RANGE vs # BALL_AVEL_RANGE in net_codec.gd) — it is not carried on the wire, because # slot order already tells both peers which body is which (§1.3: "entities # are addressed by integer slot, never by path"). var position := Vector3.ZERO var rotation := Quaternion.IDENTITY var linear_velocity := Vector3.ZERO var angular_velocity := Vector3.ZERO var frozen := false var turbo := false var thrust_z := 0.0 # -1..1; re-quantised to a 3-bit bin on the wire var stalled := false var avel_range := 4.0 # NetCodec.SHIP_AVEL_RANGE; set to BALL_AVEL_RANGE for the ball # Self-referential preload, not get_script().new() — this file deliberately # has no class_name (same cache-timing reason as test_case.gd and other # path-`extends`d files in this project), and get_script().new() throws # "Nonexistent function 'new' in base 'GDScript'" from within the script's # own body in this Godot version. const _NetBodyState = preload("res://scripts/net_body_state.gd") # Same contract as ShipAction.copy() (see its own comment): a distinct # instance with equal fields, for callers that hold onto a state past the # tick/comparison it was returned in. func copy() -> RefCounted: var c := _NetBodyState.new() c.position = position c.rotation = rotation c.linear_velocity = linear_velocity c.angular_velocity = angular_velocity c.frozen = frozen c.turbo = turbo c.thrust_z = thrust_z c.stalled = stalled c.avel_range = avel_range return c