# 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](https://github.com/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_dict` → `BaseNetInput` 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 - [netfox docs](https://foxssake.github.io/netfox/) - [netfox GitHub](https://github.com/foxssake/netfox) - [naxIO/netfox-cs-sample — CS 1.6 reference](https://github.com/naxIO/netfox-cs-sample) - [godot-jolt GitHub](https://github.com/godot-jolt/godot-jolt) - [VANTIX tactical shooter reference (C#, architecture only)](https://github.com/justin-bobr/vantix)