PURPOSE
Zustand store that holds “frozen” before-values for wallet, counter, and progress displays during reward presentation so the UI can show pre-reward state while animations and chips play, then release back to live store values once each animation completes. Currently a stub: lockForBatch, releaseProgress, and releaseCounter are no-ops; only releaseAll and the walletOverrides field are functional. Exported alongside the store is a progressKey helper that composes a track plus optional trackId into a stable override key.
OWNS
walletOverrides: Map<string, number> | null— frozen wallet display values keyed by currency name (gems,credits);nullwhen no override is active.- The override lifecycle API surface:
lockForBatch(batch),releaseProgress(key),releaseCounter(counter),releaseAll(). - The
progressKey(track, trackId?)key-composition function.
READS FROM
RewardBatchtype from../data/reward-types(parameter type forlockForBatch).- Nothing else at runtime — the store is self-contained.
PUSHES TO
- React subscribers via Zustand selectors.
V32ShellreadswalletOverridesand substituteswalletOverrides?.get('gems') ?? liveGems(and same for credits) so the hub HUD shows the frozen value while a reward batch is mid-presentation.
DOES NOT
- Mutate
walletStoreorinventoryStore— it only holds an overlay value; the underlying wallet/inventory stores are updated by the reward finalizer pipeline separately. - Track progress-bar or counter override values yet — the stub does not populate any progress/counter map, so consumers currently fall back to live values.
- Drive animations or timing — it only freezes/unfreezes display values;
RewardOrchestratorandHomeResolveControllerdecide when to call the release methods.
Signals
walletOverridesflips fromnullto a populatedMapwhenlockForBatchis wired, and back tonullonreleaseAll. Selector subscribers re-render on change.
Entry points
useDisplayOverrideStore— Zustand hook; called as a selector (useDisplayOverrideStore(s => s.walletOverrides)inV32Shell) or imperatively (useDisplayOverrideStore.getState().lockForBatch(batch)inreward-finalizers).progressKey(track, trackId?)— used byHomeResolveControllerto build the key passed toreleaseProgressafter a bar animation finishes.lockForBatch(batch)— called fromreward-finalizersat the end of each finalize path, guarded by!isNoOpBatch(batch), before enqueueing the batch ontorewardQueueStore.releaseCounter(counter)— called fromRewardOrchestratorafter the collect phase, iterating the batch’scountersdeltas.releaseProgress(key)— called fromHomeResolveControlleronce a progress bar animation completes for a given track delta.releaseAll()— called fromRewardOrchestratorwhen the queue drains (next phase isnull).
Pattern notes
- Stub status is intentional and load-bearing: the call sites in
reward-finalizers,RewardOrchestrator, andHomeResolveControlleralready expect the lifecycle, so the eventual non-stub implementation slots in without changing callers. - The store is the chosen seam for the “display vs. truth” split — live stores (
walletStore,inventoryStore, progress tracks) are always current; this overlay layer is the only place that lies to the UI, and it lies only for the duration of a reward presentation. - Override keys are namespaced by domain: currency keys are bare strings (
gems,credits); progress keys go throughprogressKey(track, trackId?)which yieldstrackortrack:trackId. Counter keys use thedelta.counterstring directly. - Imperative
getState()access is used from non-React code paths (finalizers, advance functions); React components use the hook with a selector to get reactivity. releaseAllis the only mutator currently implemented and unconditionally clearswalletOverridestonull; it does not touch progress/counter maps because none exist on the state yet.