> Instead, it's a bunch of pieces of data: x- and y-locations (and maybe z, as well), extents, x-and y- (and maybe z-) velocities. You can easily get that into an inconsistent state - two objects occupying the same space.
I think that's precisely the OPs point. You model the data (with types) in a way that this invalid state becomes unrepresentable:
Map[(x,y) -> object]
Now you simply can't have two objects at the same coordinate in your game. Of course you can now come up with more constraints and you will then have to refine your types (and create new ones) to match these constraints.
Certain constraints might be hard to express as types in many languages (especially most mainstream languages lack here), but that's not a general problem of the approach but rather a problem of specific languages - for which you then have to find alternatives.
> If you're in a multithreading situation, it gets even worse. Yes, it still works for your unsigned 8-bit situation, because it can be written in one assembly instruction. But if your data takes more than one assembly instruction to write, you have to worry about threading.
Not with immutable data and functional programming (and I mean that's what the article is all about). This style forces you to make state changes explicit by only making copies and, if copies are made concurrently, explicitly specifying how to merge these copies.
Compare:
var map = Map(...)
fill_randomly1(map)
fill_randomly2(map)
do_something_cool(map)
Now if the fill_randomly functions work in parallel/concurrent you have to somehow ensure that they are called in the right way/order and do_something_cool is called after they have finished. Or worse: if they are supposed to run after each other then you have to be super careful about how your whole code is executed to ensure sequential execution of the parts of your code that calls these functions. Which brings us to your valid point that the places that have to be checked to understand the execution should be as limited as possible.
But compare it to the pure functional style where mutation is not possible:
immutable var map = Map(...)
immutable var map1 = fill_randomly1(map)
immutable var map2 = fill_randomly2(map)
do_something_cool(???) // What do we put inside?
The compiler here will force you to give it a map - but which one? It explicitly makes you aware that you have to somehow merge the results. So let's do that:
immutable var map = Map(...)
immutable var map1 = fill_randomly1(map)
immutable var map2 = fill_randomly2(map)
immutable var map3 = merge(map1, map2)
do_something_cool(map3)
The important thing here is that it does not matter if fill_randomly1 and fill_randomly2 are run one after each other, in parallel or somehow concurrently - it is explicitly specified how the results are merged and the result will always be the same. Also, do_something_cool is guaranteed to run after the other functions, simply because it refers to a variable that relies on the output of the previous functions, so you simply cannot run it "at the wrong time".
I believe that was the suggestion of OP here.