Files
tactical-shooter/docs/migration-netfox-plan.md
T
shawn e7299b17e9 Phase 7: netfox + godot-jolt stack upgrade
Stack installed:
- netfox v1.35.3 (core + extras + noray + internals)
- godot-jolt v0.16.0-stable

Architecture:
- Server: ENet transport (works headless, no netfox deps)
- Client/Editor: netfox rollback (RollbackSynchronizer, TickInterpolator)

New/modified:
- docs/migration-netfox-plan.md — migration architecture
- scripts/network/network_manager.gd — netfox-aware ENet fallback
- scripts/network/player.gd — clean base player
- client/characters/player_netfox.gd — rollback player w/ WeaponManager
- client/characters/input/player_net_input.gd — BaseNetInput subclass
- client/characters/character/fps_character_controller.gd — netfox input feed
- client/weapons/ — weapon data, registry, TacticalWeaponHitscan, WeaponManager
- client/scripts/round_replicator.gd — client-side round state bridge
- server/scripts/round_manager.gd — improved state machine
- server/scripts/plugin_api/plugin_manager.gd — refined plugin system
- config: enemy_tag, ally_tag for meatball targeting

Removed: old C++ SimulationServer GDExtension (replaced by netfox rollback)
2026-07-02 17:39:22 -04:00

11 KiB

Phase 7: Stack Upgrade — netfox + godot-jolt Migration Plan

Overview

Migrate from raw ENetMultiplayerPeer to netfox (foxssake/netfox, 1020 MIT) for rollback netcode, client prediction, and lag compensation. Add godot-jolt (godot-jolt/godot-jolt, 2535 MIT) for deterministic 128Hz physics.

Reference architecture: naxIO/netfox-cs-sample — CS 1.6 clone using netfox rollback patterns (teams, economy, bomb, 6 weapons).


Current Architecture (Pre-Migration)

┌───────────────────────────────────────────────────┐
│                   project.godot                   │
│   Autoloads: NetworkManager, ServerConfig,         │
│              RoundManager, PluginManager           │
│   Physics: 128Hz (Godot built-in)                  │
│   Rendering: Vulkan Forward+                       │
│   Main Scene: server_main.tscn                     │
└─────────────────────┬─────────────────────────────┘
                      │
        ┌─────────────┴──────────────┐
        ▼                            ▼
┌─────────────────┐    ┌──────────────────────────┐
│ network_manager  │    │    SimulationServer       │
│ .gd (ENet)       │    │    (GDExtension C++)      │
│                  │    │                           │
│ - ENetMultiplayer│    │ - entity spawning         │
│ - 3 channels     │    │ - apply_input()           │
│ - RPC broadcasts │    │ - fire_weapon()           │
│ - signals:       │    │ - get_entity()            │
│   player_*       │    └──────────┬────────────────┘
│   round_*        │               │
│   score_*        │    ┌──────────▼────────────────┐
└─────────────────┘    │ fps_character_controller   │
                        │ .gd (CharacterBody3D)      │
┌─────────────────┐    │                           │
│ round_manager    │    │ - Input → SimulationServer│
│ .gd (state      │    │ - move_local() fallback   │
│ machine)        │    │ - crouch, sprint, jump    │
│                 │    └────────────────────────────┘
│ - signals:      │
│   round_*,score │    ┌──────────────────────────┐
│ - _process tick │    │   Player (Node3D)         │
└─────────────────┘    │   - basic BoxMesh         │
                        │   - movement_speed param │
                        └──────────────────────────┘

Key issue: ENet has no rollback, no client prediction, no lag compensation. Players on slower connections will feel inconsistent hit registration.


Target Architecture (Post-Migration)

┌────────────────────────────────────────────────────┐
│                     project.godot                   │
│   Autoloads: (same, NetworkManager rewired)         │
│   Physics: Jolt @ 128Hz (godot-jolt GDExtension)    │
│   Rendering: Vulkan Forward+                        │
│   Addons: netfox, netfox.noray, netfox.extras       │
└─────────────────────┬──────────────────────────────┘
                      │
        ┌─────────────┴──────────────┐
        ▼                            ▼
┌──────────────────────┐  ┌──────────────────────────┐
│   netfox core         │  │   netfox.noray           │
│   (NetworkEvents,     │  │   (NAT punchthrough)     │
│    RollbackSynch.,    │  └──────────────────────────┘
│    TickInterpolator)  │
└──────────┬───────────┘  ┌──────────────────────────┐
           │              │   netfox.extras           │
           ▼              │   (BaseNetInput,          │
┌──────────────────────┐  │    RewindableStateMachine)│
│  NetworkManager.gd    │  └──────────────────────────┘
│  (wrapper over netfox │
│   NetworkEvents +     │  ┌──────────────────────────┐
│   MultiplayerSynch.)  │  │   godot-jolt             │
└──────────┬───────────┘  │   (deterministic physics) │
           │              └──────────────────────────┘
           ▼
