The portal pipeline
How tspml.vercel.app turns the live, official PolyTrack into a modded one, in your browser, with nothing installed and nothing redistributed.
The pieces
- The proxy (
/api/proxy) fetches the game (HTML, bundles, assets) from the official servers on each request, forwarding what the game's own endpoints need. Nothing is permanently stored server-side. - The service worker sits between the game iframe and the network, routing the game's requests through the proxy.
- The transform applies the patch set to the game's main bundle as it passes through: TSPML's own bridge patches (which wire the events and registries) plus every enabled mod's mixins, in one compose pass, using the mappings to resolve stable names.
- The mod loader runs in the portal page: it loads your stored mods as real ES modules and hands each one the
apiobject, which talks to the bridge inside the game frame.
Boot speed
Transforming a multi-megabyte bundle takes a few seconds of parse and rewrite work. Two things keep that off your critical path:
- The base transform is memoized in server memory. The compose with no user patches involved (the pure TSPML bridge transform) is cached in-process, so a warm request serves in tens of milliseconds instead of seconds. Only the base is ever memoized; anything involving a user's patch plan is composed fresh, per request, and never cached.
- The page pre-warms the bundle. While the boot overlay is still on its early steps, the portal kicks off a background fetch of the transformed bundle so the work is already done (or in flight) by the time the game iframe asks for it.
The boot overlay shows a step list, a progress bar, and a live tail of the boot log while all of this happens.
How user mixins ride along
Your mods live in your browser's storage; the server must never become a store of user code. So when your mods declare mixins, the portal:
- builds the patch plan client-side and parks it in the browser's Cache API,
- has the service worker attach the plan to the bundle request (a POST body, never a URL, never a header that could leak or reflect),
- lets the server compose base plus user patches in one pass, in memory, and
- embeds a report of what applied inside the served bundle, which is what the sidebar's Your mixins rows read.
The transformed result of a user plan is never cached or shared; only the base transform (no user code involved) is memoized briefly in server memory for boot speed.
Where mods come from and where they go
Everything about a mod stays client-side:
- Pasted mods exist only in your browser's
localStorage. - URL-imported mods are fetched by your browser, directly from the host, never through the portal's proxy. The stored copy keeps the source URL so the reload button can re-fetch it and the share button can reference it.
- Share links carry mod URLs only, never mod code, and importing from one requires explicit confirmation. The server neither sees nor stores what the link carries; parsing and importing happen entirely client-side.
Failure containment
The base transform is all-or-nothing: if TSPML's own patches can't apply (say, a game update changed the bundle before mappings caught up), the portal serves the vanilla game and says so; degraded honestly, not broken. User-mod patches are per-mod: one mod's failing mixin fails that mod's report row and nothing else.
What never happens
- No redistribution. The game is fetched live per user, transformed in memory, and served to that user's iframe. No game files in the repo, in CI, or in any store.
- No cross-user anything. One user's patch plan can never reach another user's bundle; plans ride the Cache API and request bodies, both scoped to your browser.
- No upstream surprises. The proxy talks only to the game's official host, and nothing from a user's patch plan is ever forwarded upstream.
- No user code on the server. Mod code is stored in your browser, fetched by your browser, and executed in your browser. The server composes patches in memory and forgets them.