PURPOSE
Gacha-style reveal screen that sits between ShipPullScreen and the Hub. Rolls grid-mod drops earned during the just-completed mission, persists them to the player’s inventory, and stages a timed reveal animation with a stat inspector popover. Auto-skips to the Hub when there is no missionResult in session or when the drop count resolves to zero.
OWNS
- The mounted overlay (
position: fixed,zIndex: 300) with radial gradient backdrop, the title element, the row of drop cards, and the bottom action buttons. - Drop roll lifecycle: the one-shot mount-time roll, application of those rolls to inventory, and the local
dropsstate holding the resultingDropRoll[]. - Reveal staging state:
showTitle,revealedCount,showButtons, plus the array ofsetTimeouthandles cleared on unmount. - The skip-to-end behavior that collapses all stages instantly when the background is tapped.
- The drop inspector popover (
zIndex: 350) and itsinspectstate. - Internal helper components
DropCard,ShapeMini,DropPopoverand thepillButtonstyle factory. - Local
STAT_LABELmap andformatStatValuehelper (mirrorsUpgradesTab; intentionally not extracted). STAGEtiming constants:titleIn0ms,firstReveal400ms,revealStagger600ms,buttonsAfterLast400ms.
READS FROM
useSessionStore—missionResult(used for both the redirect guard and the drop-count inputs).getTierPB(nodeId)from@starship-survivors/data/run-history— prior personal-best tier for the node.computeDropCount(tierReached, tierPB)from@starship-survivors/services/mission-drops.MOD_TEMPLATES_BY_IDandRARITY_COLORSfrom@starship-survivors/data/mods.resolveShapefrom@starship-survivors/data/mods/_typesforShapeMinicell grids.CARD_SHADOW_CSSfrom@starship-survivors/engine/rendering/card-themefor the primary button shadow.- React Router’s
useNavigatefor redirects.
PUSHES TO
rollMissionDrops(dropCount)— produces theDropRoll[]rendered on screen.applyMissionDrops(rolled)— persists the rolls into the player inventory (called once, guarded bydidRollRef).- Navigation:
navigate('/', { replace: true })whenmissionResultis absent ordropCount === 0.navigate('/')from the CONTINUE button.navigate('/games/starship-survivors/ships?tab=upgrades&highlight=new')from the VIEW IN MENU button.
DOES NOT
- Roll or grant any rewards other than grid-mod drops (no XP, currency, ship pulls, or mission-progress writes — those happen upstream in
ShipPullScreen/ mission resolution). - Render anything when
missionResultisnullor whendrops.length === 0— returnsnulland lets the redirect effect handle navigation. - Re-roll drops on re-render:
didRollRefensuresrollMissionDrops+applyMissionDropsrun exactly once per mount. - Validate or de-duplicate templates; trusts whatever
rollMissionDropsreturns. - Persist any UI state across navigation — staging timers,
inspect, anddropsare all local and reset on mount. - Block backgrounding/tab-hide: timers continue running while hidden (no visibility-handling).
Signals
showTitle: boolean— fades the “LOOT RECOVERED” header in atSTAGE.titleIn.revealedCount: number— number of cards currently revealed; advanced viasetRevealedCount(n => Math.max(n, i + 1))so skip-to-end can monotonically jump ahead.showButtons: boolean— toggles opacity andpointerEventsfor the bottom button row; also drives thedisabledattribute on both buttons.inspect: DropRoll | null— non-null mounts theDropPopover.drops: DropRoll[]— driven once by the roll effect; an empty array short-circuits render tonull.didRollRef: MutableRefObject<boolean>— guards single-execution of the roll.
Entry points
- React route in the metagame shell that renders
<LootBagScreen />after a completed mission (the screen readsmissionResultrather than route params; entered when navigation lands on the loot-bag URL with a populated session). - Mount flow: redirect-guard effect → one-shot roll effect (or redirect on zero drops) → staging-timer effect → user input (tap card / tap background / press button).
Outgoing entry points (where this screen sends the user):
- Hub root
/— on missingmissionResult, on zero drops, and from the CONTINUE button. - Ships upgrades tab
/games/starship-survivors/ships?tab=upgrades&highlight=new— from VIEW IN MENU.
Pattern notes
- The outer container’s
onClick={skipToEnd}is the catch-all; revealedDropCards and both buttons calle.stopPropagation()so taps on interactive elements don’t also collapse the staging. - Unrevealed cards intentionally do not stop propagation, so tapping a hidden card still triggers
skipToEnd. skipToEndsets all staging flags to their terminal values rather than canceling timers; the timers stay in flight but are no-ops because state setters useMath.maxor are idempotent.- Both navigation redirects use
replace: trueto keep the loot-bag screen out of the browser history. - Stat rendering convention: keys ending in
Pctand the special-casedamageReductionare stored as fractions and rendered as+N%; everything else is rendered as+N. ShapeMiniderives a cell size asMath.min(10, Math.floor(56 / max(w, h)))to keep the mod’s shape grid inside the card regardless of template bounding box.pillButtonusesall: 'unset'to neutralize default button styles before reapplying the visual treatment; primary variant pulls its drop shadow from the sharedCARD_SHADOW_CSStoken.- The popover uses a two-layer click structure: the outer scrim calls
onClose, the inner card callse.stopPropagation()so taps inside don’t dismiss. STAT_LABELis duplicated withUpgradesTabby design — comment explicitly defers extraction until a third caller exists.