/**
* Проверка произвольного графа (для сервера редактора, D8). Читает stdin:
* { "op": "validate", "graph": {...} } — Invariants + анализ
* { "op": "dry-run", "graph": {...}, "preset": "fresh" } — ещё и прогон раннера
* Печатает JSON:
* { errors, warnings, orphans, cycles, silentEnds, run? }
* Запуск: vite-node apps/game/tools/dialogues/probe.ts
*/
import { GameState, DialogueRunner } from '@rpg/engine';
import type { DialogueGraph } from '@rpg/engine';
import { analyzeGraph, checkGraph, type GraphRefs } from '../../src/data/dialogueRules';
import { FLAGS, VARS } from '../../src/data/ids';
import { ITEMS } from '../../src/data/items';
import { DIALOGUE_CUSTOM } from '../../src/data/effects';
/** Реестры игры (те же множества, что в validate.ts и dry-run.ts). */
function refs(): GraphRefs {
return {
flags: new Set(Object.keys(FLAGS)),
vars: new Set(Object.keys(VARS)),
items: new Set(Object.keys(ITEMS)),
customs: new Set(Object.keys(DIALOGUE_CUSTOM)),
strings: new Set()
};
}
/** Пресеты состояния — как в dry-run.ts (общая семантика с CLI). */
const PRESETS: { name: string; setup: (s: GameState) => void }[] = [
{ name: 'fresh', setup: () => {} },
{
name: 'quest-taken+flowers',
setup: (s) => {
s.setFlag('quest_bells_taken');
s.setFlag('met_elder');
s.setVar('flowers', 3);
}
},
{
name: 'quest-done',
setup: (s) => {
s.setFlag('quest_bells_taken');
s.setFlag('met_elder');
s.setFlag('quest_bells_done');
}
}
];
/** Один прогон раннера: реплики до первого выбора (или до конца). */
function walk(graph: DialogueGraph, preset: string): string {
const state = new GameState();
(PRESETS.find((p) => p.name === preset) ?? PRESETS[0]!).setup(state);
const runner = new DialogueRunner(state);
const shown: string[] = [];
runner.setView({
show: (n) =>
void shown.push(
`${n.speaker ? n.speaker + ': ' : ''}${n.text}` +
(n.choices.length > 0 ? ` [${n.choices.map((c) => c.text).join(' | ')}]` : '')
),
hide: () => {}
});
runner.start(graph);
while (runner.active && !runner.waitingForChoice) runner.advance();
return shown.join(' → ') || '(молча)';
}
const main = (): void => {
const chunks: string[] = [];
process.stdin.on('data', (c) => chunks.push(c));
process.stdin.on('end', () => {
const req = JSON.parse(chunks.join('')) as {
op?: string;
graph: DialogueGraph;
preset?: string;
};
const graph = req.graph;
const a = analyzeGraph(graph);
const inv = checkGraph('editor', graph, refs());
const out: Record<string, unknown> = {
errors: inv.filter((i) => i.severity === 'error'),
warnings: inv.filter((i) => i.severity === 'warn'),
orphans: a.orphans,
cycles: a.textlessCycles,
silentEnds: a.silentEnds
};
if (req.op === 'dry-run') {
out.run = walk(graph, req.preset ?? 'fresh');
}
console.log(JSON.stringify(out));
});
};
main();