The issues you described here are inherently messy because you’re trying to compensate for not having necessary data on time. This is fine for predictable stuff like “what position would this projectile be in, with a given initial position and velocity?” But as you’ve seen, it starts to break down once the clients try to predict data they don’t have access to (eg, if the spell has actually hit into any players, who do not move predictably). So if you try to accelerate the spell’s position to compensate for lag, you risk it being desynced from the server telling the client what it hit into, which can be annoying for players to have to dodge, and leads to situations where it looked like it hit but it actually didn’t. Other factors to consider is when the the spell finally diffuses; presumably, this spell would be destroyed if it hit into a stationary wall. What happens when the clients predicted they would travel through a wall (since they don’t handle collisions, the server does), but it actually smacked against the wall serverside, destroying it? From the clients’ perspective, it would look like it passed through it, only to snap back and play some diffuse particle animation.
Granted, you could have a special case done on the clients’ side to locally play the spell diffuse animation if it hits into a stationary wall, since it’s highly unlikely the server would disagree about where it is. But the point is, speeding up projectiles to compensate for lag is a deceptively simple thing to do that involves a lot of questions.
This is why I’d recommend you dispense with the lag compensation and just have the server give the remote events as it happens and accept any natural lag you may run into. It would simplify your codebase and sidestep a few of the types of issues I discussed here. After all, you can only do so much with what the clients’ pings can give you. Unless your game is really fast-paced, the ~150 ms difference in time probably isn’t gonna be hurting anything. And if somebody has a terrible ping for it to matter, they have to accept they’ll have a terrible online experience, no matter what online game they’re playing.
New players would have missed the event call, so what would be the way to let them know that they need to create and sync the effects for active spells?
You could go the route of handling state entirely within scripts (ie having tables that track projectiles, then handing them off to new players). This might be easier on you if you actually created the projectile instance serverside, then passed the instance reference to clients for them to locally manipulate it, such as moving it around and giving it visibility. That way you wouldn’t need to have more code for reconciling the local versions of the projectile with the serverside version of it. Then have some code that would regularly update its position / other state that would fire to all clients at given time steps.
if I created explosions using TweenService, how would I skip to where the Tween should be?
Just a side thought, but if you have very long tweens you could always use the often neglected TweenService:GetValue. You could use a syncing remote event that gives the two position pairs for newly joining players to use GetValue on. (You would also need to give the amount of time since the projectile was last updated for the alpha to give GetValue). Then you could use CFrame:Lerp on the alpha value it gives you, along with the two position/CFrame pairs it received from the server to interpolate between. But if you’re tweening one small tween after another, I don’t think it’d be worth your time.