GitHub - Ethical-Tech-CoLab/3d-agentic-evacuation-simulation: 3D Agentic Evacuation Simulation — browser 3-D evacuation twins of four cities (Mariupol, Lower Manhattan, Las Vegas Strip, Miami) on a CARTO basemap. An agentic synthetic population, classified by who they are, who they travel with and how they behave, picks between real road-routed evacuation routes. · GitHub
Skip to content

About

3D Agentic Evacuation Simulation — browser 3-D evacuation twins of four cities (Mariupol, Lower Manhattan, Las Vegas Strip, Miami) on a CARTO basemap. An agentic synthetic population, classified by who they are, who they travel with and how they behave, picks between real road-routed evacuation routes.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

16 Commits

Folders and files

Repository files navigation

3D Agentic Evacuation Simulation

Browser 3-D city twins on a CARTO basemap, populated by an agentic synthetic population that has to get out — choosing between real evacuation routes traced over each city's actual OpenStreetMap road network.

City Hazard Routes Buildings Cohort figures
Mariupol Siege, March 2022 4 45,544 Published ETC severity-model cohorts, five surveyed zones
Lower Manhattan Coastal storm surge (Zone 1) 5 road + 7 transit 17,689 ACS 2024, New York County
Las Vegas Strip Mass-gathering egress 4 2,607 ACS 2024, Clark County — but see below
Downtown Miami & Brickell Hurricane (Zone A) 4 14,054 ACS 2024, Miami-Dade County

Real demography

Every city now carries real cohort figures, not a generic average. The three US cities come from the Census Bureau's American Community Survey 2024 1-year estimates (tables B01001 and B18101, via the Census Reporter API); Mariupol keeps its published ETC severity-model cohorts.

The four cohorts are made mutually exclusive so they partition the population, which is the only way they can be counted without double-counting anyone: child is under 18, elderly is 65 and over, disabled is ages 18–64 with a disability, and adult is everyone else.

Child Elderly Disabled (18–64) Adult
Mariupol 16.1% 21.7% 4.9% 57.2%
Lower Manhattan 13.3% 18.7% 5.9% 62.1%
Miami-Dade 19.7% 17.2% 3.9% 59.2%
Clark County 21.5% 16.5% 7.4% 54.6%
Strip visitors 6% 11% 4% 79%

Two caveats, both stated in the app. These are county figures applied to a district — Lower Manhattan is not demographically identical to all of New York County, and sub-county geographies were not reachable through the open API. Zone populations remain modelled; only the cohort proportions are sourced.

And the Las Vegas Strip is a genuine problem. At any given hour the Strip is mostly visitors, not Clark County residents, and their age structure is nothing like it. A separate Strip visitors population mix is provided: its child share is derived from LVCVA visitor-profile data (~13–14% of visitors bring anyone under 21; mean visitor age 43.6 in 2024), but its elderly and disabled shares are modelled, not sourced, and the app says so. Residents against visitors is a 45.4% vulnerable share against 21.0%, and it moves median clearance from 3.03 h to 2.68 h.

Switch cities in the console, or with ?city=mariupol|nyc|vegas|miami.

Live: https://ethical-tech-colab.github.io/3d-agentic-evacuation-simulation/

Open-data, non-operational. A planning and teaching artifact: one retrospective case that ended in 2022, and three illustrative ones built on real geography with modelled populations. It is not a tracking tool, it is not keyed to any live individual location, no agent corresponds to a real person, and none of it should be read as operational guidance or as an evacuation plan for any of these cities.


What it is

Two existing pieces of work, joined:

From What it contributes
ETC mariupol-evacuation-model The real city and the real demography — 45,544 OSM building centroids, 783 UNITAR/UNOSAT verified damage points, and the five published emergency-zone cohorts (37,663 exposed, 16,102 vulnerable)
Race Condition (via the ETC fork race-condition-mod) The agent model — thousands of autonomous agents on a shared spline, each with its own pace, the field stretching out behind the leaders

Race Condition puts a marathon field on a route through a 3-D city. An evacuation is the same shape of problem: many people, imperfect information, shared routes, bottlenecks, and a clock. This repo keeps that loop and changes two things.

What slows an agent down. A runner is slowed by fatigue; an evacuee is slowed by congestion where the crowd funnels onto a route, by hazard on the leg out of the district, and by not knowing whether the way out is open.

How many courses there are. A marathon has one. Every city here has four or more, each a Dijkstra path over its real road graph, and every agent has to choose. That choice — made on imperfect information, with a pull toward whatever the crowd is doing — is most of what the model is about. A route is only fast until everyone picks it.

