The central registry for Subway Builder community mods and custom maps.
Warning
Work in Progress - The Railyard is still under active development. Expect changes to the submission process, schema, and tooling.
All submissions are handled through GitHub Issues. Pick a template to get started:
The Railyard stores metadata only - manifests, gallery images, and pointers to where your mod or map is actually hosted (GitHub Releases, CDNs, etc.). When you submit through an issue template, CI validates your submission and opens a PR automatically. Once merged, your listing goes live.
publish-map.yml and update-map.yml are generated from a shared script:
- Generate both templates:
pnpm --dir scripts run generate:map-templates
- Verify templates are up to date:
pnpm --dir scripts run check:map-templates
Download and release-integrity snapshots are generated into:
maps/downloads.jsonmods/downloads.jsonmaps/integrity.jsonmods/integrity.json
Local commands:
- Generate full registry analytics (downloads + integrity):
pnpm --dir scripts run generate-registry-analytics
- Generate maps only:
pnpm --dir scripts run generate-registry-analytics:maps
- Generate mods only:
pnpm --dir scripts run generate-registry-analytics:mods
- Generate maps only (download-only mode):
pnpm --dir scripts run generate-downloads:maps:download-only
- Generate mods only (download-only mode):
pnpm --dir scripts run generate-downloads:mods:download-only
- Generate map demand stats only:
pnpm --dir scripts run generate-registry-demand-stats
- Sync map manifest
file_sizesfrom latest complete integrity versions:pnpm --dir scripts run sync-map-file-sizes
Integrity behavior:
- Only semver versions (
vX.Y.ZorX.Y.Z) are eligible for download counting. - Versions that fail integrity checks are excluded from
downloads.json. - Non-semver versions are still recorded in
integrity.jsonas incomplete.
Automation:
regenerate-downloads-hourly.ymlruns hourly in download-only mode (updates downloads only; no ZIP integrity pass).regenerate-registry-analytics.ymlruns every 3 hours in full mode (refreshes downloads + integrity + integrity cache, map demand stats, and syncs map manifestfile_sizesfrom integrity).- Full mode posts two Discord summaries (downloads/integrity and map demand stats) to the same webhook secret:
DISCORD_WEBHOOK_URL.
Mod security scanning runs inside the full integrity pass and is contributor-configurable.
Configuration:
- Rules live at
security-rules.jsonin the repository root. - Each rule has:
id(stable identifier)severity(ERRORorWARNING)type(literal,regex, orast)pattern(string forliteral/regex, object forast)- optional
description,enabled
Current enforcement model:
- The scanner inspects all
.jsand.tsfiles inside each mod ZIP. ERRORfindings hard-fail version completeness (is_complete=false), so those versions are excluded frommods/downloads.json.WARNINGfindings are recorded but do not block completeness.- Security findings are written to
mods/integrity.jsonand cached inmods/integrity-cache.jsonundersecurity_issue.findings. - Full analytics posts separate Discord alerts for security
ERROR(red) andWARNING(yellow/orange).
AST rule support:
call-arg-call: match calls likeeval(atob(...)).call-in-while: match configured calls insidewhileloops, with optional alias resolution (allow_aliases: true).
Fixtures and validation:
- Rule fixtures live under
scripts/tests/fixtures/security-rules/<rule-id>/. - Each enabled rule must have a matching folder with at least one
.jsfixture that triggers it. - You can add multiple fixtures per rule (for example alias and non-alias variants).
Commands:
- Run focused security-rule fixture validation:
pnpm --dir scripts run test:security-rules
- Run full scripts test suite:
pnpm --dir scripts run test
- Force a fresh mod integrity/security evaluation (ignore cache):
pnpm --dir scripts run generate-registry-analytics:mods -- --force
How to add a new security rule:
- Add the rule to
security-rules.json(withenabled: truewhen ready). - Create
scripts/tests/fixtures/security-rules/<rule-id>/and add one or more offending.jsfixtures. - Run
pnpm --dir scripts run test:security-rulesand confirm all fixture checks pass. - Run a full mod integrity pass (
generate-registry-analytics:mods -- --force) to verify runtime behavior.
Daily combined download snapshots are cached under:
history/snapshot_YYYY_MM_DD.json
Each snapshot includes:
mapsandmodssections- embedded current
downloadsandindexpayloads total_downloadsnet_downloadsversus the previous snapshot (or total on first snapshot)entriescount from the correspondingindex.json
Local command:
- Generate/update today’s history snapshot:
pnpm --dir scripts run generate-download-history
Shared-pack attribution audit:
- Export an attribution audit bundle under
tmp/shared-map-attribution-audit/:bash scripts/export-shared-map-attribution-audit.sh 2026_03_30
- Audit JP shared-pack listings by prefix:
pnpm --dir scripts run audit-shared-map-attribution -- --snapshot-date 2026_03_30 --listing-prefix yukina-
- Audit a shared pack by exact source repo:
pnpm --dir scripts run audit-shared-map-attribution -- --snapshot-date 2026_03_30 --repo rslurry/subwaybuilder-mapspnpm --dir scripts run audit-shared-map-attribution -- --snapshot-date 2026_03_30 --repo maximilian284/subwaybuilder-it-maps
- Audit a single listing:
pnpm --dir scripts run audit-shared-map-attribution -- --snapshot-date 2026_03_30 --listing-id yukina-osaka
The audit writes:
tmp/shared-map-attribution-audit/results/shared-map-attribution-audit.jsontmp/shared-map-attribution-audit/results/shared-map-attribution-audit.csv
It compares snapshot attribution against the exact repo/tag/asset_name stored in maps/integrity.json, which is especially useful for shared custom repos where listing version and release tag differ.
Automation:
cache-download-history.ymlruns daily (and on manual dispatch), commitshistory/snapshot_YYYY_MM_DD.json, and posts summary stats to Discord.
Separate Railyard app download analytics:
- Capture hourly
Subway-Builder-Modded/railyardrelease download history:pnpm --dir scripts run capture-railyard-app-downloads
- Generate app download analytics artifacts:
pnpm --dir scripts run generate-railyard-app-analytics
The hourly workflow writes:
history/railyard_app_downloads.jsonanalytics/railyard_app_downloads.jsonanalytics/railyard_app_downloads.csv
Map manifests now support auto-derived demand metrics from map ZIPs:
population(auto-derived from residents total, kept for backwards compatibility)residents_totalpoints_countpopulation_countmaps/<map-id>/grid.geojson(generated demand grid artifact committed by the registry analytics workflow)file_sizes(synced from integrity for the latest complete semver version)
The demand-stats cache at maps/demand-stats-cache.json is schema-versioned. Grid algorithm changes should bump the grid schema version so the next registry analytics run regenerates cached grids.
Local command:
- Generate/refresh map demand stats:
pnpm --dir scripts run generate-registry-demand-stats- (alias)
pnpm --dir scripts run generate-map-demand-stats - Force refresh all maps (ignore SHA/cache skip):
pnpm --dir scripts run generate-registry-demand-stats -- --force
- Refresh one map by id:
pnpm --dir scripts run generate-registry-demand-stats -- --id <map-id>
For technical details, see ARCHITECTURE.md.
