Skip to content

Mappings

The mappings system is TSPML's most important component. The game ships as minified code whose names change every build, so something has to own the translation from stable, human-meaningful names to what's actually in the bundle. In TSPML, the mappings file owns it.

The problem

PolyTrack is a webpack bundle passed through a minifier. The class modders think of as Car might be Xr in one build and qd in the next; its method controlCar(carId, input, …) might have parameters (e, t, n, …) today and (a, b, c, …) tomorrow. Any mod that writes those names down breaks on every game update, or worse, silently patches the wrong thing.

The answer: one map per build

A versioned JSON file per PolyTrack build:

jsonc
{
  "formatVersion": 1,
  "gameVersion": "0.6.2",
  "bundleHash": "sha256:…",          // integrity pin against the real bundle
  "symbols": {
    "Car":            { "kind": "class",  "locator": {  } },
    "Car.controlCar": { "kind": "method", "locator": {  } }
  },
  "targets": {  }                    // pinned mixin-target locators
}

Mods speak only the left-hand side. The resolver translates stable to concrete at transform time, trying a fallback chain of locator strategies (export references, prototype functions, anchored call expressions) before declaring failure.

Fail-closed, by design

The map carries the hash of the exact bundle it was authored against. If the game updates and the served bundle no longer matches, TSPML applies no AST patches from that map; every patch reports hash-mismatch instead. A mis-targeted patch in a re-minified bundle wouldn't error; it would do the wrong thing silently, which is strictly worse. Refusing loudly is the contract.

The same philosophy shows up at every level: an unknown symbol is symbol-unresolved, an unresolvable parameter placeholder is param-unresolvable, and none of them ever take down anything beyond the single patch that failed.

How maps are made

A semi-automated pipeline regenerates the map on each game release: fetch the new bundle, structurally match its modules against the previous build (lexical fingerprints, structural tie-breaks, call-graph edges; roughly 97% of game-logic modules relocate automatically), then surface the remainder for human review. A verification gate re-checks every pinned mixin target against the new bundle before a map ships, which is what makes carrying stable names across a version bump safe.

The stable namespace itself (Car, Track, Checkpoint, and so on) is human-curated and owned by the project, generated fresh from the live bundle, not copied from third-party dumps.

What this buys mods

  • Update resilience. A game update means TSPML regenerates one map and touches its own bridge; published mods keep working unmodified.
  • Meaningful names. Car.controlCar in your mixin config, never Xr.prototype.qd.
  • Honest failure. When translation isn't possible, your patch fails with a named reason in the sidebar, not a silent misfire.

TSPML is a fan-made tool. It never redistributes PolyTrack; the portal transforms your own live copy of the game.