The Railyard is the central metadata registry for Subway Builder community mods and maps. This repository stores manifests, gallery assets, and update pointers. It does not store mod/map binaries.
The-Railyard/
|-- .github/
| |-- ISSUE_TEMPLATE/
| | |-- config.yml
| | |-- publish-mod.yml
| | |-- publish-map.yml # auto-generated
| | |-- update-mod.yml
| | |-- update-map.yml # auto-generated
| | `-- report.yml
| `-- workflows/
| |-- publish.yml
| |-- update-metadata.yml
| |-- regenerate-index.yml
| |-- regenerate-downloads.yml
| |-- cache-download-history.yml
| |-- regenerate-map-demand-stats.yml
| |-- close-invalid.yml
| `-- report.yml
|-- scripts/
| |-- lib/
| | |-- manifests.ts
| | |-- map-constants.ts
| | |-- map-field-utils.ts
| | |-- map-update-logic.ts
| | |-- downloads.ts
| | |-- download-history.ts
| | |-- map-demand-stats.ts
| | |-- release-resolution.ts
| | |-- discord-webhook.ts
| | |-- registry-manifest.ts
| | |-- mod-manifest.ts
| | |-- gallery.ts
| | |-- github.ts
| | `-- custom-url.ts
| |-- tests/
| |-- generate-map-templates.ts
| |-- validate-publish.ts
| |-- validate-update.ts
| |-- create-listing.ts
| |-- update-listing.ts
| |-- generate-downloads.ts
| |-- generate-download-history.ts
| |-- generate-map-demand-stats.ts
| |-- notify-discord.ts
| `-- regenerate-indexes.ts
|-- mods/
| |-- index.json
| |-- downloads.json
| `-- <mod-id>/
| |-- manifest.json
| `-- gallery/
|-- maps/
| |-- index.json
| |-- downloads.json
| |-- demand-stats-cache.json
| `-- <map-id>/
| |-- manifest.json
| `-- gallery/
|-- history/
| `-- snapshot_YYYY_MM_DD.json
|-- README.md
`-- ARCHITECTURE.md
mods/index.jsonmaps/index.json
Both are generated from existing listing directories after merges.
mods/downloads.jsonmaps/downloads.json
Each file maps listing IDs to versioned cumulative download counts:
{
"some-listing-id": {
"v1.0.0": 120,
"v1.1.0": 237
}
}Count policy:
- zip assets only
- for unresolved custom versions (non-GitHub URL, missing tag/asset), version is skipped and warning is emitted in workflow logs
history/snapshot_YYYY_MM_DD.json
Each daily snapshot includes:
maps.downloadsandmods.downloadsmaps.indexandmods.indextotal_downloads(sum of all version counts indownloads)net_downloads(delta versus previous snapshot; falls back to total if first snapshot)entries(cardinality ofindex.jsonlisting array)
{
"schema_version": 1,
"id": "better-trains",
"name": "Better Trains",
"author": "someuser",
"github_id": 123456,
"description": "Adds new train models.",
"tags": ["trains", "cosmetic"],
"gallery": ["gallery/screenshot1.png"],
"source": "https://github.com/someuser/better-trains",
"update": {
"type": "github",
"repo": "someuser/better-trains"
}
}Maps include all mod fields plus map-specific metadata:
{
"schema_version": 1,
"id": "raleigh",
"name": "Raleigh",
"author": "muffintime",
"github_id": 87654321,
"description": "Custom map of the Raleigh metro area.",
"tags": ["north-america", "airports"],
"gallery": ["gallery/screenshot1.png"],
"source": "https://github.com/muffintime/sb-raleigh",
"update": {
"type": "github",
"repo": "muffintime/sb-raleigh"
},
"city_code": "RDU",
"country": "US",
"population": 1500000,
"residents_total": 1500000,
"points_count": 4242,
"population_count": 1500000,
"data_source": "LODES",
"source_quality": "high-quality",
"level_of_detail": "medium-detail",
"location": "north-america",
"special_demand": ["airports"]
}Map-specific fields:
city_code:^[A-Z0-9]{2,4}$country: ISO-3166-1 alpha-2 codepopulation: integer >= 0 (auto-derived from demand data)residents_total: integer >= 0 (sum of demand point residents)points_count: integer >= 0 (number of demand points)population_count: integer >= 0 (number of population entries)data_source: non-empty stringsource_quality:low-quality | medium-quality | high-qualitylevel_of_detail:low-detail | medium-detail | high-detaillocation: exactly one location tagspecial_demand: array of feature tags
Backward compatibility rule:
- For map manifests,
tagsis still maintained as the union oflocationandspecial_demand.
OSM quality rule:
- OSM-based data sources cannot be
high-quality. - Create/update logic caps OSM-derived
high-qualitytomedium-quality. - Update validation rejects OSM +
high-qualitycombinations.
Category tags from issue templates:
cosmetic, gameplay, library, misc, qol, stations, tracks, trains, ui.
caribbean, central-america, central-asia, east-africa, east-asia, europe, middle-east, north-africa, north-america, oceania, south-america, south-asia, southeast-asia, southern-africa, west-africa.
airports, entertainment, ferries, hospitals, parks, schools, universities.
update supports two formats:
{ "type": "github", "repo": "owner/repo" }{ "type": "custom", "url": "https://example.com/update.json" }Validation checks reachable/updateable endpoints at publish/update time.
Map ZIPs follow the patcher/map-manager expectations and place files at archive root:
map-name.zip
|-- config.json
|-- demand_data.json
|-- buildings_index.json
|-- roads.geojson
|-- runways_taxiways.geojson
`-- XXX.pmtiles
publish-map.ymlandupdate-map.ymlare generated byscripts/generate-map-templates.ts.- The generator uses a shared field definition after
map-idand enforces identical template tails. - Generation/check uses YAML + AJV schema validation.
- Templates should not be edited manually.
- User opens publish issue (
publish-modorpublish-map). - Workflow parses issue form values.
validate-publish.tsvalidates payload and external references.create-listing.tswrites listing files and downloads gallery images.- Workflow opens a PR for maintainers.
- User opens update issue (
update-modorupdate-map). validate-update.tschecks listing existence and ownership (github_id).- Map updates additionally validate map field constraints via shared map update logic.
update-listing.tsapplies only requested field changes.- Workflow opens a PR.
regenerate-indexes.tsrebuildsmods/index.jsonandmaps/index.json.
regenerate-downloads.ymlruns hourly and on manual dispatch.- It runs map and mod download generation in separate jobs and then commits updated
downloads.jsonfiles if changed. - Uses GitHub GraphQL
ReleaseAsset.downloadCountwithGITHUB_TOKENby default (GH_DOWNLOADS_TOKENoptional override). cache-download-history.ymlruns daily and on manual dispatch.- It snapshots current
maps/downloads.json+mods/downloads.jsonwith indexes intohistory/snapshot_YYYY_MM_DD.json. - It computes
net_downloadsagainst the previous snapshot for trend and popularity analysis. regenerate-map-demand-stats.ymlruns every 8 hours and on manual dispatch.- It refreshes map demand-derived metadata in manifests and updates
maps/demand-stats-cache.json. - Skips ZIP extraction when source fingerprints are unchanged:
- For
sha256:*fingerprints, skip regardless of age. - For other fingerprints, skip when last checked within 9 hours.
- Reason for non-
sha256fallback: - Tag/asset-name or URL-based fingerprints can remain unchanged while upstream ZIP content is replaced, so periodic rechecks prevent stale derived stats.
validate-publish.ts: publish-time validation for maps/mods.validate-update.ts: update-time validation and ownership checks.create-listing.ts: creates new manifests and gallery files.update-listing.ts: applies manifest metadata updates.regenerate-indexes.ts: reindexes listings.generate-downloads.ts: generatesmaps/downloads.jsonandmods/downloads.json.generate-download-history.ts: caches daily combined download snapshots inhistory/.generate-map-demand-stats.ts: updates mappopulation/residents_total/points_count/population_count.generate-map-templates.ts: generates and verifies map issue templates.notify-discord.ts: shared Discord webhook notifier for workflow summaries.
Script-level tests live under scripts/tests and currently cover:
- map field utility behavior/defaults
- map template generation invariants
- map update integration behavior (changed-fields-only + invalid existing-state failure)