Spaces:
Paused
Paused
Claude
Claude Sonnet 5
T-012: port the single-player client onto session + loopback
42b40d2 unverified Download session.js from Almaatla/SpaceCities: direct link, hf CLI and curl.
- Browser
- Download file 9.95 kB
-
https://huggingface.co/spaces/Almaatla/SpaceCities/resolve/main/session.js
- Command line
-
hf download hf://spaces/Almaatla/SpaceCities/session.js
-
curl -L -o session.js https://huggingface.co/spaces/Almaatla/SpaceCities/resolve/main/session.js
9.95 kB
| /* ============================================================ | |
| The live game session β the single mutable holder the UI modules read | |
| and boot.js reassigns. Before the split these were module-level `let` | |
| bindings in main.js that every function closed over; now they live on one | |
| shared object so hud / boot / save / the top-level listeners all see the | |
| same current game across a "choose another battlefield" restart. | |
| Read at CALL time (never destructured at module scope), exactly as the old | |
| closure vars were, so a fresh game swapped in by bootState is picked up by | |
| every consumer automatically. | |
| ============================================================ */ | |
| ; | |
| export const game = { | |
| state: null, // the current engine game state (engine/state.js), or null on the splash screen | |
| input: null, // the current input controller (input.js attachInput), or null before a game | |
| // The ADR-0004 seam every command-issuing UI module (input.js, inputCommands.js, | |
| // hudSelection.js) submits through β never null once a game is running, though which kind it | |
| // is varies with boot.js's own T-012 scoping: a real, server/session.js-backed | |
| // net/loopback.js transport for an ordinary skirmish, a live competition fixture, or a | |
| // loaded skirmish save; a net/directTransport.js adapter (ADR-0013) with no session behind it | |
| // β applying commands straight to `state`, no ownership/fog validation, no tick of its own β | |
| // for every boot path T-012 doesn't port yet (Odyssey, a scenario/raider/bounty, a spectated | |
| // match). Set by bootState/focusActivePlanet next to `state`/`input` above; UI code never | |
| // branches on which kind this is, by design (see ADR-0013). | |
| transport: null, | |
| // The Odyssey galaxy (engine/galaxy.js) when in open-world mode, else null. In | |
| // Odyssey `state` is the active planet's state = galaxy.planets.get(activeId); | |
| // credits + the other planets live on the galaxy. Read at call time like the rest. | |
| galaxy: null, | |
| // Which COMPETITION FIXTURE the running match belongs to, or null for an ordinary skirmish β | |
| // `{ kind: "gauntlet", index, opponent, humanName }` (docs/competitions-and-elo.md Phase 4). | |
| // Set by boot.js's startCompetitionMatch right after bootState (which clears it, exactly like | |
| // `galaxy` above), read at game-over by competition.js's captureCompetitionResult β which | |
| // records the result into the ledger and clears this again, so one finished match can only ever | |
| // be rated once β and by saveload.js's Home confirm, which has to warn that leaving a live | |
| // gauntlet match forfeits it. Never part of the sim or the persisted save: like `galaxy`, it | |
| // says which run this state belongs to, not anything about the state itself. | |
| competition: null, | |
| // Timestamp until which the supply readout flashes red after a blocked | |
| // production attempt: written by boot.js's frame-event pump, read by hud.js's | |
| // renderHUD. Kept here because it crosses that module boundary. | |
| supplyBlockedUntil: 0, | |
| // World-space {x,y} of the most recent under-attack alert, or null before the first one this | |
| // game β written by boot.js's frame-event pump (triggerUnderAttack), read by input.js's | |
| // Backspace "jump to last alert" keybind and by boot.js's own under-attack banner click | |
| // handler. Kept here, next to supplyBlockedUntil above, for the same reason: it crosses that | |
| // same module boundary. | |
| lastAttackAt: null, | |
| // { id, charge } of the player's Antimatter Gate as last observed by hud.js's renderHUD (P7 | |
| // "Persistent Gate charge strip"), or null before the first sighting β lets the chip tell | |
| // "charge climbed since the last tick" from "charge is stalled" without engine/ carrying | |
| // UI-only comparison state (the engine side only ever knows the CURRENT charge, on the | |
| // building itself). Keyed by wonder id, not just the charge float, so a Gate razed mid-charge | |
| // and rebuilt (engine/wonder.js's own header documents that risk) starts its own clean | |
| // comparison instead of reading as stalled against the dead Gate's final charge. Reset by | |
| // hud.js's resetPanelSignature() β the same per-boot/per-jump reset the topbar signature | |
| // itself already goes through, so a new world's Gate never inherits the old one's charge | |
| // history (the exact bug class boot.js's resetWorldUiBookkeeping comment documents for | |
| // gateMilestone/lastAttackAt). | |
| lastGateCharge: null, | |
| // Live per-world alert badges for the Odyssey starmap (P6 "Starmap live colony ledger"): | |
| // planetId -> {type, at}, written by boot.js's notifyColony for every attack/hostile/lost | |
| // notification a background colony raises (engine/galaxy.js sweepColonies) β the SAME trigger | |
| // that already fires the transient toast, just remembered here too so starmap.js's | |
| // renderStarmap can badge a world at a glance instead of relying on whichever toast the player | |
| // happened to catch. `at` is a performance.now() timestamp; starmap.js treats an attack/hostile | |
| // entry as "live" only within its own ~30s window, while a "lost" entry persists as a badge | |
| // until boot.js's focusActivePlanet clears that ONE planet's entry β i.e. until the player | |
| // actually jumps back and looks. Reset to {} on every fresh boot (bootState), like `groups` | |
| // below; never part of the deterministic sim or the persisted save (same reasoning as | |
| // lastAttackAt above β this crosses the boot.js/starmap.js module boundary, nothing more). | |
| colonyAlerts: {}, | |
| // Control groups, keyed per planet id β { digit: [unitIds] }. Lives on the session (not | |
| // in the per-game input controller) so a group survives an Odyssey jump β which tears down | |
| // and rebuilds attachInput β and can be shown in the HUD and persisted UI-side. Never part | |
| // of the deterministic sim; kept out of engine/persist.js's sanitized save payload. | |
| groups: {}, | |
| // The player's current formation choice (engine/formation.js FORMATION_SHAPES / | |
| // LEADER_POSITIONS) β a UI preference, not per-game state, so it's fine to just carry over | |
| // across a "choose another battlefield" restart like any other setup preference. Read by | |
| // input.js when issuing a move/attack-move/Hold-Formation order, and mutated directly by | |
| // hudSelection.js's formation picker buttons. | |
| formation: { shape: "grid", leaderPos: "front" }, | |
| // Which collapsible panel sections (the Build submenu's per-category groups, the CC/Barracks | |
| // Produce lists, the Refinery/Datacenter Research lists, Market, the freighter Cargo list, the | |
| // Spaceport Jump list, β¦) the player has manually collapsed β a UI preference like `formation` | |
| // above, not per-unit/per-game state, so collapsing one keeps it collapsed across every later | |
| // selection until toggled back. A key ABSENT from the set means "expanded" (starts open, | |
| // unchanged behaviour) β so a new collapsible section needs zero migration code here, it just | |
| // starts expanded like everything else. Keys are the stable strings passed to hudSelection.js's | |
| // sectionToggle(), e.g. "build:economy", "cc:produce", "market". | |
| collapsedSections: new Set(), | |
| // Observer Mode (observer.js): a free-look, read-only spectator over the Odyssey galaxy β | |
| // fog reveals, any world/base is a free camera jump, no orders can be issued. Never touches | |
| // `state`/`galaxy.activeId` or the real input camera, so exiting always resumes normal play | |
| // exactly where it was left; never part of the deterministic sim or the persisted save. | |
| observerMode: false, | |
| // Which world is currently being spectated while observerMode is on β a planetId, or null | |
| // when observerMode is off. Defaults to the real active world on entry. | |
| spectateId: null, | |
| // The throwaway state observer.js is showing for a DORMANT world (engine/galaxy.js | |
| // previewPlanet), or null whenever the spectated world is one the galaxy is really simulating. | |
| // Only a seeded handful of worlds are alive from turn one (BACKGROUND_WORLDS), but Observer | |
| // Mode's promise is a free look at ANY world β so a dormant one is shown as the pristine world | |
| // it would be on arrival, WITHOUT waking it. Never ticked, never registered on the galaxy, | |
| // never saved: the read-only guarantee above holds. | |
| spectatePreview: null, | |
| // The observer's own camera (camera.js createCamera shape), separate from the real | |
| // game.input camera so panning/zooming while spectating never disturbs normal play's view β | |
| // and so clamping uses whichever world's map is actually being looked at, not the active one's. | |
| observerCamera: null, | |
| // A WATCHED AI-vs-AI exhibition match (docs/competitions-and-elo.md Phase 5), or null for every | |
| // ordinary game β `{ aName, bName, world, seed, difficulty, matchTimeLimit, onLeave }`. Set by | |
| // boot.js's startSpectatedMatch right after bootState (which clears it, exactly like `galaxy` | |
| // and `competition` above) and read in three places: boot.js's loop, which drives BOTH seats | |
| // through tools/selfplay.js's tickSelfPlay instead of the ordinary tick(); observer.js, which | |
| // lets Observer Mode be entered without an Odyssey when β and only when β this is set (a | |
| // spectated match is one the human is NOT playing, so revealing its fog is not a cheat); and the | |
| // spectate UI (observerPanel.js), whose Leave button calls `onLeave` rather than importing | |
| // competition.js and closing a new module cycle. Never part of the sim or the persisted save β | |
| // saveShape.js's resumableMode refuses to checkpoint a match this flag marks. | |
| spectateMatch: null, | |
| // The spectated match's speed multiplier β one of observer.js's SPECTATE_SPEEDS (1/2/4/8). | |
| // engine/loop.js reads it live through its `speed` option, and scales the SIM TIME a real second | |
| // buys, never the fixed timestep itself. Ignored (pinned to 1) whenever spectateMatch is null, | |
| // so an ordinary game can never inherit a leftover multiplier. | |
| spectateSpeed: 1, | |
| }; | |