Difference between revisions of "OOS (Out of Sync)"

From The Battle for Wesnoth Wiki
(How to make your code safe)
(Game becomes out of sync)
Line 15: Line 15:
  
 
=== Game becomes out of sync ===
 
=== Game becomes out of sync ===
During the actual game all clients have their own local gamesate. The Clients communicate by sending 'player actions' like 'move unit at (3,4) to (5,5) with the route ((3,4),(3,5),(4,5),(5,5))' then these actions are evaluated on the other clients which should lead to the same local gamesate on all clients. If clients don't have the same local gamestate we call that OOS. The clients usually send these actions as soon as they know that they cannot be undone. Those actions that are sended to the other clients are called "synced user actions". For example move,recall,recuit ... are such actions. There are also unsynced user action like "select" that only happen on one client. The consequence is that "select" events only run on one client, thus changign the gamestate from inside a select event results in OOS.
+
During the actual game all clients have their own local gamesate. The Clients communicate by sending 'player actions' like 'move unit at (3,4) to (5,5) with the route ((3,4),(3,5),(4,5),(5,5))'. These actions are evaluated on the other clients which should lead to the same local gamesate on all clients (asuming that they had the same local gamestate before that action). If at any time clients don't have the same local gamestate we call that OOS. The clients usually send these actions as soon as they know that they cannot be undone. Those actions that are sended to the other clients are called "synced user actions". For example move,recall,recuit ... are such actions. There are also unsynced user action like "select" that only happen on one client and are not sended to other clients. The consequence is that "select" events only run on one client, thus changing the gamestate from inside a select event results in OOS.
 +
 
 +
