db+game: crew table and pure seeded crew generation (starting crew + exploration roll)
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
// Pure, seeded crew generation — no DB, no Date, no Math.random. Same seed → same starting
|
||||
// crew; the exploration crew roll draws from the caller's per-planet rng stream so a found
|
||||
// member is deterministic per save+planet (same derivation as the resource/part haul).
|
||||
|
||||
import { mulberry32 } from './world'
|
||||
|
||||
export interface CrewMember {
|
||||
name: string
|
||||
role: string
|
||||
traitGood: string
|
||||
traitBad: string
|
||||
}
|
||||
|
||||
const FIRST_NAMES = [
|
||||
'AVA', 'BEN', 'CASS', 'DANA', 'ELI', 'FERN', 'GIDEON', 'HALYA',
|
||||
'IVAR', 'JUNO', 'KAI', 'LORE', 'MARA', 'NICO', 'ODESSA', 'PETRA',
|
||||
]
|
||||
|
||||
const LAST_NAMES = [
|
||||
'VANCE', 'OKONKWO', 'SORENSEN', 'DELACRUZ', 'HALLOWAY', 'IBARRA', 'KOVAC', 'LINDQVIST',
|
||||
'MERCER', 'NADEAU', 'ORLOV', 'QUINTANA', 'REYES', 'STALKE', 'TANAKA', 'UNDERWOOD',
|
||||
'VALDEZ', 'WHITLOCK', 'YAMADA', 'ZUBEROV', 'ASHFORD', 'BLACKWELL',
|
||||
]
|
||||
|
||||
const ROLES = ['COMMANDER', 'PILOT', 'ENGINEER', 'MEDIC', 'NAVIGATOR', 'SCIENTIST', 'GUNNER', 'TECHNICIAN']
|
||||
|
||||
const GOOD_TRAITS = [
|
||||
'STEADY UNDER PRESSURE', 'RECKLESS COURAGE', 'CALM IN A CRISIS', 'FIXES ANYTHING',
|
||||
'SHARP SHOOTER', 'READS STARS BLIND', 'QUICK HANDS', 'IRON WILL',
|
||||
'FINDS HIDDEN THINGS', 'TALKS TO ANYONE',
|
||||
]
|
||||
|
||||
const BAD_TRAITS = [
|
||||
'RIGID — SLOW TO ADAPT', 'SHORT FUSE', 'OVERCAUTIOUS', 'CARELESS WITH FUSES',
|
||||
'SLOWS THE CREW DOWN', 'ARGUES WITH EVERYONE', 'FORGETS TO EAT', 'JINXED IN A DOGFIGHT',
|
||||
'HOARDS RATIONS', 'DOUBTS THE CAPTAIN',
|
||||
]
|
||||
|
||||
const STARTING_CREW_MIN = 2
|
||||
const STARTING_CREW_SPREAD = 2 // 2..3 starting members
|
||||
export const CREW_CHANCE = 0.18 // a scan finds a crew member who joins the ship (~1 in 5-6 scans)
|
||||
|
||||
function title(word: string): string {
|
||||
return word.charAt(0) + word.slice(1).toLowerCase()
|
||||
}
|
||||
|
||||
function pick<T>(rng: () => number, arr: T[]): T {
|
||||
return arr[Math.floor(rng() * arr.length)]
|
||||
}
|
||||
|
||||
// A name unique against `taken` (the current roster), claimed into the set on success.
|
||||
// 16×22 = 352 combinations — a roster realistically never approaches that size.
|
||||
export function makeCrewName(rng: () => number, taken: Set<string>): string {
|
||||
for (;;) {
|
||||
const name = `${title(pick(rng, FIRST_NAMES))} ${pick(rng, LAST_NAMES)}`
|
||||
if (!taken.has(name)) {
|
||||
taken.add(name)
|
||||
return name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The crew a new game starts with: 2–3 members, unique names and roles (roles are drawn
|
||||
// without replacement). Pure — the caller derives the rng from the save seed.
|
||||
export function generateStartingCrew(rng: () => number): CrewMember[] {
|
||||
const taken = new Set<string>()
|
||||
const roles = [...ROLES]
|
||||
const crew: CrewMember[] = []
|
||||
for (let i = 0; i < STARTING_CREW_MIN + Math.floor(rng() * STARTING_CREW_SPREAD); i++) {
|
||||
const role = roles.splice(Math.floor(rng() * roles.length), 1)[0]
|
||||
crew.push({ name: makeCrewName(rng, taken), role, traitGood: pick(rng, GOOD_TRAITS), traitBad: pick(rng, BAD_TRAITS) })
|
||||
}
|
||||
return crew
|
||||
}
|
||||
|
||||
// The exploration crew roll: with CREW_CHANCE a member is found and joins the ship. Draws
|
||||
// from the SAME per-planet rng stream as the resource/part haul (called after it in
|
||||
// save.ts), so the outcome stays deterministic per save+planet. `taken` is the current
|
||||
// roster's names, keeping every name on the ship unique.
|
||||
export function rollCrewMember(rng: () => number, taken: Set<string>): CrewMember | null {
|
||||
if (rng() >= CREW_CHANCE) return null
|
||||
const role = pick(rng, ROLES)
|
||||
return { name: makeCrewName(rng, taken), role, traitGood: pick(rng, GOOD_TRAITS), traitBad: pick(rng, BAD_TRAITS) }
|
||||
}
|
||||
|
||||
// Independent PRNG stream for the starting crew of one save (same pattern as bodyRng /
|
||||
// explorationRng in world.ts — a fixed offset keeps it away from every other derivation).
|
||||
export function startingCrewRng(seed: string): () => number {
|
||||
return mulberry32((Number(seed) + 0x51ed270b) >>> 0)
|
||||
}
|
||||
Reference in New Issue
Block a user