Scrum Lord
Warlord
- Joined
- Jul 2, 2017
- Messages
- 201
Based on code inspection (and some unexpected results while debugging a map script) I found what looks like a possible root cause of multiplayer desyncs: map generator calls to table.sort() on lists that have ties combined with RNG in the sorting algorithm itself.
For context, here is an earlier discussion on why any RNG other than official Civ-seeded RNG is bad for multiplayer games:
https://forums.civfanatics.com/thre...-scripts-in-multiplayer.626600/#post-14981091
Now to explain how table.sort() relates to this and could potentially cause problems:
For example, one call at function ResourceGenerator:__PlaceStrategicResources(eContinent) at L621 sorts through all valid locations for each strategic resource type and sorts them by score :
If there is a tie then the order of the strategic resource locations list (self.aaPossibleStratLocs[row.ResourceIndex]) is no longer deterministic. And since not all locations in the list are chosen, then the locations of resources on the map become non-determinstic too. This means that players in a MP game running the exact same map settings and seed could potentially get maps with resources in different places.
Has anyone else noticed any inconsistencies in things like natural wonder placement, resource placement, and starting locations? This seems like a bug that could exist in many places in the base game Lua as well as map mods and map scripts.
Suggested fix: pass-in a comparator that uses a unique field such as an ID or database index to break ties so that only one valid outcome is possible for table.sort().
EDIT: I forgot to mention that I also found calls to table.sort in CoastalLowlands.lua (L110, choosing which tiles to mark for inland flooding) and MapUtilities.lua (L867, placing ley lines for Secret Societies).
For context, here is an earlier discussion on why any RNG other than official Civ-seeded RNG is bad for multiplayer games:
https://forums.civfanatics.com/thre...-scripts-in-multiplayer.626600/#post-14981091
Now to explain how table.sort() relates to this and could potentially cause problems:
- Lua's built-in table.sort() algorithm is quicksort (lua.org) (not sure if 5.3.x is the exact lua version that Civ 6 uses, but it seems close)
- The quicksort alrgorithm requires arbitrary pivot values, and the Lua 5.3.6 implementation mentioned above may use RNG to choose pivots for sufficiently large arrays.
- Any table that contains ties, as in two or more elements that are equal to each other, can be sorted into multiple valid outputs.
- Therefore calling table.sort() with a large enough array that has ties results in a table that is at least partially randomized, since the result may be one of multiple valid outputs determined by RNG.
For example, one call at function ResourceGenerator:__PlaceStrategicResources(eContinent) at L621 sorts through all valid locations for each strategic resource type and sorts them by score :
Code:
table.sort (self.aaPossibleStratLocs[row.ResourceIndex], function(a, b) return a.Score > b.Score; end);
Has anyone else noticed any inconsistencies in things like natural wonder placement, resource placement, and starting locations? This seems like a bug that could exist in many places in the base game Lua as well as map mods and map scripts.
Suggested fix: pass-in a comparator that uses a unique field such as an ID or database index to break ties so that only one valid outcome is possible for table.sort().
EDIT: I forgot to mention that I also found calls to table.sort in CoastalLowlands.lua (L110, choosing which tiles to mark for inland flooding) and MapUtilities.lua (L867, placing ley lines for Secret Societies).
Last edited: