miwarnec / uMMORPG_CE

4 stars 0 forks source link

Move rejected when walking bumping to stuff #33

Open delgelato opened 3 years ago

delgelato commented 3 years ago

So it seems there's a desync in how server handles the move packet coming from clients, when bumping to stuff. Maybe a split second difference between when exactly the unit stops "walking against" the wall/collider/stuff. Client thinks that he's already free from getting bumped, thus keeps walking, while the result of controller.Move in server, which is the value of controller.velocity right after, is not a value that's valid for a non-bumping unit, thus the server gets position value (perhaps on the next frame) much larger than the valid speed

This happens in the non-combined move, but sometimes even in the combined move too. In the non-combined, worst invalid value was like, 9.8 / 6 (that's almost 200% the original valid speed, which is 5)

move rejected walk bump

Tested on Unity 2019.4.13f1 Using uMMORPG CE 2.20

Lines to check: PCCM.FixedUpdate 1160 if (!WasValidSpeed(velocity, previousState, state, false))

The other related issue is, (or, a critic), i dont think Warp()ing the player when they go over the speed limit is a good thing. Quoting my convo with Chismar on discord:

Chismar: If you happen to cross it and it doesn't happen too often or for a prolonged period, server should sync to your position,rather than warping you. delgelato: Thanks. This is a fair suggestion too. I probably should have a "low" and "high" threshold. If the incoming speed is over the "low" threshold, then do a correction / resync rather than warping and discarding pendingMoves. If past the high threshold, even if for a single packet, then do warp

Also an alternative way that i'm currently trying is this:

The most "brute force" solution i can think of right now is:

  1. So, try to avoid the clearing of remaining pendingMove
  2. When the Warp happens and i still apply the ClampMag'd delta move, the "remainder" of the illegal move is used to offset all current pendingMove
  3. Client side, they should be Warp'd by the ClampMag'd too. But he'll still send a few packets before the Warp comes in
  4. So server will still have to apply this offset to the next incoming move packets, until the first packet after the client gets the Warp

So basically, i dont think Warp() is the most correct thing to do for server invalidation, because pendingMove is cleared and that's potentially 3 pending moves in the server, PLUS whatever movement the client's already doing on their end before they get the Warp (considering RTT, maybe 6-7 frames of move gone) But of course before going to the usage of Warp, first is the issue of why this is happening. The slight de-sync when walking into obstacles is the cause, and i'm still not sure exactly what's the best way to handle this The detailed "cause" of this is mentioned in the beginning of the post, but i'm only like 80-90% sure that this is exactly what's happening

Cheers

delgelato commented 3 years ago

Another alternative i'm wondering, actually

So this desync is caused by

  1. the fact that the move position are a liitle bit different on client and server
  2. controller.Move is non-deterministic(?). Or maybe it is, but the fact is server re-simulating with .Move, using position packets from client, is causing the desync. But then again, what's deterministic in an online, non input-based simulation..?
  3. Simulation is done in client for their own chara, then move packet is sent. Then simulation is done again in server for the real official pos and speed check. So, just from this fact, there's no way anything is deterministic

So based on these points, i wonder if.... server should just NOT re-simulate .Move, and instead lerp between the move packets.

Bcoz really, server being drunk thinking move packet has invalid speed is only bcoz of its own failure to resimulate as how it was simulated in client

I did check the incoming packets by the time it's received (instead of when it's processed), and none of them are invalid..

So yeah, why server have to resimulate, right?

delgelato commented 3 years ago

Actually it does need to.. bcoz in the case of lag, client's environment might not be up to date. If server complies without questioning client's Cc.Move, then possibly client will be walking through a newly spawned obstacle

Another theory is: The other reason the simulation is non deterministic is bcoz client sends pos after Cc.Move(input*speed) Whereas server simulates with Cc.Move(movePacket.pos - current transform.pos)

Proof: Transform.position result of server after doing server's CC.Move is mostly different than the received movePacket.position I added the following: if(transform.position != next.position) { Vector3 dif = next.position - transform.position; Debug.Log("Dif after server Cc.Move: " + dif + " mag: " + dif.magnitude); }

And the result (and a move rejected at the end of it): image

So as i suspected, the position difference is accumulating, and when the difference of (nextPacket.position - transform.position) in server is too large, move rejected is called

edit: It's not accumulating, because next frame's Cc.Move will simply just start at whatever current transform.position towards nextPacket.position. And as long as it's less than 120% (the default tolerance), then the server will "correct" itself

delgelato commented 3 years ago

A better debug test if(transform.position != next.position) { Vector3 dif = next.position - transform.position; Vector2 flatMove = new Vector2(move.x, move.z) / Time.fixedDeltaTime; Vector2 flatVelo = new Vector2(velocity.x, velocity.z); Debug.Log(NetworkTime.time + " Dif after server Cc.Move: " + dif + " mag: " + dif.magnitude + "\nmove: " + flatMove.magnitude + " velo: " + flatVelo.magnitude); } And also adding NetworkTime.time in the move rejected message, to find that the desync pos is never at the same frame as the move rejected Move rejected happens only AFTER the result of previous frame's Cc.Move in server was somehow slower/more obstructed than it should. This frame's offending Cc.Move landed 100% exactly at nextPacket.position(hence it doesn't call the first Debug.Log), except that it started at a more behind starting transform.position caused by the previous frame's slowness. Maybe this is when, in the server, the move was right after the chara was "free" from the obstruction of the wall (unobstructed straight Cc.Move)

image