Files
tactical-shooter/docs/netfox-migration-plan.md
T
shawn e70ce76207 P7.9: fix headless export — resolve Resource class_name ordering + duplicate RoundManager + simulation stub API
- 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
2026-07-02 17:57:09 -04:00

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

Passesgodot --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:

  • TickInterpolator child for smooth interpolation (dynamically created in _setup_tick_interpolator())
  • TickInterpolator on 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 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)
  • TickInterpolator on 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 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
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