/** * Shared accessors for reading numeric knobs out of a merged simulator config * (`sports_season_simulator_configs.config` merged over the profile defaults). * * These were previously duplicated inside individual simulators (EPL, MLS, * NCAA-M). Centralizing them keeps every simulator reading engine knobs the same * way, so values set in the unified config screen reliably drive the math. * * Two zero-handling variants exist deliberately: * - `configNumber` accepts 0 (`>= 0`) — for knobs where 0 is meaningful * (e.g. a draw decay or blend weight of 0). * - `positiveConfigNumber` requires `> 0` — for counts/divisors where 0 is * nonsensical and should fall back to the default. */ type Config = Record | undefined; /** Read a finite, non-negative number from config, else the fallback. */ export function configNumber(config: Config, key: string, fallback: number): number { const value = config?.[key]; return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback; } /** Read a finite, strictly-positive number from config, else the fallback. */ export function positiveConfigNumber(config: Config, key: string, fallback: number): number { const value = config?.[key]; return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback; } /** * Read a required finite, strictly-positive number from config. Throws when the * key is missing or non-positive — use only for knobs a profile always seeds. */ export function requiredConfigNumber(config: Config, key: string, label = "simulator"): number { const value = config?.[key]; if (typeof value === "number" && Number.isFinite(value) && value > 0) return value; throw new Error(`${label} config is missing positive numeric ${key}.`); } /** Coerce a raw value (already extracted from config) to a positive number. */ export function optionalPositiveNumber(value: unknown, fallback: number): number { return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback; }