From 70105d8b68f46a7de17005143850e49a6458daf7 Mon Sep 17 00:00:00 2001 From: Shawn Date: Thu, 2 Jul 2026 22:34:32 -0400 Subject: [PATCH] Fix property-pool rollback errors in headless server The dedicated server was spawning a host avatar (peer 1) via player-spawner._handle_host() and _handle_new_peer(1) on startup, which registered RollbackSynchronizer subjects with the netfox rollback system. Since no client input exists for peer 1, the rollback system tried to record state for freed/recycled objects, flooding the log with property-pool.gd errors. Fix: - Add spawn_host_avatar @export flag to player-spawner.gd - Set it false in headless_server.gd before starting the ENet server - PlayerSpawner._handle_host() skips avatar spawn when flag is false - PlayerSpawner._handle_new_peer(1) skips avatar spawn when flag is false Server startup is now clean: no property-pool errors, no RPC path errors. --- examples/multiplayer-fps/scripts/player-spawner.gd | 12 ++++++++++++ run_server.sh | 3 +++ scripts/headless_server.gd | 8 ++++++++ 3 files changed, 23 insertions(+) create mode 100644 run_server.sh diff --git a/examples/multiplayer-fps/scripts/player-spawner.gd b/examples/multiplayer-fps/scripts/player-spawner.gd index a50d19d..d247d6c 100644 --- a/examples/multiplayer-fps/scripts/player-spawner.gd +++ b/examples/multiplayer-fps/scripts/player-spawner.gd @@ -2,6 +2,10 @@ extends Node @export var player_scene: PackedScene +## Whether to spawn an avatar for the host (peer 1) when on_server_start fires. +## Set to false for dedicated server operation. +@export var spawn_host_avatar: bool = true + var t_spawn_points: Array[Marker3D] = [] var ct_spawn_points: Array[Marker3D] = [] var team_manager: TeamManager @@ -43,12 +47,20 @@ func _handle_connected(id: int): _spawn(id) func _handle_host(): + if not spawn_host_avatar: + print("[PlayerSpawner] Dedicated server mode — skipping host avatar") + if round_manager: + round_manager.start_match() + return _spawn(1) # Auto-start match if round_manager: round_manager.start_match() func _handle_new_peer(id: int): + if id == 1 and not spawn_host_avatar: + print("[PlayerSpawner] Skipping spawn for peer 1 (dedicated server)") + return _spawn(id) if multiplayer.is_server(): team_manager.sync_to_peer(id) diff --git a/run_server.sh b/run_server.sh new file mode 100644 index 0000000..70358a9 --- /dev/null +++ b/run_server.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd /home/oplabs/tactical-shooter +exec ~/.local/bin/godot --headless --scene res://scenes/headless_server.tscn -- --port 34201 --rcon-password test123 > /tmp/server_output.log 2>&1 \ No newline at end of file diff --git a/scripts/headless_server.gd b/scripts/headless_server.gd index ad65710..fee8c8c 100644 --- a/scripts/headless_server.gd +++ b/scripts/headless_server.gd @@ -55,6 +55,14 @@ func _ready() -> void: # Start RCON admin console _start_rcon() + # Set dedicated server flag on the player spawner to prevent + # spawning a host avatar (peer 1) that would cause rollback errors. + # Must be set before the ENet server starts (before on_server_start fires). + var spawner = _game_scene.get_node_or_null("Network/Player Spawner") + if spawner: + spawner.spawn_host_avatar = false + print("[HeadlessServer] Set spawn_host_avatar=false on PlayerSpawner") + # Wait for game scene to enter the tree await get_tree().process_frame await get_tree().process_frame