Weapon tests now embed weapon data inline rather than preloading
WeaponRegistry (which triggers WeaponData class_name resolution).
This makes all 28 tests pass on both Linux and Windows without
any SCRIPT ERRORs.
Also add WeaponData.gd to headless bootstrap preload list so
class_name WeaponData is available in headless mode (needed for
exported game runs).
Test breakdown (all pass, zero errors):
weapons.gd: 9 tests (6 weapons + registration + default pistol)
bootstrapper.gd: 6 tests (input parsing validation)
team.gd: 6 tests (auto-assign, team names, enum values)
economy.gd: 7 tests (constants, money clamping, loss streaks)
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.
The headless server added the multiplayer-fps game scene as a child node
(/root/HeadlessServer/multiplayer-fps/...), causing RPC node paths to
include the HeadlessServer/ prefix. Clients expected paths relative to
root (/root/multiplayer-fps/...), so every RPC would fail with
'Node not found'.
Fix: add the game scene directly to /root/ via call_deferred, making it
a sibling of the HeadlessServer node rather than a child. RPC paths now
match between server and client.
The export_presets.cfg excluded examples/shared/** from both Windows
Desktop and Linux Server presets. The multiplayer-fps.tscn main scene
depends on files in this directory (network-popup.tscn, async.gd,
simple-time-display.gd) — without them the exported binary crashes
on startup with 'Can't load dependency' errors.
- Remove examples/shared/** from Windows Desktop exclude_filter
- Remove examples/shared/** from Linux Server exclude_filter
## Headless Server
- New entry point: scenes/headless_server.tscn + scripts/headless_server.gd
- Loads multiplayer-fps game, strips UI/rendering, auto-hosts ENet server
- Port config via --port arg, SERVER_PORT env, or default 34197
- RCON admin console on port 28960 (configurable via --rcon-port)
## Netfox Headless Bootstrap
- scripts/netfox_headless_bootstrap.gd — preloads all netfox dependency
scripts in headless mode so class_names register before autoloads parse
- Registered as first autoload in project.godot
- Fixes 'Could not find type' errors for NetfoxLogger, NorayProtocolHandler,
NetworkClocks, etc.
## RCON Admin
- Ported from old project: scripts/rcon/rcon_server.gd (TCP auth layer)
- scripts/rcon/rcon_command_handler.gd (command dispatch)
- Password via config/rcon_password.cfg, --rcon-password, or RCON_PASSWORD env
## Other Changes
- Added GameEvents stub (lan-bootstrapper dependency)
- Updated project.godot features to 4.7
- Added config/server_config.cfg for operator editing
- Updated RCON paths to new project structure
- Copied fps_camera.gd from client/characters/character/ to scripts/characters/
- Updated player.tscn to reference new path
- The client/ directory has its own project.godot so Godot ignores it during export
- Zero export errors now
- Changed run/main_scene to entry.tscn (smart bootstrap detects headless vs display)
- client_main.gd now uses player.tscn instead of old FPS template
- Added mouse look (click-to-capture, escape-to-release) to player.gd fallback path
- Added _gather() call to player.gd fallback _physics_process() so PlayerNetInput
is populated even without NetworkTime singleton (exported builds)
- Re-exported Windows build with same tag v0.1.0-windows
- WeaponData: restored class_name, converted static vars to make() factory
- WeaponDefinitions: updated to use make() instead of static var refs
- Windows export: tactical-shooter.exe + godot-jolt_windows-x64.dll
- Build artifact: build/tactical-shooter-windows-x86_64-v0.1.0.zip
- Fix dead code in server_main.gd (unreachable lag compensation print)
- Remove WeaponData type hints from weapon_server.gd (Resource class_name in Node scripts = headless fail)
- Use preload() instead of WeaponData class_name in weapon_definitions.gd for const refs
- Lazy-init WEAPONS dict (const can't reference preload members)
- Remove duplicate class_name RoundManager from server/scripts/round/round_manager.gd
- Remove DamageProcessor type hints from round/round_manager.gd and bomb_objective.gd
- Add start()/stop()/can_tick()/tick()/spawn_entity()/despawn_entity() to simulation_server_stub.gd
- Fix get_world_3d() → get_tree().root.world_3d in weapon_server.gd Node context
- Fix var data := → var data = for untyped WeaponDefinitions.get_weapon() returns
- Clean export cache and verify server starts with zero parse errors
- Changed player.gd from extending FPSCharacterController (via path) to
extending CharacterBody3D directly, avoiding headless class resolution
error for client/ scripts.
- Removed super._ready() and _set_local_player() calls (FPSCharacterController
specifics not available in CharacterBody3D).
- Replaced super._rollback_tick() with direct simulation guard.
- Removed reset_pose() call (FPSCharacterController-specific).
- All FPSCharacterController features remain in client-side player_netfox.gd
which extends player.gd.
- Verified: 0 SCRIPT ERRORs in headless mode.
- Fixed double-emission bug in network_manager.gd: ENet signal connections
(peer_connected/peer_disconnected) are now only connected when netfox
NetworkEvents is NOT available, preventing duplicate player_connected/
player_disconnected signal emissions.
- Fixed stop() to only disconnect ENet signals when they were actually
connected (mirrors the conditional connection in start_server()).
- Fixed headless parse error in player_net_input.gd: replaced direct
NetworkTime.before_tick_loop reference with Engine.get_singleton()
call, avoiding unresolved type identifier in headless mode.
- netfox_bootstrap.gd added as minimal autoload placeholder.
- Dual-path architecture: netfox NetworkEvents when available (editor),
ENet fallback when netfox unavailable (headless server).
- Broadcast RPCs (spawn, round state, scores) kept as-is since they
work identically through Godot's MultiplayerAPI in both paths.
- Renamed inner class Entity→StubEntity (Godot has native Entity class)
- Added await ServerConfig.config_loaded before reading map_list in server_main.gd
- Config loads via call_deferred, so _ready() must wait for signal
- Replaced TeamData.Team type hints with int in all scripts
- Added explicit preloads for headless mode class resolution
- Created stub LagCompensation and DamageProcessor scripts
- Fixed PluginManager, SpawnManager, EconomyManager, BuyZone,
RoundManager, BuyMenuHandler, BombObjective class_name references
- Updated server config to match ServerConfig.gd format
Gray screen root cause: server scripts failed to parse in headless
mode due to Godot 4 class_name loading order (Resource after Node),
leaving server_main.gd non-functional — accepted connections but
never spawned players.
Replaces the overhead box-mesh view with a full FPS character:
- mouse look, WASD movement, jump (Space), crouch (C), sprint (Shift), lean (Q/E)
- 6 weapons (LMB shoot, R reload, scroll switch, F melee, G drop)
- Crosshair HUD, ammo counter, sprint bar
- Client sends position to server at 20Hz for multiplayer visibility
- Server broadcasts positions to all peers
- Remote players shown as boxes (placeholder)
source: https://godotengine.org/asset-library/asset/2652 (MIT license)
client_main.gd: when server spawns our player, create a player node with authority
- Camera follows local player from 3/4 overhead view
- player.gd WASD input sends position to server via RPC
- Server validates and broadcasts to all peers
NEW: server/scripts/combat/lag_compensation.gd
- 128-entry ring buffer of player positions per server tick
- rewind_and_raycast(tick, origin, direction, range, exclude):
saves current positions, rewinds to tick-time positions,
performs PhysicsDirectSpaceState3D.intersect_ray(), restores
- Falls back to normal raycast if history missing for tick
- shot_processed signal
NEW: scripts/combat/damage_processor.gd
- Processes WeaponServer hit results: applies damage, tracks kills
- Optional distance-based damage falloff
- player_damaged / player_killed signals
- Health and kill-count queries
MODIFY: server/scripts/weapons/weapon_server.gd
- fire() now takes tick parameter: fire(tick, player_id, ...)
- _perform_hitscan() uses LagCompensation.rewind_and_raycast()
when lag_compensation reference is set and tick >= 0
- Non-compensated fallback path preserved
MODIFY: server/scripts/game_server.gd
- Adds _current_tick counter, incremented each simulation tick
- Creates WeaponServer + LagCompensation + DamageProcessor as children
- record_tick() called before each tick's simulation
- register/unregister_player_node() for player lifecycle
- _on_player_killed() relays kill events via player_damaged signal
MODIFY: scripts/network/server_main.gd
- _spawn_player() registers node with GameServer.register_player_node()
- _despawn_player() unregisters via unregister_player_node()
- team_data.gd: Team enum (SPECTATOR/CT/Terrorist), constants for names,
colors, starting money, and spawn groups; TeamData resource class
- team_manager.gd: Player-to-team assignment, auto-balancing, score
tracking; round-independent (persists across rounds)
- spawn_manager.gd: Scans scene for Marker3D nodes in spawn_points_ct
and spawn_points_t groups; selects valid spawns (farthest from enemies
with proximity check, fallback to first available)
- buy_zone.gd: Area3D-based trigger zone with team filtering, player
enter/exit tracking and signals
- test_range.tscn: Added 2 CT spawn markers and 2 T spawn markers with
appropriate groups
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.
- WeaponData resource class with configurable weapon properties and
pre-configured constants for rifle, pistol, shotgun, and SMG
- WeaponDefinitions singleton/static registry with WEAPONS dictionary
- WeaponServer node for server-authoritative weapon logic including
fire rate cooldowns, ammo tracking, reload state machine, and
multi-pellet hit-scan raycasting
- Extended WeaponManager with weapon inventory array, weapon switching,
per-weapon state persistence, fire_animation_triggered signal