397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
139 lines
6.1 KiB
JavaScript
139 lines
6.1 KiB
JavaScript
#!/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: превышен предел итераций');
|
|
}
|