┌────────────────────────────────────────────────────┐
│   RollbackSynchronizer (player state)               │
│   - _rollback_tick() → deterministic state sync     │
│   - TickInterpolator → smooth visuals               │
│   - Client prediction: apply input locally,         │
│     reconcile from server snapshot                  │
└────────────────────────────────────────────────────┘
           │
           ▼
┌────────────────────────────────────────────────────┐
│   RewindableAction (hitscan weapons)               │
│   - Server-authoritative hit detection             │
│   - Server rewinds physics to match client timing  │
│   - Per-limb damage: head/chest/waist/legs         │
└────────────────────────────────────────────────────┘

Migration Phases (Task Breakdown)

P7.1 Research — netfox migration plan [THIS DOCUMENT]

P7.2 Install — netfox addon

  1. Download latest release from GitHub
  2. Extract addons/netfox/, addons/netfox.noray/, addons/netfox.extras/, addons/netfox.internals/
  3. Enable in Project Settings → Plugins
  4. Verify godot --headless --check-only compiles

P7.3 Install — godot-jolt physics

  1. Download from releases or Asset Library
  2. Copy addons/godot-jolt/ into project
  3. Enable plugin, configure physics → 128Hz
  4. Verify headless compilation

P7.4 Port — NetworkManager.gd

Source: scripts/network/network_manager.gd (214 lines, ENet wrapper) Changes:

  • Replace ENetMultiplayerPeer → netfox NetworkEvents
  • start_server() → use netfox's NetworkEvents server flow
  • join_server() → use netfox's NetworkEvents client flow
  • Player replication signals → netfox MultiplayerSynchronizer or RollbackSynchronizer
  • Round state RPCs (broadcast_round_*) → netfox events
  • Remove channel system (netfox handles prioritisation internally)

Equivalent vs New:

Current ENet netfox Equivalent
ENetMultiplayerPeer NetworkEvents (connect/disconnect)
@rpc("authority", "call_local", "reliable") MultiplayerSynchronizer / RollbackSynchronizer
broadcast_spawn_player RPC NetworkEvents + spawn hook
broadcast_round_start RPC NetworkEvents event
3 channels (input/events/telemetry) Automatic (netfox manages ordering)
_process polling TickInterpolator / _rollback_tick()

P7.5 Build — Client Prediction + Rollback

Key netfox patterns:

  • RollbackSynchronizer with _rollback_tick() for deterministic state sync
  • BaseNetInput with _gather() for input collection
  • TickInterpolator for smooth visual interpolation between ticks
  • Client-side prediction: apply input immediately, reconcile when server snapshot arrives

P7.6 Build — Rollback Hitscan + Lag Compensation

Key netfox patterns:

  • RewindableAction for rollback-safe weapon firing
  • Server rewinds entity positions to match client's input timestamp
  • Hit detection inside rollback loop
  • Per-limb damage (head 2x, chest 1.0x, waist 0.75x, legs 0.5x)

P7.7 Port — RoundManager to netfox

Source: server/scripts/round_manager.gd (647 lines) Changes:

  • Round state signals (round_started, round_ended, etc.) → netfox events
  • player_died etc. → netfox replicated state or RPCs
  • get_round_state_snapshot() → netfox synchronized property

P7.8 Port — FPS Controller to BaseNetInput

Source: client/characters/character/fps_character_controller.gd (401 lines) Changes:

  • Replace manual _input_dictBaseNetInput subclass
  • Use _gather() to collect input per tick
  • Movement logic becomes server-authoritative (no more move_local fallback except singleplayer)
  • Keep mouse look, crouch transition, sprint toggle — adapt for netfox tick

P7.9 Test — Headless Export + Deployment

  • godot --headless --export-debug "Linux/X11" build/server/tactical-shooter.x86_64
  • Server starts clean, clients connect, rollback works across LAN
  • Push to Gitea, update systemd service, verify on server

Reference Files

File Lines Key Role
scripts/network/network_manager.gd 214 ENet transport wrapper, RPC broadcasts
scripts/network/player.gd ~60 Basic player node
scripts/network/server_main.gd ~150 Server entry point
scripts/network/client_main.gd ~120 Client entry point
client/characters/character/fps_character_controller.gd 401 FPS movement + input to SimulationServer
client/characters/character/fps_camera.gd ~80 Camera look
server/scripts/round_manager.gd 647 Round state machine
server/scripts/plugin_api/plugin_manager.gd 573 Plugin system
scenes/server/server_main.tscn 14 Server scene
scenes/client/client_main.tscn 14 Client scene
scenes/player.tscn 15 Player prefab
project.godot 33 Autoloads, physics settings

Resources