The rendering is a deliberate departure from the fork: instead of a photogrammetry GLB and a bespoke Three.js scene, this is deck.gl over a CARTO vector basemap — keyless, free, ~40 KB of application code, and every layer is georeferenced rather than model-space.

Basemaps and camera

Three basemaps, because they answer different questions:

What it is Good for
Dark CARTO dark matter The city recedes; the agents carry all the colour
Colour CARTO Voyager Street names, parks, land use — sharp at every zoom
Satellite Sentinel-2 cloudless (EOX, CC BY 4.0, Copernicus) Checking a route goes where the ground says it does

The imagery is Sentinel-2 rather than Esri. Esri's World Imagery is sharper in cities, but it is display-only under Esri's terms; this repo's own terrain plan sets the rule of preferring Copernicus so an open, attributable, redistributable dataset stays open. The honest cost is resolution: the mosaic is 10 m, so it softens as you zoom into a street. Attribution follows the active basemap.

The camera turns a full 360°. Drag with the right mouse button, press Q and E, or use the buttons: rotate, face north, tilt to street level, and a slow orbit. Any manual input stops the orbit rather than fighting it.

Reading the map

By default every household has its own colour, built from four of its own variables at once, so a crowd looks like a crowd of individuals rather than four flat categories:

Channel Variable
Hue behaviour, nudged by cohort — an elderly seeker is visibly not an adult seeker
Saturation information quality — washed out means badly informed
Lightness walking speed — dark means slow
Size household headcount

A big, dark, washed-out dot is a large, slow, badly-informed household, and you can find it on the map without reading a number. The other four modes colour by a single axis when you want to answer "where are the elderly?" instead.

The agents

Every agent is classified on three independent axes. It matters that they are independent: an elderly person travelling alone and an elderly person inside a family that is waiting for a son to come home are the same row in a census and completely different evacuation outcomes.

One agent is one household, not one person, and it carries a weight: how many real people it stands for. Everything the model counts is counted in people, by summing weights. This is what makes the Agents slider a resolution control rather than a population size — the weights always sum to the exposed population, and the answer does not move when you change it. npm test asserts exactly that.

1 · Who they are — adult, child, elderly, disabled. Sets base walking speed. Weights are post-stratified so the person-weighted cohort shares match the published split exactly, correcting for the fact that household size is not independent of who is in the household.

2 · Who they travel with — the decision unit, which is what actually moves, at the pace of its slowest member:

Unit Share Size Why it matters
Alone 28% 1 Nobody to wait for. First out.
Family 44% 2–5 Will not leave until it is whole — the largest single source of delay
Ad-hoc group 20% 3–10 Forms out of people already moving; the strongest transmitter of crowd route choice
Institutional 8% 12–40 A ward, a care home, a coach. Cannot self-evacuate; waits for transport that may not come

3 · How they behave — the axis no census records, and the one that decides who is still inside when a route closes:

Behaviour Share What it does
Prompt 24% Acts on the first warning it believes
Information-seeker 30% Will not move until it confirms the warning elsewhere
Wait-and-see 26% Watches the neighbours; a district sits still, then empties at once
Reluctant to leave 13% Will not go while staying seems survivable. Only rising hazard moves them
Returner 7% Leaves, then goes back — for a person, a document, an animal

Each agent then carries an information quality (how soon the warning reaches it, how well it can judge a route), a risk tolerance, and an origin jittered inside its zone.

The lifecycle

UNAWARE → SEEKING → MILLING → EVACUATING → (RETURNING) → DONE — the same vocabulary the CoLab's Evacuation Behavior Simulator uses, deliberately. Milling burns down faster the more of the district is visibly already moving, so the whole population can stall and then go at once. Every transition rule, the full route-choice and closure logic, and what each marker on the map represents are written out in docs/AGENT_DECISIONS.md.

Controlling the population

The shares are the weakest numbers in the model, so they are the ones you can move. Every share is a slider; editing one rebalances the rest of its axis to keep the total at 100%, marks the axis custom, and lands in the URL — so a configured population is a link you can send someone.

By default the cohort split is the city's own per-zone figures, which no single set of city-wide sliders can represent without losing that resolution. Touch a cohort slider and the app says plainly that you have replaced them.

There are also four variability controls — how much individuals differ from their group's mean in speed, information, risk and timing. At zero, everyone in a cohort is identical: a useful control run and a terrible model of a city.

