Zum Inhalt

src/engine/data/dictionary.ts

// @ts-nocheck
// Spoiler-free atmospheric dictionary, NeuroVim-flavored.
// Pools are mixed at runtime; lore intensity controls categorical weights.
//
// FACADE over the shared content SSOT (story-content.json) since Slice 1b: the pools
// moved into flat JSON sections (boot/terminalDicts/hudHandles/modeLabels/flashPhrases/
// alertPhrases/commandSessions). DICT is rebuilt here in the original key order so the
// facade output is byte-identical (guarded by tests/dict-presets-parity.test.ts). The
// lore-intensity WEIGHTS table + pickTerminalLine/pickCommandSession/pickFrom stay
// code-side (logic, per design spec §2).

import story from './story-content.json';

export const DICT = {
  BOOT_LINES: story.boot.lines,
  BOOT_HEADERS: story.boot.headers,
  TERMINAL: story.terminalDicts,
  HUD_HANDLES: story.hudHandles,
  MODE_LABELS: story.modeLabels,
  FLASH_PHRASES: story.flashPhrases,
  ALERT_PHRASES: story.alertPhrases,
  COMMAND_SESSIONS: story.commandSessions,
} as const;

export type LineCategory =
  | 'STATUS' | 'WARNING' | 'LORE' | 'QUOTES'
  | 'CMD' | 'RESP'
  | 'HQ' | 'DENY'
  | 'GHOSTLINK'   // system status from the secure mentor channel
  | 'INSTR';      // inbound message from the instructor (mentor)

export interface TerminalLine {
  category: LineCategory;
  text: string;
}

// Faux command sessions (story.commandSessions) — a `cmd` is typed at the prompt, then
// 1-3 response lines stream in, giving the terminal an interactive-session feel instead
// of a passive log. Each response item is a RespItem:
//   • plain string  → renders as RESP category (indented "↳ ...")
//   • { cat, text } → renders with that override category (e.g. QUOTES gets the "[":] ..."
//                     prefix) — used to surface lore/quotes with full impact INSIDE the
//                     conversational flow, not as ambient log noise.
// (Doc relocated here in Slice 1b — JSON can't carry the comment that lived above the
// COMMAND_SESSIONS literal; the consuming switch lives in hud/index.ts runCommandSession.)
export type RespItem = string | { cat: LineCategory; text: string };
export interface CommandSession {
  cmd: string;
  resp: readonly RespItem[];
}

const WEIGHTS: Record<string, [number, number, number, number]> = {
  // [STATUS, WARNING, LORE, QUOTES]
  none:   [1.0, 0.0, 0.0, 0.0],
  subtle: [0.6, 0.25, 0.05, 0.10],
  full:   [0.30, 0.25, 0.25, 0.20],
};

export function pickTerminalLine(
  intensity: 'none' | 'subtle' | 'full',
  rng: () => number,
): TerminalLine {
  const w = WEIGHTS[intensity];
  const r = rng();
  let acc = 0;
  const cats = ['STATUS', 'WARNING', 'LORE', 'QUOTES'] as const;
  for (let i = 0; i < cats.length; i++) {
    acc += w[i];
    if (r <= acc) {
      const pool = DICT.TERMINAL[cats[i]];
      return { category: cats[i], text: pool[Math.floor(rng() * pool.length)] };
    }
  }
  const pool = DICT.TERMINAL.STATUS;
  return { category: 'STATUS', text: pool[Math.floor(rng() * pool.length)] };
}

export function pickCommandSession(rng: () => number): { cmd: string; resp: readonly string[] } {
  const arr = DICT.COMMAND_SESSIONS;
  return arr[Math.floor(rng() * arr.length)];
}

export function pickFrom<T>(arr: readonly T[], rng: () => number): T {
  return arr[Math.floor(rng() * arr.length)];
}