# 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")` — no `class_name` refs **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()` uses `load()` + `new()` from path — no class_name refs - ✅ Duck-typed netfox checks via `has_method()`, `Engine.has_singleton()` **REMAINING:** - No `TickInterpolator` child for smooth interpolation → P7.5 --- ### 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 `BaseNetInput` headless 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/despawned` for visualization **REMAINING:** - Creates full Player nodes with RollbackSynchronizer for each remote player (✅ correct pattern) - May want TickInterpolator on remote player instances later (P7.5) --- ### 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 `NetworkTime` singleton for freeze tick tracking (line 84, 123) **REMAINING:** - Could be replaced by a `RoundState` autoload with `StateSynchronizer` for 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_process` gathers 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 | ❌ P7.5 | | 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 | ❌ P7.5 | | 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) 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 | 0 | 0 | Add TickInterpolator P7.5 | | 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 |