netfox icon indicating copy to clipboard operation
netfox copied to clipboard

Input is predicted by server and clients, but the correct/received input isn't used

Open TheYellowArchitect opened this issue 5 months ago • 0 comments

It is safe to assume the server does a rollback, because it receives inputs from clients and rolls back to the tick of the received input up to the current state. The below is the prediction/extrapolation the server does netfox-input-prediction and it seems input prediction/extrapolation is done not only by server but clients too. input-prediction-for-clients-too

This conflicts an old claim that there is no input prediction and the server just waits for inputs: rollback-server-doesnt-extrapolate-or-rollback

The weird thing is that once the correct input arrives, there seems to be no rollback from that input up to latest tick, so the correct input doesn't matter. The new input once it arrives, should trigger a rollback_tick and apply itself, but it doesn't. rollback_tick runs only once per frame. To prove this, put this code in brawler-controller.gd:

var debug_tick_directions: Dictionary = {} #<int, Vector3>

...

func _rollback_tick(delta, tick, is_fresh)
...
    # Place the below under the direction section
    if (multiplayer.is_server() && player_id != 1):
	if (debug_tick_directions.has(tick) == false):
		debug_tick_directions[tick] = direction
	else:
		print("Server: NetworkTime.tick (%s) Rollback Correction for Avatar %s at tick %s directions are: %v | %v" % [NetworkTime.tick, player_id, tick, direction, debug_tick_directions[tick]])

I cannot get the above to ever print with 3 players and 200ms delay.


The rollback only happens for the latest NetworkTime.tick because of this line, which applies to server and clients regardless of authority:

func _after_loop():
	_earliest_input = NetworkTime.tick

and on submit_input rpc function, it goes down for non-authorities (those who receive input RPC):

earliest_input = min(_earliest_input, tick_received)

Why focus on the variable _earliest_input? Because it is used to determine which tick a rollback starts server-rollback-input

## Submit the resimulation start tick for the current loop.
##
## This is used to determine the resimulation range during each loop.
func notify_resimulation_start(tick: int):
	_resimulate_from_tick = min(_resimulate_from_tick, tick)

rollback

rollback-spam

From my understanding, assuming all clients take exactly 3 ticks to send an input to the server: At tick 100 (latest), the server has the clients' inputs of tick 97 and the input ticks of 98, 99, 100, are predictions/extrapolations using the same input of tick 97. Let's say a client changes input at tick 99, and this input arrives to the server at tick 102. The server does replace its _inputs[] but it doesn't rollback to tick 99 up to 102. It rolls back to 101 (see after_loop function above) if not just proceeds with 102.

This would mean that the server, whenever it sends the state of any tick, e.g. tick 100, it isn't the true/final state, as it contains predicted/extrapolated inputs. And by extension, the server will rollback many ticks per frame because there is no input delay, which is not the case currently. Or have I missed a good part of the puzzle?


Related to all of the above "rollback doesn't work as expected" is that the state is RPC submitted at record_tick which happens within a rollback. So multiple states can be RPC submitted in a single tick if multiple rollbacks happen. This ofc doesn't currently happen, because rollback_tick doesn't run more than once per tick (as shown in the code example with debug_ticks_direction: Dictionary). So the state submission must be moved to after_loop in order to send only the latest state. Unless a new rollback is to also send corrected states? (I would suggest against this, as it kills the network because of bandwidth)


Being unable to debug further without spending days, I just want to understand how input prediction works in netfox. Forest brawl works, no one can deny that. But seeing how the bombs are directly injected into the state and are unaffected by rollback, and this general weirdness I discovered above, I feel something is fundamentally wrong, despite it working smoothly even at 200ms in a demo. I want to understand netfox fundamentally so as to expand it for physics or items.


P.S. I tried making a commit which closes this issue, by:

  1. Updating the value of _earliest_input for input authority, to equal the latest NetworkTime.tick
  2. Updating the value of _earliest_input for non-input authorities, to equal the latest _inputs[] index received.
  3. Moved the state submission so it doesn't trigger per rollback tick, but after each rollback ends.
  4. Debug print for _rollback_tick to confirm it triggers multiple times per rollback (it does)

But it bugs a lot, and idk why :cry:

TheYellowArchitect avatar Sep 21 '24 19:09 TheYellowArchitect