# Zustand Stores This document explains how Zustand is used in the current project. ## Why Zustand Exists Here The project needs shared state that is durable enough to be read by multiple React and React Three Fiber systems. Zustand is used for: - game progression - settings - subtitle display It is not used for high-frequency frame values. Values such as player velocity, temporary vectors, object positions during a grab, raycasts, and animation-loop data stay in refs or manager-local state. ## Store Locations Current Zustand stores: ```txt src/managers/stores/useGameStore.ts src/managers/stores/useSettingsStore.ts src/managers/stores/useSubtitleStore.ts ``` They are under `src/managers/stores/` because they are shared runtime state, not state owned by one visual component. ## Store Responsibilities | Store | Responsibility | | ------------------ | ------------------------------------------------------------- | | `useGameStore` | Durable game progression, mission steps, cinematic input lock | | `useSettingsStore` | Menu visibility, volumes, and subtitle options | | `useSubtitleStore` | Currently displayed subtitle cue | ## Managers vs Stores Managers own imperative runtime objects and side effects. Examples: - `AudioManager` owns audio elements, music playback, sound pools, category volumes, and optional panner nodes. - `InteractionManager` owns transient interaction handles and input-oriented focus/holding state. Stores own durable shared state: - current game phase - mission sub-step - progression flags - settings values - currently displayed subtitle cue Rule of thumb: - manager = runtime objects, side effects, frame-adjacent imperative logic - store = shared state that UI, world, or gameplay components need to subscribe to ## Game Store Shape `useGameStore` exposes the main game progression. Main states: | Main state | Role | | ---------- | ------------------------------- | | `intro` | Onboarding and opening sequence | | `ebike` | E-bike repair sequence | | `pylon` | Power pylon repair sequence | | `farm` | Vertical farm repair sequence | | `outro` | Ending sequence | Other important state: - `isCinematicPlaying` - `intro` - `ebike` - `pylon` - `farm` - `outro` Mission steps: ```ts "locked" | "waiting" | "inspected" | "fragmented" | "scanning" | "repairing" | "reassembling" | "done"; ``` `isCinematicPlaying` is read by `PlayerController` to ignore player input while camera timelines are active. ## Reading State In Components Use selectors to read only what the component needs. ```tsx import { useGameStore } from "@/managers/stores/useGameStore"; export function Example(): React.JSX.Element { const mainState = useGameStore((state) => state.mainState); return
Current state: {mainState}
; } ``` This is better than reading the whole store, because the component re-renders only when `mainState` changes. ## Updating Game State Prefer explicit actions from the store. ```ts const advanceGameState = useGameStore((state) => state.advanceGameState); advanceGameState(); ``` For development and debug tooling, direct setters also exist: ```ts const setMainState = useGameStore((state) => state.setMainState); setMainState("ebike"); ``` Direct setters are useful for debug panels, but production gameplay should prefer business actions such as: - `advanceGameState` - `completeEbike` - `completePylon` - `completeFarm` - `completeMission` Mission gameplay that can target `ebike`, `pylon`, or `farm` should prefer generic mission actions: ```ts const setMissionStep = useGameStore((state) => state.setMissionStep); const completeMission = useGameStore((state) => state.completeMission); setMissionStep("ebike", "inspected"); completeMission("ebike"); ``` This keeps reusable gameplay components such as `RepairGame` from duplicating mission-specific branches like `setEbikeState`, `setPylonState`, and `setFarmState`. ## Settings Store `useSettingsStore` owns player-facing settings and forwards audio volume changes to `AudioManager`. State: - `isSettingsMenuOpen` - `musicVolume` - `sfxVolume` - `dialogueVolume` - `subtitlesEnabled` - `subtitleLanguage` Audio setters clamp values between `0` and `1`, then call: ```ts AudioManager.getInstance().setCategoryVolume(category, nextVolume); ``` This keeps UI state and browser audio state synchronized. ## Subtitle Store `useSubtitleStore` is intentionally tiny. State/actions: - `activeSubtitle` - `setActiveSubtitle` - `clearActiveSubtitle` `playDialogueById()` writes to this store while dialogue audio plays. `Subtitles` reads from it and respects `useSettingsStore().subtitlesEnabled`. ## World Integration `src/world/GameStageContent.tsx` subscribes to `mainState` and mounts the repair-game content. Current production repair placement: ```tsx