Files
tactical-shooter/docs/netfox/tutorials/rollback-caveats.md
T
shawn b0c83af092 Fresh start: replace with naxIO/netfox-cs-sample foundation
Complete replacement of the tactical-shooter project with the
netfox-cs-sample (MIT) — a CS 1.6 inspired multiplayer FPS built
with Godot 4 and netfox.

## What's new
- Full CS-style gameplay: teams (T/CT), rounds, economy, buy menu
- 6 weapons: Knife, Glock, USP, AK-47, M4A1, AWP
- Bomb plant/defuse with 2 bombsites
- Flashbang & smoke grenades
- Proper netfox rollback netcode at 64 tick
- Network popup UI for host/join
- HUD, crosshair, round timer, scoreboard
- All netfox singletons registered as autoloads (works in exported builds)

## Architecture
- Listen-server (host from client, no dedicated server binary)
- Multiplayer-fps game lives at examples/multiplayer-fps/
- Netfox addons registered as autoloads for exported build compat
- Godot 4.7 with Forward+ renderer

## Removed
- Old headless-server architecture (client_main, server_main, player.gd, etc.)
- Custom netfox bootstrap with ENet fallback
- Old ChaffGames FPS template (2,420 lines, 844 KB)
- SimulationServer GDExtension stub
- Godot-jolt physics (netfox sample uses default Godot physics)
- Duplicate weapon_data.gd, anti_cheat.gd, round_manager.gd, etc.
- Server browser API Python venv (87 MB)
- test_range map and modular assets

## Preserved
- Git history
- Server config at config/default_server_config.cfg
- Windows export preset
- Build directory (gitignored)

Co-authored-by: naxIO <naxIO@users.noreply.github.com>
2026-07-02 20:55:20 -04:00

4.4 KiB

Rollback caveats

As with most things, rollback has some drawbacks along with its benefits.

CharacterBody velocity

Godot's move_and_slide() uses the velocity property, which is set in meters/second. The method assumes a delta time based on what kind of frame is being run. However, it is not aware of netfox's network ticks, which means that movement speed will be off.

To counteract this, multiply velocity with NetworkTime.physics_factor, which will adjust for the difference between Godot's assumed delta time and the delta time netfox is using.

If you don't want to lose your original velocity ( e.g. because it accumulates acceleration over time ), divide by the same property after using any built-in method. For example:

# Apply movement
velocity *= NetworkTime.physics_factor
move_and_slide()
velocity /= NetworkTime.physics_factor

CharacterBody on floor

CharacterBodies only update their is_on_floor() state only after a move_and_slide() call.

This means that during rollback, the position is updated, but the is_on_floor() state is not.

As a work-around, do a zero-velocity move before checking if the node is on the floor:

extends CharacterBody3D

func _rollback_tick(delta, tick, is_fresh):
	# Add the gravity.
	_force_update_is_on_floor()
	if not is_on_floor():
		velocity.y -= gravity * delta

  # ...

func _force_update_is_on_floor():
	var old_velocity = velocity
	velocity = Vector3.ZERO
	move_and_slide()
	velocity = old_velocity

Physics updates

Godot's physics system is updated only during _physics_process, while rollback updates the game state multiple times during a single frame.

Unfortunately, Godot does not support manually updating or stepping the physics system, at least at the time of writing. This means that:

  • Rollback and physics-based games ( RigidBodies ) don't work at the moment
  • Collision detection can work, but with workarounds

If there's a way to force an update for your given node type, it should work, i.e.

While kinematic nodes like CharacterBody3D can be used with rollback, physics queries can still cause issues (e.g. PhysicsDirectSpaceState3D.intersect_shape()). This is due to the lack of updates mentioned earlier. To work around this, run the following for each CollisionObject that has its position rolled back before each tick of the rollback loop:

# Works for both Jolt and GodotPhysics3D.
func _force_update_physics_transform():
  PhysicsServer3D.body_set_mode(get_rid(), PhysicsServer3D.BODY_MODE_STATIC)
  PhysicsServer3D.body_set_state(get_rid(), PhysicsServer3D.BODY_STATE_TRANSFORM, global_transform)
  PhysicsServer3D.body_set_mode(get_rid(), PhysicsServer3D.BODY_MODE_KINEMATIC)

The above forces an update by setting the object to static, updating its transform, and then setting it back to its original, kinematic state.

Note that the above code needs to run for any kinematic object that is to be detected by the query and is manipulated during rollback.

!!!tip The netfox.extras addon provides optional support for physics simulation with rollback. See Physics

State Machines

State machines don't usually expect to be updated multiple times in a single frame or be snapped back to a previous point in time. Be cautious of:

  • Safeguards that implement a cooldown to changes.
  • States based on values not updated in _rollback_tick.
  • Transitions that enforce a specific order to state changes.
  • Transitions that trigger on any state change.

The key concept to keep in mind is that netfox stores the configured states for each processed tick. When it rolls back everything is snapped back to that point in time and then played forward to the present in a single frame.

!!!tip The netfox.extras module provides an implementation of state machines compatible with rollback. See RewindableStateMachine