PURPOSE
Persists completed match results to the server via the finalize_run Supabase RPC. Called after the engine bridge fires onGameOver. The server atomically inserts into match_history, calculates currency rewards, updates player_currencies, and inserts transactions. Returns a canonical finalized response (match_id, wallet snapshot, optional tier/challenge/planet records) that drives ResultsScreen display and downstream store updates.
OWNS
finalizeRun(result: MissionResult, planetIndex: number): Promise<FinalizeRunResponse>— single exported function.FinalizeRunResponseinterface — shape of the RPC return payload (match_id, wallet, rewards, optional tier_record, challenge_completions, planet_stats, planet_xp).- Client-side computation of
credits_earnedviacomputeArcadeCredits(result). - Sync-status lifecycle around the RPC call (
syncing→synced|error).
READS FROM
useSessionStore.getState().runDef?.context.planetId— actual planet id (3, 12, 21), defaults to 0 if missing.MissionResultfields:identity.shipId,performance.timeElapsedSeconds,combat.totalKills,progression.levelReached,progression.tierReached,progression.weaponsAtEnd,progression.eventsCompleted,outcome.survived.computeArcadeCreditsfrom../data/economy.invokeRpcfrom@metagame/services/supabase.
PUSHES TO
useWalletStore.replaceFromSnapshot({ gems, credits })— canonical wallet update from server response.useTierStore.updateFromRunResult(planet_id, highest_tier)— whentier_recordpresent.useChallengeStore.addCompletions(challenge_completions)— when array non-empty.useChallengeStore.updatePlanetStats(planet_id, total_kills, total_events)— whenplanet_statspresent.usePlanetProgressStore.setXp(planet_id, xp)— whenplanet_xppresent; planet_id narrowed to3 | 12 | 21.usePlayerStore.setSyncStatus('syncing' | 'synced' | 'error')— bracketing the RPC.- Supabase
finalize_runRPC — payload with run identity, performance, combat, progression, planet, weapons_at_end, events_completed.
DOES NOT
- Compute or send
xp_earned(hardcoded 0) orscrap_earned(hardcoded 0). - Use
planetIndexparameter for the RPC payload — planet_id is read from session store instead. - Mutate
MissionResultor any local game state. - Render or display anything — ResultsScreen consumes the return value separately.
- Retry on RPC failure — error propagates; sync_status flips to
error. - Author currency rewards server-side — credits are computed client-side (noted as moving server-side for real-money launch).
- Persist before
missionResultis set in sessionStore (caller contract).
Signals
syncing/synced/errorsync-status broadcast throughusePlayerStore.- Thrown error on RPC failure — caller responsible for handling.
- Wallet snapshot replacement triggers wallet store subscribers.
- Tier, challenge, planet-stats, and planet-xp store updates fire individually only when their respective response fields are present (defensive against pre-migration responses).
Entry points
- Called after the engine bridge
onGameOverfires, postmissionResultbeing set insessionStore. - Caller must
awaitcompletion before allowing user interaction withResultsScreenrewards. - Return value flows into
ResultsScreenfor canonical display.
Pattern notes
- Single async function module; no class, no internal state.
- All state mutations go through Zustand
getState()calls — no React hooks here (service layer). - Defensive optional-chaining on response fields tied to migration phases (031 for challenges/planet_stats, 033 for planet_xp).
- Wallet is replaced from snapshot rather than incremented locally — server is canonical.
xp_earnedandscrap_earnedare wired but zero-valued, reserved for future expansion.tier_reachedis forwarded fromMissionResult.progression, distinct fromwave_reached/levelReached.resultfield is a string enum ('survived'|'died') derived fromoutcome.survived.- Planet id sourced from
runDef.context.planetId(canonical 3/12/21), explicitly distinguished from theplanetIndex(0-2 chapter index) parameter, which is accepted but not forwarded to the RPC. - Sync-status try/finally pattern is split:
erroris set in the catch,syncedonly after all store updates succeed.