A complete list of unsynchronized events can be found here: [[EventWML#Multiplayer_safety]].
  
 
The replay works the same way:  
 
The replay works the same way:  
Line 21: Line 23:
 
the replay begins with the start gamesate and then eveluates all the synced user actions it can find on the replay. If the resulted gamesate is different than the original gamesate we have an OOS. That's why in most times replay safety is the same as multiplayer safety.
 
the replay begins with the start gamesate and then eveluates all the synced user actions it can find on the replay. If the resulted gamesate is different than the original gamesate we have an OOS. That's why in most times replay safety is the same as multiplayer safety.
  
If wml/lua code is invoked ny a synced user action and thus runs on all clients we say "the code runs in a synced context" otherwise not.  
+
Generally speaking a replay file receives the same kind of information about the game that a multiplayer observer of a game would receive, so any technique which would cause OOS for multiplayer will also cause corrupted replays.
 +
 
 +
If wml/lua code is invoked by a synced user action and thus runs on all clients we say "the code runs in a synced context" otherwise not.  
 
{{DevFeature1.13|0}} You can know whether the current code runs in a synced context by checking wesnoth.current.synced_state.
 
{{DevFeature1.13|0}} You can know whether the current code runs in a synced context by checking wesnoth.current.synced_state.
  
In order to get the same local gamesate on all clients you should only make the gamesate depend on deterministic functions that return the same value on all clients, for example side.cotroller s getter or math.rand is no such function. If you want to call these functions you should use wesnoth.syncronize_choice which allows you to run code on one client and then return the result to all clients so all clients are guaranteed to get the same result. But note that since the code in synconize_choice only runs one one client and therefor should only calculate teh return value and not change the gamestate.
+
In order to get the same local gamesate on all clients you should only make the gamesate depend on deterministic functions that return the same value on all clients, for example side.cotroller s getter or math.rand is no such function. If you want to call these functions you should use wesnoth.syncronize_choice which allows you to run code on one client and then return the result to all clients so all clients are guaranteed to get the same result. But note that becasue the code in synconize_choice only runs one one client it should only calculate the return value and not change the gamestate.
 
 
 
 
During a Wesnoth MP scenario with add-on content, the cfg files for the add-on reside on the host. As the game proceeds the host evaluates this WML and transmits new events / changes to the game state, through the server, to the other clients.
 
 
 
Network economy demands that not only is not all data transmitted, but also data is only transmitted at certain times in response to certain events ("synchronized events").
 
 
 
A scenario designer will cause OOS if she runs WML during an unsynchronized time which requires user input / randomness to evaluate properly.
 
 
 
Generally speaking a replay file receives the same kind of information about the game that a multiplayer observer of a game would receive, so any technique which would cause OOS for multiplayer will also cause corrupted replays.
 
 
 
A list of unsynchronized events can be found here: [[EventWML#Multiplayer_safety]].
 
  
 
=== The rng ===
 
=== The rng ===

Revision as of 21:55, 28 May 2015

work in progress, not comprehensive

This page will be very much obsolete in Wesnoth 1.13, possibly in 1.12, hard to say right now.

OOS is an error that occurs in a Wesnoth scenario when two machines disagree (or one machine disagrees with a replay file) about the game state, i.e. which units are where, how much HP they have, how much gold each side has, etc. More precisely, OOS is announced when a client reads a command, such as "move Dwarf Fighter 8,4 -> 8,5 -> 8,6" or "recruit Gryphon Rider 9,7", which is illegal or nonsensical based on its understanding of the game state.

OOS could in principle be caused by network issues such as dropped packets, by players having mismatched game files, or even by cheating, but it is commonly caused by scenario designers / add-on makers writing unsafe code. This page is about how to write WML that won't cause OOS. If you want to know what you should do if you get OOS in an online game, see [here].

Why does OOS happen?

Game begins out of sync

This can happen if one of the players has modified their game files, or if the players don't have matching add-on versions.

The OOS may still not appear until a mismatched resource actually appears and does something.

Game becomes out of sync

During the actual game all clients have their own local gamesate. The Clients communicate by sending 'player actions' like 'move unit at (3,4) to (5,5) with the route ((3,4),(3,5),(4,5),(5,5))'. These actions are evaluated on the other clients which should lead to the same local gamesate on all clients (asuming that they had the same local gamestate before that action). If at any time clients don't have the same local gamestate we call that OOS. The clients usually send these actions as soon as they know that they cannot be undone. Those actions that are sended to the other clients are called "synced user actions". For example move,recall,recuit ... are such actions. There are also unsynced user action like "select" that only happen on one client and are not sended to other clients. The consequence is that "select" events only run on one client, thus changing the gamestate from inside a select event results in OOS.

A complete list of unsynchronized events can be found here: EventWML#Multiplayer_safety.

The replay works the same way:

the replay begins with the start gamesate and then eveluates all the synced user actions it can find on the replay. If the resulted gamesate is different than the original gamesate we have an OOS. That's why in most times replay safety is the same as multiplayer safety.

Generally speaking a replay file receives the same kind of information about the game that a multiplayer observer of a game would receive, so any technique which would cause OOS for multiplayer will also cause corrupted replays.

If wml/lua code is invoked by a synced user action and thus runs on all clients we say "the code runs in a synced context" otherwise not. (Version 1.13.0 and later only) You can know whether the current code runs in a synced context by checking wesnoth.current.synced_state.

In order to get the same local gamesate on all clients you should only make the gamesate depend on deterministic functions that return the same value on all clients, for example side.cotroller s getter or math.rand is no such function. If you want to call these functions you should use wesnoth.syncronize_choice which allows you to run code on one client and then return the result to all clients so all clients are guaranteed to get the same result. But note that becasue the code in synconize_choice only runs one one client it should only calculate the return value and not change the gamestate.

The rng

Wesnoth has a synced random number generator that returns the same value on all clients, this rng is uses by

  • [set_variable] rand=.. [/set_variable]
  • traits & name generation in [unit]
  • calculation whether an attack hits

To keep the rng in sync it is important to call the rng the same number of times on all clients. Othwewise the random results will be of by one which causes OOS. Luckily the synced rand is smart enough to know whether we are in a synced context. An it redirects to an unsynced rng (like math.random) if it was called from outside a synced context so that calling this rng from outside teh synced context doesnt have an effect on the rng used in the synced context.

This rng gets reseeded at the beginning of every "synced user action", the reason is, that otherwise players could know which random results they'll get next. Expecialy im MP this is very bad since it means players could know whether an attack will hit or not before they command it. In netwroked Mp the Client get their random seeds from the server. This happens layzly that means it doesn't happen at the beginning of the synced user action, but it happens as soon as the rng is used inside the synced context for this action (Especialy it doesn't happen if the rng isn't used). Since sending the "generate seed" to the server cannot be undone, an action that invoked the synced rng (for example attacks, but also moves with a [set_variable] rand=.. in a moveto event) cannot be undone. This is usually the intended behaviour since calling the rng usually implies a information gain that shouldn't be able to be undone. If you want a random number but don't want to make the gamestate depend on it (maybe just use it for visual features), and thus want the action to be undoable you should use the unsynced math.random from lua.

Found dependent command

It's posible to get a OOS eror with the message "Found (in-)dependent command" or siilar here is explained what this means: During the game the clients send each other packages ([command] see http://wiki.wesnoth.org/ReplayWML). There are 2 types of these packages: the 'normal' commands that contains new user actions like attack, recruit, recall, move... . And there are the 'dependent' packages that contains answer to questions asked to specific clients (the results of local choices). For example advancement choices. But also new random seeds (questions asked to the server) and get_global_variable are in this category. If a client received an answer to a local choice when none was expected the games gives a "Found dependent command when is_synced=false" OOS error message. The opposite sitation when a client expectes an answer to a local choice but receives a new user action also gives a OOs message.

Some examples

If Player A changes the White Mage's magic attack from 9-3 to 10-3 while Player B did not, that would result in an OOS since as the game progresses each client would see a different amount of damage being done.

Say that the White Mage attacks a Spearman with 30 HP left and hits all 3 times. To Player A, it would appear that the Spearman died. However Player B would see that unit as having 3 HP left. As a result...

  • What if Player A then tried to move another unit to where the Spearman was? To Player A's client, it would work fine. To Player B's client, that wouldn't make any sense, since it's not possible to have multiple units on the same hex.
  • What if Player B tried to attack one of Player A's units? To Player B's client it seems like a normal move, but to Player A's client it seems like an empty hex is trying to attack him.

How to make your code safe

  • Make the WML run at a synchronized time instead, i.e. in a moveto event.
  • Use helper.rand instead of math.random, unless you know exactly what you are doing.
  • Use Lua wesnoth.synchronize_choice when gathering informaton to make sure that all clients match. Note that this function doesn't work right before prestart.

Additional tricks and tips

  • You can modify some things. So while you shouldn't change the damage of a unit's attack, there's nothing wrong with changing it's portrait.

See Also

MultiplayerContent