Zum Inhalt

tests/dict-presets-parity.test.ts

import { describe, it, expect } from 'vitest';
import { PRESETS } from '../src/engine/data/presets';
import { DICT } from '../src/engine/data/dictionary';

// Additive-invariant guard for the Slice 1b JSON-SSOT migration: PRESETS (presets.ts)
// and DICT (dictionary.ts) move verbatim into story-content.json, then both modules
// become facades re-reading from the SSOT. This pins an FNV-1a hash of the *facade
// output* (JSON.stringify of each export) so the move is proven byte-identical:
// the pinned hashes were captured from the pre-migration literals and must not change.
//
// Sibling of tests/script-bank-parity.test.ts (the Slice 1a guard). Together these are
// the safety nets that every later content/arc change must keep green.
//
// Scope (what this guard does NOT cover, on purpose):
//   • lore-intensity WEIGHTS + pickTerminalLine stay code-side (logic, per design §2) —
//     not part of this content hash; revisit if a later slice moves WEIGHTS into the SSOT.
//   • This is a WEB-INTERNAL move-fidelity guard, not a web↔native parity check. Native
//     still hand-mirrors this content (Palette.swift/Script.swift); web↔native parity is
//     Slice 1.3's job (StoryContent Codable + schema test), covered separately there.

function fnv1a(s: string): bigint {
  let h = 0xcbf29ce484222325n;
  const enc = new TextEncoder().encode(s);
  for (const b of enc) { h = ((h ^ BigInt(b)) * 0x100000001b3n) & 0xffffffffffffffffn; }
  return h;
}

describe('dict/presets SSOT migration parity', () => {
  it('PRESETS survives the move into story-content.json unchanged', () => {
    expect(fnv1a(JSON.stringify(PRESETS))).toBe(7533571888607463794n);
  });

  // Hash re-pinned when the `metro` scene was added (its modeLabels.metro +
  // boot.headers.metro entries are legitimate new DICT content, not a refactor).
  // Re-pinned again 2026-08-21: two boot lines named the web renderer ("WEBGL RENDERER",
  // "UNREAL BLOOM") and would have been a lie once the native Metal app started reading
  // the same pool, so they were neutralised to "GPU RENDERER" / "BLOOM". Authored content
  // change, not a refactor — the native side pins the same pool in tests/main.swift.
  it('DICT pools survive the move into story-content.json unchanged', () => {
    expect(fnv1a(JSON.stringify(DICT))).toBe(17523085815514849234n);
  });
});