Shared plumbing for the DOM HUD, no framework: - voice.ts: every player-facing string, so the OSHA-poster deadpan register lives in one place instead of being re-invented per panel. - style.ts: injected terminal-industrial stylesheet. Panels re-enable pointer-events; #ui itself stays transparent to the mouse. - chips.ts: the item chip (swatch + name + count), keyed off GameData so new items from LANE-DATA light up with no code change. - hotkeys.ts: the whole keymap in one file, per lane orders. - selection.ts: pending build selection, written through to bus._sel. - pick.ts: tile -> entity hit-testing plus the flattened roster the UI keeps instead of holding a snapshot across frames (CONTRACTS says sim may reuse those buffers, and input handlers fire between frames). - palette.ts: machine accent colors. Stopgap — MachineDef has no color field; contract request filed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
98 lines
3.8 KiB
TypeScript
98 lines
3.8 KiB
TypeScript
/**
|
|
* 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 { SimEvent } from '../contracts';
|
|
|
|
/**
|
|
* Flavor lines for the inspector, keyed by MachineDef.id.
|
|
*
|
|
* TEMPORARY: hand-transcribed from docs/FKTRY_LORE.md §4 by LANE-UI. These belong in
|
|
* data/machines.json as a `flavor` field owned by LANE-DATA — CONTRACT REQUEST filed in
|
|
* NOTES. Unknown ids fall back to `GENERIC_FLAVOR`, so new machines never crash the
|
|
* panel, they just sound like a filing cabinet until the codex line lands.
|
|
*/
|
|
const MACHINE_FLAVOR: Record<string, string> = {
|
|
'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(defId: string): string {
|
|
return MACHINE_FLAVOR[defId] ?? GENERIC_FLAVOR;
|
|
}
|
|
|
|
/** 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)} HAS STOPPED. ${ev.reason.toUpperCase()}.`;
|
|
case 'brownout':
|
|
return ev.on
|
|
? 'BUFFER UNDERRUN. THIS SECTOR IS NOW A STILL LIFE.'
|
|
: 'BUFFER RESTORED. RESUME SINNING.';
|
|
case 'commissionDone':
|
|
return 'FAX SENT. THE COLLECTOR DOES NOT SAY THANK YOU.';
|
|
default:
|
|
return null; // shipped/crafted/placed/removed are too frequent to toast
|
|
}
|
|
}
|
|
|
|
export const COPY = {
|
|
bandwidth: 'BANDWIDTH',
|
|
brownout: 'BROWNOUT',
|
|
shipped: 'SHIPPED',
|
|
paused: 'HALTED',
|
|
running: 'RUNNING',
|
|
tick: 'TICK',
|
|
|
|
buildBarTitle: 'REQUISITION',
|
|
inspectorEmptyRecipe: 'NO PROCESS ASSIGNED. UNIT IS BEING PAID TO EXIST.',
|
|
inspectorInput: 'INTAKE',
|
|
inspectorOutput: 'OUTPUT',
|
|
inspectorProgress: 'PROCESS',
|
|
inspectorJamPrefix: 'FAULT: ',
|
|
inspectorClose: 'X',
|
|
|
|
faxHeader: 'INCOMING FAX',
|
|
faxSubheader: 'GLITCH ACQUISITIONS — UNSOLICITED',
|
|
faxNoCommission: 'NO ACTIVE COMMISSION. THE FAX MACHINE IS RESTING.',
|
|
faxStamp: 'FAX SENT',
|
|
|
|
/** Shown on the build bar when the player has something selected. */
|
|
placingHint: 'LMB PLACE · R ROTATE · ESC CANCEL',
|
|
idleHint: '1-9 SELECT · SPACE HALT · TAB MANIFEST',
|
|
|
|
/** Inspector fallback when no entity is under the cursor. */
|
|
manifestEmpty: 'NO UNITS ON RECORD. THE FLOOR IS AUDITED AND EMPTY.',
|
|
} 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;
|