feat(game): crew-costs balance module + computeDuration duration model (#44)
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
// Pure task-engine model (spec §3–§4): the task types and the duration formula. No lib/db
|
||||
// imports — the frontend imports this for the live duration preview in the crew picker
|
||||
// (same precedent as tech.ts / ship-costs.ts).
|
||||
import { MISMATCH_CANCEL_TRAIT, PREFERRED_ROLES, TRAIT_DURATION_MODIFIERS, WORK_POINTS } from './crew-costs'
|
||||
import type { CrewMember } from './crew'
|
||||
|
||||
export type TaskType = 'JUMP' | 'EXPLORE' | 'SHIP_UPGRADE' | 'SHIP_EXPAND' | 'SHIP_BUILD' | 'RESEARCH'
|
||||
|
||||
// Work points per action (spec §4): JUMP scales with hop count, the rest are flat.
|
||||
function workPoints(type: TaskType, hopCount: number): number {
|
||||
switch (type) {
|
||||
case 'JUMP':
|
||||
return hopCount * WORK_POINTS.JUMP_PER_HOP
|
||||
case 'EXPLORE':
|
||||
return WORK_POINTS.EXPLORE
|
||||
case 'RESEARCH':
|
||||
return WORK_POINTS.RESEARCH
|
||||
default:
|
||||
return WORK_POINTS.SHIP_WORK // SHIP_UPGRADE | SHIP_EXPAND | SHIP_BUILD
|
||||
}
|
||||
}
|
||||
|
||||
// One trait's modifier for a lead on an action type: its per-type entry plus the 'ANY' entry.
|
||||
// Traits without a map entry contribute 0.
|
||||
function traitModifier(trait: string, type: TaskType): number {
|
||||
const mods = TRAIT_DURATION_MODIFIERS[trait]
|
||||
if (!mods) return 0
|
||||
return (mods[type] ?? 0) + (mods['ANY'] ?? 0)
|
||||
}
|
||||
|
||||
// duration = max(1, ceil(work / totalPower) + mismatchPenalty + traitModifierSum) — spec §4.
|
||||
// totalPower = Σ powerOf over the WHOLE team (lead + helpers); the default powerOf is the
|
||||
// constant 1 until PR B feeds the skill band. The mismatch penalty and the trait modifiers
|
||||
// apply to the LEAD only — helpers contribute power, are never penalised, and their traits do
|
||||
// not apply. `hopCount` is only meaningful for JUMP (pass 1 for the other types).
|
||||
export function computeDuration(
|
||||
type: TaskType,
|
||||
hopCount: number,
|
||||
lead: CrewMember,
|
||||
helpers: CrewMember[],
|
||||
powerOf: (m: CrewMember) => number = () => 1,
|
||||
): number {
|
||||
const totalPower = Math.max(1, [lead, ...helpers].reduce((sum, m) => sum + powerOf(m), 0))
|
||||
const mismatchPenalty = lead.traitGood === MISMATCH_CANCEL_TRAIT || PREFERRED_ROLES[type].includes(lead.role) ? 0 : 1
|
||||
const traitModifierSum = traitModifier(lead.traitGood, type) + traitModifier(lead.traitBad, type)
|
||||
return Math.max(1, Math.ceil(workPoints(type, hopCount) / totalPower) + mismatchPenalty + traitModifierSum)
|
||||
}
|
||||
Reference in New Issue
Block a user