/** * LANE-UI — the house voice. * * Register: OSHA-poster deadpan (lore §8). Everything horrifying is labelled like * routine industrial equipment. Nothing here apologises, explains itself, or uses an * exclamation mark. The player is an employee who has already read the placard. * * All copy in the UI comes from this file so the tone stays in one place. */ import type { MachineDef, SimEvent } from '../contracts'; /** * Fallback flavor, keyed by MachineDef.id. * * `MachineDef.flavor` (contracts v2) is the real source and wins whenever LANE-DATA has * filled it. These are the seven I hand-transcribed from the codex in round 1, kept as * the fallback per orders. Unknown ids get `GENERIC_FLAVOR` — a new machine never * crashes the panel, it just sounds like a filing cabinet until its line lands. */ const MACHINE_FLAVOR: Record = { 'seam-extractor': 'Chews the payload seam and spits raw ore. Do not place hands in the seam. ' + 'The seam has no policy regarding hands.', belt: 'Moves things. Moving things is the entire job. Back-pressure is a normal ' + 'operating condition and is not a grievance.', demuxer: 'Cleaves raw ore into luma and chroma. One intake, four chutes, no apologies. ' + 'Contents were never yours.', quantizer: 'Rounds coefficients to zero. The dial goes to CRIME. Material leaking from the ' + 'seams is HF DUST and is not glitter. Do not lick the machine.', 'mosh-reactor': 'A decoder sealed in a vessel and starved of anchors until it hallucinates. ' + 'The hallucination is the product. Certified vessel. Uncertified hallucination.', 'decode-asic': 'Handles standard profiles only. Cool, efficient, incurious. Generates bandwidth ' + 'by declining to be interesting.', shipper: 'Composites shipped freight onto THE SCREEN. All deliveries final. ' + 'All sins permanent. Congratulations.', }; const GENERIC_FLAVOR = 'Industrial equipment. Operates within tolerances. Tolerances not disclosed.'; export function machineFlavor(def: MachineDef | undefined, id: string): string { return def?.flavor || MACHINE_FLAVOR[id] || GENERIC_FLAVOR; } /** * Jam reasons, in the house voice. The sim owns the reason strings; we translate the * ones we know and pass anything else through uppercased, so a new stall reason shows * up honestly rather than being swallowed. */ const JAM_TEXT: Record = { 'output full': 'OUTPUT BUFFER AT CAPACITY. THIS IS A YOU PROBLEM.', starved: 'INTAKE STARVED. NOTHING IS ARRIVING.', 'no recipe': 'NO PROCESS ASSIGNED. UNIT AWAITS INSTRUCTION.', 'no power': 'INSUFFICIENT BANDWIDTH. THE MATHEMATICS ARE UNMOVED.', }; export function jamText(reason: string): string { return JAM_TEXT[reason] ?? reason.toUpperCase(); } /** Toast copy for events the player should notice without reading a manual. */ export function eventToast(ev: SimEvent, machineName: (id: number) => string): string | null { switch (ev.kind) { case 'jammed': return `${machineName(ev.entity)}: ${jamText(ev.reason)}`; case 'brownout': return ev.on ? 'BUFFER UNDERRUN. THIS SECTOR IS NOW A STILL LIFE.' : 'BUFFER RESTORED. RESUME SINNING.'; case 'scram': return ev.on ? `THERMAL SCRAM. ${machineName(ev.entity)} HAS EXCUSED ITSELF.` : `${machineName(ev.entity)} IS COOL ENOUGH TO CONTINUE. REGRETTABLY.`; case 'commissionDone': return 'FAX SENT. THE COLLECTOR DOES NOT SAY THANK YOU.'; case 'removed': return 'UNIT RECLAIMED. THE FLOOR REMEMBERS.'; case 'researched': return 'STANDARD RATIFIED. NEW CRIMES AVAILABLE.'; default: return null; // shipped/crafted/placed are too frequent to toast } } export const COPY = { bandwidth: 'BANDWIDTH', brownout: 'BROWNOUT', /** Deficit covered by the tanks. Not a fault — the machine is doing its job. */ onReserve: 'ON RESERVE', shipped: 'SHIPPED', paused: 'HALTED', running: 'RUNNING', tick: 'TICK', inspectorEmptyRecipe: 'NO PROCESS ASSIGNED. UNIT IS BEING PAID TO EXIST.', inspectorInput: 'INTAKE', inspectorOutput: 'OUTPUT', inspectorProgress: 'PROCESS', inspectorRecipe: 'PROCESS SELECT', inspectorNoRecipeOption: 'IDLE', inspectorClose: 'X', inspectorHeat: 'HEAT', heatThrottling: 'THROTTLING', heatScram: 'SCRAM — UNIT OFFLINE', faxHeader: 'INCOMING FAX', faxSubheader: 'GLITCH ACQUISITIONS — UNSOLICITED', faxNoCommission: 'NO ACTIVE COMMISSION. THE FAX MACHINE IS RESTING.', faxStamp: 'FAX SENT', faxStanding: 'STANDING ORDER', faxNext: 'NEXT IN TRAY', /** Shown against shipments The Correction quietly buys back (M3 fiction). */ sanitising: 'SANITISING', techHeader: 'STANDARDS COMMITTEE', techSubheader: 'MEDIA ARCHAEOLOGY — RESEARCH DOWN, DERIVE UP', techActive: 'IN COMMITTEE', techDone: 'RATIFIED', /** Shown when the sim reports no research state at all. */ researchOffline: 'RESEARCH OFFLINE. THE COMMITTEE HAS NOT CONVENED.', requires: 'REQUIRES:', placingHint: 'LMB PLACE · R ROTATE · ESC CANCEL', idleHint: '1-9 SELECT · [ ] PAGE · X REMOVE · T RESEARCH · SPACE HALT · CLICK A UNIT', removeLabel: 'REMOVE', removeKey: 'X', removeHint: 'REMOVE ARMED · LMB DEMOLISH · ESC STAND DOWN', } as const; /** Direction names for the rotation readout. Dir 0..3 = N,E,S,W clockwise. */ export const DIR_NAME = ['N', 'E', 'S', 'W'] as const;