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

Keeping the leaderboard honest

What a cheater can and can't do, how names are kept clean, and how a friend gets the same luck.

A public leaderboard attracts cheaters and trolls. This chapter is about the game's defenses, and about being honest on what they can't do.

The honest limit

The game runs in each player's browser. Anything running on someone's own computer can be tampered with, so a determined person could always fake a score. The goal is to stop the easy kinds of cheating.

A ticket for every run

When you start a shared tour, the server gives your run a ticket. When you post a score, it checks the ticket: that it exists, that it hasn't been used, that the run took a believable amount of time, and that the numbers fit the tour:

●trust.py : 79–100trace ↗
79def check_run(store, run_id: str, rec: dict, legend_pts: int, ip: str) -> tuple[dict | None, str | None]:
80    """Validate a submission against its run ticket. Returns (ticket, None) or (None, reason)."""
81    if ip and store.incr("rl:" + ip, ttl=RATE_WINDOW) > RATE_MAX:
82        return None, "Too many scores from here in the last hour. Try again later."
83    ticket = store.get("run:" + str(run_id)) if run_id else None
84    if not ticket:
85        return None, "That run is already on the board, or it wasn't started from a shared tour."
86    stops, years = ticket["stops"], ticket["years"]
87    if time.time() - ticket["t0"] < MIN_SECONDS_PER_STOP * stops * years:
88        return None, "That run finished faster than the road allows."
89    limits = {
90        "shows": stops * years, "legendary": years, "tapes": 40 * stops * years,
91        "miles": 12000 * years, "day": 400 * years,
92    }
93    for k, cap in limits.items():
94        v = rec.get(k, 0)
95        if not isinstance(v, int) or v < 0 or v > cap:
96            return None, "Those numbers don't fit this tour."
97    if not isinstance(rec.get("score"), int) or not 0 <= rec["score"] <= score_cap(rec, stops, years, legend_pts):
98        return None, "That score doesn't fit this tour."
99    store.delete("run:" + str(run_id))  # each ticket counts once
100    return ticket, None
Each check returns a plain-English reason if it fails. The ticket is used up at the end, so one run can only score once.

Concept: "validation". Validation means checking that incoming data makes sense before using it. A server never trusts what it's sent; it checks.

Keeping names clean

Names on the board go through a filter. It catches tricks like using 0 for o or adding spaces, but it's careful not to block innocent names. Matching 'rape' anywhere would block 'grape', so short words only count when they're a whole word:

●trust.py : 43–55trace ↗
43def clean_name(raw: str) -> tuple[str | None, str | None]:
44    """Return (name, None) if usable, or (None, reason) if not."""
45    swapped = str(raw or "").lower().translate(_SWAPS)
46    name = re.sub(r"\s+", " ", str(raw or "")).strip()
47    name = re.sub(r"[^A-Za-z0-9 _.'\-]", "", name)[:24].strip()
48    if len(name) < 2:
49        return None, "Pick a name with at least 2 letters."
50    letters = _squash(re.sub(r"[^a-z]", "", swapped))
51    words = {_squash(w) for w in re.split(r"[^a-z]+", swapped) if w}
52    bad = any(_squash(w) in letters for w in _BLOCKED_ANYWHERE) or any(_squash(w) in words for w in _BLOCKED_WORDS)
53    if bad:
54        return None, "That name can't go on a public board. Pick another."
55    return name, None
Undo letter swaps, then check long words anywhere and short words only as whole words.

The same luck

Every random event in the game (a flat tire, a cop, which crew you get) comes from one random-number function. A run can be given a seed, a starting number that makes the random numbers come out the same every time:

●assets/game/game.js : 19–28trace ↗
19  if (typeof window !== "undefined" && window.__TT_SEED__) {
20    var luckSeed = window.__TT_SEED__ >>> 0;
21    rand = function () {
22      luckSeed = (luckSeed + 0x6D2B79F5) >>> 0;
23      var t = luckSeed;
24      t = Math.imul(t ^ (t >>> 15), t | 1);
25      t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
26      return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
27    };
28  }
If the page passes a seed in, swap the random-number function for one that follows that seed.

Concept: "seeded randomness". Computers fake randomness with math. Start the math from the same number (the seed) and you get the same 'random' sequence. A challenge link carries your seed, so your friend starts with the same luck. Once they make different choices, the games drift apart.

The ideas to keep
  • A determined cheater can't be stopped when the game runs in their browser; casual cheating can be.
  • Every score needs a one-time ticket and has to pass validation.
  • A seed lets a friend replay your tour with the same starting luck.