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:
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."},
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:
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")
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)}
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:
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 })();
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:
1481 var gas = Math.ceil(next.miles / 70 * DNA.gas); // TT-DNA gas guzzling
1557 n = Math.min(3, Math.round(Math.min(n, 3) * DNA.chaos)); // TT-DNA road chaos
Taper's code is a new rule rather than a new number: selling a tape now costs Lot Cred when it's switched on.
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 }
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.
- 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.