The API does not already take care of input delay (yet, at least - word going around saying they’re working on it). There’s a property that exists but doesn’t seem to do anything beyond sit at zero since the last time I’ve checked, and still cannot be interacted with. Regardless, playtesters from across the U.S. agree that changing the input latency according to the network conditions does help simulation consistency (specifically, with four players). There is weight to what I’m saying.
However, if you’re using stock humanoids and don’t really touch them at all, it really should be as simple as flipping the switch in the vast majority of cases.
If you’re making a simple game, maybe? This only applies in games that are completely hands off with the game’s state. There are very few situations in which developers are able to modify the client’s state in server authority that won’t get returned to sender unless their game is only changed by the server.
Input delay is a static value added to the frame your inputs are logged in the input buffer. This means that the client receives the input event at RAW_INPUT_FRAME but the side-effects come out at RAW_INPUT_FRAME + INPUT_DELAY_FRAMES only. All this does is give the client more time to see if an action that has a side-effect on the world actually affects them, allowing them to prioritize that hit instead of causing an invalid client state or a series of gross rollbacks until the client’s attacker has their framedata complete. Rebuilding what causes these side-effects to run allows you to mitigate the rollbacks by picking up where the server expects the client to be - which is a large piece, but without some form of input delay to have the client catch up, there will always be a very noticeable rollback on the initial “counter-hit” (in a fighting-game).
The Server Authority model article already touches on this. In this case, the “fuse effect” is not the “startup frames” in what would be a fighting game since that needs to be tracked (which therefore requires an immediate change from when you press the input). The fuse effect instead gets moved to the client’s inputs.
If you’re delaying every player’s input by the same arbitrary number of frames, then nothing is accomplished aside from making your game less responsive. I don’t know why you seem to think this will reduce mispredictions or prevent invalid client state.
I think you’re confusing two different netcode models (lockstep vs. rollback).
“Picking up where the server expects the client to be” is precisely what rollback is.
Not only are you being rude, you are also extremely wrong. If you want to argue over this, argue with the Roblox wiki that says word for word:
As another example, a gameplay mechanic where players can instantly trigger a large explosion by pressing an input will have more network artifacts than if the explosion is delayed after the input, as if by lighting a fuse. This puts the resimulation on the fuse effect instead of on the explosion effect which is a less noticeable network artifact.
I don’t know how else to explain to you why giving the client more frames to catch up to pending server state is necessary. At frame 0, the client gets the state via InputAction:GetState(). If the side effect of activating the InputAction also occurred at frame 0, you will fall into the exact gap that the Wiki talks about (“a gameplay mechanic where players can instantly trigger a large explosion by pressing an input will have more network artifacts”). The “large explosion,” in this case, is whatever special thing you do to the World and its components.
“Picking up where the server expects the client to be” is precisely what rollback is.
Does rollback also comprise of somehow forwarding every iota of script state to the client? Because that’s news to me.
But that isn’t what you’re talking about. You’re talking about literally delaying the processing of a player’s input. You explicitly said so:
Next:
Yes, if you write it all to attributes, which you should be doing in order to ensure mispredictions are detected correctly. You should be writing your script state to attributes and reading it back at the start of your BindToSimulation callback.
I don’t need to, I know how this networking model works because I’ve been using it for over a year already. I know all the techniques to reduce misprediction artifacts, but that’s something entirely different than what you were talking about. You spoke explicitly of delaying the actual processing of inputs, not delaying their effects. Those are two fundamentally different things.
Whatever you say, man. It’s almost as if you didn’t read the first paragraph of the second rule.
No, I didn’t. Your assumption does not mean my utterance.
To process an input means to first obtain the signal from the InputAction, the tool we are supposed to use for Server Authority. That sentence holds no weight. I do not tamper with what goes on between the keyboard and the InputAction itself (i.e., using Scriptable bindings). I assume that you meant the latter.
The Input Buffer gets stacked on top of the InputActions. The input delay is stacked on top of the input buffer. You can’t even apply input delay to an input action instance without doing something bizarre like manually firing the InputBinding through ContextActionService or UserInputService and scaffolding everything from far-below-the-ground-up. That is far more in-depth than simply saying “it’s just addition,” and as a result I would have clarified that if I were talking about that case. I do not understand where you got “delaying the actual processing of the inputs” in the way that I assume that you mean because I clarified several times that the game state can change before you can apply your world modifications. The server and client get the same input at the same time by virtue of how InputActions are replicated. I even specifically said “add the INPUT_DELAY_FRAMES value to the World Frame that every input sends.” Albeit, I memory-meshed the “how would you implement it” with actually executing simulation-independent effects and gating them behind client/server but the explanation itself is almost the exact same, with the server instead being the input buffer. The traditional input-jailing method of input delay simply gets turned into a layer that simulates the delay. Has a nice ring to it, probably because of how Server Authority is based around simulating.
I too, have been using Server Authority since when it was gated by a survey. We’ve all talked to the developers behind the scenes because that’s part of the beta and we know most about Server Authority. Please, stop reciting that. It doesn’t mean anything.
The only sensible way that you could have acquired the idea that I was talking about what goes on in-between the InputAction and the keyboard is by interpreting the paragraph that thoroughly explains why Input Delay is necessary in the context of Roblox despite me clearly putting the context in the abstract in the very first sentence plus directly including the nuance of peer-to-peer networking [“to receive the inputs your opponent sends (or the packets the server sends)”] and then converting to Roblox context for the (admittedly, terribly mis-explained at the time of writing) implementation details. Even by that assumption, it simply just does not add up. By continuously riding the idea that I said something that I absolutely did not and have clarified such several times against in the original article by virtue of context, you are only serving as a source of confusion rather than information. Please maintain your good faith, and stop.
My last bit of generosity ends here: when people say “input delay” in a rollback environment - and especially in a fighting game, that typically does not mean that the client holds the input hostage (like it would mean if you take the term literally). That is delay-based netcode, or in your terms, lockstep netcode. That is completely counter-intuitive to a prediction-based system. The client receives the raw input instantly, but the layer in which the actions the clients’ entity are fired through receives the signal to execute those actions INPUT_DELAY frames later. I will recite it again: that artificial difference in time which the client (and the server’s, and the other client’s) input buffer forwards to the processors of the simulated input buffer signal is exactly what the Developer Wiki calls the “fuse.”
I believe this response is comprehensive and leaves no room for misunderstanding. As such, I will not be reiterating any further.
Data is not streamed to the server and back. It’s a one-way street from the server to client with the only exception being inputs, which are a one-way street from client to server. Anyway, here’s the entirety of my simulation-critical script state serialized into attributes in the DOM:
I wrote a utility module in about an hour to do this for me for any arbitrary data type.
Yeah man, that’s my entire point, but go off I suppose.
From right here, actually:
But now I think I understand what you’re saying, although I don’t know if that makes it any better. If I’m understanding correctly now, what you’re doing precisely defeats the purpose of having rollback in the first place, which is to immediately display inputs to the player, with no delay and without knowing the other players’ input for that frame, and then correct it later if it was wrong. If you’re going to add an arbitrary delay to inputs being displayed to the player in order to give more time for authoritative state to come down from the server, why are you even bothering with rollback? Just build a lockstep system at that point. The “fuse effect” mentioned by the wiki doesn’t work if you don’t show anything to the player immediately – the entire point is that you DO, oftentimes by triggering some cosmetic-only side-effect.
The issue is that ping exists. It takes time for data from other players to arrive to your client. Adding a frame of delay gives the client 0.016ms of overhead to stabilize the rollback and avoid/reduce other players snapping around
If I might interject, I feel like there’s a bit of disconnect between expectations here. I suspect Rocky is looking at this from the perspective of an FPS player, whereas Noire is looking at it from a fighting game perspective. I do think the same server authority framework can work for both, but it goes without saying the expectations for things like input delay etc are quite different depending on purpose.
Most fighting games do have a little bit of lockstep delay even with rollback to keep things smooth - plus moves have startup/recovery which hides artefacts (as you know) whereas this is usually not the case with an FPS.
Even cases with some level of commonality e.g. shooting a projectile often require very different approaches.
I think you’re both right as far as I see, but talking from your own use cases.
Hello! I was wondering how you did your hitboxes/hit detection?
I’m currently facing an issue with hit detection being inaccurate from opponent’s pov. Let’s say, for example, the Attacker is facing away from the Opponent. Attacker initiates an Attack, and right before the Hit Detection method is called, turns around at the Opponent.
On Attacker’s screen, the Hit Detection detected Opponent and saves them into the table. Later in the code right after Hit Detection hit effects are applied.
However, on Opponent’s screen the moment Hit Detection method was called, Attacker wasn’t turned fully towards the opponent due to latency, and as such on their end their character wasn’t detected. No Hit effects are applied though Attributes that get changed during successful Hit Detection are corrected.
My main problem is that when Attributes are corrected and rollback is started, the resimulation seems to begin after Hit Detection was called, because of which the Hit Detection isn’t being recalled and Opponent still thinks they weren’t hit. Although since the Attacker’s character isn’t being predicted I suppose the Hit Detection would still fail even if Rollback happened before it since Attacker’s rotation would be still out dated.
I even added a LastHit attribute that Hit Detection sets the moment an Entity was detected for the hopes that Rollback will start at the time when LastHit was set, but it still doesn’t seem to work.
There’s also another question I have if you don’t mind. I suppose your Snapshots contain data that can’t be contained with attributes. When a rollback occurs, do you synchronize the last non-discarded Snapshot with Server’s, or do you just hope it’s already accurate and don’t bother with it?
Hi. I’ll need a bit more information than this. Are we talking about client-side hit effects? What does the server see? Are there any visualizations that depict the hitbox/player over time?
I’ll address what I can.
To be clear: the rollback step does not occur before the simulation step, otherwise you’d need to wait one more frame to handle a rollback that’s already detected and handled in the internal API. I’m saying this with the potentially-naïve assumption that Roblox wouldn’t implement rollback like that, with some vague memory of seeing the steps in the microprofiler (which you can also do yourself!).
Throwing this out there since I don’t know your codebase: could maybe be caused by having several BindToSimulation handlers? After some issues, I came to the conclusion that BindToSimulation hooks should be whole (no handling player/enemy status-effects, you just handle status-effects), hierarchic (hooks run and dispatch subroutines in a specific order), atomic (things shouldn’t be “passed around” from one hook to another), and mutex (should not address the same class of concerns that another hook does in any way, somewhat included by atomic). Which lines up almost one-to-one with what I saw when I dabbled in patching BBScript files for Arc-System Works games.
That being said, this sounds like deduplication that’s a bit too overzealous. Do not keep track of solely what entities were hit, also keep track of when the entities were hit. When you rollback, filter out the entities that were considered hit after the time the session was rollbacked, then re-evaluate your correct script state off of the new state. After you do that (all in your rollback step), then you can simulate on top of that (your BindToSimulation loop).
Again, no real information here, so this is just a shot in the dark.
I’m definitely misunderstanding here - why do you say that the attacker’s character isn’t being predicted? To be quite honest, I’m unsure how that even work. Never took the time to experiment. The attacker’s character should be predicted, otherwise the defender is going to feel like they’re in the past unless they’re next to the server.
No. Everything that’s inaccessible via attributes is instead referenced via some form of symbol and then resolved by the manager that keeps track of that item. This reduces the amount of computational load on weaker devices by a large margin, and opens up to further compression if you so choose for things like match replays (at the cost of compute).
It’s already guaranteed to be accurate, because both the client and the server not only works off of the same state, but they work to the same conclusion. If a snapshot is logged to the client and it doesn’t get culled after the prediction window, it’s a 100% guarantee that the simulated items within that snapshot are almost one-to-one with the server (the almost carrying the load of imprecisions so small that the simulation state is effectively unharmed).
The problem lies in correcting the state of things that have an imperative origin post-rollback in a way that makes sense from the state pre-rollback (basically, turning imperative things determinative). If the time in which a skill is predicted to be cast is variable (e.g., due to hitstop or getting hit at the edge of cast-time), then you have to conjure solutions for cases that make consistent changes (visual or otherwise) as a result of that cast while also making it look pretty to the user, so they don’t notice that a minor rollback occurred.
There’s also partial mispredictions as well, where an irrelevant entity rollbacks (thus affecting your own), which will also need to be reconciled accordingly. That’s also a biter. (edit)
Yes I mean the hit effects on the client side, but the problem itself is in the hit detection and not hit effects themselves.
Dunno what to tell about what server sees. All it sees at the moment is players’ character and the progress of their action.
I do have a visualization of the Hitbox, here’s a little demo of it.
The Rollback on the Right/Opponent’s client happens due to the Hitstun, Health and some over attributes being changed on the server. Since the Opponent never saw himself getting hit he couldn’t predict those attributes being changed. And while that’s okayish, the main problem is that the visual effects are played at the moment of hit detection. So even if the attributes were changed, the Opponent will be left in confusion, since he will see himself getting stunned and damaged from seemingly nothing, not to mention some possible problems in effects: for example Grab animation could be not played while the Grab weld is there. But that’s a problem that lies in how Hit Detection and Visual Effects are implemented in my game.
I was mostly asking for how you implemented your hit detection in your game, even a vague explanation can suffice, since trying to explain my codebase and how it works is a long and tedious task that I’m sure none of us wanna go through. So having at least some sort of reference I could go off would be nice.
Not to mention, I’m currently rewriting it to consider the server authority nuances I didn’t really know that well during the making of this one.
I do have only 1 BindToSimulation on Server and Client. I have “System” modules with functions for BindToSimulation, Heartbeat and RenderStepped that the Server and Client call each frame in respective connections. Though I do have a priority execution order but I don’t really use it, so the order the Systems’ functions are executed in is not deterministic.
After watching Overwatch GDC that mentions some of your conclusions I figured my whole system design currently sucks for lack of a better word so I’m working on rewriting it to consider those things.
I was writing the post around midnight so I might have confused some things. The other players’ character’s root part doesn’t have prediction mode on by default, and are usually not predicted. So other player’s root part behaves like in normal roblox game - lagging a bit behind. However, before the rollback occurs, the player already should know the accurate position of the other player’s character, so during rollback the resimulation should be fixed. That is, if the resimulation touches the moment we need.
Not to mention that with the prediction mode on on another player it will cause constant rollbacks because even the slightest change in camera orientation will change another player’s trajectory, though I believe it can be mitigated by limiting the amount of directions players can go in and adding an input delay to the movement. Though it will require changing the core scripts and while limiting the amount of directions isn’t that hard implementing an input delay into the PlayerModule feels like a total pain.
Sorry if the post feels a bit confusing, I was figuring out how to write it for a day.
I can’t exactly say what fuels my hit detection in a rollback scenario because it’s comprehensive. It goes all the way down to how I handle input processing per-player. Hitstop and all those things are just along for the ride, deduplicated on-rollback when two boxes collide. Again, there’s no extra machinery that decides whether a hit connects or not beyond “did the hit connect already?”
I guess that I should note that hitboxes do not depend on any form of instance collision, but instead depends on AABB bounding boxes (since hitboxes should never rotate). This allows for performance bonuses (less to replicate on-instantiation + on-rollback/deletion) and also for dynamic hitboxes/hurtboxes depending on the entity state. All colliders are meant to do is collide, and that is it.
After a few replays of your video, it seems like the problem is how the opponents’ inputs are handled during simulation / restored during resimulation. Though, what could be a possibility is that my lack of experiencing this is tied to my refusing to use Humanoids, and so the physics-based controller ends up maintaining the movement velocity accumulated from simulation-valid inputs since it utilizes AngularVelocity, which slowly deteriorates rather than “appears one frame, disappears another” like Humanoid rotational movement. I could be wrong though on both counts, though.
Hey, I managed to get my animation working with Server Authority, I believe.
My approach is to calculate the attack’s current frame from the simulation clock. If Server Authority causes a rollback, I get the corrected attack frame and move the animation to that frame so the animation stays synced with the simulation.
My concern is that when I turn Server Authority off, playing the animation normally from a LocalScript works fine. When I turn Server Authority on, it doesn’t seem to work the same way.
Is this expected behavior with Server Authority? Does Server Authority change how client-side animations are handled because animation state is being predicted and rewound during rollback, or am I approaching the animation syncing incorrectly?
Very good work. A solid way to approach to Server Authority is ‘set, never increment.’
Server Authority rollbacks instances that were changed during simulation, so it’s very possible that the server is killing your Motor6D transformations because of the AnimationController/Animator simulating its effects over your unpredicted client effects. I do not encounter these problems because I do not use either of the animators, nor do I use a Humanoid.
It’s not exclusive to Server Authority, it’s essentially the same reason why Script Builder animations back in the day opted for transforming the C0 and C1 properties of the Motor6D rather than the Transform.
Since you don’t use a Humanoid, how do you handle character physics and health? Do you still use a HumanoidRootPart with Roblox physics, or is movement handled by a custom controller? Also, where do you store authoritative health and handle damage, death, and respawning?
I’m a beginner with physics-based character controllers, so could you explain how your movement system works in simple terms? I tried using Roblox’s physics-based Character Controller Library, which uses ControllerManager, but my character started shaking and stuttering—especially with Server Authority enabled. Did you experience this problem? If so, how did you prevent it, and what parts of the default controller did you change?