Architecture
The thesis in one sentence: concentrate all version-coupling in two maintained artifacts, the mappings file and the API bridge, so ordinary mods target stable names and ride through PolyTrack updates untouched.
The layers
Loader core
Clean TypeScript with zero knowledge of minified game code. Its job: discover mods, parse mod.json, resolve semver dependencies, topologically sort the load order (cycle detection, explicit conflict errors), invoke entrypoints, and isolate every failure per mod. Environment/targets/breaks mismatches soft-disable the offending mod with a typed report rather than aborting the boot.
Mappings file
TSPML's most important component: a versioned JSON, one per PolyTrack build, mapping stable names (Car.controlCar, and so on) to concrete locators in the minified bundle. Mods only ever speak stable names; the resolver translates at transform time, and refuses (fail-closed) when the map doesn't match the bundle. Full explanation.
API bridge
The deliberately version-coupled layer: a small set of maintained patches wiring the stable event bus and registries to real game functions. When PolyTrack updates, only this layer and the mappings change; mods keep working. Each concern (car events, checkpoints, track loading) gets one small internal patch, so the version-coupled surface stays as small as possible.
The two tiers for mods
- Tier 1: events + registries.
api.events.on('race.finished', …),api.keybinds.register(…),api.tracks.register(…),api.audio.register(…). Wired by the bridge; mods never see minified code. This is where ~90% of mods should live. - Tier 2: declarative mixins. JSON patch descriptors (
before/after/around/replaceand more) against stable names, applied at transform time. Chaining is priority-ordered;replaceis single-winner with a load-time conflict error. The escape hatch for what Tier 1 can't reach.
Guiding principles
- Concentrate fragility. Only the bridge and mappings are version-coupled; everything above rides through updates.
- Events for everyone, mixins for power users. Route most mods to the stable API; reserve surgery for the few that need it.
- Fail loudly, fail small. Typed per-patch failures, per-mod reports, never a boot abort, never a silent no-op, and fail-closed on stale maps.
- Ship metadata, not the game. Fetch the user's live copy, transform in place, never redistribute.
- Keep the core tiny. Resist hand-modding every game internal; that's the treadmill that makes updates slow.
Delivery surfaces
- The portal (flagship, live) at tspml.vercel.app: a service-worker plus proxy pipeline that fetches, transforms, and serves the game with the mod layer active. How it works.
- A browser extension (roadmap, #7): network-layer rewriting on the real
kodub.comorigin; the path to modded online play.