Releases: pendulating/charleston-sc
Release list
1.5.0
Reverts the map to its original 1.0.0 extent.
The tri-county extent introduced in 1.2.0 was defensible on coverage — it reached Moncks Corner and St. George, which this box does not — but at 3.4× the area it made the map heavy to play. The performance work in 1.3.1, 1.4.0 and 1.4.1 took the payload about as far as it goes without giving something up, so this gives up the area.
Back to -80.3018, 32.5526 → -79.6701, 33.1133, about 3,663 km².
Existing networks line up. Coordinates are absolute lat/lon and nothing is stored relative to the bounding box, so changing the extent moves nothing — a station stays exactly where it was. What falls off the map is the outer ring: Moncks Corner, St. George, McClellanville, Edisto Beach, and the northern Berkeley and Dorchester countryside. If you built out there, back your save up first.
| 1.4.1 | 1.5.0 | |
|---|---|---|
| points | 2,566 | 1,448 |
| pops | 32,927 | 24,039 |
| people | 583,085 | 513,188 |
| people per pop | 17.7 | 21.3 |
CHS.pmtiles |
65.8 MB | 38.1 MB |
| buildings index (raw) | 54 MB | 41 MB |
| ocean depth (raw) | 22.1 MB | 8.4 MB |
| archive | 129.3 MB | 82.2 MB |
At 21.3 people per pop the map now sits close to Tampa's 26.5, well above the 13.8 median across the 50 US LODES maps in the registry.
Everything the recent releases fixed carries over: the attendance-zone school model, commute exponents calibrated against the real LODES distances, the airport and lodging tourism split, no embedded driving paths, and the packaging preflight.
1.4.1
Performance release. The ocean depth index drops from 79 MB to 22 MB, and it was the largest thing the game parsed for this map.
It was not the offshore data. 91% of the file was the shallowest band. depot patches −5 m with water_gaps — "everywhere OSM says there is water but GEBCO left a gap" — and GEBCO's global grid is about 450 m, so every tidal creek and marsh channel in the Lowcountry arrived as hand-detailed shoreline: 81,134 polygons and 3.07 million vertices for that one band. Deep water is nothing beside it; the −30 m band is 190 polygons and 0.1 MB.
That detail is far finer than anything consuming it. The index addresses water through a grid of 0.0027° cells, roughly 250 m a side, so shoreline resolved to a metre buys nothing.
Simplifying at ~9 m and rounding coordinates to ~1 m:
| before | after | |
|---|---|---|
| vertices | 3,306,500 | 741,633 |
| uncompressed | 79.1 MB | 22.1 MB |
| gzipped | 16.2 MB | 4.0 MB |
Total water area falls 0.53% (1.55% on the −5 m band) — outlines tightening by well under half a grid cell.
Deleting far-from-shore polygons would have been the cheaper cut but the wrong one: absence of a polygon means no water, so dropping marsh creeks would have turned them into buildable land. Every polygon and every hole is kept, so water stays water.
Archive drops from 150.4 MB to 129.3 MB. Demand unchanged at 583,085 people across 2,566 points and 32,927 pops.
Across 1.3.1, 1.4.0 and this release, what the game loads has gone from roughly 380 MB to 160 MB.
1.4.0
Performance release. Half the pops to simulate, at the same population.
The simulation iterates pops, not people, so the number that matters is people per pop. Across the 50 US LODES maps in the registry the median is 13.8 and Tampa is 26.5. This map was at 8.8 — below the 25th percentile.
| pops | people/pop | |
|---|---|---|
| 1.3.1 | 66,179 | 8.8 |
| 1.4.0 | 32,927 | 17.7 |
| US LODES median (n=50) | — | 13.8 |
| Tampa | 60,755 | 26.5 |
The base LODES layer was the whole of it — 5.1 people per pop, against 19.3 for the special demand. Two settings at the generator, both matching Tampa's config:
DISTANCE_THRESHOLD_NONCBD0.1 → 0.2, which on its own took the base from 53,586 pops to 21,805 for identical peopleCONSOLIDATE_POPSon, merging the small ones
The finer CBD threshold still applies downtown, so the peninsula keeps its resolution — the coarsening is only outside the central business district.
On top of that, identical commutes are now merged after the special demand is placed: the school and lodging allocators hand one residence node several pops bound for the same school or hotel, and those are one commute, not several. Anything above a 200-person trainload is split back out.
School pop size deliberately stays at 15 rather than rising to reach Tampa's ratio. Raising it to 25 would save another ~4,000 pops but drops the residential nodes eligible for a school seat from 1,470 to 1,231, which erodes the attendance-zone model for less than it gains.
Demand is unchanged in substance: 583,085 people across 2,566 points, median commute 20.8 km. Map files unchanged from 1.2.0.
Together with 1.3.1, demand_data.json has gone from 74 MB to 5.7 MB and the simulated entity count has halved.
1.3.1
Performance release. demand_data.json drops from 74 MB to 11 MB.
depot embeds the full polyline of every commute in the demand file when it routes. At 43 vertices per pop that came to 2.85 million vertices — 74 MB, of which 85% was path geometry. That is 1.1 KB per pop against a median of 0.18 KB across the 274 registry maps that ship an uncompressed demand file, a 6× outlier. Tokyo fits 314k pops in 50 MB; at Charleston's density it would need 340 MB.
Nothing reads them from there. The game requests a path one pop at a time over map://paths/{cityCode}/{popId}, which Railyard's map loader intercepts and serves — which is why essentially no other map embeds them.
drivingSeconds and drivingDistance, which the simulation does need, are unchanged.
This was not the bounding box. Measured against the registry, every area-driven file is unremarkable for a map this size:
| file | Charleston | peers |
|---|---|---|
| PMTiles | 66 MB | Baltimore-DC 143 MB, Portland 132 MB |
| buildings index | 54 MB | Tampa 155 MB |
| ocean depth | 1.3 KB/km² | Warsaw 2.9, Tokyo 9.9 |
Archive drops from 160.8 MB to 151.1 MB. Demand unchanged at 582,306 people across 2,359 points and 66,179 pops. Map files unchanged from 1.2.0.
1.3.0
Correctness release. An independent review of the special demand implementation found six real bugs, all confirmed by running code. Every one produced plausible-looking output with no error, which is why none surfaced in play.
Lodging visitors were inflated 2.6×. OSM maps most hotels twice — once as a POI node, once as the building outline — and the dedup key was name plus rounded coordinates, so representative points tens of metres apart kept both. 604 apparent properties were really 374. Overnight visitors go from 42,740 to 19,698. This was the largest quantitative error in the special demand, and it shipped in 1.2.1 through 1.2.3.
A school was silently disappearing. Point ids are built from a POI's code, falling back to its name, and there are two different Palmetto Christian Academy campuses. Both resolved to the same id and were merged, putting 1,470 people on one point and dropping the other. Schools now carry their NCES id.
The special-demand schema accumulated garbage. save_schemas appends to the file already on disk, and del_points never touches it, so every surgical rebuild left replaced points in twice. The working file had reached 530 entries for 313 points with 9,317 dead pop_ids.
Rebuilds were not reproducible. The staff sampler was seeded with hash(name), which Python salts per process — so each run allocated differently, pupils included, since the draws share a budget.
Two fully-virtual schools generated physical commutes to an administrative address.
merge_within was a silent no-op on 15 of 35 points. The airport's nearest LODES block is 954 m away against a 700 m radius, so AIR_CHS had no employment at all. Radii are now sized against real geometry. Universities were also processed before hospitals, so UNI_MUSC had absorbed the 10.5k-job medical centre block; hospitals now go first and it lands on HOS_MUSC.
Also fixed: airport arrivals lost 11% to double truncation; a latent AttributeError in depot that would discard an entire routing pass on any unroutable point; zero-length self-loop pops dropped. package.py now runs a preflight that refuses to ship unrouted pops, a duplicated schema, or a config population disagreeing with the demand.
Commute distances still track real Charleston travel (LODES 14.0 km median): military 14.1, airport 14.1, resorts 14.9, hospitals 12.4. School load per residential node stays at zero nodes above 100%.
Demand totals 582,306 across 2,359 points and 66,179 pops. Map files unchanged from 1.2.0.
1.2.3
Calibrates worker commute distances against real Charleston travel.
Charleston is not transit-oriented — people drive a long way to work — but depot's default distance decay exponents assume otherwise, and every worker flow into a special demand point was coming out far too tight.
The base demand already holds the evidence. Its LODES commutes are real Charleston journeys: a 13.9 km median, 34.8 km at the 90th percentile. Measured against that, the exponents are now set explicitly rather than left at the type default:
| flow | exponent | median commute |
|---|---|---|
| School staff | 2.5 → 1.2 | 9.5 → 13.4 km |
| Military personnel | 1.2 → 0.8 | 11.9 → 14.4 km |
| Resort staff | 2.0 → 1.2 | 0.3 → 24.7 km |
The resort figure was the real defect. At the default exponent of 2.0 the median resort worker lived 300 metres from the resort — the barrier islands have almost no residents of their own, so a steep decay handed the entire staff to the handful who are there. Kiawah and Seabrook service staff are priced off the islands and drive in from Johns Island and West Ashley, which is what 1.2 reproduces.
Categories whose capacity is patients, shoppers or visitors rather than staff are deliberately unchanged — non-work travel genuinely is shorter than commuting, and those sites' own employees arrive through merge_within already carrying their real LODES commutes. Pupils keep their short trips because they are zoned.
Also fixes a latent ordering bug on the full-rebuild path, where the lodging clusters' airport links could bind to the wrong point silently.
Demand totals 607,276 across 2,361 points and 67,890 pops, rebuilt from seed. Map files are unchanged from 1.2.0.
1.2.2
Widens school attendance zones and lands airport arrivals downtown.
Attendance zones → 5 / 8 / 12 miles
Catchments were 3/5/8 km, which is tight for South Carolina — districts here are county-wide and rural zones run long. They are now 5 miles for primary, 8 for middle and 12 for high schools.
| 1.2.1 | 1.2.2 | |
|---|---|---|
| residence nodes feeding a median school | 26 | 40 |
| Summerville High feeders | 122 | 213 |
| nodes sending >100% of their residents to school | 19 | 9 |
| 95th percentile | 81% | 74% |
| school trip p50 / p90 | 2.9 / 9.2 km | 7.1 / 16.3 km |
Airport arrivals now reach the peninsula
Arrivals previously dispersed by gravity from the airport, which put most of them in North Charleston — it's 3 km away, and no distance decay is going to favour a peninsula 15 km off.
Arrivals now split. Thirty per cent still disperse: residents coming home, people staying with family, business trips to the North Charleston office parks. The other 70% are visitors heading for a hotel, and 70% of those go to the downtown peninsula. Weighting them by room count alone would have sent only 37% downtown, because the airport strip and North Charleston hold a lot of rooms serving business and Boeing traffic, while leisure visitors flying into Charleston overwhelmingly stay on the peninsula.
76% of hotel-bound arrivals now land on the peninsula, and 54% of all arrivals.
Demand totals 607,276 across 2,361 points and 67,890 pops. Map files are unchanged from 1.2.0.
1.2.1
Fixes how school demand is distributed, and models Charleston's tourism properly.
Schools were collapsing onto their nearest neighbour
Every special demand category lets depot place people by gravity, weighting a residential node by residents / distance ** exponent. For schools that breaks down: distances are in metres and the default school exponent is 2.5, so a node 0.5 km from the school outweighs one at 5 km by 316×, and the whole intake lands on the one or two closest nodes. A median school in 1.2.0 drew from 7 residence nodes; Wando High took 95% of its pupils from a single one.
That is visible in play because add_points credits pupils back to the residence node as residents, so those nodes inflate until their population is mostly their own outbound student flow. Measured against the LODES residents those nodes actually have, 1.2.0 left 172 of them sending more children to school than they house — the worst at 3,990%.
Schools are the one category whose destination has a legally defined catchment, so they are now placed explicitly. Every residential node in the attendance zone is entered in proportion to its residents: 3 km for primary, 5 km middle, 8 km high, growing only where a zone cannot supply its school. Nodes take a pop only when they have headroom for a whole one, and schools are placed largest-first against a running per-node budget so overlapping catchments cannot oversubscribe a node between them. Staff stay on the gravity model — teachers are not zoned.
| 1.2.0 | 1.2.1 | |
|---|---|---|
| nodes sending >100% of their residents to school | 172 | 19 |
| 95th percentile | 603% | 81% |
| worst node | 3,990% | 333% |
| residence nodes feeding a median school | 7 | 26 |
| Summerville High feeders | ~7 | 122 |
Tourism
The airport was carrying 13,800 passengers a day, anchored on the original map's 6,900 departures rather than on real traffic. Charleston International served 6.1M passengers in 2023 — 16,712 a day both directions — so it now carries that, split evenly between arrivals and departures.
The bigger gap: airport passengers are only a slice of Charleston tourism. Most visitors drive in, and none of them were on the map. There are now 58 lodging clusters holding 42,740 overnight visitors, built from OSM accommodation — rooms where tagged, the median for that lodging type where not, at 70% occupancy and 1.9 guests per room, gridded at roughly a kilometre so tourism spreads across the hotel districts instead of piling onto one node. The downtown peninsula alone holds 7,575. Anything within 3 km of an existing beach resort is dropped so the two don't double-count.
Visitors are pure origin points: they live at their hotel for the day and travel out to the peninsula, the beaches and the forts.
Demand totals 607,876 across 2,361 points and 67,740 pops. Map files are unchanged from 1.2.0.
1.2.0
Expands the map to the Charleston tri-county area and rebuilds special demand.
Coverage
The playable area goes from 3,663 km² to 12,392 km² — -80.68, 32.47 → -79.43, 33.43, 117 × 106 km. The old boundary cut off both Berkeley and Dorchester at the county line, leaving Moncks Corner and St. George — the two county seats — outside the map entirely.
The new box was derived rather than eyeballed: it contains all 27 incorporated places in Charleston, Berkeley and Dorchester counties, 99.3% of tri-county LODES activity by block group, and every barrier-island resort, while trimming the empty Cape Romain marsh and the deepest Francis Marion forest. That keeps it to 3.4× the old area rather than the 4.5× a strict county rectangle would have cost.
Demand
Base demand is regenerated from LODES 2023 via slurry's US Demand Generator — an expanded bbox needs this, or the new territory has no demand at all.
Special demand went from 20 points to 255, applied through depot's add_points, which also emits the .railyard_map/special_demand_*.json schema that 1.0.0 and 1.1.0 both shipped without. Until now the game had no type information for any of these points.
- Schools — 215 points, entirely new. Real per-school enrollment from the NCES Common Core of Data and Private School Universe Survey, public and private, with staff at teacher FTE × 1.9. Summerville High, for instance, now carries 3,308 students and 308 staff. The map previously modelled no school trips whatsoever.
- Airport tourism.
AIR_CHShad 6,900 jobs and zero residents, so the map had departing passengers and no arriving ones. It now splits evenly between the two, giving Charleston the inbound visitor flow a tourist city should have. - Resorts. Kiawah, Wild Dunes, Folly Beach, Seabrook and Edisto, weighted heavily residential — a guest in a beach rental travels out for the day.
- Hospitals, ports, retail, heritage. Eight hospitals sized on OSM bed counts, five port terminals including cruise arrivals at Union Pier, the three malls, the aquarium, City Market, both forts, Patriots Point and the International African American Museum.
- Reclassification. PSWBSF's custom
JBCS/JBCN/NWS/NPTU/NPTC/CGCcodes now map onto depot's military type, and the genericENTpoints are typed as shopping centres, museums, heritage sites and an aquarium.
Sizing follows one rule, documented in build/special/pois.py: a point carries only the trips LODES never sees. Airports carry passengers, not airport workers. Hospitals carry patients, not nurses. merge_within folds the site's existing LODES block point in so its staff aren't counted twice. Schools are the deliberate exception, carrying students and staff without a merge.
Demand totals 561,462 travellers across 2,303 points and 60,290 pops, up from 323,867 across 1,113 points. Commutes and driving paths recomputed through OSRM; median commute is 25.2 km / 27.2 min, up from 21.1 km as the outer county towns come into range.
Map
All layers rebuilt for the new extent from current OSM and Overture data, with GEBCO 2026 bathymetry. CHS.pmtiles is 66 MB, CHS_foundations.pmtiles 20 MB.
Map by PSWBSF. Demand from slurry's US Demand Generator. Schools from NCES. Map data © OpenStreetMap contributors, Overture Maps Foundation, and GEBCO.
1.1.0
Rebuilt with depot 1.2.7 for current Subway Builder. The 1.0.0 map predates the game's 1.4 map format.
The playable area is unchanged (-80.3018, 32.5526 → -79.6701, 33.1133), so networks built on 1.0.0 still line up.
Map
CHS.pmtilesrebuilt to game map format version 2 with the layers the game expects —ocean_foundations,commercial,hospital,industrial,residential, and the city/suburb/neighborhood label layers. The 1.0.0 tiles were a raw Protomaps basemap with none of these.- Building foundations (
CHS_foundations.pmtiles) and ocean foundations added, so track placement is constrained by buildings and by the sea bed. - Ocean depth index and contours added from GEBCO 2026.
- Buildings index rebuilt from current Overture data (272,897 buildings), shipped in both the packed binary and JSON forms.
- Roads and aeroways regenerated from current OSM. Runways and taxiways are now distinguished — 1.0.0 tagged all 124 aeroway features, aprons included, as runways.
- Island place labels added: Charleston names 46 of them, and they read as places here rather than landforms.
Demand
- Same demand as 1.0.0 — 19,301 pops, 323,867 residents, all 20 special-demand points — with commutes recomputed through OSRM. Every pop now carries the
drivingPathgeometry the current game renders. Median commute is unchanged at ~21 km. - Dropped 72 points that carried neither residents nor jobs. These fail Railyard's current integrity check.
Config
config.jsongains the bounding box it was missing, and reports the actual resident total (323,867) rather than the metro-area population (849,400).
Map by PSWBSF. Demand from slurry's US Demand Generator. Map data © OpenStreetMap contributors, Overture Maps Foundation, and GEBCO.