Presets — each one a claim about a population, with the claim written down: As sourced, Ageing district, Family district, Visitors & crowd, Drilled population, Warning not believed, Uniform (control).

Weather and time of day

The same population, on the same roads, evacuates very differently in freezing rain at three in the morning than on a clear afternoon. Eight weather states and three times of day compose into multipliers on things the model already has — walking pace, route capacity, added hazard, how well anyone can judge a route, how long the warning takes to land, and how long a household takes to gather. The console prints the resulting effect in plain terms, so a run is never quietly shaped by numbers nobody looked at.

Conditions apply live, without rebuilding the population — you can watch fog roll in over a run already under way.

Lower Manhattan, same population and seed, 3,000 agents:

Conditions 50th 90th Elderly Disabled
Clear · evening 1.3 h 2.3 h 1.7 h 2.0 h
Clear · day 1.5 h 2.5 h 1.8 h 2.1 h
Heavy rain · day 1.8 h 3.4 h 2.4 h 3.2 h
Clear · night 1.8 h 3.2 h 2.3 h 2.8 h
Ice · day 2.1 h 4.1 h 3.5 h 4.7 h
Ice · night 2.9 h 6.6 h 5.6 h 7.4 h

The headline number roughly doubles. The number for disabled evacuees more than triples. That gap — not the median — is what the weather actually does.

What it shows

The console foregrounds 50th and 90th percentile clearance time, measured on when an agent actually reached the far end of its route — not when it left the district, which flatters anyone who turned back. Some results that fall out of the model rather than being put into it:

  • Districts mobilise at different times, not in lockstep. Social proof is measured per zone — milling is watching your own neighbours, not reading a city-wide statistic — and the warning reaches districts unevenly. Zones diverge by up to 42 percentage points: one district can be 15% mobilised while the one beside it is at 58%.
  • People re-route around jams, if they can see them. An agent still in its own district reconsiders every five minutes and will switch if another route is clearly better. In fog, far fewer do — you cannot be re-routed by a queue you cannot see.
  • Institutional units clear in ~5.9 h against ~3.3 h for people travelling alone — nearly double, on the same routes.
  • A returner costs itself ~2.6 hours.
  • At low hazard, a persistent minority refuses to leave; raise the hazard slider and they finally move — and the median clearance time gets worse, because they are now leaving late and into a crowd.
  • Reaching an exit is not reaching safety. Mariupol's eastward route led to filtration, and the households that take it are counted separately, never as evacuated.

Transit — a door the weather can shut

