← Back to the gamebug or idea?Terrapin Trail//Code BibleIIhow the game works, archive to van
Reel 15 · 7 min read

Game DNA: rules as data

The rule list, hashtag-safe rule codes, the spots the game reads them, and the published-rules gallery.

Game DNA is the set of rules a player can change before a run: eight sliders and four ready-made rule sets. The rules are data, the game reads them at a handful of spots, and a rule set travels as a short code inside the tag and the link.

The rules are a list

RULES in dna.py is the single source of truth. The browser reads it from /api/dna to draw its sliders; the server decodes codes with it; the play page hands the decoded values to the game:

●dna.py : 16–27trace ↗
16RULES: list[dict[str, Any]] = [
17    {
18        "id": "code", "name": "Taper's code", "unit": " Cred per tape sold", "min": 0, "max": 10, "step": 1, "def": 0,
19        "isNew": True,
20        "hint": "Old-school tapers traded, never sold. Lot Cred you lose for each tape you sell. The game as built has none.",
21    },
22    {"id": "gas", "name": "Gas guzzling", "unit": "x", "min": 0.5, "max": 2, "step": 0.25, "def": 1,
23     "hint": "How fast the van burns gas between shows."},
24    {"id": "chaos", "name": "Road chaos", "unit": "x", "min": 0, "max": 2, "step": 0.25, "def": 1,
25     "hint": "How many surprises hit you on each drive: cops, flat tires, strangers, stews."},
26    {"id": "wear", "name": "Van wear", "unit": "x", "min": 0.5, "max": 2, "step": 0.25, "def": 1,
27     "hint": "How hard the road is on the van."},
Each rule: id, label, unit, range, step, the game-as-built default, and the hint shown under the slider. Taper's code is the one new rule.

A code you can put in a hashtag

Named presets use their name. Anything else packs the slider positions into one mixed-radix number written in base 36, so it fits in a hashtag and decodes back to exactly the same sliders:

●dna.py : 72–86trace ↗
72def encode(values: dict) -> str:
73    """Rule values -> a code: a preset name, or a short base-36 string of slider positions."""
74    vals = normalize(values)
75    for name, p in PRESETS.items():
76        if vals == normalize(p["values"]):
77            return name
78    n = 0
79    for r in RULES:
80        n = n * _positions(r) + round((vals[r["id"]] - r["min"]) / r["step"])
81    s = ""
82    while n:
83        n, d = divmod(n, 36)
84        s = _ALPHA[d] + s
85    # A leading "X" marks a custom code so it can never collide with a preset name.
86    return "X" + (s or "0")
encode: a preset name if the values match one; otherwise 'X' plus the positions in base 36.
●dna.py : 89–108trace ↗
89def decode(code: str | None) -> dict:
90    """A code -> rule values. Unknown or garbled codes fall back to the game as built."""
91    if not code or code == "CLASSIC":
92        return defaults()
93    if code in PRESETS:
94        return normalize(PRESETS[code]["values"])
95    if not code.startswith("X"):
96        return defaults()
97    try:
98        n = int(code[1:], 36)
99    except ValueError:
100        return defaults()
101    idx = []
102    for r in reversed(RULES):
103        n, d = divmod(n, _positions(r))
104        idx.append(d)
105    if n:
106        return defaults()
107    idx.reverse()
108    return {r["id"]: _snap(r, r["min"] + i * r["step"]) for r, i in zip(RULES, idx)}
decode: unknown or garbled codes fall back to the game as built rather than failing.

The browser has a line-for-line JavaScript copy of encode so the tag updates instantly as a slider moves. Change one and you must change the other: the codes in shared links depend on them agreeing.

Where the game reads them

The game builds one DNA object at load time from window.__TT_DNA__, falling back to the built-in values:

●assets/game/game.js : 31–36trace ↗
31  var DNA = (function () {
32    var d = { code: 0, gas: 1, chaos: 1, wear: 1, hustle: 1, cash: 1, legend: 60, loyal: 1 };
33    var g = (typeof window !== "undefined") && window.__TT_DNA__;
34    if (g) Object.keys(d).forEach(function (k) { if (typeof g[k] === "number" && isFinite(g[k])) d[k] = g[k]; });
35    return d;
36  })();
DNA: every rule has a default, so the game behaves exactly as built when no rules are passed in.

Each rule then touches one or two spots: gas per leg, van wear, events per drive, cash from hustles and tape sales, starting cash, the legendary-tape score, and the odds a van-mate quits. Two examples:

●assets/game/game.js : 1481–1481trace ↗
1481    var gas = Math.ceil(next.miles / 70 * DNA.gas); // TT-DNA gas guzzling
Gas guzzling scales the gas a leg needs.
●assets/game/game.js : 1557–1557trace ↗
1557    n = Math.min(3, Math.round(Math.min(n, 3) * DNA.chaos)); // TT-DNA road chaos
Road chaos scales how many events a drive queues (0 means a quiet road).

Taper's code is a new rule rather than a new number: selling a tape now costs Lot Cred when it's switched on.

●assets/game/game.js : 43–48trace ↗
43  function tapersCode(s, n) {
44    if (!DNA.code) return "";
45    var hit = Math.round(DNA.code * n);
46    s.cred = clamp(s.cred - hit, 0, 100);
47    return " The tapers saw that. -" + hit + " Lot Cred.";
48  }
tapersCode runs at every sale, both the blanket hustle and the haggled sale.

The gallery

A custom rule set can be named and published (POST /api/rulesets). Names pass the same filter as player names, each code is stored once, and every run started under it counts as a play, so the gallery lists the most-played rule sets first. The board for a published code shows its name instead of the code.

Key takeaways
  • Rules are data in dna.py; the game reads one DNA object at a handful of spots.
  • A rule set is a preset name or 'X' plus base-36 slider positions, reversible and hashtag-safe.
  • Every rule set gets its own board, so scores under different rules never mix.