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

Anatomy of one file

The IIFE, the HAS_DOM seam, the single state global, and the surface handed out at the bottom.

The entire game is one immediately-invoked function expression (IIFE): (function(){ "use strict"; … })(). Nothing leaks to the global scope by accident; the only things the outside world can touch are handed out deliberately at the very end. The first real decision the file makes is the seam from the foreword — is there a DOM to render into?

●assets/game/game.js : 9–15trace ↗
9  // DOM, so rendering, animation, and network are skipped -- the pure game logic
10  // still runs, which is what the headless tests exercise.
11  var HAS_DOM = typeof document !== "undefined" && typeof window !== "undefined";
12
13  // ---------- tiny utils ----------
14  // rand is swappable so tests can install a seeded PRNG for deterministic runs.
15  var rand = Math.random;
HAS_DOM: one boolean, computed once, that every visual entry point checks.

Everything branches on it. In a browser (HAS_DOM true) the file wires a click listener, an input listener, boots to the title screen, and paints via innerHTML. Under Node (HAS_DOM false) rendering, audio, animation, and network all early-return, and instead the IIFE hangs a test surface on globalThis.TerrapinTrail.

The state global

There is exactly one place the whole run lives: a module-global S. It is a plain object — current stats (cash, gas, grub, van, vibes, tapes, blanks, cred), progress (stop, day, miles), the crew, inventory, flags, and crucially S.view, a string naming the current screen. State splits into two kinds on purpose: persistent run data (plain JSON, safe to snapshot) and transient in-flight screen data (holds references to catalog objects and functions, and is stripped before any save — see Persistence).

The surface handed out at the bottom

Because nothing is global, the test/data surface is explicit — one object assigned at the end of the IIFE, exposing the catalogs, the lifecycle functions the UI itself calls, a seedable RNG, and viewHtml() (which returns a screen's HTML as a string, no DOM needed).

●assets/game/game.js : 3898–3906trace ↗
3898    globalThis.TerrapinTrail = {
3899      // data
3900      ROLES: ROLES, STOPS: STOPS, EVENTS: EVENTS, MODS: MODS, MOD_SLOTS: MOD_SLOTS,
3901      TOURS: TOURS, setTour: setTour, tourByYear: tourByYear, currentTourYear: function () { return TOUR_YEAR; },
3902      VAN_PARTS: VAN_PARTS, CONSUMABLES: CONSUMABLES, LOT_ITEMS: LOT_ITEMS,
3903      HUSTLES: HUSTLES, PEAKS: PEAKS, CAP: CAP, ACHIEVEMENTS: ACHIEVEMENTS,
3904      VANMATES: VANMATES, CREW_MIN: CREW_MIN, CREW_MAX: CREW_MAX,
3905      rosterName: function (id) { return rosterName(S, id); },
3906      toggleVanmate: toggleVanmate, setCrewName: setCrewName, rebuildCrew: rebuildCrew, crewHas: crewHas,
The one deliberate export: data catalogs plus the exact functions the browser UI drives.

This is why the tests never duplicate game logic — they call the same functions the browser does, just without a screen.

Key takeaways
  • HAS_DOM is the seam between the browser build and the headless build — there is no second codebase.
  • All run state is one plain object, S, with S.view naming the current screen.
  • The IIFE keeps the global scope clean; the test surface is a single explicit export at the bottom.