// Local game id helpers, in a tiny standalone module with no engine/generator imports, so the // dispatcher (lib/gamesource) can recognise a local game id — and the game screen can branch on it // — without eagerly bundling the whole offline engine. The engine is dynamically imported only when // a local game is actually played (keeping the app entry bundle within its size budget). /** LOCAL_ID_PREFIX marks a game id as a local (offline) game. */ export const LOCAL_ID_PREFIX = 'local:'; /** isLocalGameId reports whether an id belongs to a local game. */ export function isLocalGameId(id: string): boolean { return id.startsWith(LOCAL_ID_PREFIX); } /** newLocalGameId mints a fresh, device-unique local game id (time + random suffix — no crypto API, * so it also works on the older engines the SPA still supports). */ export function newLocalGameId(): string { return `${LOCAL_ID_PREFIX}${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`; } // A test seam for the mock e2e: pinning the seed makes a local game deal a known rack, so a spec // can play a precomputed opening word. Only the mock build's window.__mock wires setForcedSeed // (see lib/gateway.ts); a production build never calls it, so randomSeed stays truly random. let forcedSeed: bigint | null = null; /** setForcedSeed pins the value randomSeed returns (the mock-e2e seam); pass null to restore * randomness. */ export function setForcedSeed(seed: bigint | null): void { forcedSeed = seed; } /** randomSeed returns a random 32-bit seed for a new local game's deterministic bag (or the pinned * seed when a test has set one via setForcedSeed). */ export function randomSeed(): bigint { if (forcedSeed !== null) return forcedSeed; return BigInt(Math.floor(Math.random() * 0xffffffff)); }