#!/usr/bin/env node /** * step-pointer — указатель шага плана как ДЕРЕВО (волны D6/OQ1). Заменяет линейный * счётчик Машины 2, когда шаг раскрывается в под-план. СТЕНДОВЫЙ модуль: построен и * оттестирован, но в живой enforce-supreme-gate.main() НЕ врезается в этой сессии * (см. журнал вопросов — отдельный шаг). Линейный режим обратно совместим. */ /** Указатель = стек уровней [{index, length}]; верхний — текущий под-план. */ export function createPointer({ length }) { return { stack: [{ index: 0, length }], indexAtRoot: 0 }; } /** Текущий путь — индексы по уровням стека. */ export function currentPath(p) { return p.stack.map((l) => l.index); } /** Индекс на корневом уровне (обратная совместимость с линейным счётчиком). */ function rootIndex(p) { return p.stack[0].index; } /** Продвинуть указатель на текущем (верхнем) уровне. */ export function advance(p) { const stack = p.stack.map((l) => ({ ...l })); stack[stack.length - 1].index += 1; const np = { stack }; np.indexAtRoot = rootIndex(np); return np; } /** Текущий шаг раскрылся в под-план длины length → углубляемся. */ export function enterSubPlan(p, { length }) { const stack = p.stack.map((l) => ({ ...l })); stack.push({ index: 0, length }); const np = { stack }; np.indexAtRoot = rootIndex(np); return np; } /** Под-план исчерпан → возвращаемся к родителю (на его текущем шаге). */ export function exitSubPlan(p) { if (p.stack.length <= 1) return p; const stack = p.stack.slice(0, -1).map((l) => ({ ...l })); const np = { stack }; np.indexAtRoot = rootIndex(np); return np; } /** Готово, когда корневой уровень исчерпан. */ export function isDone(p) { const root = p.stack[0]; return p.stack.length === 1 && root.index >= root.length; } // ── R-08: сериализация + дерево-навигация (волны) ── /** Предел глубины дерева плана (SE-4: терминируемость + анти-DoS). */ export const MAX_TREE_DEPTH = 8; /** §4.0: depth-1 стек → целое N; глубже → массив индексов [i0,i1,…]. */ export function serializePointer(p) { if (!p || !Array.isArray(p.stack) || p.stack.length === 0) return 0; if (p.stack.length === 1) return p.stack[0].index; return p.stack.map((l) => l.index); } /** §4.0 строгий (SE-5): целое/массив-индексов + steps (length уровней из дерева) → стек; иначе null. */ export function deserializePointer(state, steps) { if (!Array.isArray(steps)) return null; const idxs = Number.isInteger(state) ? [state] : (Array.isArray(state) && state.length > 0 && state.every(Number.isInteger)) ? state : null; if (!idxs || idxs.length === 0 || idxs.some((n) => n < 0)) return null; let level = steps; const stack = []; for (let d = 0; d < idxs.length; d++) { if (!Array.isArray(level)) return null; stack.push({ index: idxs[d], length: level.length }); if (d < idxs.length - 1) { const node = level[idxs[d]]; if (!node || !Array.isArray(node.substeps)) return null; // путь требует контейнер level = node.substeps; } } return { stack, indexAtRoot: stack[0].index }; } /** Узел по currentPath (или null за пределами). */ export function nodeAt(steps, p) { if (!p || !Array.isArray(p.stack)) return null; let level = steps; let node = null; for (const lvl of p.stack) { if (!Array.isArray(level) || lvl.index < 0 || lvl.index >= level.length) return null; node = level[lvl.index]; level = node && Array.isArray(node.substeps) ? node.substeps : null; } return node ?? null; } /** Контейнер = есть массив substeps. */ export function isContainer(node) { return !!node && Array.isArray(node.substeps); } /** Спуск к листу: контейнер → enterSubPlan к первому ребёнку; лист → сам; за концом → null. * Пустой контейнер → null (validatePlanTree это заранее блокирует). Предел глубины → throw. */ export function normalizeToLeaf(steps, p) { let cur = p; for (let guard = 0; guard <= MAX_TREE_DEPTH + 1; guard++) { const node = nodeAt(steps, cur); if (node == null) return null; if (isContainer(node)) { if (node.substeps.length === 0) return null; cur = enterSubPlan(cur, { length: node.substeps.length }); continue; } return cur; // лист } throw new Error('normalizeToLeaf: превышен предел глубины дерева'); } /** Один ход depth-first: +1 на текущем уровне; новый узел-контейнер → спуск; уровень исчерпан → * подъём + сиблинг родителя; корень исчерпан → done-указатель (nodeAt=null). Предел итераций → throw. */ export function advanceOverTree(steps, p) { let np = advance(p); for (let guard = 0; guard <= (MAX_TREE_DEPTH + 1) * 64; guard++) { const node = nodeAt(steps, np); if (node != null) { if (isContainer(node)) { if (node.substeps.length === 0) return np; // validate блокирует; стоп (nodeAt null далее) np = enterSubPlan(np, { length: node.substeps.length }); continue; } return np; // лист } if (np.stack.length === 1) return np; // корень исчерпан → done np = advance(exitSubPlan(np)); // подъём + сиблинг родителя } throw new Error('advanceOverTree: превышен предел итераций'); }