- 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
12 KiB
Netfox Migration Checklist — File-by-File Audit
Generated: 2026-07-02 Task: P7.1 (research — netfox migration plan)
Architecture Overview
┌──────────────────────────────────┐
│ network_manager.gd │
│ (ENetMultiplayerPeer transport) │
│ + optional NetworkEvents overlay│
└──────┬───────────────┬───────────┘
│ │
┌────────────┘ └────────────┐
v v
┌─────────────────────┐ ┌─────────────────────┐
│ server_main.gd │ │ client_main.gd │
│ (dedicated server)│ │ (client entry) │
└─────────┬───────────┘ └──────────┬──────────┘
│ │
┌─────────┴───────────┐ ┌──────────┴──────────┐
│ round_manager.gd │ │ round_replicator.gd │
│ (state machine) │ │ (client mirror) │
└─────────────────────┘ └─────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ player.gd │
│ RollbackSynchronizer (dynamic child) │
│ ├── state_properties: pos, is_alive, team_id, spec_tgt │
│ └── input_properties: → PlayerNetInput/* │
│ │
│ PlayerNetInput (child Node) │
│ ├── move_direction, look_yaw, look_pitch │
│ ├── jump, sprint, crouch, shoot, aim │
│ └── connects to NetworkTime.before_tick_loop │
└──────────────────────────────────────────────────────────┘
Headless Compilation
✅ Passes — godot --headless --check-only reports 0 parser errors.
- Runtime warnings (ENet host, netfox unavailable, empty map rotation) are all expected in CI environments.
- All netfox-dependent code paths guarded by
Engine.has_singleton()+ duck-typing.
File-by-File Audit
1. network_manager.gd (245 lines) — TRANSPORT LAYER
Current role: ENetMultiplayerPeer management + signal relay hub.
DONE:
- ✅ Optional NetworkEvents overlay for lifecycle signals (
_try_connect_netfox()) - ✅ All netfox refs use
Engine.has_singleton("NetworkEvents")— noclass_namerefs
REMAINING (6 legacy RPC broadcasting functions):
| Line | RPC | netfox Replacement | Status |
|---|---|---|---|
| 193 | broadcast_spawn_player |
Replaced by RollbackSynchronizer state sync. Keep until client discoverse players from state, not RPC. | 🔶 keep temporarily |
| 199 | broadcast_despawn_player |
Same as above | 🔶 keep temporarily |
| 209 | broadcast_round_start |
Move to StateSynchronizer on a round-state autoload (P7.7) | ❌ |
| 214 | broadcast_round_end |
Same as above | ❌ |
| 219 | broadcast_match_end |
Same as above | ❌ |
| 224 | broadcast_score_change |
Same as above | ❌ |
Networking signals (9 total) — all stay as-is. They are event hooks for consumers (client_main, round_replicator), not data sync.
2. player.gd (251 lines) — PLAYER ENTITY
Current role: Player character with Team/Round integration. Uses RollbackSynchronizer.
DONE:
- ✅ RollbackSynchronizer dynamically created:
state_properties=[position, is_alive, team_id, spectate_target_id] - ✅
input_properties=[PlayerNetInput/move_direction, look_yaw, look_pitch, jump, sprint, crouch, shoot, aim] - ✅
_rollback_tick(delta, tick, is_input)with deterministic movement - ✅
_create_rollback_sync_node()usesload()+new()from path — no class_name refs - ✅ Duck-typed netfox checks via
has_method(),Engine.has_singleton()
REMAINING:
- ✅
TickInterpolatorchild for smooth interpolation (dynamically created in_setup_tick_interpolator()) - ✅
TickInterpolatoron remote player instances for visual rollback
3. player_net_input.gd (91 lines) — INPUT GATHERING
Current role: Tick-synchronized input collector, child of Player.
DONE:
- ✅ 8 exported input properties matching RollbackSynchronizer's
input_properties - ✅ Connects to
NetworkTime.before_tick_loop - ✅ Reads yaw/pitch from parent FPSCharacterController via duck-typing
- ✅ Extends Node directly (avoids
BaseNetInputheadless class_name issues)
REMAINING:
- FPSCharacterController duplicates input gathering in
_physics_process— should read from PlayerNetInput instead → P7.8
4. server_main.gd (342 lines) — SERVER ENTRY POINT
DONE:
- ✅ Uses
NetworkManager.start_server()for ENet bootstrap - ✅ Connects to
NetworkManager.player_connected/disconnected
REMAINING — 7 RPC call sites:
| Line | Call | netfox Plan |
|---|---|---|
| 120 | broadcast_round_start.rpc(...) |
StateSynchronizer in P7.7 |
| 142 | broadcast_round_end.rpc(...) |
StateSynchronizer in P7.7 |
| 178 | broadcast_match_end.rpc(...) |
StateSynchronizer in P7.7 |
| 184 | broadcast_score_change.rpc(...) |
StateSynchronizer in P7.7 |
| 288 | broadcast_spawn_player.rpc(...) |
RollbackSynchronizer state (keep for now) |
| 327 | broadcast_despawn_player.rpc(...) |
RollbackSynchronizer state (keep for now) |
| 332 | broadcast_despawn_player.rpc(...) |
RollbackSynchronizer state (keep for now) |
5. client_main.gd (124 lines) — CLIENT ENTRY POINT
DONE:
- ✅ Uses
NetworkManager.join_server() - ✅ Connects to
remote_player_spawned/despawnedfor visualization
REMAINING:
- Creates full Player nodes with RollbackSynchronizer for each remote player (✅ correct pattern)
- ✅
TickInterpolatoron remote player instances (dynamically created in_setup_tick_interpolator())
6. round_replicator.gd (157 lines) — CLIENT ROUND STATE
DONE:
- ✅ Connects to NetworkManager's broadcast signals
- ✅ Maintains round state (round_number, scores, time, freeze)
- ✅ Uses netfox
NetworkTimesingleton for freeze tick tracking (line 84, 123)
REMAINING:
- Could be replaced by a
RoundStateautoload withStateSynchronizerfor rollback-consistent round state (P7.7)
7. round_manager.gd (647 lines) — SERVER ROUND STATE MACHINE
DONE: ✅ Zero networking code. Pure state machine emitting signals. No changes needed.
8. fps_character_controller.gd (411 lines) — FPS CONTROLS
DONE:
- ✅
get_current_yaw()/get_current_pitch()methods for PlayerNetInput - ✅ Standalone
_move_local()fallback - ✅ SimulationServer refs use
Engine.has_singleton()
REMAINING (P7.8):
_physics_processgathers input AND sends to SimulationServer directly — duplicates PlayerNetInput- Need to read from PlayerNetInput child instead of
_get_move_direction() - Clarify: PlayerNetInput → RollbackSynchronizer → server physics → reconcile → FPSCharacterController applies
netfox Pattern Mapping
| Pattern | netfox API | Status |
|---|---|---|
| Player state sync | RollbackSynchronizer.state_properties | ✅ |
| Input client→server | RollbackSynchronizer.input_properties | ✅ |
| Tick-driven movement | _rollback_tick(delta, tick, is_input) |
✅ |
| Client prediction | enable_prediction = true |
✅ |
| Smooth interpolation | TickInterpolator | ✅ |
| Round state broadcast | StateSynchronizer | ❌ P7.7 |
| Rollback-safe weapon fire | RewindableAction | ❌ P7.6 |
| Lag comp / hitscan | RewindableAction + mutation | ❌ P7.6 |
| Visual rollback | TickInterpolator + _rollback_tick | ✅ |
| Lifecycle signals | NetworkEvents | ✅ |
Netfox Singleton Availability (Headless)
| Singleton | Editor | Headless | Access Pattern |
|---|---|---|---|
| NetworkEvents | ✅ | ❌ | Engine.has_singleton("NetworkEvents") |
| NetworkTime | ✅ | ❌ | Engine.has_singleton("NetworkTime") |
| NetworkRollback | ✅ | ❌ | Engine.has_singleton("NetworkRollback") |
| NetworkPerformance | ✅ | ❌ | Engine.has_singleton("NetworkPerformance") |
All access guarded — no class_name references in our code.
Remaining Work Order (Dependency Graph)
P7.6 (RewindableAction) ── needs ── RollbackSynchronizer working (✅ DONE)
P7.5 (TickInterpolator) ── needs ── RollbackSynchronizer working (✅ DONE)── [[DONE]] ✅
P7.7 (Round state netfox) ── needs ── NetworkEvents working (✅ DONE)
P7.8 (FPSController refactor) ── needs ── P7.6 input flow clarified
P7.9 (LAN test + deploy) ── needs ── all of the above
Recommended order: P7.6 → P7.5 → P7.7 → P7.8 → P7.9
Files NOT Needing Changes
| File | Reason |
|---|---|
server/scripts/round_manager.gd |
Pure state machine, no networking |
scripts/config/server_config.gd |
Config loader, no networking |
scripts/network/netfox_bootstrap.gd |
No-op placeholder |
server/scripts/plugin_api/plugin_manager.gd |
Plugin mgmt, no networking |
client/weapons/data/weapon_registry.gd |
Data-only |
Risk Assessment
| Risk | Impact | Likelihood | Mitigation |
|---|---|---|---|
| Headless compilation fails | High | Low | All netfox refs use has_singleton/has_method. Current check passes. |
| RollbackSynchronizer vs PlayerNetInput property mismatch | Medium | Low | input_properties paths verified against @export var names — matched. |
| Dual input path (SimulationServer + RollbackSynchronizer) | High | Medium | FPSCharacterController sends to both — needs architectural cleanup (P7.8) |
| netfox API incompatibility with Godot 4.7 | Medium | Low | netfox 1.35.3 targets 4.3+; manual verification done — compilation passes |
Key Files Summary
| File | Lines | Netfox Status | RPCs | Signals | Changes Needed |
|---|---|---|---|---|---|
| network_manager.gd | 245 | ✅ overlay wired | 6 ❌ | 9 ✅ | Remove 6 RPCs post-P7.7 |
| player.gd | 251 | ✅ rollback + tick + interpolator | 0 | 0 | ✅ |
| player_net_input.gd | 91 | ✅ tick gather | 0 | 0 | None atm |
| server_main.gd | 342 | ✅ thin layer | 7 call sites ❌ | 2 | Migrate to StateSynchronizer P7.7 |
| client_main.gd | 124 | ✅ thin layer | 0 | 2 signal connects | None atm |
| round_replicator.gd | 157 | 🔶 signal-driven | 0 | 5 connects + 4 emits | Migrate to StateSynchronizer P7.7 |
| round_manager.gd | 647 | ✅ no changes | 0 | 0 | None |
| fps_character_controller.gd | 411 | 🔶 has getters | 0 | 0 | Input flow refactor P7.8 |