extends Node
## Reads local input and exposes synced properties.
## Only the authority writes; remotes receive via InputSynchronizer.
var input_direction: Vector2 = Vector2.ZERO
var camera_rotation_y: float = 0.0
var camera_pitch: float = 0.0
var wants_jump: bool = false
## Benchmark mode: generate synthetic circular movement instead of real input.
var benchmark: bool = false
## DRIVEN BY A BOT. When true this node stops reading Input and leaves the four
## synced fields exactly as they were set from outside, so a brain can steer this
## player.
##
## An early-return branch, the same shape `benchmark` already uses, and for the
## same reason: _physics_process rewrites input_direction and wants_jump from
## Input on EVERY physics frame, so anything written from outside is erased within
## a frame unless this function is told to stand down.
##
## ⚠ THIS IS THE WHOLE POINT OF A BOT AS A TEST. A driven player writes the same
## input_direction / camera_rotation_y / wants_jump a human does and is replicated
## by the same MultiplayerSynchronizer, so it exercises the identical ephemeral
## path. A bot that teleported its body instead would prove nothing about
## replication -- which is why nothing here moves a position.
var driven: bool = false
var _benchmark_angle: float = 0.0
const BENCHMARK_CIRCLE_PERIOD: float = 6.0 # seconds per full circle
func _physics_process(delta: float) -> void:
if not is_multiplayer_authority():
return
if driven:
return # a brain owns these fields; see `driven`
if benchmark:
input_direction = Vector2(0, -1)
_benchmark_angle += (TAU / BENCHMARK_CIRCLE_PERIOD) * delta
camera_rotation_y = _benchmark_angle
wants_jump = false
# Drive camera head rotation directly — no mouse input in headless mode.
# _process_local() reads camera_head.global_transform to determine forward
# direction, so we must rotate it here for circular movement to work.
var player: Node = get_parent()
if player:
var cam_head: Node3D = player.get_node_or_null("CameraHead")
if cam_head:
cam_head.rotation.y = _benchmark_angle
return
# NOT WHILE TYPING (editor redesign E0): movement POLLS the keyboard, so focus in a
# text field (chat, texture search) never reached it and typing "wasd" walked you.
var km := get_node_or_null("/root/Keymap")
if km != null and not km.movement_allowed():
input_direction = Vector2.ZERO
wants_jump = false
return
input_direction = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
wants_jump = Input.is_action_just_pressed("jump")