/** * LANE-SIM's tuning constants — the single source of truth. * * These were private to index.ts until round 4. They're exported because other lanes' * tests were hand-copying the numbers and going stale in silence: a copy of 0.7 doesn't * fail when the sim moves to 0.65, it just quietly asserts the wrong thing forever. * Import these rather than retyping them. * * Anything DATA can already set per-machine (heatPerTick, coolPerTick, bufferCap, * beltSpeed, coolRadius) lives in data/machines.json, not here. These are the sim-wide * rules that no machine overrides. */ // ------------------------------------------------------------------ belts /** Minimum gap between two items, in belt-tiles. Enforced across the tile seam too. */ export const BELT_SPACING = 0.45; /** Items a single belt tile may hold. */ export const BELT_CAPACITY = 2; /** Items a splitter holds while it waits for an output to open. */ export const SPLITTER_CAPACITY = 2; // -------------------------------------------------------------- machines /** A machine stalls ('output full') once any output piles to this multiple of its yield. */ export const OUTPUT_STALL_FACTOR = 4; /** A machine accepts an input up to this multiple of the recipe requirement. */ export const INPUT_BUFFER_FACTOR = 2; // ------------------------------------------------------------------ heat // // heatPerTick is GROSS (contracts v4): net climb = heatPerTick - coolPerTick, so a // machine whose heatPerTick never clears its cooling can never overheat. /** Below this heat, throttling is purely cosmetic. */ export const HEAT_THROTTLE_START = 0.7; /** Speed multiplier at heat 1.0 — the codex's "tick rate visibly halves". */ export const HEAT_THROTTLE_FLOOR = 0.5; /** Heat at which a machine scrams. */ export const HEAT_SCRAM = 1; /** Cool back below this and a scrammed machine restarts. */ export const HEAT_RESTART = 0.5; /** Per-tick cooling when a machine's def doesn't specify `coolPerTick`. */ export const HEAT_COOL_DEFAULT = 0.004; /** * Ceiling on a machine's total cooling once auras stack (own rate + every cooler in * range). At 0.05 a machine sheds a full 1.0 of heat in 20 ticks (~0.67s), which is * already effectively instant — past this, extra coolers would buy nothing but would * still cost bandwidth and floor space, and stacking them would look broken rather than * generous. See NOTES round 4. */ export const HEAT_COOL_MAX = 0.05; // ---------------------------------------------------------------- research /** * Ticks a lab spends turning ONE delivered science pack into one unit of progress. * Research is an investment, not a purchase: at 30 tps this is ~2s per pack, so a * 10-pack tech is ~20s of lab time on one lab, or ~10s on two (labs parallelize). * Stepped, not fractional — a pack is consumed and progress ticks up by a whole 1 only * when the timer completes, which keeps `research.progress` integer and the determinism * trivial to reason about. */ export const RESEARCH_TICKS_PER_PACK = 60; // ---------------------------------------------------------------- wildlife // // The dust→swarm loop, M2's living hazard. These item ids are the coupling to DATA: the // pile grows from the dust a machine is HOLDING (so a factory that belts its dust away // never hatches), and the trap is any machine whose recipe cans the swarm. /** The item whose accumulation breeds swarms (codex: HF DUST "attracts and hatches"). */ export const DUST_ITEM = 'hf-dust'; /** The canned live-swarm item — a trap's output, and what commissions ask for. */ export const SWARM_ITEM = 'mosquito-swarm'; /** * Pile growth per tick per unit of dust BACKED UP in a machine's output buffer — where * "backed up" means beyond one craft's worth (see DUST_SPILL_GRACE). A line that keeps its * dust moving never crosses the grace threshold and never breeds; a line that ignores it * climbs to the stall cap and hatches in roughly 400 ticks (~14s). That gap is the mechanic. */ export const DUST_GROWTH_PER_HELD = 0.00025; /** * A machine may hold this multiple of its own dust yield without shedding any on the floor. * Normal buffering between belt pushes is not neglect, and punishing it would make the * hazard feel like weather rather than a consequence. */ export const DUST_SPILL_GRACE = 1; /** A pile at or above this hatches a swarm and is consumed. */ export const DUST_PILE_MAX = 1; /** Tiles from the emitter a new pile appears (seeded RNG picks the exact offset). */ export const DUST_PILE_SPREAD = 2; /** Swarm drift toward its target, tiles/sec. */ export const SWARM_DRIFT_TILES_PER_SEC = 1.5; /** Within this chebyshev range of its target's footprint, a swarm is attached and biting. */ export const SWARM_ATTACH_RANGE = 1; /** Speed multiplier a fully-severe attached swarm inflicts (0.5 = half speed). */ export const SWARM_SLOW_FLOOR = 0.5; /** Severity gained per tick while attached, up to 1.0 — the longer it sits, the worse. */ export const SWARM_SEVERITY_GROWTH = 0.004; /** A trap catches swarms within this chebyshev range of its footprint. */ export const TRAP_RADIUS = 4; /** * Live swarms allowed at once. A machine that is permanently jammed on dust would * otherwise breed without limit — measured 29 of them in a 20k demo run, which is a * plague rather than a hazard. At the cap, full piles simply sit there waiting for room. */ export const SWARM_MAX = 8; // ------------------------------------------------------------------ relic /** Nearest a buried relic sits to origin (tiles, euclidean) — well past the starter area. */ export const RELIC_MIN_DIST = 18; /** Farthest a buried relic sits from origin. */ export const RELIC_MAX_DIST = 28; /** * Keep-outs for relic placement, sized to enclose the reference factory's two works — the * western melt line and the eastern research annex — so the demo can always dig its relic * up. A real world has no factory here yet; this just keeps the secret clear of the ground * everyone builds on. relic.test.ts checks, against the layout's real occupied tiles, that * a spread of seeds never buries it. When DATA supplies a `relicReserve` the sim prefers * that corner instead and these become belt-and-braces. */ export const RELIC_KEEPOUT = [ { x0: -33, y0: -16, x1: 2, y1: 16 }, // the melt line { x0: 6, y0: -9, x1: 29, y1: 12 }, // the research annex ]; /** Chebyshev range within which an `excavate` uncovers the relic. */ export const RELIC_DIG_RANGE = 1; // ---------------------------------------------------------------- tracing /** Belt graphs bigger than this are treated as unterminated (cheap loop guard). */ export const MAX_TRACE = 512;