What happens to the world when you change the rules?
FlowWorld is a simulator of 320 real cities with a vectorized three sector economy, an autonomous
infrastructure planner that picks its own build sites, and a machine learning optimizer that hunts
for the best mix of policy levers under randomized shocks. I ran eight contrasting scenarios for
two hundred timesteps each, then one shock augmented optimizer search across forty eight trials.
What follows is what came back, including the results that fell apart when I went looking for the
mechanism behind them.
8scenarios analyzed
200timesteps each
320cities × 1,800 edges
+341%best optimizer growth
The question
“If you change borders, transit links, housing rules, and infrastructure spending, what happens to
where people live, the global economy, and which cities boom or collapse over time?”
FlowWorld isn't trying to predict reality. It's a policy laboratory. You twist a knob, press play,
and a specific number comes back in under a minute. Because the engine is deterministic, the same
seed and the same city data give you the same trajectory every time, which matters more than it
sounds: a disagreement about a result can be settled by re-running it instead of by arguing.
The appeal is that a simulation forces you to commit. Most “what if we opened the borders” arguments
are thought experiments where nobody has to name a number. Here you set border friction to 0.05, you
press play, and the model hands you a figure for global GDP, a list of cities that grew, and a list
of cities that emptied out. You can attack the assumptions all you like. What you can't do is stay
vague.
The eight scenarios weren't picked to produce a tidy story. They're presets straight out of the
project's own library, chosen to spread across the design space of borders, infrastructure, housing,
migration and prices. The numbers came out how they came out. Several came out badly for conclusions
I'd expected to draw, and I've said so where that happened rather than quietly dropping them, because
how a result falls apart usually teaches you more than the result would have.
How the project started
Before any simulation code existed, I wrote out what I wanted to be able to ask of it. Those four
questions are still legible in the codebase, because nearly every feature exists to answer one of
them.
The original mission
Build a simulator that can honestly answer what the world looks like under different border policies,
where people end up moving when cities run out of room, how resources and trade routes shape the
economy over time, and which infrastructure decisions actually pay off under a real budget constraint.
Everything else follows from that. The migration model is there because of borders and crowding. The
three sector economy is there because of resources and trade. The autonomous planner and its budget
cap are there because of the infrastructure question. If you're ever looking at a feature and
wondering why it exists, the answer is almost always that one of those four questions needed it.
The four research questions
Each one is deliberately built around a tradeoff rather than a right answer:
Migration dynamics. How strongly do wages, housing costs, amenities and connectivity drive where people move? And at what point do crowding and rent in the destination start to throttle the inflow?
Infrastructure policy. Which kinds of infrastructure (rail, pipeline, shipping, road) produce the highest long run GDP gain per dollar? Under a real budget, when do you build and when do you wait?
Resource economy. How do extraction, depletion, logistics and market pricing interact? Does better connectivity make resource prices more competitive, or does it just move who captures the margin?
Stability and equity. Can a policy raise GDP while also holding down overcrowding, concentration and volatility? Which mixes survive a shock like a resource crunch, a severed trade route, or a wave of migration?
Growth against crowding. Investment now against investment later. Prices against rents. Aggregate
output against distribution. The project assumed from the start that good policy is a negotiation
rather than a maximum, and the scoring function the optimizer uses later reflects that directly.
The honesty pledge
One early commitment was a hard line against fantasy modeling, written down roughly like this:
100 percent real-world accuracy is not achievable in complex social and economic systems. The goal is
high fidelity calibrated modeling with quantified uncertainty, not perfect prediction.
A lot of policy modeling goes wrong at the point where the modeler forgets their assumptions were
assumptions, so the simulator was built to treat itself as a calibrated estimator rather than an
oracle. You can see that in the interface. Every run shows a confidence level in the top bar, and the
right panel carries a factor coverage disclosure listing every economic factor the model knows about
alongside how complete the underlying data actually is.
That machinery earns its keep later in this report. The project's automated threshold registry flags
the optimizer's own best scoring configuration as failing, which is the single most important caveat
in the whole study and would have been easy to miss without it.
The eight design principles
Eight constraints, written down as non-negotiable. They're less a feature list than a definition of
what counts as a valid version of the project:
Deterministic replay. The same seed and the same city data reproduce a run exactly. Every result here uses seed 42.
Real budget constraints on infrastructure. The autonomous planner can't build forever. It runs out of money and stops.
Explicit affordability and capacity limits. Housing costs respond to density, and people can't ignore what it costs to live somewhere.
Real resource stocks that deplete. A mineral rich city can't pump indefinitely. Reserves draw down.
Supply and demand pricing. Three sectors (resources, goods, IP), each with a price index feeding both GDP and the migration choice.
Full causality logging. Every timestep writes a plain text record of what happened and why, so any decision can be reviewed after the fact.
Scenario testing under shocks. Twenty four contrasting scenarios ship with the project, and the optimizer evaluates candidates under randomized shocks so that policy which breaks under stress gets penalized.
Explainability. The optimizer outputs its learning curve, the spread of candidates and the score breakdown, not just a winning parameter set.
How it got built
Three rough phases, each with a different goal. None of them are visible to someone dropping into the
app today, but the order they happened in explains a lot about how the simulator behaves.
First: set the rules of the game
Before writing simulation code I wrote down what would count as a finished feature, what would count
as a valid experiment, and how decisions would get reviewed. A governance charter, a quality
checklist, a template for documenting assumptions, and a schema for describing an experiment well
enough that someone else could reproduce it.
It's tedious work, and it's the reason the project didn't turn into a research toy nobody could
trust. Once the simulator started producing data, every result had somewhere to live, a bar it had to
clear, and a paper trail behind it. The failure mode this avoids is the one where somebody asks
whether a number came from the current engine or the early prototype and nobody can say.
Second: build a data layer that doesn't lie
The middle phase was about making the cities real. The loader reads a global city gazetteer, and it
supports two of them: the SimpleMaps World Cities CSV and the GeoNames dumps such as
cities5000.txt. Those are separate datasets with separate licenses, and the code tracks
which one each record came from. If neither is installed the simulator falls back to synthetic
placements, and it flags every city it invents.
Getting that data in wasn't a matter of importing a CSV. It needed ingestion quality protocols, a
license and jurisdiction matrix documenting each source's legal boundaries, validation rules to catch
missing or anomalous records, and provenance audits tracing each field back to where it came from.
With those running, the simulator can say which of its cities are real and which are filler, and can
report a per city confidence figure. That's the “city accuracy” field in the place inspector.
One caveat matters for reproducing anything here: the gazetteer is an optional download and isn't
committed to the repository. The runs in this report used real city data, which is why Yunfu and
Bahawalpur show up by name, but a fresh clone won't regenerate them without fetching the dataset
first.
Third: make the research tool look alive
The most ambitious phase was about making the simulator worth watching. The aspiration was explicit:
something closer to Cities: Skylines than to a plotting library, but with real planetary data
and a real economic engine underneath. That meant a lot of frontend work on the deck.gl rendering
layer, the timeline scrubber, the camera and the flow animations, plus a lot of new simulation output
in the form of per timestep flow records, per leg throughput, named bottlenecks and replay bundles.
The same phase expanded the economy. Three abstract sectors became a supply chain twin with named
commodities (oil, raw materials, food and agriculture, manufactured goods, technology and IP), modal
transport across rail, pipeline, shipping, road and air, corridor level throughput tracking, and
automatic bottleneck detection. The commodity master list in the right panel is the visible surface
of all that.
Current state
Implementation is done, the runs work, the optimizer works, and the visualization is fully
operational. What's outstanding is formal research signoff. Five gates covering city schema accuracy,
resource accuracy, connectivity, supply chain economics and visualization integrity are drafted but
unsigned, largely because nobody had assembled the evidence for them in one place. This report is a
first pass at that bundle, and on the evidence below at least two of those gates should stay unsigned
for now.
Architecture, briefly
Two services run locally. A Python backend on port 8010 does the math, a web frontend on port 5173
draws the map and the dashboards, and they talk over a WebSocket so the simulation can stream
timesteps into the browser as it computes them.
Backend
Python, FastAPI for the API, NumPy for the math. Every per city calculation runs as an array
operation across all 320 cities at once rather than as a Python loop, which is what makes a
200 step run take about a minute instead of being unusable. A separate optimizer module wraps
the simulator in a cross entropy search loop. World generation reads real city data if a
gazetteer is installed and falls back to synthetic placements if not. Runs persist in three
formats: structured data for replay, raw rows for analysis, and a readable play by play log.
React 18 with TypeScript, served by Vite. The map is deck.gl over a MapLibre GL basemap.
MapLibre is the community fork of the last open source release of Mapbox GL JS, split off in
December 2020 when Mapbox moved to a proprietary license, and developed independently since.
Each timestep arrives over the socket as JSON carrying per city state, top migration arcs and
global metrics, and each renders as its own toggleable layer.
A from scratch cross entropy search over fourteen parameters: the twelve exposed as sliders in
the UI, plus the planner's build interval and build strength. Each candidate runs a full
simulation augmented with three shocks drawn at random from resource, infrastructure and
migration, then gets scored on final GDP, growth, volatility, concentration, overpopulation and
budget overrun. The top thirty percent of each iteration become the elites and refit the
sampling distribution for the next round.
POST /api/optimize
Data layer
Storage follows a lakehouse layout separating raw inputs, curated tables and ready to use
features. Every run produces a timestamped summary, a replay bundle and a decision audit trail.
Twenty four scenario presets ship with the project, and every result here can be regenerated
from those presets and seed 42, given the same city gazetteer. That gazetteer is an optional
download rather than part of the repository.
Measured performance
200 timesteps at 320 cities and 1,800 edges: 64 to 71 seconds per scenario
Sustained throughput: 2.8 to 3.1 timesteps per second, consistent across all eight runs
Those are wall clock figures recorded by the runner on a Windows laptop CPU, not estimates. The
bottleneck is the simulation math rather than the socket. The browser is never the thing waiting.
What the interface looks like
Three panels. The map fills the middle, the left sidebar holds the scenario library, twelve economy
sliders and the layer toggles, and the right panel carries the inspector plus the live dashboards. In
Analyst mode the inspector has five tabs: Node, Leg, Chain, Policy and Trust.
The default view, about 170 timesteps in. Cyan and blue arcs are the top migration flows for this frame. The bright yellow arc near East Asia is a rail link the autonomous planner has just built. The right panel lists the largest active corridors by throughput, with Tianjin to Beijing leading at roughly 211,690 tons per day.
Money mode, revenue overlay. Corridors are recolored by whether moving goods along them pays: green where throughput times unit price clears the transport cost, amber where it doesn't. Line width still encodes throughput, so this is the quickest way to separate routes that are economically alive from routes that merely exist.The population heatmap layer. Hotspots are East Asia, South Asia, West Africa and Western Europe, which is also roughly where the model concentrates its migration pressure.
Zoomed into northwest Spain. The labels (Santiago de Compostela, Lugo, A Coruña) come from the installed city gazetteer, at their real coordinates. The geography isn't abstract even where the economics is.Analyst mode with the Open Borders preset applied. All twelve sliders are exposed: border friction, connectivity, agglomeration, housing elasticity, migration propensity, softmax temperature, three price indices, R&D efficiency, infrastructure budget and link base cost. The right panel switches to the five tab inspector with SLO dashboards and the commodity master list.
The same world, recolored six different ways
The Node metric dropdown changes only what the city dots are colored by. The underlying simulation is identical in all six, which makes this useful for asking where wages are high versus where housing is expensive without perturbing anything.
WageGDP per capitaHousing costResource outputGoods outputIP / technology output
How a single timestep works
This is the part the README doesn't cover. Every timestep the simulator does the same five things, in
order, across every city at once. The findings make a lot more sense once these are clear, including
the two findings that turn out not to mean what they look like they mean.
Step 1: every city gets an attractiveness score
When you're deciding where to move you weigh a handful of things: what you can earn, how pleasant the
place is, what a roof costs, and how much of your industry is nearby. The simulator does the same
thing explicitly. Each city gets a “destination utility” every step, built from wages, productivity,
amenity, an agglomeration bonus for being large, minus housing cost, plus proximity bonuses for
resources, factories, ports, universities and talent.
The agglomeration bonus is the load bearing part. A city gets extra attractiveness purely because a
lot of people already live there, and that single mechanic drives most of what's interesting in the
model. It's why Tokyo keeps growing and why Pittsburgh shrank for fifty years, and it's a feedback
loop: bigger cities attract more people, who make them bigger. Without a counterweight, and housing
cost is the only one on offer here, the whole world collapses into a single megacity.
Step 2: residents pick where to go
Each city's residents then decide whether to move and where to. A softmax handles that: the
probability you pick city X is X's score divided by the summed scores of everywhere you might go,
with a temperature knob controlling how sharply you optimize. Low temperature and everyone piles into
the single best option. High temperature and choices get noisy.
Two things happen here that shape everything downstream. First, a destination's score depends not
just on how good it is but on how hard it is to reach, which is where friction enters: distance
friction, the connectivity multiplier, and border friction, an extra penalty when the destination
sits in a different group from where you live. Crank border friction up and people stop crossing
groups even when the other side is plainly better. Crank it down and the borders effectively
dissolve.
Why migration volume can't be a result
The number of movers each step is migration_propensity × population. Nothing else
enters it. And because migration conserves population, with total world population shifting by
0.0007% across a 200 step run, the total volume of movement in any scenario is just the propensity
slider multiplied by a constant.
I checked this against all eight runs, and predicted volume matched actual volume to a ratio of
1.0000 in every one. So the tempting headline, that migration varies 7.5× across the panel, is
really 0.06 ÷ 0.008. It's the ratio of two slider positions wearing the costume of a
discovery. Finding 01 is written with that in mind.
Step 3: cities update based on who arrived and who left
After the migration round every city has a new population, and a chain of feedback effects fires.
Cities that grew get a small productivity bump, which is agglomeration again, this time on the supply
side. Housing costs respond to the new density, rising where cities grew and falling where they
shrank, at a rate set by the housing elasticity parameter. Wages rise with productivity and fall with
housing cost.
Housing elasticity does more work in this study than any other single parameter, so it's worth being
exact. The default is 0.08. Low elasticity means housing supply can't expand easily, so a growing
city's rent climbs steeply, which makes it less attractive to the next wave of movers even while its
productivity stays high. The math pushes growth outward instead of letting it pile up. The Housing
Constrained preset sets elasticity to 0.02, and it also raises agglomeration strength
from 0.25 to 0.32 and migration propensity from 0.020 to 0.022. Three changes, not one, which turns
out to matter a great deal.
Step 4: three sectors produce output
Every city makes three things each step. Resources, proportional to its mineral,
energy and agriculture deposits, with those deposits drawing down over time.
Manufactured goods, a function of industrial capacity, manufacturing specialization
and proximity to raw materials. IP and technology, knowledge stock times tech
specialization times human capital, with an R&D efficiency multiplier on top.
City GDP combines wage income with monetized sector output, weighted by the three price indices the
scenario sets. Those indices change what a unit of output is worth, not how much gets physically
produced, which is exactly why a scenario like Resource Crunch shifts the composition of GDP without
moving its magnitude much. Finding 06 comes back to this.
Step 5: every twelve steps, the planner builds something
By default the autonomous infrastructure planner wakes on every twelfth timestep, scans for the
highest scoring pair of currently unconnected cities, and builds a link between them. The default is
policy_interval = 12, and over a 200 step run that fires at t=12 through t=192, which is
exactly the sixteen build events every scenario recorded. The optimizer is free to tune the interval
and settled on 15 for its own run, so both numbers show up in this report and they mean different
things.
The autonomous infrastructure planner
Every twelve timesteps the simulator pauses and decides where to put one new piece of infrastructure.
There's no machine learning in it. It's a fixed, deterministic opportunity scoring rule, and its
behavior across the eight scenarios says something quite specific about the model.
How it picks a site
Each time it runs, the planner assembles a candidate set: the top 160 cities by current population,
the top 160 by raw resource endowment, and the top 160 by innovation potential, meaning tech
specialization times knowledge stock. Those three lists get unioned. It then walks every pair in the
set that isn't already directly connected and scores it on four things:
Supply chain match. Resource endowment at one end times industrial capacity at the other. Western Australian iron ore wired to Chinese steel mills.
Innovation pull. Tech specialization at one end times knowledge stock at the other, at 35% weight. Boston biotech to San Diego pharma.
Coastal logistics. The average port access of the two endpoints, at 50% weight, so one good port earns half the bonus rather than none.
Raw demand. A weighted blend of the two populations, 55% at one end and 45% at the other.
The sum gets multiplied by a distance penalty that falls off gently, and the top scoring pair wins.
Link type follows two rules applied in order: pipeline if either endpoint has resource endowment above
0.95, then shipping if both endpoints clear 0.7 port access and the pair is far apart, which overrides
the pipeline choice. Everything else is rail. The cost comes out of the policy budget, and the planner
simply stops when the money runs out.
What it actually built
Rail: 128 links, being sixteen in every one of the eight scenarios
Pipeline: 0
Shipping: 0
There's no chart here because there's nothing to plot. Eight identical bars of the same color
carry exactly as much information as the three numbers above, which is the first hint that
something about this planner is less dynamic than it looks.
Why the count is identical everywhere
It's tempting to read that uniformity as the planner being blind to the macro regime. Reading the
scoring function carefully, that isn't what's going on.
Sixteen is what a twelve step interval yields over a 200 step horizon when the budget never binds. No
macro parameter could have moved that number, whatever the planner was paying attention to. And the
planner does see the regime, just indirectly, because its demand term reads current population and
population is exactly what the macro parameters redistribute. Under Fortress World it's scoring a
different population map than under Migration Surge, so it may well be choosing different pairs while
building the same number of links.
Whether it actually chose different pairs is something these runs can't answer, because the saved
artifacts record build counts and types but not endpoints. That's a gap in the instrumentation rather
than a property of the planner, and it's fixable in an afternoon. What the data does support is
narrower: the planner's build cadence is regime independent by construction, and every link
it built in this study was rail.
What I ran, and how
Setup
Eight scenarios from a library of twenty four
320 cities, 1,800 edges
Seed 42 throughout
200 timesteps each
One optimizer search: four iterations of twelve trials, 120 step horizon, three random shocks per trial
Driven directly from a Python script rather than through the frontend, so the runs are headless and clean
What I measured
Total world GDP, summed across cities
Migration volume per step
Top 10 concentration, meaning the share of world population in the ten largest cities
Sector outputs: resources, manufactured goods, IP
Per city population change over the run
Autonomous build events, with type breakdown
Wall clock time per scenario
The eight scenarios, with every parameter they change
Scenario names are misleading if you take them at face value, so here is what the library file
actually sets. Note how few of these move only one lever. That alone constrains what any single
scenario can prove.
Scenario
Every override from default
GDP growth
Final top 10
Baseline (Default)
none, reference state
+148.1%
20.0%
Open Borders
border friction 1.0→0.05, propensity 0.020→0.030, temperature 0.70→0.75
+142.2%
18.2%
Fortress World
border friction 1.0→3.2, propensity 0.020→0.008, connectivity 1.0→0.9, temperature 0.70→0.55
+143.2%
19.3%
High Connectivity
connectivity 1.0→2.2, budget 2000→3600, link cost 25→20, propensity 0.020→0.024
+142.9%
18.7%
Network Fragmentation
connectivity 1.0→0.55, link cost 25→55, budget 2000→1500, border friction 1.0→1.4
Two rows deserve a second look. Housing Constrained moves three parameters, so it can't be read as a
housing experiment. And Resource Crunch lowers the resource price index to 0.6 while raising
the goods index to 1.3, which models resources becoming cheap relative to manufactured goods. That's
close to the opposite of what the name suggests.
Findings
01
Output is strikingly insensitive to the policy levers
Across eight scenarios that vary border friction by a factor of 64, connectivity by a factor of
four, migration propensity by a factor of 7.5 and the infrastructure budget by a factor of 2.4,
final GDP growth lands between +142.2% and +163.9%. A 1.15× spread in outcome against enormous
spreads in input, and it's the most robust thing in the study.
The obvious framing of this result, that migration moves 7.5× while GDP barely moves, doesn't
hold up. Migration volume is propensity × population, population is conserved, and
61.1 million against 8.1 million per step is precisely the ratio of the Migration Surge and
Fortress World propensity sliders. Putting an input on one axis and calling the comparison a
finding is the sort of thing you catch only by going back to the code. The two charts below are
still worth looking at, but they picture the experimental design rather than the world.
What does survive is the insensitivity itself, and it has a mechanism. The softmax allocates
movers over local utilities, and once people are choosing well, more people choosing well adds
very little. The marginal mover goes roughly where the average mover goes. Tripling the flux
through the network doesn't meaningfully change the destinations, so it doesn't change aggregate
output. With a single seed, though, I can't say how much of even that 21.6 point spread is noise.
Average movers per timestep by scenario, from Fortress World at 8.1 million to Migration Surge at 61.1 million. Read this as a chart of the propensity slider, because arithmetically that's what it is.Cumulative movement over 200 steps: 12.2 billion person moves under Migration Surge against 1.6 billion under Fortress World. The separation is real movement inside the model. It just isn't a discovered quantity.
02
Housing Constrained only wins if you stop at step 200
Housing Constrained finishes first on GDP growth at +163.9% and first on distribution with the
lowest top 10 concentration at 17.5%. Moving both of those the right way at once is genuinely
unusual, and it's the most quotable number in the study.
Then you plot its rank over the run. At step 100 it's last of eight. At step 120
it's seventh. At step 150 it's still seventh, sitting 12.2 points of growth below
baseline. It doesn't overtake baseline until step 181, takes the top spot the step after that,
and is still accelerating when the run ends eighteen steps later. Stop the same experiment at 150
and the headline reads “housing constraints are the worst policy in the panel.” Run it to 300 and
nobody knows what it says, because the curves hadn't settled.
A second problem sits underneath the first. The preset isn't a housing experiment. It drops
elasticity from 0.08 to 0.02, but it also lifts agglomeration strength from 0.25 to 0.32, a 28%
increase in exactly the force that drives late compounding growth, and nudges migration propensity
up as well. The late breakaway is at least as consistent with the agglomeration bump as with the
housing mechanism, and this run can't separate them.
So the honest statement is small. A bundle of three changes, one of which is inelastic housing,
produced the highest growth and the lowest concentration at one particular horizon, after
spending three quarters of the run in last place. Attributing that to housing policy needs an
experiment I haven't run: elasticity varied alone, across multiple seeds, to a horizon long enough
for the curves to converge. Until then it's a hypothesis with a suggestive chart attached.
Each scenario as one point, concentration on the horizontal axis and growth on the vertical. Housing Constrained sits alone in the upper left. This is a snapshot at step 200; at step 150 the same chart puts it near the bottom.Final growth, ranked. Housing Constrained clears Open Borders by 21.6 points. Whether that exceeds run to run noise is unknown, because every result here is one seed and no variance estimate exists.
03
Open Borders flattens, but the bottom is a four-way tie
Open Borders posts the lowest growth in the panel at +142.2%, alongside the lowest concentration
of any scenario other than Housing Constrained at 18.2%. Removing friction lets people spill out
of the largest cities into the next tier, which evens outcomes out, and the model doesn't pay
extra output for it.
The ranking itself deserves very little weight. Open Borders at 142.2%, High Connectivity at
142.9%, Migration Surge at 143.2% and Fortress World at 143.2% are separated by a single
percentage point across four scenarios with almost nothing in common. Calling one of them last is
a statement about the fourth significant figure of a single seeded run. What is defensible: all
four sit clearly below Housing Constrained and Network Fragmentation, and Open Borders is nowhere
near the top.
That's still the opposite of the usual expectation. The standard argument says frictionless labor
allocation should lift aggregate productivity by moving workers to where they're most valuable. In
this model that effect exists but is small, and it's offset by the loss of agglomeration
concentration when the biggest cities stop pulling hardest. Open borders reads as a distributional
lever rather than a growth lever, with one caveat: the optimizer later picks near zero border
friction as part of its highest scoring blend, so the lever isn't useless. It just isn't
sufficient alone.
GDP trajectories. The curves cluster tightly for the first hundred steps and only separate once agglomeration feedback has had time to compound. The Housing Constrained curve (orange) is the lowest of the eight through the middle of the run and crosses above baseline only at step 181. That late crossover, not an early breakaway, is what produces its headline number.The same data as one panel per scenario, with final growth annotated. Easier to compare trajectory shapes, and easier to see how similar most of them are.
04
The planner built sixteen rail links every time, for a dull reason
All eight scenarios produced exactly sixteen build events, and all 128 of those builds were rail.
The uniformity looks like evidence that the planner ignores the macro regime, and it isn't, for
the reasons set out in the planner section: sixteen is what a twelve step interval yields over
200 steps when the budget never binds, and the planner's demand term reads live population, which
the scenarios redistribute. It may have built sixteen quite different links each time. The
artifacts don't record endpoints, so there's no way to check from these runs.
The genuinely interesting part is the zeroes. Not one pipeline, not one shipping route, in any
scenario. Those branches require resource endowment above 0.95, or two endpoints both above 0.7
port access at long range, and nothing in the generated world clears either bar. Two of the four
infrastructure types the project advertises are, in practice, unreachable code. That's a real
finding about the model, and a more useful one than the planner's build count.
05
Urban concentration lands in a narrow band regardless of policy
Final top 10 share runs from 17.5% under Housing Constrained to 20.6% under Network Fragmentation.
Three percentage points of spread across eight scenarios designed to be as different from each
other as the parameter space allows. Every run starts at 15.9% and climbs, and they all climb to
roughly the same place.
There seems to be a settling point in the model for how concentrated population wants to be. Push
migration up, drop borders, change the infrastructure budget, and the system finds its way back to
somewhere around a fifth of people living in ten cities. The two scenarios sitting lowest, Housing
Constrained at 17.5% and Open Borders at 18.2%, are the two that most directly weaken the pull of
the largest cities, which is at least a coherent story.
Top 10 share over the run, ending between 18.2% and 20.6%. Concentration looks like a deeply baked property of the agglomeration loop, hard to shift by much in either direction.
06
Prices move valuation, not production
The three sector outputs climb together in roughly fixed proportion across every run. Even in the
scenario built specifically to shock prices, relative composition barely shifts.
The reason is structural. Price indices in this version of the model scale what a unit of output
is worth, and they don't feed back into what gets produced. A city's resource extraction depends
on its deposits and remaining reserves, not on the price it can get. Drop the resource price index
to 0.6, which is what Resource Crunch actually does while lifting goods to 1.3, and the same
physical tonnage simply books at a lower value. Useful for modeling inflation and terms of trade.
Not useful for modeling supply response.
Taken with finding 04, this points at a real asymmetry: the model is strong on demography and weak
on production allocation. Cities respond in detail to who lives in them and almost not at all to
what things are worth. The obvious next version adds a specialization feedback so cities drift
toward whichever sector carries the highest price index, which would also give the price scenarios
something to actually do.
Stacked sector outputs for the baseline run. The three layers grow in tandem rather than one taking share from another, which is what you'd expect when nothing in the production function reads the price.
07
The city-level tail deepens with mobility
Aggregates hide a lot. Even where total GDP and total movement barely differ, individual cities
swing hard. Every scenario in the study has at least one city gaining more than 40%, and the worst
single collapse ranges from −49.8% to −97.2% depending on the run.
That range is the finding, and it tracks mobility almost monotonically. Fortress World, with
propensity at 0.008, has a worst case of −49.8%. Baseline, Network Fragmentation and Resource
Crunch, all at 0.020, bottom out around −88%. High Connectivity at 0.024 reaches −93.1%, Migration
Surge at 0.060 reaches −95.2%, and Open Borders at 0.030 reaches −96.9%. The more freely people
can move, the deeper the hole under the cities they move away from.
It's worth resisting the stronger version of this claim. Not every scenario contains a
near-total collapse: four of the eight do, and Fortress World's deepest loss is barely half the
90% mark. Stated as a dose-response relationship instead of a flat assertion, it's less dramatic
and considerably more useful, because it's something you could go and test.
For any policy question that uses a model like this, the tail matters more than the mean. Two
policies producing identical world GDP can differ enormously in who absorbs the cost, and the
losers under one regime aren't the losers under another. One oddity worth flagging: the single
largest gain anywhere in the study is Apia, Samoa, at +215.7% under Housing Constrained. A small
Pacific capital, not a secondary city near a superstar, which doesn't fit the spillover story at
all and probably deserves its own look.
Top gainer and worst loser per scenario. The gainers vary; the depth of the losing tail scales with how much movement the scenario permits.
The optimizer's verdict, and its asterisk
The optimizer is the most interesting thing in the project you can't see in the UI: a cross entropy
search asking which combination of fourteen parameters holds up best when shocks hit. It found
something remarkable, and the project's own guardrails flagged it as unsafe. Both halves belong in
the writeup.
What cross entropy search does
Say you're looking for the best picnic spot in a dark field. You can't see it, but you can drop a
friend anywhere and they'll tell you how nice it is where they landed. So you scatter a dozen
friends, collect their reports, and note which spots scored highest. The top thirty percent are your
elites. Then you scatter a fresh dozen around the elites' average position, with a spread matching
how spread out the elites were, and repeat. The cloud drifts toward the good part of the field.
That's the algorithm, with parameter sets instead of friends and a full shock augmented simulation
instead of a verdict on the grass. It needs no derivatives, each sample is independent, and it copes
with noisy, expensive, discontinuous objectives, which describes this simulation exactly.
What the score rewards
Higher final GDP and higher growth score well. Volatility, concentration and overpopulation all score
badly. There's an additional penalty for spending beyond 92% of the infrastructure budget, to stop the
search buying a few points of GDP by emptying the treasury. The GDP term enters logarithmically, so
doubling output doesn't double the score. That's worth knowing when reading what follows.
The learning curve
Mean score climbs monotonically across the four iterations: 3.62, then 4.22, then 4.48, then 4.68. The best-of-iteration line is noisier and actually peaks in iteration two at 5.50, ending at 5.37. A single best sample can get lucky or unlucky, which is why the mean is the line to watch for whether the search is learning. It is.
The full parameter set
The highest scoring candidate came from iteration two rather than the final round, so this is the best
set found rather than the point the search converged on. All fourteen parameters are listed, including
the several that complicate the story.
Parameter
Pick
Default
Direction
Border friction
0.015
1.00
near the floor of the search range, so effectively open borders
Connectivity multiplier
0.94
1.00
slightly below default
Agglomeration strength
0.71
0.25
2.8× default. Let big cities pull hard.
Housing elasticity
0.01
0.08
the floor of the allowed range. Supply barely responds.
Migration propensity
0.026
0.020
slightly raised
Temperature
1.05
0.70
choices noisier, less sharply optimal
Policy interval
15
12
builds a little less often
Policy strength
0.45
1.00
each new link delivers under half the usual boost
Resource price index
0.92
1.00
marginally cheaper resources
Goods price index
1.06
1.00
marginally dearer goods
IP price index
1.31
1.00
premium tech prices
R&D efficiency
0.70
1.00
cut by 30%, against the grain of the IP price rise
Infrastructure budget
2,955
2,000
48% more headroom
Link base cost
11.5
25
54% cheaper per link
The outcome, under shocks
GDP growth: +341% over a 120 step horizon
GDP volatility: 0.0098, very low. The world barely wobbles when the shocks land.
Top 10 share: 19.2%, mid pack
Infrastructure budget actually spent: 3.1% of what was available
Automated gate evaluation: FAIL
Comparing that growth figure to the hand picked scenarios takes care, because the optimizer runs 120
steps under shocks and the scenarios ran 200 clean. Matched at 120 steps, the eight scenarios grew
between +35% and +58%. The optimizer's blend grew +341%, roughly seven times the best hand picked
result at the same horizon. A gap that large is a reason for suspicion rather than celebration.
The asterisk: the winning blend fails the project's own threshold registry
The result carries automated_gate_evaluation.pass = false. It clears four of five
checks and fails on overpopulation share: 0.394 against a ceiling of 0.35. The
rank one recommendation from the final iteration fails the same check and comes with a reliability
card reading stoplight: "RED", confidence 0.57.
That reframes the whole result. The blend pins housing elasticity to the absolute floor of the
search range and pushes agglomeration close to three times default, a combination that packs people
into cities faster than the model says those cities can carry them. The +341% is what you get by
overshooting the carrying capacity constraint and booking the output anyway. The search wasn't
cheating. It was maximizing the score it was handed, and the score's overpopulation penalty was too
weak to stop it. This is a textbook degenerate optimum, and the right response is to fix the
objective rather than to quote the number.
It would be easy to publish the +341% and leave the gate result in a JSON file nobody opens. That
would get the emphasis exactly backwards. The project built a guardrail specifically to catch this
failure mode, the guardrail fired, and the interesting result is that it fired.
Does the blend tell a coherent story?
Partly. Open borders so people can move, strong agglomeration so productive places stay productive,
inelastic housing so secondary cities catch the spillover, cheap links so the network can adapt. That
reads well. But two of the fourteen picks cut against it. Connectivity came out slightly below
default, and policy strength was more than halved, so each link the planner builds does less than
usual. Premium IP prices sit next to a 30% cut in R&D efficiency, which is close to a
contradiction if the story is that knowledge work is the engine.
A tidy narrative is easy to build by picking the parameters that fit it. With all fourteen on the
table, the likelier reading is that a handful of picks (housing at the floor, agglomeration high,
links cheap) are doing the work, and the rest is noise from a search that only ran 48 trials. The
unspent 96.9% of the infrastructure budget rather supports that. The optimizer didn't discover the
value of optionality so much as decline to use a lever that barely moved its score.
Deep dive: who wins and who collapses under Open Borders?
In the seed 42 run the biggest winner is Yunfu in Guangdong at +172.8%. The steepest declines are
Bahawalpur at −96.9%, Athlone at −96.8%, and Bhubaneswar and Jaffna both at −96.6%. Near total
depopulation, in other words, for a set of mid sized cities.
Open Borders over 200 steps. Left, the ten fastest growing cities: Yunfu +173%, Lucknow +62%, Puyang +58%, Shantou +58%, Shijiazhuang +54%, Lima +53%, Abidjan +53%, Lagos +48%, Bobo-Dioulasso +45%, Foshan +45%. Right, the ten steepest declines, all between −96% and −97%.
The pattern
The winners fall into two groups. Five are secondary Chinese cities (Yunfu, Puyang, Shantou,
Shijiazhuang, Foshan) close enough to Guangzhou, Beijing and Shanghai to catch agglomeration
spillover without being absorbed by them. The rest are large emerging market centers: Lucknow, Lima,
Abidjan, Lagos, each already the natural regional destination once friction drops.
The losers are harder to characterize than they first appear. It's tempting to describe Bahawalpur as
a satellite of Lahore, Bhubaneswar of Kolkata, Jaffna of Colombo, but those pairs sit 350 km, 365 km
and 305 km apart, and Bhubaneswar is a state capital and IT center in its own right. Athlone, between
Dublin and Galway at roughly 125 km and 90 km, is the only one of the four where the description
holds up cleanly.
There's a second problem with the story. The saved artifacts record each city's net population change,
not where its residents went. Saying Bahawalpur lost its population to Lahore is an inference
from the utility function rather than something these runs measured. What the data supports is
narrower: in a low friction world a set of mid sized cities empties out almost completely, and the
softmax makes nearby large cities the likely destination. Confirming that needs the flow matrix, which
the simulator computes and the export throws away.
With those qualifications, the direction is the classic one economic geography has been arguing about
for a century. As friction falls, secondary cities consolidate into fewer, larger ones, and the cities
that suffer aren't the smallest but the ones nearest a winner.
What this actually means outside the simulator
Reading these results as a research artifact is one thing. Reading them as a way to think about real
policy is another, and it's where a model like this is easiest to over-trust. Here's what I think
transfers, and what doesn't.
Housing might be the lever the discourse underrates, but this model can't show that
The story I wanted to tell was that housing elasticity is the most important upstream lever in the
urban system, ahead of borders or infrastructure budgets, and that it's unusually tractable because
zoning, height limits, parking minimums and review processes set it rather than geography.
The first half of that is well supported outside the model. Japan moved to nationally set zoning under
the 2002 Urban Renaissance Act, having weakened local control from the 1980s, and between 2003 and
2015 Tokyo added around 1.5 million people while permitting over 100,000 housing units a year, roughly
eight times San Francisco's rate. Central Tokyo's Minato ward grew its population by 66% over two
decades; a comparable increase in San Francisco came with a 231% price rise. Elasticity is a policy
choice, and the choice has enormous consequences.
What I can't claim is that my simulation shows constrained housing raising aggregate growth.
That result depended on stopping at step 200, and the scenario producing it moved three parameters at
once. It also runs against the best known work in the area. Hsieh and Moretti estimate that housing
constraints in high productivity US metros lowered aggregate US growth by about 36% between 1964 and
2009, leaving GDP roughly 9% below where it would otherwise have been, and that relaxing land use
regulation in three cities alone would raise GDP by 3.7%. On the central question, most economic
geographers have been arguing the opposite of what my run suggests.
The disagreement has a clean mechanical explanation, which is the genuinely interesting part. In Hsieh
and Moretti's framework San Francisco is exogenously more productive than Boise, so blocking access to
it destroys output that can't be recreated elsewhere. In FlowWorld productivity is endogenous to size,
because it comes from the agglomeration bonus, so it regenerates wherever people accumulate. Push
growth out of the superstar and the secondary city simply becomes the superstar. Whether real
productivity is portable in that way is precisely the open question, and my model assumes an answer
instead of testing one. The canonical result is itself contested, too: a 2026 comment in the same
journal reports replication errors that reverse the sign of the counterfactual. The literature is
unsettled, which argues for humility rather than for treating one simulation run as a vote.
Real world spillover patterns are messier than the clean version suggests. San Francisco's housing
costs did help seed Austin, Boise, Denver and Miami from 2020 to 2022. But Bay Area outbound migration
peaked in 2021 and 2022 and has slowed since, Austin now shows up as an origin about as often as a
destination, and Miami recorded the highest domestic out-migration share of any US metro, about 1.8%,
between July 2024 and July 2025. The mechanism is real. The straight line extrapolation isn't.
Open borders is a distributive lever, not a growth lever
This one holds up. Almost every popular argument about open borders is conducted as a dispute about
total output, with more people and better matching on one side and pressure on services and wage
suppression on the other, and the model suggests both sides are measuring the wrong thing.
Open Borders produced the lowest growth in the panel, though as noted it's in a four-way tie at the
bottom and the ranking shouldn't be leaned on. What it clearly produced was a flatter distribution:
less concentration, more activity in the second tier, and violent swings at the level of individual
cities. That's a distribution story rather than a growth story.
Which suggests the debate is framed wrongly. The question isn't whether it makes us richer, it's where
the prosperity lands and which places hollow out. In the model that's determined mostly by geography
and by whether receiving cities can absorb growth. Border policy opens or closes the valve. It doesn't
choose where the water goes.
Agglomeration is the strongest force in the model
Trace any dynamic here back far enough and you arrive at the same loop: bigger cities are more
attractive, attract more people, become more productive, become more attractive. That isn't
controversial. Edward Glaeser and his co-authors have been documenting it since Growth in
Cities in 1992. But a simulation makes the force visible in a way prose can't, and you can watch
the large cities pull harder as a run progresses.
The policy implication is that anything fighting agglomeration head on will underperform in this
model. Spreading investment evenly across regions sounds fair and is, mechanically, a recipe for
slower aggregate growth. The optimizer reached for nearly three times default agglomeration strength
without being told to. I'd hold that conclusion loosely, though, given that the same optimizer run
failed the overpopulation gate. Cranking agglomeration is exactly how it broke the carrying capacity
constraint.
On optionality, and why I think it's overstated
The winning configuration spent 3.1% of its infrastructure budget, and the obvious reading is that
optionality has value: hold capacity, deploy when something breaks, the way a treasury holds reserves.
It's an appealing parallel to corporate finance and emergency planning.
Looking at the full parameter set, I'm not convinced. The same configuration more than halved policy
strength, so each link it built delivered under half the usual benefit. A search that has made
building nearly worthless and then declines to build isn't discovering option value. It's responding
to a weak incentive. The optionality reading needs the unspent budget to be a deliberate hedge, and
nothing in the score function rewards holding a reserve. It only penalizes spending past 92%.
The underlying principle may still be sound. Maintained capacity probably does beat headline projects,
and a country able to build quickly is more resilient than one that has already spent everything. This
run just isn't evidence for it, and it would be a stretch to say the optimizer independently
rediscovered a principle of financial management.
Aggregates hide the distribution
Finding 07 is the one most likely to be overstated, because the dramatic version (every scenario
containing a near-total collapse) is more quotable than the accurate version (half of them do). The
accurate version is more useful anyway: the depth of the losing tail scales with how much movement a
policy permits, from −50% under the most restrictive regime to −97% under the most permissive.
The real world version of that is the question of who pays. A policy raising aggregate GDP while
emptying six mid sized cities isn't the same policy as one raising GDP while preserving them, however
identical the GDP line looks. What a model like this contributes is that the cost stops being
abstract. Bahawalpur, Athlone, Bhubaneswar, Jaffna: they're named, they're on the map, and whether the
tradeoff is worth making is a judgment someone has to make out loud.
What the model is missing
The simulator is strong on demographic and migration dynamics, on agglomeration feedback, and on the
budget logic for infrastructure. Here's what it doesn't have, and what each absence does to the
findings.
No climate. Cities don't become more or less attractive from heat, sea level rise, drought or disaster. That's likely to be a dominant migration force over the next thirty years, so read every result here as “absent climate change.”
No remote work. The utility function assumes you must be somewhere to work there. Fully remote knowledge work decouples wage from location in a way the model can't represent.
No political instability. Cities can't become unsafe, lose institutions or have governments fail. A globally stable institutional environment is a generous assumption.
No demographic transition. No births, deaths or aging. Over a long enough run, fertility collapse in East Asia and Europe would swamp every migration effect observed here.
No AI productivity shock. The IP sector grows on a smooth multiplier. If AI sharply raises knowledge worker productivity, the high knowledge cities that already win in this model would win considerably harder.
No redistribution. No taxes, transfers or safety nets, so wages accrue entirely to the city that earned them. Adding redistribution would change both which cities are attractive and how shocks propagate.
No supply response to prices. Established in finding 06 and worth repeating: prices change what output is worth, never how much of it exists.
Each is fixable in principle and none is trivial. The right way to read the findings is that they
describe a world where every other uncertainty has been resolved, with climate stable, work
co-located and institutions functioning, and only the narrow interaction of migration, infrastructure
and prices left in question. That's a much smaller question than what the world looks like in 2050.
It's also a sharper one, which is the only reason the answers are worth anything.
If these dynamics are real, what should we expect?
This is the speculative section, conditional on the model being roughly right about mechanisms, which
the findings above give you several reasons to doubt. Each prediction names the evidence that would
kill it. Two are already under pressure, and I've said which.
Prediction 1: secondary cities near constrained superstars keep gaining ground, currently under strain
Austin, Boise, Salt Lake City, Nashville, Raleigh, Miami, Calgary, Lyon, Lisbon. Not a random list:
each sits in the gravitational shadow of a constrained superstar. The mechanism says they should keep
accumulating economic weight for as long as the superstars refuse to build.
The honest update is that the last two years have run against this. Bay Area outbound migration peaked
in 2021 and 2022 and has since slowed, Austin appears as an origin about as often as a destination,
and Miami posted the largest domestic out-migration share of any US metro in the year to July 2025.
None of that falsifies the mechanism, since a pandemic-era surge unwinding isn't the same as a trend
reversing, but the straightforward version of this prediction isn't currently being borne out. I'd
want another three years of job growth, price and venture data before claiming otherwise.
Prediction 2: cities beside a megahub hollow out faster than national figures suggest
In the seed 42 run Bahawalpur, Bhubaneswar and Jaffna each lose about 97% of their population under
Open Borders. Those magnitudes are absurd and wouldn't occur, and as noted the runs don't record where
anybody went, so pairing them with Lahore, Kolkata and Colombo is inference rather than measurement.
The directional claim is still testable. Second tier cities in developing countries sitting near a
fast growing primary city should see accelerating population loss even while the national population
grows, and national statistics will mask it because the primary city's gain nets it out. Watch second
tier cities in India, Pakistan, Nigeria, Indonesia and Bangladesh. If the ones near a megahub aren't
losing ground relative to the ones distant from one, the mechanism is wrong.
Prediction 3: countries that fix housing pull ahead on growth
This prediction points the conventional way, because the model result that would have inverted it
turned out to be horizon dependent and confounded. Hsieh and Moretti's estimate says elastic housing
in productive cities raises aggregate output, and nothing in this study is strong enough to argue
against that.
Japan is the cleanest test available, being one of very few large countries with consistently elastic
supply in its biggest city. If elastic housing lets a megahub keep absorbing growth, Tokyo's share of
Japanese GDP should hold or rise over the next decade. If it stalls while Japanese growth turns up
elsewhere, something pulls activity out of megahubs even when housing is cheap. That something would
be the mechanism my simulator accidentally stumbled into.
Prediction 4: optionality looks better as the world gets shockier
The weakest of the six, for the reasons in the optimizer section. The unspent budget probably reflects
a weak build incentive rather than a discovered preference for holding capacity. The underlying bet is
still worth stating: where shocks are frequent, the ability to deploy capital quickly may be worth
more than capital already deployed.
If that's right, countries holding dry powder (Norway's sovereign wealth fund, Singapore's reserve
buffers, Switzerland's debt brake) should outperform countries front loading their infrastructure
spend on resilience measures. The US comparison to make is the Infrastructure Investment and Jobs Act,
the $1.2 trillion 2021 package that funds transport and infrastructure, rather than the Inflation
Reduction Act, which is energy, climate and health legislation and gets misused in this argument
constantly.
Top 10 concentration lands between 17.5% and 20.6% across every scenario in the study, starting from
15.9% in all of them. If that band is a property of the underlying dynamics rather than of my
parameter choices, the share of global population in the largest metros should be a slow, boring
number that barely moves year to year. The exception would be any city running a genuinely aggressive
supply program, which the model says should let it absorb a larger share. Very few cities will.
Prediction 6: open borders would surprise both sides
Advocates would be surprised that aggregate growth moves so little. Opponents would be surprised by
which places actually receive people, since the model sends them to secondary cities in the right
geographic position rather than to the global superstars that dominate the argument. If that's right,
the political coalitions on both sides are organized around a phenomenon that wouldn't look much like
either side's picture of it.
How to falsify all of this
Each prediction has a clear failure condition. Secondary city growth stalls. Megahub-adjacent cities
hold their populations. Tokyo's share of Japanese GDP falls while housing stays cheap. Buffer
countries lose to big spenders on resilience. Concentration moves sharply. Open borders, where tried,
produces one of the two conventional outcomes. Any of those would mean the model has missed something
real and the next version has to account for it, which is roughly what happened to two of the seven
findings above once I went back through the code.
Caveats
Everything above carries the following asterisks. The first four are structural limits on what this
study could ever have shown. The rest are specific weaknesses in these runs.
Single seed. Every result is seed 42. Determinism is excellent for replay and useless for error bars. A serious version re-runs at twenty seeds and reports means with intervals. The 1.15× GDP spread across scenarios may well exceed seed noise, but nothing here demonstrates that, and finding 03's four-way tie at the bottom is exactly the kind of ranking noise would scramble.
200 steps is the wrong horizon, and not only because it's short. Housing Constrained spends three quarters of the run in last or second-to-last place and doesn't cross baseline until step 181. The headline result is a function of where the run stopped. Any conclusion drawn from final-step rankings needs a convergence check first.
The presets vary several parameters each. Six of the eight scenarios move three or four levers at once, so no scenario in this study isolates anything. Every causal claim about a single parameter is, strictly, a claim about a bundle.
Migration volume is an input. Total movers is propensity × population with population conserved, so cross-scenario differences in movement carry no information beyond the slider setting. That also explains why Baseline, Network Fragmentation and Resource Crunch report identical migration: all three leave propensity at 0.020.
The optimizer's headline result fails the project's own gate. Overpopulation share 0.394 against a 0.35 ceiling, with the rank one recommendation flagged RED at 0.57 confidence. Treat +341% as a diagnostic of a weak objective function, not as a policy recommendation.
The optimizer was small. Four iterations of twelve trials, one seed, 20 minutes. A real search would run eight iterations of forty plus trials across several seeds and take most of a day. With 48 trials over fourteen dimensions, most of the parameter picks are indistinguishable from noise.
The planner's builds aren't fully instrumented. The artifacts record how many links were built and of what type, but not between which cities, so there's no way to say whether the sixteen builds differ across scenarios. Two of the four link types, pipeline and shipping, never triggered at all.
Reproducibility has a dependency. Runs are deterministic given the seed and the same city gazetteer, but the gazetteer is an optional download and isn't in the repository. A fresh clone falls back to synthetic cities and won't regenerate the named results here.
Prices don't drive production. Finding 06's sector stability is largely an artifact of price indices controlling monetization rather than physical output. The model is demography-heavy and supply-response-light.
The predictions are speculation. They extend model findings to real world expectations, and models degrade in proportion to how far they're extrapolated. Treat each as a falsifiable claim rather than a forecast.
This is a model. FlowWorld is a policy laboratory, not an oracle. It's built to show which direction things move when you twist a rule, not to predict city populations in 2050. Read the magnitudes as ordinal.