Lower Manhattan does not empty over its bridges. It empties through Fulton Street, the PATH, and the Staten Island Ferry — until the operator stops them. The city pack now carries every subway complex, PATH station and ferry landing inside the zone (66 of them, from the MTA's open station list and the NYC Ferry and PATH GTFS feeds), and seven of them are transit exits: routed over the road graph exactly like a bridge, with their own boarding capacity.

A transit exit differs from a bridge in two ways, and both are the point.

  • The weather shuts it, mode by mode. Each weather state and time of day carries a multiplier for ferries, subway and rail. A gale ties the boats up (ferry 0) before it slows anyone walking; heavy rain halves the subway through flooded tunnels; a severe storm or tornado stops surface rail and boats while the subway, underground, keeps running until the water arrives; at night every mode runs on long headways. Zero means suspended: the station is drawn hollow and grey, and anyone who can see the news stops choosing it.
  • The operator shuts it on a clock. In a forecast hazard transit stops before the hazard. The MTA suspended the subway at 19:00 the evening before Sandy, seven and a half hours after the evacuation order. The Transit runs until slider sets that hour; at 24 h only the weather decides.

A household that walks to a shut station does not sleep on the steps. It picks the best route still running, from where it is standing, and walks there along a precomputed road leg from that station — so nobody crosses the East River to reach the Brooklyn Bridge. Those households are counted: found the station shut is a number on the console, because it is the number a shutdown clock exists to produce.

Lower Manhattan, clear day, 3,000 households, road capacity 900/min:

Transit runs until 50th 90th Left by transit Found the station shut
never (no transit) 2.61 h 3.91 h 0% 7%*
T+1 h 2.69 h 4.05 h 1% 15%
T+2 h 2.71 h 4.79 h 11% 30%
T+3 h 2.35 h 3.58 h 33% 10%
T+8 h (Sandy) 2.35 h 3.23 h 43% 0%
weather only 2.35 h 3.23 h 43% 0%

* The badly informed head for a station they cannot know is shut.

The result that matters is the T+2 row: a shutdown two hours in is worse than having no transit at all. The 90th-percentile clearance is 53 minutes longer than with the stations closed from the start, because nearly a third of the population walked to a station, found it shut, and only then started for a bridge. It is an emergent result — nothing in the inputs says it — and it is exactly the trade-off an operator faces: run the trains as long as the water allows, or stop early and send everyone to the bridges from the first minute.

The same population in a gale: ferries carry nobody, the median rises from 2.35 h to 2.98 h, and 1% of households still walk to Whitehall to find the boats tied up. Station locations and line service are real; the boarding capacities and every weather multiplier are modelled and are listed in docs/METHOD.md.

Tests

npm test

No dependencies; the tests are the point. They encode the invariants that a model like this has to hold — most importantly that the answer must not depend on how many households are simulated, which is the defect that a full review of this repo turned up first. Others assert that weights sum to the exposed population in every city, that a filtration exit is never counted as safety, that capacity actually constrains a route, that no cohort can deadlock, that no agent in Lower Manhattan starts in or crosses the Hudson, that nobody boards a train after the shutdown clock and nobody is stranded by it, and that a gale carries nobody by ferry.

Layers

Layer Source Fidelity
Basemap CARTO dark-matter (OSM data) real
Buildings OSM centroids, extruded columns real positions; heights are OSM's own where recorded — 96% in Lower Manhattan, 88% in Miami, 14% on the Strip, 0% in Mariupol. The rest get a low synthetic massing, tinted blue so you can see which parts of the skyline are evidence
Damage 783 UNOSAT points, CE20220223UKR, 14 Mar 2022 real, severity 0–3
Zone cohorts ETC severity model, late Mar–Apr 2022 real counts; marker lon/lat placed in the city centre, not surveyed centroids
Roads OSM highway network per city, via Overpass real; the routes are Dijkstra paths over this graph
Agent origins real OSM building centroids inside each zone real; people start in buildings, so nobody starts in the river
Approach legs road path from each zone to each route, per zone × route real; every metre an agent walks is on a road
Evacuation routes 4–5 per city, to real named egress points real road paths; which exits are designated is a judgement, documented per city
Stations & landings MTA Subway Stations (NY Open Data), NYC Ferry GTFS, PATH GTFS; Staten Island Ferry and Penn Station placed from the operators' terminal locations real positions, lines and ADA flags; Lower Manhattan only so far
Transit exits 7 of those stations, routed over the road graph; transfer legs from each station to every other route real road paths; which stations, and their boarding capacity per minute, are modelled
Agents generated here synthetic; cohort mix real for Mariupol, modelled elsewhere; unit and behaviour mixes modelled everywhere

The hashed building heights and the schematic corridor are the two places where the picture is illustrative rather than surveyed. Both are labelled in the app.

Running it

No build step, no keys, no backend.

git clone https://github.com/Ethical-Tech-CoLab/3d-agentic-evacuation-simulation
cd 3d-agentic-evacuation-simulation
python3 -m http.server 8000
# open http://localhost:8000

Layout

index.html          console + scene shell
src/population.js   aggregates -> individuals, on three classification axes
src/engine.js       lifecycle, route choice, pace, congestion, hazard
src/cities.js       the city registry
src/map.js          CARTO basemap + deck.gl layer definitions
src/app.js          wiring, clock, statistics
tools/fetch_city.py builds a city pack from OSM: buildings, roads, routes
data/cities/<id>/   one pack per city
docs/METHOD.md      what is real, what is modelled, what is illustrative
docs/AGENT_DECISIONS.md  every agent decision rule, route closures, what each dot means
docs/LOCAL_DECISION_MODEL.md  running a decision model in-browser or on our own GPU

Attribution

Basemap © CARTO, data © OpenStreetMap contributors (ODbL). Damage points UNITAR/UNOSAT (CE20220223UKR). Zone cohorts from the Ethical Tech CoLab mariupol-evacuation-model. Agent model after Race Condition (Apache-2.0, Google Cloud Platform).

An Ethical Tech CoLab project.

Enabling CI

The test workflow is not committed: the token used to push this repo lacks the GitHub workflow scope. To turn it on, either run gh auth refresh -s workflow and commit .github/workflows/ci.yml, or add it through the web UI — it needs nothing but actions/checkout, actions/setup-node, and npm test.

About

3D Agentic Evacuation Simulation — browser 3-D evacuation twins of four cities (Mariupol, Lower Manhattan, Las Vegas Strip, Miami) on a CARTO basemap. An agentic synthetic population, classified by who they are, who they travel with and how they behave, picks between real road-routed evacuation routes.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages