Citybound – A city building game using actor-based distributed simulation
aeplay.org
aeplay.org
There's this perception that an amazing simulation will be the basis for an amazing game.
In practice, high-quality simulations seem to be interesting but not all that fun. See: F.E.A.R.'s Goal-Oriented Action Planning (https://alumni.media.mit.edu/~jorkin/goap.html) which had to be modified to broadcast its intent to the player, because play-testers felt the game was unfair and that the A.I. was cheating.
And it also seems like players can't really tell the difference between a sophisticated simulation and a handful of heuristics with some calls to random() thrown in. From the perspective of a player who does not know how the simulation works, the latter can _seem_ like the output of a complex system. See: _Designing Games_ by Tynan Sylvester, developer of RimWorld (https://tynansylvester.com/book/).
Dwarf Fortress, I think, is the exception which proves this rule.
Anyway, I think simulations like this are really cool to build, but hard to turn into a fun game.
Strong recommend, if only to understand how RimWorld was designed and built.
I've come to see one aspect of game design as "shaping randomness into more interesting and artistic forms". Just a plain rand() call is sort of interesting, you never know exactly what you're going to get, but with some art and careful shaping it can be made much more interesting.
the F.E.A.R ai is still amazing to me after all these years. I'm surprised by the play tester reaction because even without the broadcasting of intent I think the AI felt very organic. Honestly still one of the most convincing ones to this day in shooters.
Of course, shouting ‘Grenade!’ at your enemy gives them plenty of time to get away.
I guess it doesn’t matter from a game perspective since I never noticed, but from a technical perspective it seems more elegant to me.
- Sometimes AI characters can't act on their knowledge in a way that is obvious to the player, so they might seem more dumb than they are. The fact that they can vocalise their knowledge even if they can't act on it makes it clear that they are more intelligent than they appear.
- By vocalising their knowledge about the situation, fortuitous incidents will more easily appear like consequences of AI direction, even though they were not actually related. (Narrative bias in the human.)
- Since the AI actually works with a shared mind, it can seem unnatural that each AI character knows what all the others are doing without any voice communication.
Isn't that an argument for simulation of the type citybound is doing, rather against it? Even the dumbest most "gamified" AIs can have that fault. E.g. think about the enemy AI in Far Cry.
The game's legendary difficulty is entirely due to the impenetrability of its user interface and systems. When you actually finish getting through all the tutorials needed to learn how to play the game it falls flat on its face. It is quite trivial then to get a fortress up and running and produce far more food, drinks, and goods than you ever need and grow your wealth rapidly. And then when the enemy comes knocking it's quite trivial to pull up your drawbridges and line the entry halls with traps and generally grind them into a smooth red paste.
Dwarf Fortress may be a fine simulation and an interesting study in systems and a great conversation piece but it is not a very good game. It is like the Great Salt Lake of games: a hundred miles wide and a few feet deep.
I'd say that the point of this kind of open-ended simulation game without a clear goal is not to "win" since there is nothing to win, but to create your own challenges.
I've played a lot of these games and from Civilization II to Dwarf Fortress, just winning by using the game weaknesses has always been very uninteresting, but building a world-class city-state on an island or an above-ground wooden fortress without digging, for example, are challenges that you can create for yourself and that make these games interesting to play.
Since they are single-player, I find just "mastering the game" to maximise end-game score to be quite pointless, but that's just how I feel of course.
You can not like a game without saying that the game in general isn't good. It's been around for more than 10 years, and seems to be getting more and more popular, not less. The community grows, and development continues. Countless of people do find it fun, which is the most important part of a game, otherwise you won't play it.
By most metrics, Dwarf Fortress is a good game, despite its impenetrable UI and UX.
We'll also see how well received the game will be by mainstream gamers once they're done with the new UI, as it'll attract a bunch of new players then.
In the process the ability for more familiar players to emphasize “losing is fun” is lost.
It has always been trivially easy to close all the entrances and build a safe fort.
The fun aspect is always from willingly tolerating discomfort and engaging with what the system throws on you.
Fundamentally, DF doesn’t have a win condition. By defining one as having a running a fort, the experience will be less than what the game offers.
At that point it turns into a big-system management game with a variety of mechanisms to trip you up, starting with the raids and megabeasts attracted by wealth, vampire infiltration, cranky nobility, and other ways that your system turns self-destructive.
That doesn't make it a great game — it can be incredibly tedious — but, it sounds like you played until you reached prosperity, and just declared the first 1/3-of-the-game-tops as too easy.
If you’ve found a way to play the game that isn’t fun for you, just don’t play that way. Add a restriction for yourself to make it more interesting.
It's also a cultural legacy left over from early versions that were more challenging but less open ended simulations.
In particular, farming required setting up an irrigation system, which required exposing your fortress to a river that occasionally spawned hostile creatures which would disrupt farming. Coupled with winters where farms went fallow, it was much easier to lose a fortress for want of basics.
Additionally, there was a progression involved where access to better metals meant digging deeper and exposing yourself to additional hazards generated from fratures deeper inside the mountain.
Most of these dangers went away in the newer system, and what other dangers were added can often be avoided more easily.
With that said, the older versions also had a lot less replayability. Once you knew what you were doing, each fortress mostly played out the same, but it did make for better progression for someone new looking for a challenge.
Kind of like ray tracing, or voxel worlds; for the latter, Minecraft was a great example of a first game meeting that “computation required” vs “fun” slope intercept; Teardown is a more recent game that uses its voxel engine to enable novel gameplay that would be totally infeasible without it.
Another great example is Stormworks: Search and Rescue. There's no shortage of lego-like vehicle building games, but Stormworks gives you the purpose of completing various search and rescue missions with your vehicles. From that, you suddenly have practical reasons to build things certain ways. I can build a boat in a few different games, but only in Stormworks would I need to build a boat that can extract a piece of mining equipment from the sea floor and have provisions for delivering it onto a wharf.
There are just different kind of people who like different kind of experiences.
I think a great deal about good games is the communication of their black box of rules and mechanics. In a simulation this is especially hard, as much to communicate, and they are usually created by more techno-orientated people, which simply suck at these things. So the player is confronted with a highly complex world where things happen, but for the player it's not always obvious why something happened, which impact their actions have, and sometimes they don't even get that something happened. Thus, giving players a way to explore the rules of the simulation, and understand the state of the world they are playing, is far more important in a simulation because of it's complexity.
I’m happily surprised to find this on HN again.
The project is currently on a hiatus because I’m focusing on building a startup, but Citybound is an will always be my 100% passion and “anything goes” research project.
I’m not on a computer today, but I will try to get back to everyone who posted in this thread.
If you have any other questions about the project or the tech please just reply to this!
(Side note: the main way I built a wonderful community for Citybound was to directly reply to each and every comment whenever it got interest somewhere - a tactic I can highly recommend)
Looking back when I used to follow the project, it has mostly seen like you've been the sole developer with only sporadic contributions from others.
There were a couple other direct contributors to the code, which always made me really happy, but the idiosyncracy of the codebase made it hard for others to contribute more than housekeeping/refactoring.
The rest of the community contributed through lots of in-depth discussions, ideas, what-ifs etc. I'm really grateful for all the interest and thoughtfulness my community always displayed.
Anyways, I love this project.
Here's a tech demo of what I've been working on: https://www.youtube.com/watch?v=7q0l87hwmkI
I created a path finding algorithm that can simultaneously path hundreds of thousands of units to random destinations at a comfortable frame rate (or around 100-200K like in the video using one CPU core). Units can choose from any of the shortest paths between two points (there are many in a grid), and from those paths, can also choose the path that matches any preferences they have.
Very early stages of development still!
Anyway, from my experience with Songs of Syx, which also has large scale pathfinding, I’d like to encourage you to find a way to spread it (and other calculations) around. When you reach larger city sizes, the game becomes noticably slower on some ticks. Still smooth, but when your guys are moving at 4x speed, and everyone suddenly slows down to 2x speed (presumably because calculations take so long that running at 4x isn’t possible any more), it’s very jarring.
I think with any quasi-infinite growth game, you are going to run into a problem of it slowing down, eventually. That's what inspired me to create this algorithm. I wanted a game that could scale, and traditionally path finding is a CPU intensive task.
The video I linked is out of date, I'll be making a new one soon.
Now: building the path finding graph is now stored in a vector (i.e. array) instead of an unordered_map (and still has constant time access/find). This cut CPU cycles by 66% since a vector doesnt use linked list/pointers/store a hash, and reduced RAM requirements by 75%. Then I parallelized that part of the code base.
So for my 6 core machine, I can find all the all-pairs, all-possible shortest paths in a 50 x 50 grid of roads in 10 seconds. This is about 13 square miles of city if each block is the size of a Manhattan block. I'll be working around this time by implementing a planning mode so it only updates as needed.
That way, road nodes can propagate changes locally.
That being said, if they have a modern machine, then there will be no need for this at all (just need to figure out the avg user CPU GHz & core count). It takes 10 seconds for my algo to find the all pairs/all possible shortest paths between two nodes on my machine for 10,000 nodes, which by that point is a HUGE city.
The reason I did not go with A* is because it doesn't scale to 100K+ units and it always chooses the same path.
Unfortunate, but it died for good reason.
https://www.reddit.com/r/Citybound/comments/r2z8mh/this_just...
The game was a disaster for a DRM scandal; but also the game in itself required too much computing power, and the simulation wasn't that good.
edit: https://www.youtube.com/watch?v=eZfj7LEFT98 "Exploring SimCity: A Conscious Process of Discovery"
The AI is driven via needs-based utility scoring & heuristics, and action/object selection uses attenuated-delta scoring; this creates agent behavior that is "opportunistic". The AI itself is largely based on prior work, substantial portions of which I believe can be (at least largely) attributed to Don Hopkins, who I see posting here pretty regularly. :)
SimAirport - https://store.steampowered.com/app/598330/SimAirport/
SimCasino - https://store.steampowered.com/app/1158420/SimCasino/
I'd played SimAirport but would love to learn more about the tech because I am a hobbyist gamedev
If you had to deal with committees, contractors, NIMBYs, and real human cost of bulldozing houses for highways, it would have been a completely different game, likely a sad and frustrating one.
City builders also start from an empty free land, even though true greenfield development is very rare. SimCity has famously ignored the problem of parking, because it made cities look depressingly ugly. Even in Cities Skylines people pull cars out of their pockets.
A sends a message to "x = 2" to B and C. B receives this message and sends "x = 3" to C.
Now, C receives two messages one from A and one from B. It could receive them in any order. However, there is no way for it to tell whether there is a causal order or not.
With a clock embedded in the messages, one can state that "x=2" happened before "x=3". (or more strictly, "x=3" def did not happen before "x=2").
Extending this, if C and D receive copies of the above messages from A and B, we want C and D to both process them in the same (deterministic) order.
In the context of a city simulator, every road segment could be an actor, as well as every simulated vehicle. Two vehicles might try to enter a road segment at the same time, so they both send a message :enter(entity_1) to :road_segment_1. Then the :road_segment_1 actor decides what to do with those messages.
Structuring it this way allows you to massively distribute these kinds of simulations, which is exactly why citybound attempted an actor-based approach. There's a load of overhead, and there's still issues with buffers and backpressure, but in theory you could infinitely scale a simulation this way.
In your example, the vehicles' messages have no causal relationship; they are concurrent. Order does not matter. However, for the sake of determinism in a simulation, the above constraint is necessary even for concurrent messages.
For example, one of the projects I have worked on is one of the largest trading engines in the world, with high concurrency. To make it testable and fault-tolerant, all incoming messages are timestamped and handled in time-serial order. The same timestamp helps with idempotence.
When testing the engine core and downstream systems, a previous day's test data can be input in timestamp order (instead of concurrently), so that we know the behaviour of the system in the face of a fix.
Dataflow systems (which is most software) should be idempotent and deterministic. If random() is called, then fixing the seed should make it deterministic.
Basing the behavior of your simulator on yesterdays data is a neat trick, but it has no relationship to the actual world. Not in a million years would the exact same market behavior of yesterday happen again.
Instead of building it deterministic, you could simply run your system a hundred times, and maybe purposefully mess with the timings to make execution more non-deterministic. And instead of a precise number "yesterday we would have made X amount of money with our trading engine", you would come up with a distribution that is much more predictive of the future "with market circumstances similar to yesterday we would have made between X and Y amount of money with our trading engine".
Anyway, I don't work on trading systems so I probably shouldn't tell you how to do your job. This is just how I see simulation systems.
With good reason. It is called caution. We're talking about a nation's economy.
You misunderstand me about the reason for running yesterday's or any other day's full load. The idea is that after you have made a bug fix or a performance fix, you want to see that the output reflects the fix; the best way to do that is do a diff with yesterday's output given the same input, and one should be able to ensure both correctness (all changes accounted for) and completeness (all the input tuples that ought to reflect a difference do reflect the correct difference).
Given that there are a hundreds of thousands of events a second and terabytes of data, it is impractical to run a test 100 different ways. Besides, how do you know you got it correct? Events are not commutative because there are limits. Deposit is not commutative with Withdraw, because Withdraw can throw an OutOfMoneyException. Order matters.
Please refer to [1] Parallel and Distributed Simulation, fig. 9.4, pg 265 for an example of an anomaly introduced by the lack of a total order. The text also covers why it is necessary to have a model of time; it is not enough to have agents sending messages to each other and let them duke it out.
[1] https://doc.lagout.org/science/0_Computer%20Science/3_Theory...
When you said financial simulation I thought you were modeling the behaviours of actors in a market, and how your system would trade against those actors, but what you're doing is more like banking or an exchange?
Rand() and friends obviously needs to be handled appropriately but as long as you have only one owner of data (and thus one writer), by passing messages of the form “try to add 1 to x” rather than “x=5” or “add 1 to x” message-queues have a strict ordering by nature.
Dwarf Fortress is probably the best example of raw simulation building to complex organizations: https://en.wikipedia.org/wiki/Dwarf_Fortress
I've thought about building a more generic system to simulate characters that can be plugged into many of these open-world and other games that need activity to fill cities and locations.
After completion, basic things that should have been checked by inspectors should go horribly wrong. (Electrical fires, sewage backups, landslides, etc.)
There should be an optional environmental tax that doubles revenues and construction times, but causes massive environmental damage about 25% of the time, and attracts the mob, regardless.
In other places, like rural Texas, there wouldn't be any building codes or zoning in unincorporated areas, and you'd need to maintain a fleet of aircraft to enforce property taxes. 75% of buildings would just fall over every few decades.
In Texas, the entire power grid would collapse periodically. In California, there would he weekly brownouts in rural areas, and natural gas exlosions would sometimes destroy entire city blocks. When this happened, PG&E would get to arbitrarily raid the government coffers, and funnel the money into organized crime.