// Per-game selection state: which on-map object the user is // currently inspecting. Phase 13 modelled planets only; Phase 19 // widened the union to ship groups (own / foreign / incoming / // unidentified). // // The store is in-memory only: lifetime matches the in-game shell // layout instance, which itself is preserved across active-view // switches inside `/games/:id/*`. Persisting selection across // reloads is intentionally out of scope — the Phase 13 acceptance // criterion calls out "across view switches", and survival across a // reload would be a surprising contrast with the empty-state copy // users see on first load. // // Like `GameStateStore` and `OrderDraftStore`, the store is // instantiated by the layout and shared with descendants through // Svelte context. The map view pushes selection events into it; the // inspector tab and the mobile bottom-sheet read from it. // // The store deliberately carries no Svelte component imports so it // can be tested directly without rendering any UI. /** * ShipGroupRef identifies a ship group inside the current report. * `local` groups carry a stable engine UUID (passed through * `report.localGroup.id` and used by the upcoming Phase 20 order * envelopes). The remaining variants do not — they are addressed by * their position in the matching report array, which is fine for * the read-only inspector: a new report load reseeds the store and * any stale index resolves to a missing entry on lookup, collapsing * the inspector cleanly. */ export type ShipGroupRef = | { variant: "local"; id: string } | { variant: "other"; index: number } | { variant: "incoming"; index: number } | { variant: "unidentified"; index: number }; /** * Selected describes the currently selected map object. The * discriminated union is closed: every map-clickable surface maps * to one of these variants. Future phases (e.g. fleet selection) * extend by adding a new branch — extension is purely additive. */ export type Selected = | { kind: "planet"; id: number } | { kind: "shipGroup"; ref: ShipGroupRef }; /** * SELECTION_CONTEXT_KEY is the Svelte context key the in-game shell * layout uses to expose its `SelectionStore` instance to descendants. * Map view, inspector tab, and the mobile bottom-sheet resolve the * store via `getContext(SELECTION_CONTEXT_KEY)`. */ export const SELECTION_CONTEXT_KEY = Symbol("selection"); export class SelectionStore { selected: Selected | null = $state(null); private destroyed = false; /** * selectPlanet sets the active selection to the planet identified * by its engine `number`. A no-op once the store has been disposed. */ selectPlanet(id: number): void { if (this.destroyed) return; this.selected = { kind: "planet", id }; } /** * selectShipGroup sets the active selection to a ship group. The * `ref` discriminator carries the variant + the right id shape for * lookup against the current report. */ selectShipGroup(ref: ShipGroupRef): void { if (this.destroyed) return; this.selected = { kind: "shipGroup", ref }; } /** * clear drops the current selection. The mobile sheet's close * button calls this; otherwise selection persists across active- * view switches. */ clear(): void { if (this.destroyed) return; this.selected = null; } dispose(): void { this.destroyed = true; this.selected = null; } }