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)
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
- Download latest release from GitHub
- Extract addons/netfox/, addons/netfox.noray/, addons/netfox.extras/, addons/netfox.internals/
- Enable in Project Settings → Plugins
- Verify
godot --headless --check-onlycompiles
P7.3 Install — godot-jolt physics
- Download from releases or Asset Library
- Copy addons/godot-jolt/ into project
- Enable plugin, configure physics → 128Hz
- Verify headless compilation
P7.4 Port — NetworkManager.gd
Source: scripts/network/network_manager.gd (214 lines, ENet wrapper)
Changes:
- Replace
ENetMultiplayerPeer→ netfoxNetworkEvents start_server()→ use netfox's NetworkEvents server flowjoin_server()→ use netfox's NetworkEvents client flow- Player replication signals → netfox
MultiplayerSynchronizerorRollbackSynchronizer - 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:
RollbackSynchronizerwith_rollback_tick()for deterministic state syncBaseNetInputwith_gather()for input collectionTickInterpolatorfor 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:
RewindableActionfor 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_diedetc. → netfox replicated state or RPCsget_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→BaseNetInputsubclass - Use
_gather()to collect input per tick - Movement logic becomes server-authoritative (no more
move_localfallback 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 |