35 lines
1.7 KiB
TypeScript
35 lines
1.7 KiB
TypeScript
// Special-status registry (v0.37.0, ISS-073) — data-driven package modifiers: one entry per
|
||
// special status maps to reward/deadline/reputation tweaks applied at generation and delivery.
|
||
// Adding or removing a special is an edit of ONE entry here — no scattered branches elsewhere.
|
||
import type { Rng } from './seed';
|
||
|
||
export interface SpecialModifiers {
|
||
// Multiplier on the base reward (applied at package generation).
|
||
rewardMultiplier: number;
|
||
// Scale factor on the drawn deadline window (applied at generation; forced offsets skip it).
|
||
deadlineScale: number;
|
||
// Flat reputation bonus added to the distance-based gain at delivery.
|
||
repBonus: number;
|
||
}
|
||
|
||
export const SPECIALS: Record<string, SpecialModifiers> = {
|
||
priority: {
|
||
rewardMultiplier: 1.5, // ponytail: balance-tunable — a 50% premium for the tighter deadline below
|
||
deadlineScale: 0.5, // ponytail: balance-tunable — half the normal 6–72h window (3–36h)
|
||
repBonus: 0.5, // ponytail: balance-tunable — flat rep on top of the distance-based gain at delivery
|
||
},
|
||
};
|
||
|
||
// Fraction of generated packages that roll a special status (v0.37.0).
|
||
export const SPECIAL_SPAWN_CHANCE = 0.15;
|
||
|
||
// Pure spawn helper: one chance draw, then a uniform pick among the registry's keys. The registry
|
||
// is a PARAM defaulting to the real one so tests can pass a filtered/empty record — removing an
|
||
// entry cleanly disables that status (an empty registry consumes no draws at all).
|
||
export function pickSpecial(rng: Rng, registry: Record<string, SpecialModifiers> = SPECIALS): string | null {
|
||
const keys = Object.keys(registry);
|
||
if (keys.length === 0) return null;
|
||
if (rng() >= SPECIAL_SPAWN_CHANCE) return null;
|
||
return keys[Math.floor(rng() * keys.length)];
|
||
}
|