The mission board is the metagame screen where the player picks the contract for the next deploy. It is a full-screen white-panel layout titled “JOB BOARD // CYGNUS NET v3.2” that lists exactly two posting cards drawn from the static mission-postings table. Each card describes one in-universe contract (planet, hiring faction, difficulty tier, objective vibe, payout, author tag) and exposes a single primary action that assembles the run definition and launches the run. The board has no filter controls, no detail page, and no per-card refresh. The screen lives at the metagame route /games/starship-survivors/board.
Stats tables
Layout regions
Region
Position
Contents
Header bar
Top of viewport
Back-home link, screen title, UTC timestamp
Preamble
Below header
Single-line caption (“INCOMING POSTINGS — 2 OF MANY. SELECT ASSIGNMENT.“)
Cards area
Center, takes remaining vertical space
Two posting cards laid out horizontally, wrapping on narrow viewports
Footer
Bottom of viewport
In-universe legal-banner line
Posting slot count
Stat
Value
Cards rendered per visit
2
Total postings in pool
11 (one per planet in planet order)
Same posting on both cards
Never (selector enforces distinct indices)
Empty / placeholder slots
None
Per-card information shown
Field
Source
Display style
Faction
posting.faction
Uppercase secondary-color label, top row
Difficulty pill
posting.difficulty
Outlined pill, color from difficulty token
Location label
resolved planet display name via PLANETS[posting.planetId]
“LOCATION:” prefix, value in main text color
Objective headline
posting.objectiveLabel
Large uppercase cyan-accent line
Objective blurb
posting.objectiveBlurb
One-line italic-feel quote with a left accent stripe
Payout line
posting.payoutLine
Amber, bold
Posted-by tag
posting.postedBy
Small muted text, ”— Posted by {name}“
Accept action
onAccept handler
Full-width primary button labeled “ACCEPT”
Difficulty pill colors
Difficulty
Color token
Routine
status-good (green)
Standard
accent (cyan)
Hazardous
amber
Critical
status-warn (orange)
Black Flag
status-bad (red)
Any unrecognized difficulty value falls back to the muted-text color.
Action buttons
Control
Location
Behavior
HOME (back link)
Header left
Navigates to the metagame root route
ACCEPT
Footer of each card
Assembles a run definition from the selected ship and posting planet, writes it to the session store, navigates to the play route
There is no “decline,” “reroll,” “details,” “favorite,” or “compare” control. Each card has exactly one outgoing action.
Posting fields stored per record
Field
Type
Purpose
id
string
Stable unique key
planetId
numeric planet id
Drives biome, level preset, enemy multipliers at assembly
faction
one of ten in-universe factions
Header label on the card
objectiveLabel
one of Explore / Find / Protect / Battle
Headline copy + engine objective tuning
objectiveBlurb
string
One-line positive in-universe description
difficulty
one of Routine / Standard / Hazardous / Critical / Black Flag
Pill color and extraction timer
payoutLine
string
Reward copy on the card
postedBy
string
In-universe author tag
extractionTimerSeconds
number
Survive-time-until-extraction the run uses
Refresh and reroll rules
Trigger
Effect on the rendered pair
Initial mount of the screen
Pair is selected once from pickTwoPostings() using the current Date.now() as the seed
In-screen state change (re-render)
No effect — the pair is held in component state initialized once via the lazy initializer
Soft navigation back to the screen (new mount)
New pair drawn using the new Date.now() seed
Page reload
New pair drawn using the new Date.now() seed
Manual reroll button
Not present
Refresh timer
Not present
The header UTC timestamp is captured once on mount via useMemo and does not tick.
Selector behavior
Stat
Value
Selector function used
pickTwoPostings(seed)
Seed default
Date.now()
Determinism
Same seed always produces the same pair
Index A formula
hashed seed modulo pool size
Index B formula
second hash modulo (pool size − 1), shifted to skip index A
Planet-biased variant available
Yes (pickTwoPostingsForPlanet) but not consumed by this screen
Fallback on zero matches (biased variant only)
Unbiased two-card draw
How it works
Route mount instantiates the screen component; the component reads selectedShipId and setRunDef from the session store.
The two-card pair is picked once via pickTwoPostings(); the result is stored in component state with a lazy initializer so re-renders do not redraw.
The header timestamp is built once via useMemo and shown in YYYY-MM-DD HH:MM:SS UTC format.
Each card is rendered through a card panel using the medical-UI card variant. Layout flows top-to-bottom: faction + difficulty pill, location, objective headline, blurb, payout, posted-by tag, accept button.
The location label resolves the posting’s planet id to its display name via the planets map and falls back to the raw id if the entry is missing.
The accept button calls a handler that calls the run-assembly service with the current selected ship id and the posting’s planet id, writes the resulting run definition into the session store, then navigates to the play route. The objective tuning and extraction timer travel with the posting via the underlying assembly path; this screen only forwards the planet id and ship id.
The back-home link is a router link that returns to the metagame root route.
The screen has no internal state besides the held pair and the memoized timestamp.
Interactions
Mission postings data: the screen consumes the static posting table and the pickTwoPostings selector. The pool count, per-record fields, difficulty tiers, and objective vibes all live in that data module.
Planets: each card resolves its planet display name from the planets map keyed by the posting’s planet id. The accept handler forwards the planet id to the run assembler as the planet index.
Run assembly: the accept handler invokes the run-assembly service with the current ship id and the chosen planet id. The service uses the planet id to set biome, level preset, planet enemy multiplier, and starting context, and returns the run definition the play route consumes.
Session store: the screen reads selectedShipId and calls setRunDef to publish the assembled run definition before navigating away. The session store carries the run definition into the play route.
Routing: the back-home link uses the metagame root route; the accept action navigates to the play route.
Medical-UI panel: each card is rendered through the shared card-variant panel component, which provides the white-surface aesthetic, hairline border, shadow, and shared spacing tokens.
Difficulty extraction timer: each posting carries an explicit extraction-timer-seconds field (3, 4, 5, 6, or 7 minutes per tier), which is consumed downstream by the run engine to drive the in-run “SURVIVE M:SS until extraction” banner. The card itself does not display the numeric timer.
What it does NOT do
Does not show more than two cards at a time.
Does not let the player reroll, refresh, swap, or shuffle the pair without leaving and remounting the screen.
Does not surface loss conditions, fail states, or warning copy on the card body.
Does not display the extraction-timer numeric value on the card.
Does not display the per-objective engine tuning (enemy-count multiplier, weapon-box count) on the card.
Does not let the player pick the planet from this screen — the planet is bound to the posting record.
Does not let the player pick the ship from this screen — the ship is whatever the session has selected when the screen mounts.
Does not bias the pair toward a specific planet when reached via this route (the planet-biased selector exists in the data layer but is not invoked here).
Does not allow both cards to resolve to the same posting; the selector guarantees distinct indices.
Does not animate the cards in or out beyond the panel’s default mount behavior.
Does not tick the header timestamp; the timestamp is captured once on mount.
Does not consume the planet id passed from the hub at the React-Router level; the screen always draws from the unbiased pool.
Does not gate cards by player progression, account age, owned ships, owned artifacts, or any unlock state; all eleven postings are eligible on every mount.
Does not store the chosen posting on the run definition or on the session store as a posting record — only the planet id is forwarded into the assembler.
Does not surface the per-planet challenges list, tier KPI, or claim flow; those live in the challenges popover reached from the hub.
Does not persist the rendered pair across mounts or reloads; the seed defaults to wall-clock at mount time, so navigating away and back picks a fresh pair.
Does not block the accept action while a run is being assembled; assembly is synchronous.
Does not validate that the selected ship still exists or is owned; ship validity is handled upstream by the session store hydrator.