PURPOSE

Zustand store that carries data across the metagame session loop (Hub to Loadout to Game to Results to Hub). Each screen reads what it needs and writes what it produces, keeping mission setup, mission result, and ship selection in one place across screen transitions.

OWNS

  • selectedShipId — the currently selected ship ID (string), defaulting to Industria_Towncar.
  • runDef — the assembled RunDefinition for the next mission, or null.
  • missionResult — the MissionResult from the last completed mission, or null.
  • The internal initialState constant defining the default values for the three fields above.

READS FROM

  • RunDefinition type from ../data/run-config.
  • MissionResult type from ../data/mission-result.
  • HULL_CLASSES roster from ../data/ships (used by hydrateFromSave to validate incoming ship IDs).
  • invokeRpc from @metagame/services/supabase (used to persist ship selection).
  • create from zustand.

PUSHES TO

  • Supabase via invokeRpc('update_player_save', { p_selected_ship_id }) in persistSelection, called from setShip.
  • Subscribers to useSessionStore (consumers across hub, loadout, game bridge, results screens) via Zustand state updates.

DOES NOT

  • Does not persist runDef or missionResult to the cloud save — only ship selection is persisted.
  • Does not block the UI on persistence failures; persistSelection is fire-and-forget and only logs errors via console.error.
  • Does not write to the store from hydrateFromSave when the supplied ship ID is missing or not present in HULL_CLASSES — old/removed hull IDs from pre-v2 saves silently fall back to the existing starter default.
  • Does not reset selectedShipId in resetSession; only runDef and missionResult are cleared between runs.
  • Does not call the engine, render, or schedule any frame work.

Signals

  • State changes to selectedShipId, runDef, and missionResult are observed by React components via the useSessionStore hook.
  • setShip triggers a side-effect RPC to Supabase to mirror the selection into the player’s cloud save.
  • Failed cloud-save persistence emits a console.error with the prefix Failed to persist selection to cloud save:.

Entry points

  • useSessionStore — exported Zustand hook used by React screens and the game bridge to read state and dispatch actions.
  • SessionState — exported interface describing the full store surface (fields plus actions).
  • Actions on the store:
    • setShip(id) — sets selectedShipId and calls persistSelection.
    • setRunDef(def) — sets runDef.
    • setMissionResult(result) — sets missionResult, intended for the bridge onGameOver callback.
    • resetSession() — clears runDef and missionResult for a new run cycle.
    • hydrateFromSave(shipId) — sets selectedShipId only if the ID is in HULL_CLASSES; used during bootstrap.

Pattern notes

  • Single Zustand store created with create<SessionState> and an initial-state spread, no middleware.
  • Cloud persistence is colocated in a module-private persistSelection helper rather than a separate service, keeping the store self-contained.
  • hydrateFromSave uses a roster-membership guard against HULL_CLASSES to defend at the boundary between cloud save data and the in-memory store (pre-v2 saves with removed hull IDs silently fall back to the default starter).
  • Action handlers are arrow functions on the store-creator object; only set is destructured (no get).
  • Flow comment at the top of the file documents the screen-to-screen contract: Ships writes selectedShipId, LevelSelect writes selectedNodeId and assembledRunDef, Game writes missionResult via the bridge callback, Results reads missionResult and clears, Hub resets on enter.