Перейти к содержимому

Справочник API

// Редьюсер узла — вызывать внутри Behaviors.collect
W.reduce(state, pulse, nodeId, handlers) → newState
// Проверка стабильности — использовать в _isStable
W.stable(nodes, pulse) → boolean
// Экспорт состояния мира в world.app
W.export(Renkon, { node1, node2, ... }, isStable)
// Миксин самохостящихся часов — распространять в обработчики
W.localReflector(tickMsg, innerTickDelay) → handlersMixin
// Удалить инфраструктурные поля (_queue, _nextAt, _depth, _lt)
W.getState(node) → { ...userFields }

ctx.wallTime // текущее логическое wallTime (= lt)
ctx.logicalTime // текущее logicalTime (= lt)
ctx.depth // текущая глубина обратной связи
ctx.future(delay, msg, payload) // запланировать при wallTime + delay
ctx.send(targetId, msg, payload) // межузловое через исходящий ящик
ctx.feedback(msg, payload, maxDepth) // отслеживаемый по глубине future при wallTime + _fbStepMs
ctx.futureInf(msg, payload) // fireAt = wallTime — перепланирует каждый дренажный проход
ctx.localReflector(tickMsg, delay) // самохостящийся шаг часов субтика

ПримитивСемантика
ctx.future(delay, msg, payload)Запланировать msg через delay логических тиков
ctx.send(nodeId, msg, payload)Межузловое сообщение через исходящий ящик с ограничением по evalGen
ctx.feedback(msg, payload, maxDepth)Отслеживаемый по глубине future при wallTime + _fbStepMs
ctx.futureInf(msg, payload)fireAt = wallTime — перепланирует каждый дренажный проход
ctx.localReflector(tickMsg, delay, payload)Самохостящийся шаг часов субтика с опциональной полезной нагрузкой

ctx.future — Полезная нагрузка и защита от идемпотентности

Заголовок раздела «ctx.future — Полезная нагрузка и защита от идемпотентности»

payload — любое простое сериализуемое значение. Обработчик получает его вторым аргументом p:

ctx.future(delay, "beat", { depth: 2, cycleId: s.cycleId });
beat: (s, p, ctx) => {
if (p.cycleId !== s.cycleId) return s; // устаревший — игнорировать
// p.depth, p.cycleId доступны здесь
}

Почему передавать cycleId? Субтик-future’ы срабатывают асинхронно внутри цикла дренажа. Без защиты устаревший future повреждает состояние.


// Накапливающие фазу локальные часы
...W.localReflector("tick", 0.05),
tick: (s, p, ctx) => {
const phase = ((s.phase ?? 0) + 0.01) % 1; // продвинуть фазу каждый тик
ctx.localReflector("tick", 0.05, { phase }); // передать фазу вперёд в полезной нагрузке
return { ...s, phase };
}

Важно: W.localReflector определяет обработчик __macro. Не определяйте __macro в том же объекте обработчиков — spread молча перезапишет один из них.


Future’ы с delay < SUBTICK_MS = 1 дренируются в текущем пульсе:

future(0) → fireAt = wallTime → дренируется сейчас
future(0.5) → fireAt = wallTime+0.5 → дренируется сейчас (0.5 < SUBTICK_MS=1)
future(1) → fireAt = wallTime+1 → ждёт следующего тика
future(60) → fireAt = wallTime+60 → ждёт 60 тиков

__macro вызывается не более одного раза за logicalTime:

__macro: (s, p, ctx) => {
if (s.started) return s;
ctx.future(0, "startCycle", { cycleId: 1 });
return { ...s, started: true };
}

ВНИМАНИЕ: никогда не используйте Math.random() внутри узлов мира — он недетерминирован и вызовет рассинхронизацию участников.

W.rng.next() // → float в [0, 1)
W.rng.nextInt(n) // → целое в [0, n)
W.rng.seed(lt) // пересев от logicalTime (детерминированный сброс за цикл)
W.rng.state() // → { s0, s1, s2, s3 } (снимок)
W.rng.restore(state) // восстановить из снимка

Локальный рефлектор как контроль скорости симуляции

Заголовок раздела «Локальный рефлектор как контроль скорости симуляции»

Размер тика локального рефлектора напрямую определяет единицу вычислений:

внешний тик = граница сетевой синхронизации (каждые 50мс реального времени)
внутренний тик = шаг интеграции симуляции (каждые 1/N логических единиц)
innerTickDelayШагов за внешний тикЭквивалентная скорость симуляции
0.522× за сетевой тик
0.11010× за сетевой тик
0.01100100× за сетевой тик
0.00110001000× (у пола float)

ctx.feedback("respond", { value, cycleId }, 64);

ctx.feedback() планирует сообщение при wallTime + _fbStepMs и увеличивает счётчик глубины волны на 1. Если depth >= maxDepth — вызов молча игнорируется.

Диаграмма глубины волны с обратной связью

Заголовок раздела «Диаграмма глубины волны с обратной связью»
Логическое время T
пульс
├── глубина 0 estimator.__macro → ctx.future(0, "sendObserve")
├── глубина 0 estimator.sendObserve → ctx.send("corrector", "observe")
├── глубина 0 corrector.observe → ctx.feedback("respond")
├── глубина 1 corrector.respond → ctx.send("estimator", "refine")
├── глубина 1 estimator.refine → delta > ε → ctx.feedback("continueRefine")
├── глубина 2 ...цикл продолжается...
└── глубина N delta < ε → нет ctx.feedback → очереди дренируются → стабильно