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?
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;
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).
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,
This is why the tests never duplicate game logic — they call the same functions the browser does, just without a screen.
HAS_DOMis the seam between the browser build and the headless build — there is no second codebase.- All run state is one plain object,
S, withS.viewnaming the current screen. - The IIFE keeps the global scope clean; the test surface is a single explicit export at the bottom.