<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Статьи — Руслан Гильмуллин</title>
    <link>https://mellonis.ru/ru/articles/</link>
    <description>Статьи Руслана Гильмуллина.</description>
    <language>ru</language>
    <atom:link href="https://mellonis.ru/ru/articles/rss.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>DebugSession: класс-компаньон для координации</title>
      <link>https://mellonis.ru/ru/articles/klass-kompanion-debugsession/</link>
      <guid isPermaLink="false">https://mellonis.ru/ru/articles/klass-kompanion-debugsession/</guid>
      <pubDate>Sun, 07 Jun 2026 00:00:00 GMT</pubDate>
      <description>Воркер из предыдущей статьи реализовал координацию поверх хуков движка. v7 поднял эту координацию в класс-компаньон при движке.</description>
      <content:encoded><![CDATA[<p><a href="https://github.com/mellonis/turing-machine-js"><code>turing-machine-js</code></a> — интерпретатор машины Тьюринга. За последние два мажора у движка успело сложиться двухчастное API отладки: одни хуки наблюдают за исполнением, другие координируют его. Про первое — статья «<a href="https://mellonis.ru/ru/articles/three-majors-two-mistakes/">Три мажора, две ошибки: проектирование API приостановки для интерпретатора машины Тьюринга</a>» (1). Про второе — статья «<a href="https://mellonis.ru/ru/articles/pauza-dvojstvennaya-po-prirode/">Пауза, двойственная по природе: контракт хука и протокол воркера</a>» (2). <a href="https://mellonis.ru/ru/articles/pauza-dvojstvennaya-po-prirode/">Статья (2)</a> остановилась на работающем протоколе между основным потоком и воркером <a href="https://demo.machines.mellonis.ru"><code>machines-demo</code></a>: пара сообщений <code>idle</code>/<code>busy</code>, флаг <code>stepRequested</code>, синтетическое событие <code>paused</code> по клику, поле <code>intervalMs</code> в <code>resume</code>. Четыре механизма, каждый закрывает одну конкретную проблему. Эта статья — про следующий шаг.</p>
<p>Предположим, к движку приходит второй потребитель. Не воркер демо, а, скажем, IDE-расширение или встроенный учебный отладчик без Web Worker’а. Что из протокола <a href="https://mellonis.ru/ru/articles/pauza-dvojstvennaya-po-prirode/">статьи (2)</a> он унесёт с собой? Сторожевой таймер ему не нужен — нет границы потоков. Очередь сообщений не нужна — обработчики живут в том же потоке. Но «флаг шага», «координация паузы», «события <code>paused</code> по нескольким причинам» — это всё ему <em>понадобится</em>. И он, не разобрав <a href="https://mellonis.ru/ru/articles/pauza-dvojstvennaya-po-prirode/">статью (2)</a>, переизобретёт всё это заново.</p>
<p>Эта статья — про то, что переизобретать координацию приходится не потому, что воркер «специальный», а потому, что в v6 у координации не было своего места в библиотеке. v7 это место создал — класс-компаньон <code>DebugSession</code> рядом с <code>TuringMachine</code> в том же пакете.</p>
<h2 id="chto-pereizobryol-vorker"><a class="heading-anchor" href="#chto-pereizobryol-vorker">Что переизобрёл воркер</a></h2>
<p>Перечитаем протокол из <a href="https://mellonis.ru/ru/articles/pauza-dvojstvennaya-po-prirode/">статьи (2)</a> под новым углом. Четыре механизма:</p>
<ol>
<li><code>idle</code>/<code>busy</code> вокруг асинхронной приостановки в <code>onIter</code> — обвязка для сторожевого таймера.</li>
<li>Флаг <code>stepRequested</code> внутри воркера — модель «шага» как одноразового запроса.</li>
<li>Синтетическое событие <code>paused</code> по клику «Пауза» — обработка внешней команды паузы.</li>
<li>Поле <code>intervalMs</code> в <code>resume</code> — чтение скорости «в момент клика», не «в момент старта».</li>
</ol>
<p>Разнесём их по принадлежности. Первый и четвёртый — про воркер: сторожевой таймер живёт на основном потоке потому, что у него есть граница между потоками; <code>intervalMs</code> приходит на каждом <code>resume</code> потому, что поле ввода интервала и движок разъехались по разным потокам. Второй и третий — про <em>координацию</em>: модель шага как одноразового события, перевод внешней команды паузы в стандартное событие. Web Worker’ы тут ни при чём — те же самые механизмы понадобились бы в любом отладчике, который переключает движок между паузой и исполнением.</p>
<p>Два механизма из четырёх — кандидаты на подъём из потребителя в библиотеку. Воркеру осталось бы тогда два: обвязка сторожевого таймера и проброс параметра скорости при возобновлении. Это уже не «переизобретение координации», а адаптер между двумя интерфейсами: между протоколом сообщений воркера и API сессии.</p>
<h2 id="gde-mozhet-zhit-koordinatsiya"><a class="heading-anchor" href="#gde-mozhet-zhit-koordinatsiya">Где может жить координация</a></h2>
<p>В v6 координацию можно было разместить в двух местах:</p>
<ul>
<li>На стороне потребителя. Каждый новый отладчик строит её заново. Пока потребитель один, выбор места для координации выглядит обоснованным: сравнить не с чем. Двое уже подсветили бы дублирование, трое — подтвердили бы паттерн.</li>
<li>В хуках движка. Этот соблазн разобран в <a href="https://mellonis.ru/ru/articles/pauza-dvojstvennaya-po-prirode/">статье (2)</a>: расширение <code>onStep</code> до асинхронной сигнатуры (v6.2.0) склеивало наблюдение и координацию. Появление выделенного хука <code>onIter</code> (v6.4.0) решило задачу «где живёт приостановка», но не сняло принципиального ограничения: хук — это точка уведомления, а не машина состояний. У хука нет своего внутреннего состояния, лайфсайкла, методов управления — он отдаёт обработчику только текущий момент и ждёт его Promise. А координация — это именно машина состояний: «пауза при брейкпоинте → continue / step-in / step-over / step-out / stop». При этом для step-over и step-out момент следующей паузы определяется по глубине стека.</li>
</ul>
<p>Третий вариант в v6 не появился, потому что вопрос не был ещё <em>вынужденным</em>. С двумя потребителями (или тремя, или N) ответ становится очевидным: координация живёт в <em>соседнем</em> классе при движке. Не вниз по графу зависимостей (в потребителе), не внутри самого класса движка (в виде ещё одного хука), а вбок — в классе-компаньоне на том же уровне библиотеки.</p>
<p>Побочное доказательство: в v7 <code>TuringMachine.run()</code> снова стал синхронным, возвращающим void — без колбэков. В v4 этот же метод стал асинхронным ради асинхронного хука паузы (<code>onDebugBreak</code>, в v5 переименованного в <code>onPause</code>). Подъём координации обнулил эту причину, и сигнатура вернулась в исходное состояние. Если бы движку самому нужна была асинхронность, после исчезновения <code>onPause</code> сигнатура не вернулась бы к прежней форме — а вернулась. Значит, async в <code>run()</code> поддерживался только ради этого хука, а не ради самой логики исполнения.</p>
<figure><img src="https://mellonis.ru/articles/klass-kompanion-debugsession/architecture-ru.svg?v=8cdd76de" alt="Архитектура: DebugSession рядом с TuringMachine в пакете движка; потребитель композирует обе сущности." width="880" height="560"><figcaption>Архитектура: DebugSession рядом с TuringMachine в пакете движка; потребитель композирует обе сущности.</figcaption></figure>
<h2 id="chem-obladaet-debugsession"><a class="heading-anchor" href="#chem-obladaet-debugsession">Чем обладает DebugSession</a></h2>
<p>Класс лежит в <code>packages/machine/src/classes/DebugSession.ts</code> — там же, где <code>TuringMachine</code>. Конструируется напрямую — <code>new DebugSession(machine, {initialState})</code>. Движок про сессию ничего не знает; сессия знает про движок только через его публичный синхронный генератор <code>runStepByStep</code> плюс один пакет-приватный аксессор по символу <code>MACHINE_STATE_INTERNAL</code>, который сосед-модуль может прочитать, а публичный <code>index.ts</code> не реэкспортирует. Тот же паттерн пакет-приватной видимости через <code>Symbol</code>, что и <code>STATE_INTERNAL</code> (<a href="https://github.com/mellonis/turing-machine-js/issues/180"><code>turing-machine-js#180</code></a>) — даёт сессии доступ к снимку halt-стека на каждой итерации, не выпуская его наружу через публичный API.</p>
<p>Сессия слушает четыре события, каждое со своим договором о диспетчеризации:</p>
<ul>
<li><code>step</code> — без ожидания Promise. Синхронный, горячий цикл наблюдения; Promise, возвращённый слушателем, не дожидается. Один-в-один контракт <code>onStep</code> из v6.</li>
<li><code>iter</code> — <em>дожидается</em>. Покадровая координация: приостановка между итерациями, синтез границы шага, всё, ради чего движку нужно дождаться слушателя. Один-в-один контракт <code>onIter</code> из <a href="https://mellonis.ru/ru/articles/pauza-dvojstvennaya-po-prirode/">статьи (2)</a>.</li>
<li><code>pause</code> — слушатели вызываются без ожидания Promise (как у <code>step</code> и <code>halt</code>: Promise, который вернул бы async-слушатель, не дожидается). Но после их вызова сама сессия блокируется на собственном внутреннем Promise — и пока этот Promise не разрешится, цикл <code>#drive</code> не дёрнет следующий шаг генератора. Разрешает этот Promise один из методов управления сессией: <code>continue()</code> / <code>stepIn()</code> / <code>stepOver()</code> / <code>stepOut()</code> / <code>stop()</code>. Все они по дизайну вызываются откуда-то снаружи — из клика в UI, из сообщения от основного потока (когда сессия живёт в воркере) или по таймеру.</li>
<li><code>halt</code> — без ожидания Promise. Терминальное событие сессии.</li>
</ul>
<p>Управление сессией зеркалит подходы DevTools там, где это имеет смысл. <code>pause()</code> — внешний запрос паузы; срабатывает на before-стороне следующей итерации с <code>cause: ’manual’</code>. <code>stepIn()</code> — пауза на следующей итерации. <code>stepOver()</code> — пауза на следующей итерации с <code>depth &#x3C;= clickTimeDepth</code>: фреймы, которые текущая итерация уложила в стек, доигрываются до возврата без остановки. <code>stepOut()</code> — пауза, когда глубина halt-стека упадёт ниже момента клика (<code>depth &#x3C; clickTimeDepth</code>): текущая обёртка закончилась, управление перешло в её override. <code>stepOut()</code> из глубины 0 бросает исключение: «выйти не из чего» — это ошибка в коде потребителя, а не молчаливая no-op. <code>setRunInterval(ms)</code> — задаёт покадровую задержку между итерациями. <code>stop()</code> — терминал, сессия завершается. Сессия одноразовая: <code>start()</code> вызывается один раз; для повторного прогона — новая сессия.</p>
<p>Обнаружение брейкпоинтов <em>тоже переехало</em>. В v6 генератор <code>runStepByStep</code> сам решал, какая итерация «должна выстрелить» по <code>state.debug</code>. В v7 генератор отдаёт минимальное <code>MachineState</code>, а сессия через <code>MACHINE_STATE_INTERNAL</code> читает текущий символ ленты, проверяет его на попадание в фильтры <code>before</code>/<code>after</code> у <code>state.debug</code> с помощью <code>matchFilter</code> и сама диспетчеризует <code>pause</code> с <code>cause: ’breakpoint’</code>. У движкового генератора больше нет «отладочной» логики — он чистое исполнение. У сессии — все три причины паузы (<code>breakpoint</code>, <code>step</code>, <code>manual</code>) в одном описании <code>PauseInfo { side, cause }</code> с порядком приоритета <code>breakpoint > step > manual</code>, если несколько причин совпали на одной итерации.</p>
<p>Это и есть «координация как машина состояний», которая не помещалась в хук: внутреннее состояние (Promise паузы, который держит движок остановленным; активный режим step-in/over/out; зафиксированная при клике глубина halt-стека), методы управления, лайфсайкл — всё в одном объекте, у которого есть собственный конструктор и собственный метод <code>start()</code>.</p>
<h2 id="chto-ostalos-v-vorkere"><a class="heading-anchor" href="#chto-ostalos-v-vorkere">Что осталось в воркере</a></h2>
<p>После подъёма координации воркер не оказался пустым. За что он отвечает теперь:</p>
<ul>
<li>Сама граница Web Worker’а — протокол сообщений, сэндбокс пользовательского кода, лайфсайкл запросов на сборку, прогон, шаг. Воркер — естественное место для этого слоя.</li>
<li>Покадровая задержка, обёрнутая сообщениями <code>idle</code>/<code>busy</code> для сторожевого таймера <code>WORKER_TIMEOUT_MS</code>. У сессии для задержки между итерациями есть свой <code>setRunInterval(ms)</code>. Но воркеру он не годится: эта задержка выполняется внутри <code>#drive</code>-цикла сессии, и обернуть её парой сообщений <code>idle</code>/<code>busy</code> снаружи невозможно — а без обвязки сторожевой таймер на основном потоке сработает на легитимной задержке как на зависании. Поэтому воркер реализует задержку в собственном обработчике события <code>iter</code> (которое сессия дожидается асинхронно): внутри обработчика пара сообщений <code>idle</code> и <code>busy</code> оборачивает <code>await setTimeout(intervalMs)</code> ровно там, где нужно.</li>
<li>Хранение и обновление <code>intervalMs</code>. Семантика «<code>withPause</code> читается в момент клика» из <a href="https://mellonis.ru/ru/articles/pauza-dvojstvennaya-po-prirode/">статьи (2)</a> никуда не делась — поле интервала в UI это UI-уровень. Воркер держит актуальное значение в своей локальной переменной и обновляет его на каждом сообщении <code>resume</code> от основного потока.</li>
<li>Трансляция UI-кликов в методы сессии: клик «Пауза» → <code>activeSession.pause()</code>, клик «Шаг» → <code>activeSession.stepIn()</code>, клик «Продолжить» → <code>activeSession.continue()</code>. Воркер больше не синтезирует событие <code>paused</code> из <code>onIter</code> — все паузы (брейкпоинт, шаг, ручная) возвращаются через единственное событие <code>pause</code> сессии с дискриминантом <code>cause</code>.</li>
</ul>
<p>Граница чистая. Осталось то, чем сессия не может владеть, не залезая на территорию потребителя. Ушло то, что воркер-специфичным никогда не было — это была <em>координация</em>, замаскированная под воркер-специфику. Другой потребитель — встроенный в IDE отладчик, DAP-сервер, учебная демонстрация без Worker’а — соберёт <code>DebugSession</code> иначе (без postMessage, без сторожевого таймера), но с <em>тем же самым API сессии</em>.</p>
<h2 id="podyom-obobshchaetsya"><a class="heading-anchor" href="#podyom-obobshchaetsya">Подъём обобщается</a></h2>
<p><code>PostMachine.debugRun()</code> возвращает <code>PostDebugSession</code>, который оборачивает движковый <code>DebugSession</code>. Те же четыре события, те же методы шага, тот же лайфсайкл. Сверху — оборачивание <code>MachineState</code> в post-специфичные <code>arrivalPath</code> / <code>candidatePaths</code> и фильтрация по реестру брейкпоинтов <code>PostMachine</code> перед прокидыванием события <code>pause</code> наружу. Воркер демо обрабатывает обе сессии через структурный тип <code>SessionLike</code> — оба выглядят как одно и то же снаружи.</p>
<p>Это и есть <em>компаньон-компаньона</em>: паттерн обобщается на один уровень выше. Библиотека движка один раз поднимает координацию в свой класс-компаньон; следующая библиотека сверху поднимает свою специфику в свой класс-компаньон, оборачивающий движковый. Доказательство того, что «класс-компаньон» — не одноразовый трюк под одну библиотеку, а воспроизводимая форма.</p>
<h2 id="chto-iz-etogo-sleduet"><a class="heading-anchor" href="#chto-iz-etogo-sleduet">Что из этого следует</a></h2>
<p>В линейке статей про API движка вырисовываются три урока подряд:</p>
<ul>
<li>Из <a href="https://mellonis.ru/ru/articles/three-majors-two-mistakes/">статьи (1)</a>: именование лайфсайкла фиксирует решения о форме API — назовите состояния правильно, и поверхность сама окажется в нужной форме.</li>
<li>Из <a href="https://mellonis.ru/ru/articles/pauza-dvojstvennaya-po-prirode/">статьи (2)</a>: наблюдение и координация — разные задачи, и каждой нужен свой договор хука со своей семантикой диспетчеризации.</li>
<li>Из этой статьи: когда N потребителей переизобретают одну и ту же координационную обвязку поверх ваших хуков, ваш хук-контракт не <em>неполный</em> — он на не той <em>гранулярности</em>. Координационная машина состояний — это сосед хуков, а не их расширение.</li>
</ul>
<p>Диагностика для автора библиотеки: если приходится расширять сигнатуру хука, чтобы потребитель смог сделать ту работу, которая у него и так есть (мажор v6.2.0 из <a href="https://mellonis.ru/ru/articles/pauza-dvojstvennaya-po-prirode/">статьи (2)</a>), — или если форма обработчика повторяется из потребителя в потребителя без существенных различий (тот случай, который и подтолкнул к этому рефакторингу), — значит, от хука просят то, чего хук в принципе сделать не может.</p>
<p>Для движка это вылилось в чёткую границу: публичный синхронный <code>runStepByStep</code>, его координационный компаньон <code>DebugSession</code>, и любой потребитель — воркер демо, DAP-сервер, in-process отладочная панель — пишет адаптер между своей точкой входа и API сессии, а не координационный слой заново.</p>
<hr>
<p><em>Код: <a href="https://github.com/mellonis/turing-machine-js"><code>turing-machine-js</code></a> (движок, v7 — <code>DebugSession</code>), <a href="https://github.com/mellonis/post-machine-js"><code>post-machine-js</code></a> (<code>PostDebugSession</code>) и <a href="https://demo.machines.mellonis.ru"><code>machines-demo</code></a> (воркер демо, переписанный вокруг сессии в <a href="https://github.com/mellonis/machines-demo/pull/80">PR #80</a>).</em></p>]]></content:encoded>
    </item>
    <item>
      <title>Три ипостаси машины Тьюринга: объект, граф, поведение</title>
      <link>https://mellonis.ru/ru/articles/tri-ipostasi-mashini/</link>
      <guid isPermaLink="false">https://mellonis.ru/ru/articles/tri-ipostasi-mashini/</guid>
      <pubDate>Tue, 02 Jun 2026 00:00:00 GMT</pubDate>
      <description>Сериализовал машину в Mermaid-диаграмму и собрал обратно. Осталось доказать, что это та же машина — а «та же» оказалось тремя вопросами: объект, граф, поведение</description>
      <content:encoded><![CDATA[<p>В <a href="https://github.com/mellonis/turing-machine-js"><code>@turing-machine-js/machine</code></a> я сделал то, чего давно хотел: машину состояний можно сериализовать в <a href="https://mermaid.js.org/syntax/flowchart.html">Mermaid</a>-диаграмму и собрать обратно. <code>toGraph</code> превращает машину в граф, <code>toMermaid</code> — в текст диаграммы; <code>fromMermaid</code> и <code>fromGraph</code> проходят путь назад. Диаграмма и машина стали взаимозаменяемы: можно нарисовать, отредактировать, сохранить — и восстановить рабочую машину.</p>
<p>И сразу захотелось регрессионный тест: прогнать машину через round-trip и проверить, что на выходе — та же машина. Вот на слове «та же» всё и завертелось.</p>
<p>Побайтово? Нет: id узлов — это runtime-id инстансов, и <code>fromGraph</code> назначает их заново. Той же структуры? Иногда да, иногда нет — смотря как устроена композиция. Так же себя ведёт? Это вообще другая проверка, графа она не касается.</p>
<p>Оказалось, «та же» — не один вопрос, а три, и на каждый отвечает своё API. Эти три ответа я добавлял в библиотеку годами, по одному, и долго не проговаривал себе, что это три отдельные границы, а не один растяжимый «equals». Объект, граф, поведение — три ипостаси одной машины, и в плоской машине они дают один и тот же ответ. Композиция — место, где ответы расходятся. Дальше — про то, что у движка для этого есть, где проходят границы и как v7 уложила две ипостаси из трёх в форму графа, оставив для третьей отдельную функцию <code>equivalentOn</code>.</p>
<h2 id="chto-u-dvizhka-voobshche-yest"><a class="heading-anchor" href="#chto-u-dvizhka-voobshche-yest">Что у движка вообще есть?</a></h2>
<p>Прежде чем разводить три ипостаси, нужен инвентарь.</p>
<p>В движке три слоя, на которые ложится вопрос «а та же ли машина?»:</p>
<p><strong>Объекты времени исполнения.</strong> <code>State</code> — узел графа машины. У него есть имя, переходы (на каждый возможный символ ленты — следующее состояние, что писать, куда двигаться), и идентичность: два разных <code>State</code> — это два разных объекта, как бы ни выглядели их таблицы переходов.</p>
<p><strong>Граф как сериализация.</strong> <code>State.toGraph(state, tapeBlock)</code> обходит достижимые состояния и возвращает <code>Graph</code> — обычные данные: узлы с именами и переходами, индексированные числовыми id. <code>State.fromGraph(graph)</code> принимает граф и собирает машину обратно: <code>{start, tapeBlock, states}</code>. Поверх графа — <code>toMermaid(graph)</code> и <code>fromMermaid(text)</code>, отображение в синтаксис <a href="https://mermaid.js.org/syntax/flowchart.html">Mermaid flowchart</a> и обратно. Парсер строгий: принимает только то, что эмиттер выпускает.</p>
<p><strong>Поведенческая эквивалентность.</strong> <code>equivalentOn(reference, candidate, cases)</code> в <code>utilities/equivalence.ts</code> — запускает обе машины на тестовых лентах, сравнивает выходы и снимки на каждом шаге. Отдельный модуль, ничего не знает про граф.</p>
<p>Три слоя — три вопроса «те же ли?». Дальше — про то, как они расходятся.</p>
<h2 id="ploskaya-mashina-prostoi-sluchai"><a class="heading-anchor" href="#ploskaya-mashina-prostoi-sluchai">Плоская машина: простой случай</a></h2>
<p>У плоской машины — без композиции — все три ипостаси совпадают.</p>
<p><strong>Объектно:</strong> один <code>State</code> равен другому, если это тот же экземпляр (<code>===</code>). Это базовая семантика JavaScript, ничего движок к ней не добавляет.</p>
<p><strong>Графово:</strong> возьмите <code>toGraph</code>, прогоните через JSON туда-обратно, восстановив через <code>fromGraph</code> — полу́чите двойник с теми же именами и переходами. Совпадение не байт-в-байт, а с точностью до переименования id: id узлов — это runtime-id инстансов, и при пересборке через <code>fromGraph</code> они назначаются заново. В репозитории есть регрессионный тест на это (<a href="https://github.com/mellonis/turing-machine-js/issues/139"><code>turing-machine-js#139</code></a>). Проверяет не «выглядит так же», а «имеет ту же структуру».</p>
<p><strong>Поведенчески:</strong> <code>equivalentOn</code> прогонит обе машины по тестовым лентам и подтвердит «да, отработали одинаково».</p>
<p>Простой случай. Все три ипостаси совпадают, потому что нет места, где они могли бы разойтись. Но место расхождения есть — композиция.</p>
<h2 id="kompozitsiya-gde-stanovitsya-interesno"><a class="heading-anchor" href="#kompozitsiya-gde-stanovitsya-interesno">Композиция: где становится интересно</a></h2>
<p>Композиция в движке выглядит так:</p>
<pre class="shiki shiki-themes github-light github-dark language-javascript" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> wrapper</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> baseState.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">withOverriddenHaltState</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(overrideState);</span></span></code></pre>
<p>Новое состояние-обёртка <code>wrapper</code> <em>делегирует</em> поиск переходов своему <code>baseState</code>, но <em>подменяет</em> halt-выход: вместо остановки машины — переход в <code>overrideState</code>.</p>
<p>Концептуально обёртка — это «выполнить базовое состояние как подпрограмму, на выходе перейти в <code>overrideState</code>». Имя композиции строится по правилу: для базы с именем <code>A</code> и оверрайда с именем <code>B</code> обёртка получает имя <code>A(B)</code>. О том, как и почему именно это имя — отдельная статья <a href="https://mellonis.ru/ru/articles/seven-arrows-429-trees/">«Семь стрел, 429 деревьев»</a> (1).</p>
<p>Здесь начинают расходиться три ипостаси.</p>
<p><strong>Объектно:</strong> обёртка и база — два разных <code>State</code>. <code>wrapper !== baseState</code>. Это следствие того, что <code>withOverriddenHaltState</code> возвращает новый экземпляр.</p>
<p><strong>Графово:</strong> обёртка своих переходов <em>не имеет</em>. Они живут на базе. Если визуализировать граф, обёртка — это узел с делегированием, не с переходами.</p>
<p><strong>Поведенчески:</strong> обёртка и база <em>не эквивалентны</em>. У них разный halt-выход. На любом входе трейс совпадает шаг в шаг вплоть до halt-выхода базы — и расходится ровно в этот момент.</p>
<p>Обёртка существует как самостоятельный объект ровно для того, чтобы хранить эту подмену. Дальше вопрос: как уложить её в граф, чтобы <code>fromGraph</code> сумел её восстановить?</p>
<h2 id="kak-kompozitsiya-popadaet-v-graf"><a class="heading-anchor" href="#kak-kompozitsiya-popadaet-v-graf">Как композиция попадает в граф</a></h2>
<p>Мне нужно было, чтобы граф сам нёс композицию, — это убивало двух зайцев. Во-первых, <code>fromGraph</code> восстанавливает машину прямо из структуры, а не угадывает композицию по runtime-подсказке. Во-вторых, граф остаётся читаемым: композиция видна на самой диаграмме, а не растворяется в плоском списке переходов. В v7 (<a href="https://github.com/mellonis/turing-machine-js/issues/180"><code>turing-machine-js#180</code></a>) обёртка эмитируется двумя узлами:</p>
<ul>
<li><strong>Узел-обёртка</strong> — call-site без переходов, форма <code>[[wrapper-name]]</code>, поле <code>bareStateId</code> указывает на базу.</li>
<li><strong>Узел базы</strong> — обычный узел с переходами, форма <code>[baseState-name]</code>, помещённый внутрь Mermaid subgraph «callable subtree of NAME».</li>
</ul>
<p>Целиком, на примере <code>scanToX(eraseHere)</code>:</p>
<svg xmlns="http://www.w3.org/2000/svg" id="mermaid-0" width="100%" aria-roledescription="flowchart-v2" class="flowchart" style="font-family:arial,sans-serif;font-size:16px;fill:#333;max-width:427.20782470703125px" viewBox="0 0 427.208 549.897"><style>@keyframes edge-animation-frame{0%{stroke-dashoffset:0}}@keyframes dash{to{stroke-dashoffset:0}}#mermaid-0 .edge-thickness-normal{stroke-width:1px}#mermaid-0 .edge-pattern-solid{stroke-dasharray:0}#mermaid-0 .edge-pattern-dotted{stroke-dasharray:2}#mermaid-0 .marker{fill:#333;stroke:#333}#mermaid-0 .marker.cross{stroke:#333}#mermaid-0 svg{font-family:arial,sans-serif;font-size:16px}#mermaid-0 p{margin:0}#mermaid-0 .label{font-family:arial,sans-serif;color:#333}#mermaid-0 span{fill:#333;color:#333}#mermaid-0 .node circle,#mermaid-0 .node path,#mermaid-0 .node rect{fill:#ececff;stroke:#9370db;stroke-width:1px}#mermaid-0 .flowchart-link{stroke:#333;fill:none}#mermaid-0 .edgeLabel,#mermaid-0 .node .label{text-align:center}#mermaid-0 .edgeLabel,#mermaid-0 .edgeLabel p,#mermaid-0 .edgeLabel rect{background-color:rgba(232,232,232,.8)}#mermaid-0 .edgeLabel rect{opacity:.5;fill:rgba(232,232,232,.8)}#mermaid-0 .labelBkg{background-color:rgba(232,232,232,.5)}#mermaid-0 [data-look=neo].cluster rect,#mermaid-0 [data-look=neo].node circle,#mermaid-0 [data-look=neo].node rect{stroke:#9370db;filter:drop-shadow(1px 2px 2px #b9b9b9)}#mermaid-0 [data-look=neo].node path{stroke:#9370db;stroke-width:1px}#mermaid-0 [data-look=neo].node .outer-path{filter:drop-shadow(1px 2px 2px #b9b9b9)}#mermaid-0 :root{--mermaid-font-family:arial,sans-serif}</style><marker id="mermaid-0_flowchart-v2-pointEnd" class="marker flowchart-v2" markerHeight="8" markerUnits="userSpaceOnUse" markerWidth="8" orient="auto" refX="5" refY="5" viewBox="0 0 10 10"><path d="m0 0 10 5-10 5z" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></path></marker><marker id="mermaid-0_flowchart-v2-pointStart" class="marker flowchart-v2" markerHeight="8" markerUnits="userSpaceOnUse" markerWidth="8" orient="auto" refX="4.5" refY="5" viewBox="0 0 10 10"><path d="m0 5 10 5V0z" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></path></marker><marker id="mermaid-0_flowchart-v2-pointEnd-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="10.5" orient="auto" refX="11.5" refY="7" viewBox="0 0 11.5 14"><path d="m0 0 11.5 7L0 14z" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></path></marker><marker id="mermaid-0_flowchart-v2-pointStart-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="11.5" orient="auto" refX="1" refY="7" viewBox="0 0 11.5 14"><path d="m0 7 11.5 7V0z" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></path></marker><marker id="mermaid-0_flowchart-v2-circleEnd" class="marker flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="11" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-0_flowchart-v2-circleStart" class="marker flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="-1" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-0_flowchart-v2-circleEnd-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="14" orient="auto" refX="12.25" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-0_flowchart-v2-circleStart-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="14" orient="auto" refX="-2" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-0_flowchart-v2-crossEnd" class="marker cross flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="12" refY="5.2" viewBox="0 0 11 11"><path d="m1 1 9 9m0-9-9 9" class="arrowMarkerPath" style="stroke-width:2;stroke-dasharray:1,0"></path></marker><marker id="mermaid-0_flowchart-v2-crossStart" class="marker cross flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="-1" refY="5.2" viewBox="0 0 11 11"><path d="m1 1 9 9m0-9-9 9" class="arrowMarkerPath" style="stroke-width:2;stroke-dasharray:1,0"></path></marker><marker id="mermaid-0_flowchart-v2-crossEnd-margin" class="marker cross flowchart-v2" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="12" orient="auto" refX="17.7" refY="7.5" viewBox="0 0 15 15"><path d="m1 1 13 13M1 14 14 1" class="arrowMarkerPath" style="stroke-width:2.5"></path></marker><marker id="mermaid-0_flowchart-v2-crossStart-margin" class="marker cross flowchart-v2" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="12" orient="auto" refX="-3.5" refY="7.5" viewBox="0 0 15 15"><path d="m1 1 13 13M1 14 14 1" class="arrowMarkerPath" style="stroke-width:2.5;stroke-dasharray:1,0"></path></marker><g class="root"><g class="clusters"><g id="mermaid-0-w_1" class="cluster" data-look="classic"><path d="M8 234h241.505v307.897H8z" style="fill:#ffffde;stroke:#aa3;stroke-width:1px"></path><foreignObject width="195.672" height="24" class="cluster-label" transform="translate(30.916 234)"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5"><span class="nodeLabel" style="color:#333"><p style="background-color:transparent">callable subtree of scanToX</p></span></div></foreignObject></g></g><g class="edgePaths"><path id="mermaid-0-L_idle_s3_0" marker-end="url(#mermaid-0_flowchart-v2-pointEnd)" d="m191.363 47.5-.084 6.083c-.083 6.084-.25 18.25-.259 29.917s.14 22.834.215 28.417l.074 5.583" class="edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_idle_s3_0" data-look="classic" data-points="W3sieCI6MTkxLjM2MjUwMDAwMDc0NTA2LCJ5Ijo0Ny41fSx7IngiOjE5MC44NjI1MDAwMDA3NDUwNiwieSI6ODR9LHsieCI6MTkxLjM2MjUwMDAwMDc0NTA2LCJ5IjoxMjEuNX1d"></path><path id="mermaid-0-L_s3_s1_0" marker-end="url(#mermaid-0_flowchart-v2-pointEnd)" d="m151.666 160.5-12.637 6.083c-12.637 6.084-37.911 18.25-50.548 30.5s-12.637 24.584-7.006 34.544c5.631 9.961 16.893 17.55 22.524 21.344l5.631 3.794" class="edge-pattern-solid edge-thickness-normal flowchart-link" data-edge="true" data-et="edge" data-id="L_s3_s1_0" data-look="classic" data-points="W3sieCI6MTUxLjY2NTc2MzI3NDgyNDIsInkiOjE2MC41fSx7IngiOjc1Ljg0Mzc1LCJ5IjoxOTd9LHsieCI6NzUuODQzNzUsInkiOjIzNH0seyJ4IjoxMTIuOTQ3MTE1Mzg0OTczNTksInkiOjI1OX1d" style="stroke-width:3.5px"></path><path id="mermaid-0-L_s3_s2_0" marker-end="url(#mermaid-0_flowchart-v2-pointEnd)" d="m246.927 160.5 17.488 6.083c17.488 6.084 52.465 18.25 69.953 30.5s17.488 24.584 17.488 34.25V255" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_s3_s2_0" data-look="classic" data-points="W3sieCI6MjQ2LjkyNjcxNDYwMjY0MzU2LCJ5IjoxNjAuNX0seyJ4IjozNTEuODU2MjUwMDAxMTE3NiwieSI6MTk3fSx7IngiOjM1MS44NTYyNTAwMDExMTc2LCJ5IjoyMzR9LHsieCI6MzUxLjg1NjI1MDAwMTExNzYsInkiOjI1OX1d"></path><path id="mermaid-0-L_s1_c1_0" marker-end="url(#mermaid-0_flowchart-v2-pointEnd)" d="m120.134 313-7.51 6.167c-7.511 6.166-22.532 18.5-30.043 30.166S75.07 372 75.07 377.5v5.5" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_s1_c1_0" data-look="classic" data-points="W3sieCI6MTIwLjEzNDI1MjkzMDExODI0LCJ5IjozMTN9LHsieCI6NzUuMDcwMzEyNSwieSI6MzUwfSx7IngiOjc1LjA3MDMxMjUsInkiOjM4N31d"></path><path id="mermaid-0-L_s2_s0_0" marker-end="url(#mermaid-0_flowchart-v2-pointEnd)" d="M351.856 313v70" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_s2_s0_0" data-look="classic" data-points="W3sieCI6MzUxLjg1NjI1MDAwMTExNzYsInkiOjMxM30seyJ4IjozNTEuODU2MjUwMDAxMTE3NiwieSI6MzUwfSx7IngiOjM1MS44NTYyNTAwMDExMTc2LCJ5IjozODd9XQ=="></path><path id="mermaid-0-L_w_1_s3_0" marker-end="url(#mermaid-0_flowchart-v2-pointEnd)" d="m191.661 197-.02-32.5" class="edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_w_1_s3_0" data-look="classic" data-points="W3sieCI6MTczLjA4Mjk2Mjc0MTMyMzEyLCJ5IjoyNTl9LHsieCI6MTkxLjY2MDkzNzUwMTExNzYsInkiOjIzNH0seyJ4IjoxOTEuNjYwOTM3NTAxMTE3NiwieSI6MTk3fSx7IngiOjE5MS42MzgwNjY5MjU2NTI0LCJ5IjoxNjAuNX1d"></path><path id="mermaid-0-L_s1_s1_0" marker-end="url(#mermaid-0_flowchart-v2-pointEnd)" d="m130.79 313-3.334 4.05c-3.334 4.05-10.003 12.15.741 16.2s38.9 4.05 50.067.515c11.168-3.536 5.347-10.606 2.436-14.142l-2.91-3.535" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_s1_s1_0" data-look="classic" data-points="W3sieCI6MTMwLjc5MDEzMTU3OTY5MjQzLCJ5IjozMTN9LHsieCI6MTEwLjc4NDM3NTAwMDc0NTA2LCJ5IjozMzcuM30seyJ4IjoxOTUuMjUzMTI1MDAwNzQ1MDYsInkiOjMzNy4zfSx7IngiOjE3NS4yNDczNjg0MjE3OTc3LCJ5IjozMTN9XQ=="></path></g><g class="edgeLabels"><g class="edgeLabel"><foreignObject width="36.469" height="24" class="label" data-id="L_idle_s3_0" transform="translate(172.628 72)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>enter</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="24.016" height="24" class="label" data-id="L_s3_s1_0" transform="translate(63.836 185)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>call</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="0" height="0" class="label" data-id="L_s3_s2_0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="94.141" height="24" class="label" data-id="L_s1_c1_0" transform="translate(28 338)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>['X'] → [K]/[S]</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="84.469" height="24" class="label" data-id="L_s1_s1_0" transform="translate(110.784 341.3)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>[*] → [K]/[R]</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="83.578" height="24" class="label" data-id="L_s2_s0_0" transform="translate(310.067 338)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>[*] → [E]/[S]</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="41.797" height="24" class="label" data-id="L_w_1_s3_0" transform="translate(170.751 166.75)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>return</p></span></div></foreignObject></g></g><g class="nodes"><g id="mermaid-0-flowchart-s0-0" class="node default" data-look="classic"><g class="basic label-container" transform="translate(351.856 414.898)"><circle r="27.898" class="outer-circle"></circle><circle r="22.898" class="inner-circle"></circle></g><g class="label" transform="translate(338.958 402.898)"><rect></rect><foreignObject width="25.797" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>halt</p></span></div></foreignObject></g></g><g id="mermaid-0-flowchart-s2-1" class="node default" data-look="classic"><path d="M284.504 259h134.703v54H284.504z" class="basic label-container"></path><g class="label" transform="translate(314.505 274)"><rect></rect><foreignObject width="74.703" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>eraseHere</p></span></div></foreignObject></g></g><g id="mermaid-0-flowchart-s3-2" class="node default" data-look="classic"><path d="M0 0h161.734v-39H0V0h-8 177.734v-39H-8V0z" class="label-container" style="fill:#ececff;stroke:#9370db;stroke-width:1px" transform="translate(109.995 160)"></path><g class="label" transform="translate(117.495 128.5)"><rect></rect><foreignObject width="146.734" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>scanToX(eraseHere)</p></span></div></foreignObject></g></g><g id="mermaid-0-flowchart-idle-3" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M185.534 8h10.657l1.249.04 1.244.12c.25.04.5.08 1.234.2l1.219.278 1.198.355 1.173.432 1.143.506 1.109.578 1.069.648 1.025.715.977.78.925.84.87.898.81.952.748 1.002.682 1.047.613 1.09.543 1.126.469 1.158.394 1.187.317 1.209.239 1.227.16 1.24.08 1.247v1.25l-.08 1.247-.16 1.24-.24 1.227-.316 1.21-.394 1.186-.47 1.158-.542 1.126-.613 1.09-.682 1.047-.748 1.002-.81.952-.87.898-.925.84-.977.78-1.025.715-1.07.648-1.108.578-1.143.506-1.173.432-1.198.355-1.219.278-1.234.2-1.244.12-1.25.04h-10.656l-1.249-.04-1.244-.12-1.234-.2-1.219-.278-1.198-.355-1.173-.432-1.143-.506-1.109-.578-1.069-.648-1.025-.715-.977-.78-.925-.84-.87-.898-.81-.952-.748-1.002-.682-1.047-.613-1.09-.543-1.126-.469-1.158-.394-1.187-.317-1.209-.239-1.227-.16-1.24-.08-1.247v-1.25l.08-1.247.16-1.24.24-1.227.316-1.21.394-1.186.47-1.158c.183-.382.367-.765.542-1.126l.613-1.09.682-1.047.748-1.002.81-.952.87-.898.925-.84.977-.78c.361-.252.723-.503 1.025-.715l1.07-.648 1.108-.578 1.143-.506 1.173-.432 1.198-.355 1.219-.278 1.234-.2 1.244-.12 1.25-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M185.534 8h10.657m-10.657 0h10.657m0 0 1.249.04M196.19 8l1.25.04m0 0 1.244.12m-1.244-.12 1.244.12m0 0 1.234.2m-1.234-.2 1.234.2m0 0 1.219.278m-1.219-.278 1.219.278m0 0 1.198.355m-1.198-.355 1.198.355m0 0 1.173.432m-1.173-.432 1.173.432m0 0 1.143.506m-1.143-.506 1.143.506m0 0 1.109.578m-1.109-.578 1.109.578m0 0 1.069.648m-1.07-.648 1.07.648m0 0 1.025.715m-1.025-.715 1.025.715m0 0 .977.78m-.977-.78.977.78m0 0 .925.84m-.925-.84.925.84m0 0 .87.898m-.87-.898.87.898m0 0 .81.952m-.81-.952.81.952m0 0 .748 1.002m-.748-1.002.748 1.002m0 0 .682 1.047m-.682-1.047.682 1.047m0 0 .613 1.09m-.613-1.09.613 1.09m0 0 .543 1.126m-.543-1.126.543 1.126m0 0 .469 1.158m-.47-1.158.47 1.158m0 0 .394 1.187m-.394-1.187.394 1.187m0 0 .317 1.209m-.317-1.21c.12.463.242.925.317 1.21m0 0 .239 1.227m-.24-1.227.24 1.227m0 0 .16 1.24m-.16-1.24.16 1.24m0 0 .08 1.247m-.08-1.247.08 1.247m0 0v1.25m0-1.25v1.25m0 0-.08 1.247m.08-1.247-.08 1.247m0 0-.16 1.24m.16-1.24-.16 1.24m0 0-.24 1.227m.24-1.227-.24 1.227m0 0-.316 1.21m.317-1.21-.317 1.21m0 0-.394 1.186m.394-1.187-.394 1.187m0 0-.47 1.158m.47-1.158-.47 1.158m0 0c-.11.232-.222.463-.542 1.126m.543-1.126-.543 1.126m0 0-.613 1.09m.613-1.09-.613 1.09m0 0-.682 1.047m.682-1.047-.682 1.047m0 0-.748 1.002m.748-1.002-.748 1.002m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898-.87.898m0 0-.925.84m.925-.84-.925.84m0 0-.977.78m.977-.78-.977.78m0 0-1.025.715m1.025-.715-1.025.715m0 0-1.07.648m1.07-.648-1.07.648m0 0-1.108.578m1.109-.578-1.109.578m0 0-1.143.506m1.143-.506-1.143.506m0 0-1.173.432m1.173-.432-1.173.432m0 0-1.198.355m1.198-.355-1.198.355m0 0-1.219.278m1.219-.278-1.219.278m0 0-1.234.2m1.234-.2-1.234.2m0 0-1.244.12m1.244-.12-1.244.12m0 0-1.25.04m1.25-.04-1.25.04m0 0h-10.656m10.657 0h-10.657m0 0-1.249-.04m1.25.04-1.25-.04m0 0-1.244-.12m1.244.12-1.244-.12m0 0-1.234-.2m1.234.2-1.234-.2m0 0-1.219-.278m1.219.278-1.219-.278m0 0-1.198-.355m1.198.355-1.198-.355m0 0-1.173-.432m1.173.432-1.173-.432m0 0-1.143-.506m1.143.506-1.143-.506m0 0-1.109-.578m1.109.578-1.109-.578m0 0-1.069-.648m1.07.648-1.07-.648m0 0c-.255-.179-.51-.357-1.025-.715m1.025.715-1.025-.715m0 0-.977-.78m.977.78-.977-.78m0 0-.925-.84m.925.84-.925-.84m0 0-.87-.898m.87.898-.87-.898m0 0-.81-.952m.81.952-.81-.952m0 0-.748-1.002m.748 1.002-.748-1.002m0 0-.682-1.047m.682 1.047-.682-1.047m0 0-.613-1.09m.613 1.09-.613-1.09m0 0-.543-1.126m.543 1.126-.543-1.126m0 0-.469-1.158m.47 1.158-.47-1.158m0 0-.394-1.187m.394 1.187-.394-1.187m0 0-.317-1.209m.317 1.21-.317-1.21m0 0-.239-1.227m.24 1.227-.24-1.227m0 0c-.05-.397-.102-.793-.16-1.24m.16 1.24-.16-1.24m0 0-.08-1.247m.08 1.247-.08-1.247m0 0v-1.25m0 1.25v-1.25m0 0c.025-.38.05-.76.08-1.247m-.08 1.247c.02-.293.038-.586.08-1.247m0 0 .16-1.24m-.16 1.24.16-1.24m0 0 .24-1.227m-.24 1.227.24-1.227m0 0 .316-1.21m-.317 1.21.317-1.21m0 0 .394-1.186m-.394 1.187.394-1.187m0 0 .47-1.158m-.47 1.158.47-1.158m0 0 .542-1.126m-.543 1.126.543-1.126m0 0 .613-1.09m-.613 1.09.613-1.09m0 0 .682-1.047m-.682 1.047.682-1.047m0 0 .748-1.002m-.748 1.002.748-1.002m0 0 .81-.952m-.81.952.81-.952m0 0 .87-.898m-.87.898.87-.898m0 0 .925-.84m-.925.84.925-.84m0 0 .977-.78m-.977.78.977-.78m0 0c.267-.185.533-.371 1.025-.715m-1.025.715 1.025-.715m0 0 1.07-.648m-1.07.648 1.07-.648m0 0 1.108-.578m-1.109.578 1.109-.578m0 0 1.143-.506m-1.143.506 1.143-.506m0 0 1.173-.432m-1.173.432 1.173-.432m0 0 1.198-.355m-1.198.355 1.198-.355m0 0 1.219-.278m-1.219.278 1.219-.278m0 0 1.234-.2m-1.234.2 1.234-.2m0 0 1.244-.12m-1.244.12 1.244-.12m0 0 1.25-.04m-1.25.04 1.25-.04m0 0"></path></g><g class="label" transform="translate(178.41 15.5)"><rect></rect><foreignObject width="24.906" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>idle</p></span></div></foreignObject></g></g><g id="mermaid-0-flowchart-s1-4" class="node default" data-look="classic"><path d="M92.33 259h121.376v54H92.33z" class="basic label-container"></path><g class="label" transform="translate(122.331 274)"><rect></rect><foreignObject width="61.375" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>scanToX</p></span></div></foreignObject></g></g><g id="mermaid-0-flowchart-c1-5" class="node default" data-look="classic"><g class="basic label-container" transform="translate(75.07 414.898)"><circle r="27.898" class="outer-circle"></circle><circle r="22.898" class="inner-circle"></circle></g><g class="label" transform="translate(62.172 402.898)"><rect></rect><foreignObject width="25.797" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>halt</p></span></div></foreignObject></g></g><g id="s1---s1---1" class="label edgeLabel"><path d="M153.019 414.898h.1v.1h-.1z"></path><g class="label" transform="translate(153.019 414.898)"><rect></rect><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:10px;text-align:center"><span class="nodeLabel"></span></div></foreignObject></g></g><g id="s1---s1---2" class="label edgeLabel"><path d="M168.126 516.847h.1v.1h-.1z"></path><g class="label" transform="translate(168.126 516.847)"><rect></rect><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:10px;text-align:center"><span class="nodeLabel"></span></div></foreignObject></g></g></g></g><defs><filter id="mermaid-0-drop-shadow" width="130%" height="130%"><feDropShadow dx="4" dy="4" flood-color="#000" flood-opacity=".06" stdDeviation="0"></feDropShadow></filter></defs><defs><filter id="mermaid-0-drop-shadow-small" width="150%" height="150%"><feDropShadow dx="2" dy="2" flood-color="#000" flood-opacity=".06" stdDeviation="0"></feDropShadow></filter></defs></svg>
<p>Этого достаточно, чтобы <code>fromGraph</code> восстановил обёртку по структуре: вот call-site, вот база, на которую он ссылается. Граф <em>кодирует</em> композицию, а не намекает на неё.</p>
<blockquote>
<p><strong>Под капотом</strong> (можно пропустить). Halt-выход каждого frame-а — отдельный узел <code>isHaltMarker</code> с id <code>= -frameId</code>; в рантайме он сворачивается в синглтон <code>haltState</code>, но на графе живёт отдельно, иначе вся машина «втягивается» в один halt-узел. Когда одна база достижима из нескольких обёрток, frame-ы считаются union-find по достижимости баз (подпись subgraph — «callable subtree of A» или «callable scope: A ∪ B»). Начало машины помечено служебным <code>idle([idle])</code> с пунктирной <code>enter</code>-стрелкой — якорь, по которому парсер находит стартовое состояние. Метки переходов записаны по простому шаблону <code>[reads] → [writes]/[moves]</code> — по ячейке на ленту.</p>
</blockquote>
<h2 id="ta-zhe-mashina-posle-round-trip"><a class="heading-anchor" href="#ta-zhe-mashina-posle-round-trip">Та же машина после round-trip?</a></h2>
<p>Для простой композиции — одна обёртка, одна база, дерево не пересекается с другими — round-trip воспроизводи́м с точностью до переименования id. Граф хранит узлы, имена, переходы; <code>fromGraph</code> собирает ту же машину обратно. Композитное имя <code>scanToX(eraseHere)</code> в граф попадает — это подпись узла-обёртки, — но <code>fromGraph</code> его не читает: композитное имя он пересобирает заново из базы и оверрайда, через единственное разрешённое место мутации имени, Symbol-accessor <code>STATE_INTERNAL</code> (<a href="https://github.com/mellonis/turing-machine-js/issues/180"><code>turing-machine-js#180</code></a>). Поэтому накопления имён не происходит: на каждой пересборке имя считается из структуры, а не наращивается поверх предыдущего. Ровно это и проверяет регрессионный тест на простые обёртки.</p>
<p>Ломается воспроизводимость на общей базе — когда один и тот же <code>State</code> служит базой сразу нескольким обёрткам. Не выдумка: в <code>library-binary-numbers</code> алгоритм <code>minusOne</code> устроен так, что <code>invertNumber</code> оказывается базой двух обёрток, внешней и внутренней (<a href="https://github.com/mellonis/turing-machine-js/blob/master/packages/library-binary-numbers/states.md#minusone">полный граф</a>). Один объект, два call-site.</p>
<p>Структуру это не ломает: <code>toGraph</code> обходит достижимые состояния ровно раз, по id инстанса, поэтому общая база — это <strong>один</strong> узел, а две обёртки просто ссылаются на него двумя стрелками; <code>fromGraph</code> восстанавливает ровно один общий инстанс на обе обёртки. Сама общая база переживает round-trip. А вот <em>воспроизводимость сериализации</em> — нет: повторная пересборка может дать граф, который не сводится к исходному, даже если закрыть глаза на id.</p>
<p>Та же форма в миниатюре, на <code>scanToX</code> — одна база под двумя обёртками:</p>
<svg xmlns="http://www.w3.org/2000/svg" id="mermaid-1" width="100%" aria-roledescription="flowchart-v2" class="flowchart" style="font-family:arial,sans-serif;font-size:16px;fill:#333;max-width:219.671875px" viewBox="-6.148 0 219.672 346"><style>@keyframes edge-animation-frame{0%{stroke-dashoffset:0}}@keyframes dash{to{stroke-dashoffset:0}}#mermaid-1 .edge-thickness-normal{stroke-width:1px}#mermaid-1 .edge-thickness-thick{stroke-width:3.5px}#mermaid-1 .edge-pattern-solid{stroke-dasharray:0}#mermaid-1 .edge-pattern-dotted{stroke-dasharray:2}#mermaid-1 .marker{fill:#333;stroke:#333}#mermaid-1 .marker.cross{stroke:#333}#mermaid-1 svg{font-family:arial,sans-serif;font-size:16px}#mermaid-1 p{margin:0}#mermaid-1 .label{font-family:arial,sans-serif;color:#333}#mermaid-1 span{fill:#333;color:#333}#mermaid-1 .node circle,#mermaid-1 .node path,#mermaid-1 .node rect{fill:#ececff;stroke:#9370db;stroke-width:1px}#mermaid-1 .flowchart-link{stroke:#333;fill:none}#mermaid-1 .edgeLabel,#mermaid-1 .node .label{text-align:center}#mermaid-1 .edgeLabel,#mermaid-1 .edgeLabel p,#mermaid-1 .edgeLabel rect{background-color:rgba(232,232,232,.8)}#mermaid-1 .edgeLabel rect{opacity:.5;fill:rgba(232,232,232,.8)}#mermaid-1 .labelBkg{background-color:rgba(232,232,232,.5)}#mermaid-1 [data-look=neo].cluster rect,#mermaid-1 [data-look=neo].node circle,#mermaid-1 [data-look=neo].node rect{stroke:#9370db;filter:drop-shadow(1px 2px 2px #b9b9b9)}#mermaid-1 [data-look=neo].node path{stroke:#9370db;stroke-width:1px}#mermaid-1 :root{--mermaid-font-family:arial,sans-serif}</style><marker id="mermaid-1_flowchart-v2-pointEnd" class="marker flowchart-v2" markerHeight="8" markerUnits="userSpaceOnUse" markerWidth="8" orient="auto" refX="5" refY="5" viewBox="0 0 10 10"><path d="m0 0 10 5-10 5z" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></path></marker><marker id="mermaid-1_flowchart-v2-pointStart" class="marker flowchart-v2" markerHeight="8" markerUnits="userSpaceOnUse" markerWidth="8" orient="auto" refX="4.5" refY="5" viewBox="0 0 10 10"><path d="m0 5 10 5V0z" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></path></marker><marker id="mermaid-1_flowchart-v2-pointEnd-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="10.5" orient="auto" refX="11.5" refY="7" viewBox="0 0 11.5 14"><path d="m0 0 11.5 7L0 14z" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></path></marker><marker id="mermaid-1_flowchart-v2-pointStart-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="11.5" orient="auto" refX="1" refY="7" viewBox="0 0 11.5 14"><path d="m0 7 11.5 7V0z" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></path></marker><marker id="mermaid-1_flowchart-v2-circleEnd" class="marker flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="11" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-1_flowchart-v2-circleStart" class="marker flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="-1" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-1_flowchart-v2-circleEnd-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="14" orient="auto" refX="12.25" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-1_flowchart-v2-circleStart-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="14" orient="auto" refX="-2" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-1_flowchart-v2-crossEnd" class="marker cross flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="12" refY="5.2" viewBox="0 0 11 11"><path d="m1 1 9 9m0-9-9 9" class="arrowMarkerPath" style="stroke-width:2;stroke-dasharray:1,0"></path></marker><marker id="mermaid-1_flowchart-v2-crossStart" class="marker cross flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="-1" refY="5.2" viewBox="0 0 11 11"><path d="m1 1 9 9m0-9-9 9" class="arrowMarkerPath" style="stroke-width:2;stroke-dasharray:1,0"></path></marker><marker id="mermaid-1_flowchart-v2-crossEnd-margin" class="marker cross flowchart-v2" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="12" orient="auto" refX="17.7" refY="7.5" viewBox="0 0 15 15"><path d="m1 1 13 13M1 14 14 1" class="arrowMarkerPath" style="stroke-width:2.5"></path></marker><marker id="mermaid-1_flowchart-v2-crossStart-margin" class="marker cross flowchart-v2" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="12" orient="auto" refX="-3.5" refY="7.5" viewBox="0 0 15 15"><path d="m1 1 13 13M1 14 14 1" class="arrowMarkerPath" style="stroke-width:2.5;stroke-dasharray:1,0"></path></marker><g class="root"><g class="clusters"><g id="mermaid-1-w_1" class="cluster" data-look="classic"><path d="M1.852 121h203.672v104H1.852z" style="fill:#ffffde;stroke:#aa3;stroke-width:1px"></path><foreignObject width="195.672" height="24" class="cluster-label" transform="translate(5.852 121)"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5"><span class="nodeLabel" style="color:#333"><p style="background-color:transparent">callable subtree of scanToX</p></span></div></foreignObject></g></g><g class="edgePaths"><path id="mermaid-1-L_s3_s1_0" marker-end="url(#mermaid-1_flowchart-v2-pointEnd)" d="m94.578 47.5-3.123 6.083c-3.122 6.084-9.367 18.25-12.489 30.5s-3.122 24.584-1.206 34.329 5.749 16.904 7.666 20.483l1.916 3.579" class="edge-thickness-thick edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_s3_s1_0" data-look="classic" data-points="W3sieCI6OTQuNTc3NzEwMTc2OTkxMTUsInkiOjQ3LjV9LHsieCI6NzUuODQzNzUsInkiOjg0fSx7IngiOjc1Ljg0Mzc1LCJ5IjoxMjF9LHsieCI6ODkuMjMwMTY4MjY5MjMwNzcsInkiOjE0Nn1d"></path><path id="mermaid-1-L_s8_s1_0" marker-end="url(#mermaid-1_flowchart-v2-pointEnd)" d="m117.249 299.5 4.047-6.25c4.047-6.25 12.141-18.75 16.188-31.167 4.047-12.416 4.047-24.75 1.407-34.544s-7.92-17.05-10.56-20.677l-2.64-3.628" class="edge-thickness-thick edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_s8_s1_0" data-look="classic" data-points="W3sieCI6MTE3LjI0ODYxNzI1NjYzNzE3LCJ5IjoyOTkuNX0seyJ4IjoxNDEuNTMxMjUsInkiOjI2Mn0seyJ4IjoxNDEuNTMxMjUsInkiOjIyNX0seyJ4IjoxMjMuMzM3MTM5NDIzMDc2OTIsInkiOjIwMH1d"></path><path id="mermaid-1-L_w_1_s3_0" marker-end="url(#mermaid-1_flowchart-v2-pointEnd)" d="M141.531 121v-6.167c0-6.166 0-18.5-3.678-30.195s-11.033-22.751-14.711-28.279l-3.678-5.529" class="edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_w_1_s3_0" data-look="classic" data-points="W3sieCI6MTIzLjMzNzEzOTQyMzA3NjkyLCJ5IjoxNDZ9LHsieCI6MTQxLjUzMTI1LCJ5IjoxMjF9LHsieCI6MTQxLjUzMTI1LCJ5Ijo4NH0seyJ4IjoxMTcuMjQ4NjE3MjU2NjM3MTcsInkiOjQ3LjV9XQ=="></path><path id="mermaid-1-L_w_1_s8_0" marker-end="url(#mermaid-1_flowchart-v2-pointEnd)" d="M75.844 225v6.167c0 6.166 0 18.5 2.824 30.32 2.825 11.82 8.473 23.127 11.298 28.781l2.824 5.654" class="edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_w_1_s8_0" data-look="classic" data-points="W3sieCI6ODkuMjMwMTY4MjY5MjMwNzcsInkiOjIwMH0seyJ4Ijo3NS44NDM3NSwieSI6MjI1fSx7IngiOjc1Ljg0Mzc1LCJ5IjoyNjJ9LHsieCI6OTQuNTc3NzEwMTc2OTkxMTUsInkiOjI5OS41fV0="></path></g><g class="edgeLabels"><g class="edgeLabel"><foreignObject width="24.016" height="24" class="label" data-id="L_s3_s1_0" transform="translate(63.836 72)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>call</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="24.016" height="24" class="label" data-id="L_s8_s1_0" transform="translate(129.523 250)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>call</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="41.797" height="24" class="label" data-id="L_w_1_s3_0" transform="translate(118.739 69.153)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>return</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="41.797" height="24" class="label" data-id="L_w_1_s8_0" transform="translate(56.045 252.2)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>return</p></span></div></foreignObject></g></g><g class="nodes"><g id="mermaid-1-flowchart-s3-0" class="node default" data-look="classic"><path d="M22.82 47h161.735V8H22.82v39h-8 177.735V8H14.82v39z" class="label-container"></path><g class="label" transform="translate(30.32 15.5)"><rect></rect><foreignObject width="146.734" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>scanToX(eraseHere)</p></span></div></foreignObject></g></g><g id="mermaid-1-flowchart-s8-1" class="node default" data-look="classic"><path d="M37.93 338h131.515v-39H37.93v39h-8 147.515v-39H29.93v39z" class="label-container"></path><g class="label" transform="translate(45.43 306.5)"><rect></rect><foreignObject width="116.516" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>scanToX(goLeft)</p></span></div></foreignObject></g></g><g id="mermaid-1-flowchart-s1-2" class="node default" data-look="classic"><path d="M43 146h121.375v54H43z" class="basic label-container"></path><g class="label" transform="translate(73 161)"><rect></rect><foreignObject width="61.375" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>scanToX</p></span></div></foreignObject></g></g></g></g><defs><filter id="mermaid-1-drop-shadow" width="130%" height="130%"><feDropShadow dx="4" dy="4" flood-color="#000" flood-opacity=".06" stdDeviation="0"></feDropShadow></filter></defs><defs><filter id="mermaid-1-drop-shadow-small" width="150%" height="150%"><feDropShadow dx="2" dy="2" flood-color="#000" flood-opacity=".06" stdDeviation="0"></feDropShadow></filter></defs></svg>
<blockquote>
<p><strong>Под капотом</strong> (можно пропустить). Id узлов — это runtime-id инстансов, <code>fromGraph</code> назначает их заново. «Воспроизводимо» здесь значит: сериализовать, пересобрать, сериализовать снова и сравнить два графа, игнорируя сами значения id. У простой обёртки повторный <code>toGraph</code> даёт ту же раскладку с точностью до переименования. В случае с общей базой, например одной на две обёртки, id после пересборки могут идти в другом порядке, а узлы эмитируются отсортированными по id — раскладка съезжает, и закрытие глаз на id уже не помогает.</p>
</blockquote>
<p>И вот тут граф перестаёт быть тем ответом, на который стоит опираться. Зато <code>equivalentOn</code> на обеих машинах даёт <code>allAgree: true</code>: по поведению — та же. Граница простая: воспроизводимость сериализации — только на простых обёртках, поведенческая эквивалентность — всегда.</p>
<h2 id="gde-zhivyot-equivalenton"><a class="heading-anchor" href="#gde-zhivyot-equivalenton">Где живёт <code>equivalentOn</code></a></h2>
<p><code>equivalentOn(reference, candidate, cases)</code> живёт в <code>utilities/equivalence.ts</code>. <code>reference</code> и <code>candidate</code> — это <code>Runnable</code> (<code>{ state, getTapeBlock }</code>), <code>cases</code> — массив тестовых входов; функция запускает обе машины, сравнивает выходы и снимки на каждом шаге и возвращает отчёт <code>{ results, allAgree }</code>: пошаговые результаты плюс итоговое «да/нет» в <code>allAgree</code>.</p>
<p>Важно, что он <em>не</em> живёт в <code>utilities/stateGraph.ts</code>, где собраны <code>toGraph</code>/<code>fromGraph</code>. Это разные модули, разные слои библиотеки. <code>equivalentOn</code> ничего не знает про граф; ему всё равно, эмитируется ли обёртка как call-site или как один узел, в каких frame-ах живут переходы. Он смотрит только на наблюдаемое поведение.</p>
<p>Это и есть его роль. Когда воспроизводимость <code>toGraph</code>/<code>fromGraph</code> зыбка (общая база на две обёртки), <code>equivalentOn</code> отвечает на вопрос, который потребителю на самом деле нужен: «после восстановления — это та же машина <em>по поведению</em>?». Граф — это инструмент сериализации; поведение — это контракт.</p>
<p>Ловушка, в которую легко попасть: проверять тождество через <code>===</code> над <code>State</code>-инстансами. После пересборки это даёт <code>false</code> — <code>fromGraph</code> всегда создаёт свежие инстансы, — хотя поведение совпадает. Ложная регрессия на ровном месте. <code>equivalentOn</code> нужен ровно для того, чтобы такого не происходило, — но для этого надо знать, что он есть.</p>
<h2 id="tegi-dannie-yedut-s-grafom-identichnost-ostayotsya-lokalnoi"><a class="heading-anchor" href="#tegi-dannie-yedut-s-grafom-identichnost-ostayotsya-lokalnoi">Теги: данные едут с графом, идентичность остаётся локальной</a></h2>
<p>Маленькая, но симптоматичная добавка v7 — теги (<a href="https://github.com/mellonis/turing-machine-js/issues/186"><code>turing-machine-js#186</code></a>). У <code>State</code> появился API: <code>state.tag('hot', 'subroutine-entry')</code>, <code>state.tags</code>, <code>state.untag(...)</code>. Теги — это строковые метки на инстансе. Нужны они инструментам поверх машины: <code>toMermaid</code> раскрашивает и группирует по ним узлы через <code>classDef</code>, а потребители вроде post-machine-js помечают ими точки входа подпрограмм. На семантику машины теги не влияют.</p>
<p>Что про них важно для трёх ипостасей:</p>
<ul>
<li><strong>Поведенчески — не считаются.</strong> <code>equivalentOn</code> теги не смотрит. Машина с тегами и без них на одних и тех же переходах — поведенчески одна и та же.</li>
<li><strong>Графово — переживают round-trip.</strong> <code>GraphNode.tags</code> сериализуется; <code>toMermaid</code> эмитирует теги в подписи узла через <code>&#x3C;br></code> <em>и</em> как <code>classDef</code> для цветовой группировки в рендере; <code>fromMermaid</code> читает из подписи (<code>&#x3C;br></code> — источник истины, <code>classDef</code> декоративный).</li>
<li><strong>Объектно — локальны инстансу.</strong> Теги живут на конкретном <code>State</code>, не на базе. Две обёртки делят одну базу, но набор тегов у каждой — свой. Пометь одну — и вторая, и сама база останутся без тега.</li>
</ul>
<p>Хорошая граница: данные, описывающие узел графа, едут с графом; идентичность инстанса остаётся локальной для конкретной сборки. Теги оказываются посередине: для поведения их нет (<code>equivalentOn</code> к ним слеп), но они долговечнее <code>===</code>-идентичности — та обнуляется на каждой пересборке, а теги едут с графом и переживают round-trip.</p>
<h2 id="chto-ya-v-itoge-ponyal"><a class="heading-anchor" href="#chto-ya-v-itoge-ponyal">Что я в итоге понял</a></h2>
<p>Три ипостаси я не задумывал разом — они набрались по одной, и только задним числом стало видно, что это разные <em>проекции</em> одной сущности, не один растяжимый «equals».</p>
<p>Объектную JavaScript даёт бесплатно: <code>===</code> и <code>instanceof</code> работают сами. Поведенческую я получил почти даром — <code>equivalentOn</code> это просто прогон двух машин на тестах. А графовую пришлось проектировать: без неё <code>fromGraph</code> не восстановил бы из сериализованной машины ничего, и именно ради неё v7 уложила композицию в данные графа.</p>
<p>Две вещи дошли до меня поздно. Биективное именование композиций — не украшение, а условие: пока имена неоднозначны, граф теряет один из видов проверки на «те же» ещё до того, как <code>fromGraph</code> запустится. Об этой же неоднозначности — <a href="https://mellonis.ru/ru/articles/seven-arrows-429-trees/">статья (1)</a>. И поведенческая эквивалентность должна жить отдельно от графа: <code>equivalentOn</code> ничего не знает про <code>toGraph</code>, и это правильно — потребителю чаще нужен поведенческий ответ, а не побайтовый round-trip.</p>
<p>А ещё теги подсказали простую проверку: что переживает round-trip — описывает узел графа, что не переживает — описывает конкретный инстанс. Добавляю поле — смотрю, в какую ипостась оно попадает: от этого зависит, в каком смысле оно делает две машины «теми же».</p>
<hr>
<p><em>Код:</em> <a href="https://github.com/mellonis/turing-machine-js"><code>turing-machine-js</code></a> <em>(движок с v7-графовыми утилитами). Интерактивное демо на</em> <a href="https://demo.machines.mellonis.ru">demo.machines.mellonis.ru</a> <em>рисует машины через</em> <code>toMermaid</code> <em>и подсвечивает текущий переход.</em></p>]]></content:encoded>
    </item>
    <item>
      <title>Семь стрел, 429 деревьев: семилетняя ошибка именования, всплывшая за чисткой Mermaid-визуализации</title>
      <link>https://mellonis.ru/ru/articles/seven-arrows-429-trees/</link>
      <guid isPermaLink="false">https://mellonis.ru/ru/articles/seven-arrows-429-trees/</guid>
      <pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate>
      <description>Соглашение об именовании композиций в движке машины Тьюринга было неинъективным с 2019 года: одно имя — до C_7 = 429 разных машин. v7 чинит это скобками.</description>
      <content:encoded><![CDATA[<p>Заходят как-то две машины Тьюринга в одну диаграмму... а у них имена одинаковые.</p>
<p>Я говорю «машины», а на деле — два разных экземпляра <code>State</code> <a href="https://github.com/mellonis/turing-machine-js"><code>@turing-machine-js/machine</code></a>, сконструированные по-разному, с разным поведением во время исполнения, и со строго одинаковым <code>state.name</code>. Это происходило в библиотеке, которую я разрабатываю в качестве хобби с 2019 года, и я этого не замечал семь лет.</p>
<p>Обнаружил случайно, начиная с задачи на чистку Mermaid-визуализации. Закончил — кардинальной переделкой того, как композиция состояний <em>отражается в имени</em>. И узнал, что за этой переделкой прячется число <math><mrow><msub><mi>C</mi><mn>7</mn></msub><mo>=</mo><mn>429</mn></mrow></math>.</p>
<figure><img src="https://mellonis.ru/articles/seven-arrows-429-trees/forest.svg?v=29b1a432" alt="Лучник с натянутым луком думает, в какое из деревьев леса попадёт его стрела" width="800" height="400"><figcaption>Лучник с натянутым луком думает, в какое из деревьев леса попадёт его стрела</figcaption></figure>
<h2 id="s-chego-vsyo-nachalos"><a class="heading-anchor" href="#s-chego-vsyo-nachalos">С чего всё началось</a></h2>
<p>Композиция в движке устроена одним примитивом: <code>baseState.withOverrodeHaltState(overrideState)</code>. Возвращает копию <code>baseState</code>, у которой halt-переходы во время исполнения уходят в <code>overrideState</code> вместо реального halt’а. Дальше я буду называть <code>baseState</code> <em>базовым состоянием</em>, а возвращаемую копию — <em>обёрткой</em> над ним. Композиция через обёртку — единственный способ собрать большую машину из маленьких подпрограмм: каждая подпрограмма в идеале заканчивается halt, и каждый <code>withOverrodeHaltState</code> переопределяет, куда идти после, вместо того чтобы остановиться.</p>
<p>Имя у получившейся обёртки в старом API формировалось так:</p>
<pre class="shiki shiki-themes github-light github-dark language-javascript" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">state.#name </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">this</span><span style="color:#032F62;--shiki-dark:#9ECBFF">.</span><span style="color:#24292E;--shiki-dark:#E1E4E8">name</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}>${</span><span style="color:#24292E;--shiki-dark:#E1E4E8">override</span><span style="color:#032F62;--shiki-dark:#9ECBFF">.</span><span style="color:#24292E;--shiki-dark:#E1E4E8">name</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span></code></pre>
<p>То есть <code>A.withOverrodeHaltState(B)</code> называется <code>A>B</code>. Просто и наглядно: читается как «A, потом B».</p>
<p>Эта строка ушла в продакшен в 2019 году и с тех пор не менялась. Она просачивается в логи, сообщения об ошибках, тесты, которые сравнивают <code>state.name</code>, и в любой диагностический вывод, на который смотрит пользователь.</p>
<p>Поводом разобраться стала Mermaid-визуализация. Тикет <a href="https://github.com/mellonis/turing-machine-js/issues/138"><code>turing-machine-js#138</code></a> ставил задачу убрать дублирование. До v7 <code>toMermaid</code> рендерил обёртку как отдельный узел, на котором <em>повторял</em> все переходы базового состояния — визуально это выглядело так, будто у обёртки своя жизнь. Но переходы у обёртки те же, что у базы — даже <code>#symbolToDataMap</code> тот же. Своё у неё другое — call-frame-семантика: на входе push override на halt-стек, на halt’е базы — pop. А по сути обёртка вносит сверх базы только одно: куда уходит halt.</p>
<p>Грубо говоря, это выглядело так:</p>
<svg xmlns="http://www.w3.org/2000/svg" id="mermaid-0" width="100%" aria-roledescription="flowchart-v2" class="flowchart" style="font-family:arial,sans-serif;font-size:16px;fill:#333;max-width:426.546875px" viewBox="0 0 426.547 195.5"><style>@keyframes edge-animation-frame{0%{stroke-dashoffset:0}}@keyframes dash{to{stroke-dashoffset:0}}#mermaid-0 .edge-thickness-normal{stroke-width:1px}#mermaid-0 .edge-pattern-solid{stroke-dasharray:0}#mermaid-0 .marker{fill:#333;stroke:#333}#mermaid-0 .marker.cross{stroke:#333}#mermaid-0 svg{font-family:arial,sans-serif;font-size:16px}#mermaid-0 p{margin:0}#mermaid-0 .label{font-family:arial,sans-serif;color:#333}#mermaid-0 span{fill:#333;color:#333}#mermaid-0 .node circle,#mermaid-0 .node path,#mermaid-0 .node rect{fill:#ececff;stroke:#9370db;stroke-width:1px}#mermaid-0 .flowchart-link{stroke:#333;fill:none}#mermaid-0 .edgeLabel,#mermaid-0 .node .label{text-align:center}#mermaid-0 .edgeLabel,#mermaid-0 .edgeLabel p,#mermaid-0 .edgeLabel rect{background-color:rgba(232,232,232,.8)}#mermaid-0 .edgeLabel rect{opacity:.5;fill:rgba(232,232,232,.8)}#mermaid-0 .labelBkg{background-color:rgba(232,232,232,.5)}#mermaid-0 [data-look=neo].node circle,#mermaid-0 [data-look=neo].node rect{stroke:#9370db;filter:drop-shadow(1px 2px 2px #b9b9b9)}#mermaid-0 [data-look=neo].node path{stroke:#9370db;stroke-width:1px}#mermaid-0 [data-look=neo].node .outer-path{filter:drop-shadow(1px 2px 2px #b9b9b9)}#mermaid-0 :root{--mermaid-font-family:arial,sans-serif}</style><marker id="mermaid-0_flowchart-v2-pointEnd" class="marker flowchart-v2" markerHeight="8" markerUnits="userSpaceOnUse" markerWidth="8" orient="auto" refX="5" refY="5" viewBox="0 0 10 10"><path d="m0 0 10 5-10 5z" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></path></marker><marker id="mermaid-0_flowchart-v2-pointStart" class="marker flowchart-v2" markerHeight="8" markerUnits="userSpaceOnUse" markerWidth="8" orient="auto" refX="4.5" refY="5" viewBox="0 0 10 10"><path d="m0 5 10 5V0z" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></path></marker><marker id="mermaid-0_flowchart-v2-pointEnd-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="10.5" orient="auto" refX="11.5" refY="7" viewBox="0 0 11.5 14"><path d="m0 0 11.5 7L0 14z" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></path></marker><marker id="mermaid-0_flowchart-v2-pointStart-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="11.5" orient="auto" refX="1" refY="7" viewBox="0 0 11.5 14"><path d="m0 7 11.5 7V0z" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></path></marker><marker id="mermaid-0_flowchart-v2-circleEnd" class="marker flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="11" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-0_flowchart-v2-circleStart" class="marker flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="-1" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-0_flowchart-v2-circleEnd-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="14" orient="auto" refX="12.25" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-0_flowchart-v2-circleStart-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="14" orient="auto" refX="-2" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-0_flowchart-v2-crossEnd" class="marker cross flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="12" refY="5.2" viewBox="0 0 11 11"><path d="m1 1 9 9m0-9-9 9" class="arrowMarkerPath" style="stroke-width:2;stroke-dasharray:1,0"></path></marker><marker id="mermaid-0_flowchart-v2-crossStart" class="marker cross flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="-1" refY="5.2" viewBox="0 0 11 11"><path d="m1 1 9 9m0-9-9 9" class="arrowMarkerPath" style="stroke-width:2;stroke-dasharray:1,0"></path></marker><marker id="mermaid-0_flowchart-v2-crossEnd-margin" class="marker cross flowchart-v2" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="12" orient="auto" refX="17.7" refY="7.5" viewBox="0 0 15 15"><path d="m1 1 13 13M1 14 14 1" class="arrowMarkerPath" style="stroke-width:2.5"></path></marker><marker id="mermaid-0_flowchart-v2-crossStart-margin" class="marker cross flowchart-v2" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="12" orient="auto" refX="-3.5" refY="7.5" viewBox="0 0 15 15"><path d="m1 1 13 13M1 14 14 1" class="arrowMarkerPath" style="stroke-width:2.5;stroke-dasharray:1,0"></path></marker><g class="root"><g class="edgePaths"><path id="mermaid-0-L_A_A1_0" marker-end="url(#mermaid-0_flowchart-v2-pointEnd)" d="m71.545 28 12.544-.083c12.545-.084 37.634-.25 57.553 1.512s34.667 5.452 42.041 7.298l7.374 1.845" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_A_A1_0" data-look="classic" data-points="W3sieCI6NzEuNTQ0NjY5MjM0MTk0ODgsInkiOjI4fSx7IngiOjE0Ni44MTI1LCJ5IjoyNy41fSx7IngiOjE5NC45Mzc1LCJ5IjozOS41NDMwMTA3NTI2ODgxNzZ9XQ=="></path><path id="mermaid-0-L_A1_haltA_0" marker-end="url(#mermaid-0_flowchart-v2-pointEnd)" d="M274.516 49.5h84.234" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_A1_haltA_0" data-look="classic" data-points="W3sieCI6Mjc0LjUxNTYyNSwieSI6NDkuNX0seyJ4IjozMTguNjMyODEyNSwieSI6NDkuNX0seyJ4IjozNjIuNzUsInkiOjQ5LjV9XQ=="></path><path id="mermaid-0-L_W_A1_0" marker-end="url(#mermaid-0_flowchart-v2-pointEnd)" d="m98.688 122.593 8.02-4.141c8.021-4.14 24.063-12.421 39.52-20.643s30.33-16.384 37.767-20.466l7.436-4.081" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_W_A1_0" data-look="classic" data-points="W3sieCI6OTguNjg3NSwieSI6MTIyLjU5Mjg2MTkxOTA5MDYxfSx7IngiOjE0Ni44MTI1LCJ5Ijo5Ny43NX0seyJ4IjoxOTQuOTM3NSwieSI6NzEuMzM3NDg3NzgxMDM2MTd9XQ=="></path><path id="mermaid-0-L_W_B_0" marker-end="url(#mermaid-0_flowchart-v2-pointEnd)" d="m98.688 156.673 8.02 1.888c8.021 1.887 24.063 5.663 43.203 7.63 19.139 1.966 41.377 2.123 52.496 2.202l11.119.079" class="edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_W_B_0" data-look="classic" data-points="W3sieCI6OTguNjg3NSwieSI6MTU2LjY3MjY4NDcyMDgyOTE1fSx7IngiOjE0Ni44MTI1LCJ5IjoxNjh9LHsieCI6MjE3LjUyNTY0NDMwMjM2Mzg0LCJ5IjoxNjguNTAwMDAwMDAwMDAwMDN9XQ==" style="stroke-dasharray:2"></path></g><g class="edgeLabels"><g class="edgeLabel"><foreignObject width="40.891" height="24" class="label" data-id="L_A_A1_0" transform="translate(126.367 15.5)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>x: y/R</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="38.234" height="24" class="label" data-id="L_A1_haltA_0" transform="translate(299.516 37.5)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>y: y/L</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="40.891" height="24" class="label" data-id="L_W_A1_0" transform="translate(126.367 85.75)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>x: y/R</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="46.25" height="24" class="label" data-id="L_W_B_0" transform="translate(123.688 156)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>onHalt</p></span></div></foreignObject></g></g><g class="nodes"><g id="mermaid-0-flowchart-A-0" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M55.133 8h-3.578l1.25.04 1.243.12 1.234.2 1.219.278 1.198.355 1.173.432 1.143.506 1.109.578 1.069.648c.406.283.811.567 1.025.715l.977.78.925.84.87.898.81.952.748 1.002.682 1.047.613 1.09.543 1.126.469 1.158.394 1.187.317 1.209.239 1.227c.055.43.11.861.16 1.24l.08 1.247v1.25l-.08 1.247-.16 1.24c-.083.427-.166.854-.24 1.227l-.316 1.21-.394 1.186-.47 1.158-.542 1.126-.613 1.09-.682 1.047-.748 1.002-.81.952-.87.898-.925.84-.977.78-1.025.715-1.07.648-1.108.578-1.143.506-1.173.432-1.198.355-1.219.278-1.234.2-1.244.12-1.25.04h3.579l-1.25-.04-1.244-.12-1.234-.2-1.218-.278-1.199-.355-1.173-.432-1.143-.506-1.108-.578-1.07-.648-1.024-.715-.978-.78-.925-.84-.87-.898-.81-.952-.748-1.002-.681-1.047-.614-1.09-.542-1.126-.47-1.158-.393-1.187-.317-1.209-.24-1.227c-.041-.326-.083-.652-.16-1.24-.018-.299-.038-.598-.08-1.247v-1.25c.024-.364.048-.727.08-1.247l.16-1.24.24-1.227.317-1.21.394-1.186.469-1.158.542-1.126.614-1.09.681-1.047.748-1.002.81-.952.87-.898.925-.84.978-.78 1.025-.715c.298-.18.596-.36 1.069-.648l1.108-.578 1.143-.506 1.173-.432 1.199-.355c.345-.08.691-.158 1.218-.278l1.234-.2 1.244-.12 1.25-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M55.133 8h-3.578m3.578 0h-3.578m0 0 1.25.04M51.554 8l1.25.04m0 0 1.243.12m-1.244-.12 1.244.12m0 0 1.234.2m-1.234-.2 1.234.2m0 0 1.219.278m-1.219-.278 1.219.278m0 0 1.198.355M56.5 8.638l1.198.355m0 0 1.173.432M57.7 8.993l1.173.432m0 0 1.143.506m-1.143-.506 1.143.506m0 0 1.109.578m-1.109-.578 1.109.578m0 0 1.069.648m-1.07-.648 1.07.648m0 0 1.025.715m-1.025-.715 1.025.715m0 0 .977.78m-.977-.78.977.78m0 0 .925.84m-.925-.84.925.84m0 0 .87.898m-.87-.898.87.898m0 0 .81.952m-.81-.952.81.952m0 0 .748 1.002m-.748-1.002.748 1.002m0 0 .682 1.047m-.682-1.047.682 1.047m0 0 .613 1.09m-.613-1.09.613 1.09m0 0 .543 1.126m-.543-1.126.543 1.126m0 0 .469 1.158m-.47-1.158.47 1.158m0 0 .394 1.187m-.394-1.187.394 1.187m0 0c.12.46.24.92.317 1.209m-.317-1.21.317 1.21m0 0 .239 1.227m-.24-1.227.24 1.227m0 0 .16 1.24m-.16-1.24.16 1.24m0 0 .08 1.247m-.08-1.247.08 1.247m0 0v1.25m0-1.25v1.25m0 0-.08 1.247m.08-1.247-.08 1.247m0 0-.16 1.24m.16-1.24-.16 1.24m0 0-.24 1.227m.24-1.227-.24 1.227m0 0-.316 1.21m.317-1.21-.317 1.21m0 0-.394 1.186m.394-1.187-.394 1.187m0 0-.47 1.158m.47-1.158-.47 1.158m0 0c-.145.303-.291.607-.542 1.126m.543-1.126-.543 1.126m0 0-.613 1.09m.613-1.09-.613 1.09m0 0-.682 1.047m.682-1.047-.682 1.047m0 0-.748 1.002m.748-1.002-.748 1.002m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898-.87.898m0 0-.925.84m.925-.84-.925.84m0 0-.977.78m.977-.78-.977.78m0 0-1.025.715m1.025-.715-1.025.715m0 0-1.07.648m1.07-.648-1.07.648m0 0-1.108.578m1.109-.578-1.109.578m0 0-1.143.506m1.143-.506-1.143.506m0 0-1.173.432m1.173-.432-1.173.432m0 0-1.198.355m1.198-.355-1.198.355m0 0c-.346.08-.692.158-1.219.278m1.219-.278-1.219.278m0 0-1.234.2m1.234-.2-1.234.2m0 0-1.244.12m1.244-.12-1.244.12m0 0-1.25.04m1.25-.04-1.25.04m0 0h3.579m-3.578 0h3.578m0 0-1.25-.04m1.25.04-1.25-.04m0 0-1.244-.12m1.244.12-1.244-.12m0 0-1.234-.2m1.234.2-1.234-.2m0 0-1.218-.278m1.218.278-1.218-.278m0 0-1.199-.355m1.199.355-1.199-.355m0 0-1.173-.432m1.173.432-1.173-.432m0 0-1.143-.506m1.143.506-1.143-.506m0 0-1.108-.578m1.108.578-1.108-.578m0 0-1.07-.648m1.07.648-1.07-.648m0 0-1.024-.715m1.025.715-1.025-.715m0 0-.978-.78m.978.78-.978-.78m0 0-.925-.84m.925.84-.925-.84m0 0-.87-.898m.87.898-.87-.898m0 0-.81-.952m.81.952-.81-.952m0 0-.748-1.002m.748 1.002-.748-1.002m0 0c-.2-.31-.402-.618-.681-1.047m.681 1.047-.681-1.047m0 0c-.195-.346-.39-.69-.614-1.09m.614 1.09-.614-1.09m0 0-.542-1.126m.542 1.126-.542-1.126m0 0-.47-1.158m.47 1.158-.47-1.158m0 0-.393-1.187m.394 1.187-.394-1.187m0 0-.317-1.209m.317 1.21-.317-1.21m0 0-.24-1.227m.24 1.227-.24-1.227m0 0c-.04-.323-.082-.645-.16-1.24m.16 1.24c-.046-.361-.092-.722-.16-1.24m0 0c-.029-.462-.058-.924-.08-1.247m.08 1.247-.08-1.247m0 0v-1.25m0 1.25v-1.25m0 0c.031-.474.062-.948.08-1.247m-.08 1.247c.022-.324.042-.648.08-1.247m0 0c.035-.263.069-.526.16-1.24m-.16 1.24c.06-.459.12-.917.16-1.24m0 0 .24-1.227m-.24 1.227.24-1.227m0 0 .317-1.21m-.317 1.21.317-1.21m0 0 .394-1.186m-.394 1.187.394-1.187m0 0 .469-1.158m-.47 1.158.47-1.158m0 0 .542-1.126m-.542 1.126.542-1.126m0 0 .614-1.09m-.614 1.09.614-1.09m0 0 .681-1.047m-.681 1.047.681-1.047m0 0 .748-1.002m-.748 1.002.748-1.002m0 0 .81-.952m-.81.952.81-.952m0 0 .87-.898m-.87.898.87-.898m0 0 .925-.84m-.925.84.925-.84m0 0 .978-.78m-.978.78.978-.78m0 0 1.025-.715m-1.025.715 1.025-.715m0 0 1.069-.648m-1.07.648c.324-.195.647-.391 1.07-.648m0 0 1.108-.578m-1.108.578 1.108-.578m0 0 1.143-.506m-1.143.506 1.143-.506m0 0 1.173-.432m-1.173.432 1.173-.432m0 0 1.199-.355m-1.199.355 1.199-.355m0 0 1.218-.278m-1.218.278c.413-.095.827-.19 1.218-.278m0 0 1.234-.2m-1.234.2 1.234-.2m0 0 1.244-.12m-1.244.12 1.244-.12m0 0 1.25-.04m-1.25.04 1.25-.04m0 0"></path></g><g class="label" transform="translate(48.008 15.5)"><rect></rect><foreignObject width="10.672" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>A</p></span></div></foreignObject></g></g><g id="mermaid-0-flowchart-A1-1" class="node default" data-look="classic"><path d="M194.938 22.5h79.578v54h-79.578z" class="basic label-container"></path><g class="label" transform="translate(224.938 37.5)"><rect></rect><foreignObject width="19.578" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>A1</p></span></div></foreignObject></g></g><g id="mermaid-0-flowchart-haltA-3" class="node default" data-look="classic"><g class="basic label-container" transform="translate(390.648 49.5)"><circle r="27.898" class="outer-circle"></circle><circle r="22.898" class="inner-circle"></circle></g><g class="label" transform="translate(377.75 37.5)"><rect></rect><foreignObject width="25.797" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>halt</p></span></div></foreignObject></g></g><g id="mermaid-0-flowchart-W-4" class="node default" data-look="classic"><path d="M8 119h90.688v54H8z" class="basic label-container"></path><g class="label" transform="translate(38 134)"><rect></rect><foreignObject width="30.688" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>A>B</p></span></div></foreignObject></g></g><g id="mermaid-0-flowchart-B-7" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M236.516 148.5h-3.578l1.249.04 1.244.12 1.234.2 1.219.278 1.198.355 1.173.432 1.143.506 1.108.578 1.07.648 1.025.715.977.78.925.84.87.898.81.952.748 1.002.682 1.047.613 1.09.543 1.126.469 1.158.394 1.187.317 1.209c.065.336.13.673.239 1.227l.16 1.24.08 1.247v1.25c-.03.449-.058.897-.08 1.247l-.16 1.24-.24 1.227-.316 1.21-.394 1.186-.47 1.158-.542 1.126-.613 1.09-.682 1.047-.748 1.002-.81.952-.87.898-.925.84-.977.78-1.026.715-1.069.648-1.108.578-1.143.506-1.173.432-1.198.355-1.219.278-1.234.2-1.244.12-1.25.04h3.579l-1.25-.04-1.244-.12-1.234-.2-1.219-.278-1.198-.355-1.173-.432-1.143-.506-1.108-.578-1.07-.648-1.025-.715-.977-.78-.925-.84-.87-.898-.81-.952-.748-1.002-.682-1.047-.613-1.09-.542-1.126-.47-1.158-.393-1.187-.317-1.209-.24-1.227-.16-1.24-.08-1.247v-1.25l.08-1.247.16-1.24.24-1.227c.12-.462.242-.925.317-1.21.134-.405.27-.811.393-1.186l.47-1.158.542-1.126.613-1.09.682-1.047.748-1.002.81-.952.87-.898.925-.84.977-.78 1.026-.715 1.069-.648 1.108-.578 1.143-.506 1.173-.432 1.198-.355 1.219-.278 1.234-.2 1.244-.12 1.25-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M236.516 148.5h-3.578m3.578 0h-3.578m0 0 1.249.04m-1.25-.04 1.25.04m0 0 1.244.12m-1.244-.12 1.244.12m0 0 1.234.2m-1.234-.2 1.234.2m0 0 1.219.278m-1.219-.278 1.219.278m0 0 1.198.355m-1.198-.355 1.198.355m0 0 1.173.432m-1.173-.432 1.173.432m0 0 1.143.506m-1.143-.506 1.143.506m0 0 1.108.578m-1.108-.578 1.108.578m0 0 1.07.648m-1.07-.648 1.07.648m0 0 1.025.715m-1.026-.715 1.026.715m0 0 .977.78m-.977-.78.977.78m0 0 .925.84m-.925-.84.925.84m0 0 .87.898m-.87-.898.87.898m0 0 .81.952m-.81-.952.81.952m0 0 .748 1.002m-.748-1.002.748 1.002m0 0 .682 1.047m-.682-1.047.682 1.047m0 0 .613 1.09m-.613-1.09.613 1.09m0 0 .543 1.126m-.543-1.126.543 1.126m0 0 .469 1.158m-.47-1.158.47 1.158m0 0 .394 1.187m-.394-1.187.394 1.187m0 0 .317 1.209m-.317-1.21c.11.422.22.844.317 1.21m0 0 .239 1.227m-.24-1.227.24 1.227m0 0 .16 1.24m-.16-1.24.16 1.24m0 0 .08 1.247m-.08-1.247.08 1.247m0 0v1.25m0-1.25v1.25m0 0-.08 1.247m.08-1.247-.08 1.247m0 0-.16 1.24m.16-1.24-.16 1.24m0 0c-.086.438-.17.875-.24 1.227m.24-1.227-.24 1.227m0 0-.316 1.21m.317-1.21-.317 1.21m0 0-.394 1.186m.394-1.187-.394 1.187m0 0-.47 1.158m.47-1.158-.47 1.158m0 0-.542 1.126m.543-1.126-.543 1.126m0 0-.613 1.09m.613-1.09-.613 1.09m0 0-.682 1.047m.682-1.047-.682 1.047m0 0-.748 1.002m.748-1.002-.748 1.002m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898-.87.898m0 0-.925.84m.925-.84-.925.84m0 0-.977.78m.977-.78-.977.78m0 0-1.026.715m1.026-.715-1.026.715m0 0-1.069.648m1.07-.648-1.07.648m0 0-1.108.578m1.108-.578-1.108.578m0 0-1.143.506m1.143-.506-1.143.506m0 0-1.173.432m1.173-.432-1.173.432m0 0-1.198.355m1.198-.355-1.198.355m0 0-1.219.278m1.219-.278-1.219.278m0 0-1.234.2m1.234-.2-1.234.2m0 0c-.316.03-.633.06-1.244.12m1.244-.12-1.244.12m0 0-1.25.04m1.25-.04-1.25.04m0 0h3.579m-3.578 0h3.578m0 0-1.25-.04m1.25.04-1.25-.04m0 0-1.244-.12m1.244.12-1.244-.12m0 0-1.234-.2m1.234.2-1.234-.2m0 0-1.219-.278m1.219.278-1.219-.278m0 0-1.198-.355m1.198.355-1.198-.355m0 0-1.173-.432m1.173.432-1.173-.432m0 0-1.143-.506m1.143.506-1.143-.506m0 0-1.108-.578m1.108.578-1.108-.578m0 0-1.07-.648m1.07.648-1.07-.648m0 0-1.025-.715m1.026.715-1.026-.715m0 0-.977-.78m.977.78c-.33-.265-.662-.529-.977-.78m0 0-.925-.84m.925.84-.925-.84m0 0-.87-.898m.87.898-.87-.898m0 0-.81-.952m.81.952-.81-.952m0 0-.748-1.002m.748 1.002-.748-1.002m0 0-.682-1.047m.682 1.047-.682-1.047m0 0-.613-1.09m.613 1.09-.613-1.09m0 0-.542-1.126m.542 1.126c-.145-.3-.29-.6-.542-1.126m0 0-.47-1.158m.47 1.158-.47-1.158m0 0-.393-1.187m.393 1.187-.393-1.187m0 0-.317-1.209m.317 1.21-.317-1.21m0 0-.24-1.227m.24 1.227-.24-1.227m0 0-.16-1.24m.16 1.24-.16-1.24m0 0-.08-1.247m.08 1.247-.08-1.247m0 0v-1.25m0 1.25v-1.25m0 0 .08-1.247m-.08 1.247.08-1.247m0 0 .16-1.24m-.16 1.24.16-1.24m0 0 .24-1.227m-.24 1.227.24-1.227m0 0 .317-1.21m-.317 1.21.317-1.21m0 0 .393-1.186m-.393 1.187.393-1.187m0 0 .47-1.158m-.47 1.158.47-1.158m0 0c.122-.256.245-.51.542-1.126m-.542 1.126.542-1.126m0 0 .613-1.09m-.613 1.09.613-1.09m0 0 .682-1.047m-.682 1.047.682-1.047m0 0 .748-1.002m-.748 1.002.748-1.002m0 0 .81-.952m-.81.952.81-.952m0 0 .87-.898m-.87.898.87-.898m0 0 .925-.84m-.925.84.925-.84m0 0 .977-.78m-.977.78.977-.78m0 0 1.026-.715m-1.026.715c.374-.26.748-.52 1.026-.715m0 0 1.069-.648m-1.07.648 1.07-.648m0 0 1.108-.578m-1.108.578 1.108-.578m0 0 1.143-.506m-1.143.506 1.143-.506m0 0 1.173-.432m-1.173.432 1.173-.432m0 0 1.198-.355m-1.198.355 1.198-.355m0 0 1.219-.278m-1.219.278 1.219-.278m0 0 1.234-.2m-1.234.2 1.234-.2m0 0 1.244-.12m-1.244.12 1.244-.12m0 0 1.25-.04m-1.25.04 1.25-.04m0 0"></path></g><g class="label" transform="translate(229.39 156)"><rect></rect><foreignObject width="10.672" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>B</p></span></div></foreignObject></g></g></g></g><defs><filter id="mermaid-0-drop-shadow" width="130%" height="130%"><feDropShadow dx="4" dy="4" flood-color="#000" flood-opacity=".06" stdDeviation="0"></feDropShadow></filter></defs><defs><filter id="mermaid-0-drop-shadow-small" width="150%" height="150%"><feDropShadow dx="2" dy="2" flood-color="#000" flood-opacity=".06" stdDeviation="0"></feDropShadow></filter></defs></svg>
<p><code>A</code> и <code>A>B</code> — два узла с буквально одинаковыми исходящими переходами в <code>A1</code>, плюс у обёртки появляется пунктирная стрелка onHalt в <code>B</code>. Читающий видит «два разных автомата», хотя на уровне поведения это один автомат с одной точкой расхождения — куда уходит halt.</p>
<p>Первая попытка исправить визуализацию выглядела разумно: обёртку отрисовать одним узлом, переходы взять с базы, override-halt — отдельной стрелкой:</p>
<svg xmlns="http://www.w3.org/2000/svg" id="mermaid-1" width="100%" aria-roledescription="flowchart-v2" class="flowchart" style="font-family:arial,sans-serif;font-size:16px;fill:#333;max-width:564.40185546875px" viewBox="0 0 564.402 71.797"><style>@keyframes edge-animation-frame{0%{stroke-dashoffset:0}}@keyframes dash{to{stroke-dashoffset:0}}#mermaid-1 .edge-thickness-normal{stroke-width:1px}#mermaid-1 .edge-pattern-solid{stroke-dasharray:0}#mermaid-1 .marker{fill:#333;stroke:#333}#mermaid-1 .marker.cross{stroke:#333}#mermaid-1 svg{font-family:arial,sans-serif;font-size:16px}#mermaid-1 p{margin:0}#mermaid-1 .label{font-family:arial,sans-serif;color:#333}#mermaid-1 span{fill:#333;color:#333}#mermaid-1 .node circle,#mermaid-1 .node path,#mermaid-1 .node rect{fill:#ececff;stroke:#9370db;stroke-width:1px}#mermaid-1 .flowchart-link{stroke:#333;fill:none}#mermaid-1 .edgeLabel,#mermaid-1 .node .label{text-align:center}#mermaid-1 .edgeLabel,#mermaid-1 .edgeLabel p,#mermaid-1 .edgeLabel rect{background-color:rgba(232,232,232,.8)}#mermaid-1 .edgeLabel rect{opacity:.5;fill:rgba(232,232,232,.8)}#mermaid-1 .labelBkg{background-color:rgba(232,232,232,.5)}#mermaid-1 [data-look=neo].node circle,#mermaid-1 [data-look=neo].node rect{stroke:#9370db;filter:drop-shadow(1px 2px 2px #b9b9b9)}#mermaid-1 [data-look=neo].node path{stroke:#9370db;stroke-width:1px}#mermaid-1 [data-look=neo].node .outer-path{filter:drop-shadow(1px 2px 2px #b9b9b9)}#mermaid-1 :root{--mermaid-font-family:arial,sans-serif}</style><marker id="mermaid-1_flowchart-v2-pointEnd" class="marker flowchart-v2" markerHeight="8" markerUnits="userSpaceOnUse" markerWidth="8" orient="auto" refX="5" refY="5" viewBox="0 0 10 10"><path d="m0 0 10 5-10 5z" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></path></marker><marker id="mermaid-1_flowchart-v2-pointStart" class="marker flowchart-v2" markerHeight="8" markerUnits="userSpaceOnUse" markerWidth="8" orient="auto" refX="4.5" refY="5" viewBox="0 0 10 10"><path d="m0 5 10 5V0z" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></path></marker><marker id="mermaid-1_flowchart-v2-pointEnd-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="10.5" orient="auto" refX="11.5" refY="7" viewBox="0 0 11.5 14"><path d="m0 0 11.5 7L0 14z" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></path></marker><marker id="mermaid-1_flowchart-v2-pointStart-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="11.5" orient="auto" refX="1" refY="7" viewBox="0 0 11.5 14"><path d="m0 7 11.5 7V0z" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></path></marker><marker id="mermaid-1_flowchart-v2-circleEnd" class="marker flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="11" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-1_flowchart-v2-circleStart" class="marker flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="-1" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-1_flowchart-v2-circleEnd-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="14" orient="auto" refX="12.25" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-1_flowchart-v2-circleStart-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="14" orient="auto" refX="-2" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-1_flowchart-v2-crossEnd" class="marker cross flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="12" refY="5.2" viewBox="0 0 11 11"><path d="m1 1 9 9m0-9-9 9" class="arrowMarkerPath" style="stroke-width:2;stroke-dasharray:1,0"></path></marker><marker id="mermaid-1_flowchart-v2-crossStart" class="marker cross flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="-1" refY="5.2" viewBox="0 0 11 11"><path d="m1 1 9 9m0-9-9 9" class="arrowMarkerPath" style="stroke-width:2;stroke-dasharray:1,0"></path></marker><marker id="mermaid-1_flowchart-v2-crossEnd-margin" class="marker cross flowchart-v2" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="12" orient="auto" refX="17.7" refY="7.5" viewBox="0 0 15 15"><path d="m1 1 13 13M1 14 14 1" class="arrowMarkerPath" style="stroke-width:2.5"></path></marker><marker id="mermaid-1_flowchart-v2-crossStart-margin" class="marker cross flowchart-v2" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="12" orient="auto" refX="-3.5" refY="7.5" viewBox="0 0 15 15"><path d="m1 1 13 13M1 14 14 1" class="arrowMarkerPath" style="stroke-width:2.5;stroke-dasharray:1,0"></path></marker><g class="root"><g class="edgePaths"><path id="mermaid-1-L_W_A1_0" marker-end="url(#mermaid-1_flowchart-v2-pointEnd)" d="M98.688 35.898h86.89" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_W_A1_0" data-look="classic" data-points="W3sieCI6OTguNjg3NSwieSI6MzUuODk4NDM3NX0seyJ4IjoxNDQuMTMyODEyNSwieSI6MzUuODk4NDM3NX0seyJ4IjoxODkuNTc4MTI1LCJ5IjozNS44OTg0Mzc1fV0="></path><path id="mermaid-1-L_A1_haltA_0" marker-end="url(#mermaid-1_flowchart-v2-pointEnd)" d="M269.156 35.898h84.235" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_A1_haltA_0" data-look="classic" data-points="W3sieCI6MjY5LjE1NjI1LCJ5IjozNS44OTg0Mzc1fSx7IngiOjMxMy4yNzM0Mzc1LCJ5IjozNS44OTg0Mzc1fSx7IngiOjM1Ny4zOTA2MjUsInkiOjM1Ljg5ODQzNzV9XQ=="></path><path id="mermaid-1-L_haltA_B_0" marker-end="url(#mermaid-1_flowchart-v2-pointEnd)" d="M413.188 35.898h8.984c8.984 0 26.953 0 44.338.078 17.386.077 34.188.231 42.589.308l8.401.078" class="edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_haltA_B_0" data-look="classic" data-points="W3sieCI6NDEzLjE4NzUsInkiOjM1Ljg5ODQzNzV9LHsieCI6NDY3LjA5Mzc1LCJ5IjozNS44OTg0Mzc1fSx7IngiOjUyMS41MDAwMDAwMDAwMDEzLCJ5IjozNi4zOTg0Mzc1fV0=" style="stroke-dasharray:2"></path></g><g class="edgeLabels"><g class="edgeLabel"><foreignObject width="40.891" height="24" class="label" data-id="L_W_A1_0" transform="translate(123.688 23.898)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>x: y/R</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="38.234" height="24" class="label" data-id="L_A1_haltA_0" transform="translate(294.156 23.898)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>y: y/L</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="57.813" height="24" class="label" data-id="L_haltA_B_0" transform="translate(438.188 23.898)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>override</p></span></div></foreignObject></g></g><g class="nodes"><g id="mermaid-1-flowchart-W-0" class="node default" data-look="classic"><path d="M8 8.898h90.688v54H8z" class="basic label-container"></path><g class="label" transform="translate(38 23.898)"><rect></rect><foreignObject width="30.688" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>A>B</p></span></div></foreignObject></g></g><g id="mermaid-1-flowchart-A1-1" class="node default" data-look="classic"><path d="M189.578 8.898h79.578v54h-79.578z" class="basic label-container"></path><g class="label" transform="translate(219.578 23.898)"><rect></rect><foreignObject width="19.578" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>A1</p></span></div></foreignObject></g></g><g id="mermaid-1-flowchart-haltA-3" class="node default" data-look="classic"><g class="basic label-container" transform="translate(385.29 35.898)"><circle r="27.898" class="outer-circle"></circle><circle r="22.898" class="inner-circle"></circle></g><g class="label" transform="translate(372.39 23.898)"><rect></rect><foreignObject width="25.797" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>halt</p></span></div></foreignObject></g></g><g id="mermaid-1-flowchart-B-5" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M540.49 16.398h-3.578l1.25.04 1.243.12 1.234.2 1.22.278 1.197.356 1.174.432 1.143.506 1.108.578 1.069.648 1.025.715.977.78c.245.221.49.443.926.84l.87.898.81.951.747 1.002.682 1.048.614 1.089.542 1.126.469 1.159.394 1.186.317 1.21.239 1.226.16 1.24.08 1.247v1.25l-.08 1.248-.16 1.24-.24 1.227-.316 1.209-.394 1.186-.47 1.159-.541 1.126-.614 1.09-.682 1.047-.747 1.001-.81.952c-.3.308-.598.617-.87.898l-.926.84-.977.78-1.025.715-1.07.648-1.107.578-1.143.506-1.174.432-1.198.356-1.219.278-1.234.2c-.374.035-.748.072-1.244.12-.47.014-.94.03-1.25.04h3.579c-.449-.015-.898-.03-1.25-.04l-1.244-.12-1.234-.2-1.218-.278-1.199-.356-1.173-.432-1.143-.506-1.108-.578-1.069-.648-1.025-.715-.977-.78c-.34-.307-.679-.615-.926-.84l-.87-.898-.81-.952-.747-1.001-.682-1.048-.614-1.09-.542-1.125-.47-1.159-.393-1.186-.317-1.21c-.085-.437-.17-.876-.24-1.226l-.159-1.24-.08-1.248v-1.25l.08-1.247.16-1.24.239-1.227.317-1.209.394-1.186.469-1.159.542-1.126.614-1.09.682-1.047.747-1.002.81-.951.87-.898.926-.84.977-.78 1.025-.715 1.069-.648 1.108-.578 1.143-.506 1.173-.432 1.199-.356 1.218-.278 1.234-.2 1.245-.12 1.249-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M540.49 16.398h-3.578m3.578 0h-3.578m0 0 1.25.04m-1.25-.04 1.25.04m0 0 1.243.12m-1.244-.12 1.244.12m0 0 1.234.2m-1.234-.2 1.234.2m0 0 1.22.278m-1.22-.278 1.22.278m0 0 1.197.356m-1.198-.356 1.198.356m0 0 1.174.432m-1.174-.432 1.174.432m0 0 1.143.506m-1.143-.506 1.143.506m0 0 1.108.578m-1.108-.578 1.108.578m0 0 1.069.648m-1.07-.648 1.07.648m0 0 1.025.715m-1.025-.715 1.025.715m0 0 .977.78m-.977-.78.977.78m0 0 .926.84m-.926-.84.926.84m0 0 .87.898m-.87-.898.87.898m0 0 .81.951m-.81-.951.81.951m0 0 .747 1.002m-.747-1.002.747 1.002m0 0 .682 1.048m-.682-1.048.682 1.048m0 0 .614 1.089m-.614-1.09.614 1.09m0 0 .542 1.126m-.542-1.126.542 1.126m0 0 .469 1.159m-.47-1.159c.137.335.272.67.47 1.159m0 0 .394 1.186m-.394-1.186.394 1.186m0 0 .317 1.21m-.317-1.21.317 1.21m0 0 .239 1.226m-.24-1.227.24 1.227m0 0 .16 1.24m-.16-1.24.16 1.24m0 0 .08 1.247m-.08-1.247.08 1.247m0 0v1.25m0-1.25v1.25m0 0-.08 1.248m.08-1.248-.08 1.248m0 0-.16 1.24m.16-1.24-.16 1.24m0 0-.24 1.227m.24-1.227-.24 1.227m0 0c-.081.314-.164.627-.316 1.209m.317-1.21-.317 1.21m0 0-.394 1.186m.394-1.186-.394 1.186m0 0-.47 1.159m.47-1.159-.47 1.159m0 0-.541 1.126m.542-1.126c-.118.245-.237.49-.542 1.126m0 0-.614 1.09m.614-1.09-.614 1.09m0 0-.682 1.047m.682-1.048-.682 1.048m0 0-.747 1.001m.747-1.001-.747 1.001m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898-.87.898m0 0-.926.84m.926-.84-.926.84m0 0-.977.78m.977-.78-.977.78m0 0-1.025.715m1.025-.715-1.025.715m0 0-1.07.648m1.07-.648-1.07.648m0 0-1.107.578m1.108-.578-1.108.578m0 0-1.143.506m1.143-.506-1.143.506m0 0-1.174.432m1.174-.432-1.174.432m0 0-1.198.356m1.198-.356-1.198.356m0 0-1.219.278m1.22-.278-1.22.278m0 0-1.234.2m1.234-.2-1.234.2m0 0c-.446.042-.892.085-1.244.12m1.244-.12c-.389.037-.778.074-1.244.12m0 0c-.407.012-.814.025-1.25.04m1.25-.04-1.25.04m0 0h3.579m-3.578 0h3.578m0 0-1.25-.04m1.25.04c-.274-.01-.547-.018-1.25-.04m0 0-1.244-.12m1.245.12-1.245-.12m0 0-1.234-.2m1.234.2c-.304-.05-.61-.1-1.234-.2m0 0-1.218-.278m1.218.278-1.218-.278m0 0-1.199-.356m1.199.356-1.199-.356m0 0-1.173-.432m1.173.432-1.173-.432m0 0-1.143-.506m1.143.506-1.143-.506m0 0-1.108-.578m1.108.578-1.108-.578m0 0-1.069-.648m1.069.648-1.069-.648m0 0-1.025-.715m1.025.715-1.025-.715m0 0c-.276-.22-.553-.44-.977-.78m.977.78-.977-.78m0 0c-.319-.288-.637-.577-.926-.84m.926.84-.926-.84m0 0-.87-.898m.87.898-.87-.898m0 0-.81-.952m.81.952-.81-.952m0 0-.747-1.001m.747 1.001-.747-1.001m0 0-.682-1.048m.682 1.048-.682-1.048m0 0-.614-1.09m.614 1.09-.614-1.09m0 0-.542-1.125m.542 1.126-.542-1.126m0 0-.47-1.159m.47 1.159-.47-1.159m0 0-.393-1.186m.394 1.186-.394-1.186m0 0-.317-1.21m.317 1.21-.317-1.21m0 0c-.08-.414-.161-.828-.24-1.226m.24 1.227-.24-1.227m0 0-.159-1.24m.16 1.24-.16-1.24m0 0-.08-1.248m.08 1.248-.08-1.248m0 0v-1.25m0 1.25v-1.25m0 0 .08-1.247m-.08 1.247.08-1.247m0 0 .16-1.24m-.16 1.24.16-1.24m0 0 .239-1.227m-.24 1.227.24-1.227m0 0 .317-1.209m-.317 1.21.317-1.21m0 0 .394-1.186m-.394 1.186.394-1.186m0 0 .469-1.159m-.47 1.159.47-1.159m0 0 .542-1.126m-.542 1.126.542-1.126m0 0 .614-1.09m-.614 1.09.614-1.09m0 0 .682-1.047m-.682 1.048.682-1.048m0 0 .747-1.002m-.747 1.002.747-1.002m0 0 .81-.951m-.81.951.81-.951m0 0 .87-.898m-.87.898.87-.898m0 0 .926-.84m-.926.84c.305-.278.61-.555.926-.84m0 0c.235-.189.47-.376.977-.78m-.977.78c.196-.158.393-.315.977-.78m0 0 1.025-.715m-1.025.715 1.025-.715m0 0 1.069-.648m-1.069.648 1.069-.648m0 0 1.108-.578m-1.108.578 1.108-.578m0 0 1.143-.506m-1.143.506 1.143-.506m0 0 1.173-.432m-1.173.432 1.173-.432m0 0 1.199-.356m-1.199.356 1.199-.356m0 0 1.218-.278m-1.218.278 1.218-.278m0 0 1.234-.2m-1.234.2c.375-.06.749-.12 1.234-.2m0 0 1.245-.12m-1.245.12 1.245-.12m0 0 1.249-.04m-1.25.04 1.25-.04m0 0"></path></g><g class="label" transform="translate(533.365 23.898)"><rect></rect><foreignObject width="10.672" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>B</p></span></div></foreignObject></g></g></g></g><defs><filter id="mermaid-1-drop-shadow" width="130%" height="130%"><feDropShadow dx="4" dy="4" flood-color="#000" flood-opacity=".06" stdDeviation="0"></feDropShadow></filter></defs><defs><filter id="mermaid-1-drop-shadow-small" width="150%" height="150%"><feDropShadow dx="2" dy="2" flood-color="#000" flood-opacity=".06" stdDeviation="0"></feDropShadow></filter></defs></svg>
<p>Один узел <code>A>B</code>, переходы — те же, что у базы; override-halt — отдельной пунктирной стрелкой от halt’а в <code>B</code>. Чисто, компактно, без визуального шума. Почти сработало.</p>
<h2 id="pochti-srabotalo-slomalos"><a class="heading-anchor" href="#pochti-srabotalo-slomalos">Почти сработало = сломалось</a></h2>
<p>Возьмём <code>minusOne</code> из <a href="https://github.com/mellonis/turing-machine-js/tree/master/packages/library-binary-numbers"><code>library-binary-numbers</code></a> — он собирается через вложенные обёртки:</p>
<pre class="shiki shiki-themes github-light github-dark language-javascript" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> minusOne</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> invertNumber</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  .</span><span style="color:#6F42C1;--shiki-dark:#B392F0">withOverrodeHaltState</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    plusOne</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">      .</span><span style="color:#6F42C1;--shiki-dark:#B392F0">withOverrodeHaltState</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        invertNumber</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">          .</span><span style="color:#6F42C1;--shiki-dark:#B392F0">withOverrodeHaltState</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(normalizeNumber),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">      ),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  );</span></span></code></pre>
<p>Получается имя <code>invertNumber>plusOne>invertNumber>normalizeNumber</code> — три разделителя <code>></code> в реальном коде, к тому же одна база (<code>invertNumber</code>) встречается дважды. Если имя длиннее, его соблазнительно прочесть как «инструкцию обхода». Например, <code>A>B>B>A>A>B>B>A</code> (семь разделителей): каждая буква — база, в которую переходишь на halt’е предыдущей. Последовательность обхода баз должна совпасть с именем.</p>
<p>Только это самообман. Имя ни в каком формальном смысле не определяет последовательность обхода. Оно <em>напоминает</em> её. И только если конкретная цепочка собралась тем единственным образом, которым я её представлял, когда выбрал способ записи композиции в 2019-м.</p>
<h2 id="zagvozdka"><a class="heading-anchor" href="#zagvozdka">Загвоздка</a></h2>
<p>Возьмём имя поскромнее: <code>A>B>A</code>. Сколько способов его построить?</p>
<p>Минимум два:</p>
<pre class="shiki shiki-themes github-light github-dark language-javascript" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Конструкция 1</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> c1</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> A</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">withOverrodeHaltState</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">B</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">withOverrodeHaltState</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">A</span><span style="color:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Конструкция 2</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> c2</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> A</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">withOverrodeHaltState</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">B</span><span style="color:#24292E;--shiki-dark:#E1E4E8">).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">withOverrodeHaltState</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">A</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
<p>В обоих случаях правило <code>${this.name}>${override.name}</code> даёт одну и ту же строку — <code>A>B>A</code>. Но топология обёрток разная — каждая конструкция собирает своё бинарное дерево (внутренние узлы <code>•</code> — применения <code>withOverrodeHaltState</code>, листья — исходные <code>State</code>):</p>
<svg xmlns="http://www.w3.org/2000/svg" id="mermaid-2" width="100%" aria-roledescription="flowchart-v2" class="flowchart" style="font-family:arial,sans-serif;font-size:16px;fill:#333;max-width:183.31369018554688px" viewBox="0 -1.695 183.314 264.305"><style>@keyframes edge-animation-frame{0%{stroke-dashoffset:0}}@keyframes dash{to{stroke-dashoffset:0}}#mermaid-2 .edge-thickness-normal{stroke-width:1px}#mermaid-2 .edge-pattern-solid{stroke-dasharray:0}#mermaid-2 .marker{fill:#333;stroke:#333}#mermaid-2 .marker.cross{stroke:#333}#mermaid-2 svg{font-family:arial,sans-serif;font-size:16px}#mermaid-2 p{margin:0}#mermaid-2 .label{font-family:arial,sans-serif;color:#333}#mermaid-2 span{fill:#333;color:#333}#mermaid-2 .node circle,#mermaid-2 .node path,#mermaid-2 .node rect{fill:#ececff;stroke:#9370db;stroke-width:1px}#mermaid-2 .flowchart-link{stroke:#333;fill:none}#mermaid-2 .edgeLabel,#mermaid-2 .node .label{text-align:center}#mermaid-2 .edgeLabel,#mermaid-2 .edgeLabel p,#mermaid-2 .edgeLabel rect{background-color:rgba(232,232,232,.8)}#mermaid-2 .edgeLabel rect{opacity:.5;fill:rgba(232,232,232,.8)}#mermaid-2 .labelBkg{background-color:rgba(232,232,232,.5)}#mermaid-2 [data-look=neo].node circle,#mermaid-2 [data-look=neo].node rect{stroke:#9370db;filter:drop-shadow(1px 2px 2px #b9b9b9)}#mermaid-2 [data-look=neo].node path{stroke:#9370db;stroke-width:1px}#mermaid-2 [data-look=neo].node .outer-path{filter:drop-shadow(1px 2px 2px #b9b9b9)}#mermaid-2 :root{--mermaid-font-family:arial,sans-serif}</style><marker id="mermaid-2_flowchart-v2-pointEnd" class="marker flowchart-v2" markerHeight="8" markerUnits="userSpaceOnUse" markerWidth="8" orient="auto" refX="5" refY="5" viewBox="0 0 10 10"><path d="m0 0 10 5-10 5z" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></path></marker><marker id="mermaid-2_flowchart-v2-pointStart" class="marker flowchart-v2" markerHeight="8" markerUnits="userSpaceOnUse" markerWidth="8" orient="auto" refX="4.5" refY="5" viewBox="0 0 10 10"><path d="m0 5 10 5V0z" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></path></marker><marker id="mermaid-2_flowchart-v2-pointEnd-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="10.5" orient="auto" refX="11.5" refY="7" viewBox="0 0 11.5 14"><path d="m0 0 11.5 7L0 14z" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></path></marker><marker id="mermaid-2_flowchart-v2-pointStart-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="11.5" orient="auto" refX="1" refY="7" viewBox="0 0 11.5 14"><path d="m0 7 11.5 7V0z" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></path></marker><marker id="mermaid-2_flowchart-v2-circleEnd" class="marker flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="11" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-2_flowchart-v2-circleStart" class="marker flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="-1" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-2_flowchart-v2-circleEnd-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="14" orient="auto" refX="12.25" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-2_flowchart-v2-circleStart-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="14" orient="auto" refX="-2" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-2_flowchart-v2-crossEnd" class="marker cross flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="12" refY="5.2" viewBox="0 0 11 11"><path d="m1 1 9 9m0-9-9 9" class="arrowMarkerPath" style="stroke-width:2;stroke-dasharray:1,0"></path></marker><marker id="mermaid-2_flowchart-v2-crossStart" class="marker cross flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="-1" refY="5.2" viewBox="0 0 11 11"><path d="m1 1 9 9m0-9-9 9" class="arrowMarkerPath" style="stroke-width:2;stroke-dasharray:1,0"></path></marker><marker id="mermaid-2_flowchart-v2-crossEnd-margin" class="marker cross flowchart-v2" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="12" orient="auto" refX="17.7" refY="7.5" viewBox="0 0 15 15"><path d="m1 1 13 13M1 14 14 1" class="arrowMarkerPath" style="stroke-width:2.5"></path></marker><marker id="mermaid-2_flowchart-v2-crossStart-margin" class="marker cross flowchart-v2" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="12" orient="auto" refX="-3.5" refY="7.5" viewBox="0 0 15 15"><path d="m1 1 13 13M1 14 14 1" class="arrowMarkerPath" style="stroke-width:2.5;stroke-dasharray:1,0"></path></marker><g class="root"><g class="edgePaths"><path id="mermaid-2-L_o1_a1_0" marker-end="url(#mermaid-2_flowchart-v2-pointEnd)" d="m58.148 26.255-5.408 6.559c-5.407 6.559-16.223 19.677-21.557 31.82-5.333 12.142-5.184 23.309-5.11 28.892l.075 5.584" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_o1_a1_0" data-look="classic" data-points="W3sieCI6NTguMTQ4MzUwNDU1MTIxMzQ2LCJ5IjoyNi4yNTUzOTE5MjY4NDM1Mn0seyJ4IjoyNS43MDA5MTgxOTc2MzE4MzYsInkiOjY1LjYwOTM3NX0seyJ4IjoyNi4yMDA5MTgxOTc2MzE4MzYsInkiOjEwMy4xMDkzNzV9XQ=="></path><path id="mermaid-2-L_o1_i1_0" marker-end="url(#mermaid-2_flowchart-v2-pointEnd)" d="m71.259 26.255 5.408 6.559c5.408 6.559 16.224 19.677 21.632 33.269s5.408 27.657 5.408 34.689v7.033" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_o1_i1_0" data-look="classic" data-points="W3sieCI6NzEuMjU5MDkxNjM3Nzc0MTYsInkiOjI2LjI1NTM5MTkyNjg0MzUyfSx7IngiOjEwMy43MDY1MjM4OTUyNjM2NywieSI6NjUuNjA5Mzc1fSx7IngiOjEwMy43MDY1MjM4OTUyNjM2NywieSI6MTExLjgwNDY4NzV9XQ=="></path><path id="mermaid-2-L_i1_b1_0" marker-end="url(#mermaid-2_flowchart-v2-pointEnd)" d="m97.493 130.33-6.081 8.047c-6.081 8.046-18.244 24.139-24.251 37.769s-5.858 24.797-5.783 30.38l.074 5.584" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_i1_b1_0" data-look="classic" data-points="W3sieCI6OTcuNDkzNDAzMTI0MTk1MjgsInkiOjEzMC4zMzAzMDY1MDE3Njg4fSx7IngiOjYxLjAwNTYwNTY5NzYzMTgzNiwieSI6MTc4LjYwOTM3NX0seyJ4Ijo2MS41MDU2MDU2OTc2MzE4MzYsInkiOjIxNi4xMDkzNzV9XQ=="></path><path id="mermaid-2-L_i1_a2_0" marker-end="url(#mermaid-2_flowchart-v2-pointEnd)" d="m109.92 130.33 6.081 8.047c6.081 8.046 18.244 24.139 24.4 37.769s6.304 24.797 6.379 30.38l.074 5.584" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_i1_a2_0" data-look="classic" data-points="W3sieCI6MTA5LjkxOTY0NDY2NjMzMjA2LCJ5IjoxMzAuMzMwMzA2NTAxNzY4OH0seyJ4IjoxNDYuNDA3NDQyMDkyODk1NSwieSI6MTc4LjYwOTM3NX0seyJ4IjoxNDYuOTA3NDQyMDkyODk1NSwieSI6MjE2LjEwOTM3NX1d"></path></g><g class="edgeLabels"><g class="edgeLabel"><foreignObject width="33.75" height="24" class="label" data-id="L_o1_a1_0" transform="translate(8.826 53.61)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>база</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="57.813" height="24" class="label" data-id="L_o1_i1_0" transform="translate(74.8 53.61)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>override</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="33.75" height="24" class="label" data-id="L_i1_b1_0" transform="translate(44.13 166.61)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>база</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="57.813" height="24" class="label" data-id="L_i1_a2_0" transform="translate(117.501 166.61)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>override</p></span></div></foreignObject></g></g><g class="nodes"><g id="mermaid-2-flowchart-o1-0" class="node default" data-look="classic" transform="translate(64.704 18.305)"><circle r="10.305" class="basic label-container"></circle><g class="label" transform="translate(-2.805 -12)"><rect></rect><foreignObject width="5.609" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>•</p></span></div></foreignObject></g></g><g id="mermaid-2-flowchart-a1-1" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M27.49 102.61h-3.578l1.25.04 1.243.12 1.234.199 1.22.278 1.197.356 1.174.431 1.143.506 1.108.579 1.069.648 1.025.715.977.78.926.84.87.898.81.951.747 1.002.682 1.048.614 1.089.542 1.126.469 1.159.394 1.186.317 1.21.239 1.226.16 1.24.08 1.247v1.25l-.08 1.248-.16 1.24-.24 1.227-.316 1.209-.394 1.186-.47 1.159-.541 1.126-.614 1.089-.682 1.048-.747 1.001-.81.952-.87.898-.926.84-.977.78-1.025.715-1.07.648-1.107.578-1.143.506-1.174.432-1.198.356-1.219.278-1.234.2c-.4.038-.799.076-1.244.12-.488.015-.977.03-1.25.04h3.579c-.264-.01-.528-.018-1.25-.04l-1.244-.12-1.234-.2-1.218-.278-1.199-.356-1.173-.432-1.143-.506-1.108-.578-1.069-.648-1.025-.715c-.336-.268-.673-.536-.977-.78l-.926-.84-.87-.898-.81-.952-.747-1.001-.682-1.048-.614-1.09-.542-1.125c-.18-.447-.362-.894-.47-1.159l-.393-1.186-.317-1.21-.24-1.226c-.04-.322-.082-.644-.159-1.24L8 122.734v-1.25l.08-1.247.16-1.24.239-1.227.317-1.209.394-1.186.469-1.159.542-1.126.614-1.09.682-1.047.747-1.002.81-.951.87-.898.926-.84c.286-.23.573-.458.977-.78l1.025-.715 1.069-.648 1.108-.579 1.143-.506 1.173-.431 1.199-.356 1.218-.278 1.234-.2 1.245-.12 1.249-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M27.49 102.61h-3.578m3.578 0h-3.578m0 0c.346.01.691.022 1.25.04m-1.25-.04 1.25.04m0 0 1.243.12m-1.244-.12 1.244.12m0 0 1.234.199m-1.234-.2 1.234.2m0 0 1.22.278m-1.22-.278 1.22.278m0 0 1.197.356m-1.198-.356 1.198.356m0 0 1.174.431m-1.174-.431 1.174.431m0 0c.325.145.651.289 1.143.506m-1.143-.506c.25.112.501.223 1.143.506m0 0 1.108.579m-1.108-.579 1.108.579m0 0 1.069.648m-1.07-.648c.382.23.763.462 1.07.648m0 0 1.025.715m-1.025-.715 1.025.715m0 0 .977.78m-.977-.78c.28.222.558.445.977.78m0 0 .926.84m-.926-.84c.325.294.65.588.926.84m0 0 .87.898m-.87-.898.87.898m0 0 .81.951m-.81-.951.81.951m0 0 .747 1.002m-.747-1.002.747 1.002m0 0 .682 1.048m-.682-1.048.682 1.048m0 0 .614 1.089m-.614-1.09.614 1.09m0 0 .542 1.126m-.542-1.126.542 1.126m0 0 .469 1.159m-.47-1.159.47 1.159m0 0 .394 1.186m-.394-1.186.394 1.186m0 0 .317 1.21m-.317-1.21.317 1.21m0 0 .239 1.226m-.24-1.227.24 1.227m0 0 .16 1.24m-.16-1.24.16 1.24m0 0 .08 1.247m-.08-1.247.08 1.247m0 0v1.25m0-1.25v1.25m0 0-.08 1.248m.08-1.248-.08 1.248m0 0-.16 1.24m.16-1.24-.16 1.24m0 0-.24 1.227m.24-1.227-.24 1.227m0 0-.316 1.209m.317-1.21-.317 1.21m0 0-.394 1.186m.394-1.186-.394 1.186m0 0-.47 1.159m.47-1.159-.47 1.159m0 0-.541 1.126m.542-1.126-.542 1.126m0 0-.614 1.089m.614-1.09-.614 1.09m0 0-.682 1.048m.682-1.048-.682 1.048m0 0-.747 1.001m.747-1.001-.747 1.001m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898-.87.898m0 0-.926.84m.926-.84-.926.84m0 0-.977.78m.977-.78-.977.78m0 0-1.025.715m1.025-.715-1.025.715m0 0-1.07.648m1.07-.648-1.07.648m0 0-1.107.578m1.108-.578-1.108.578m0 0-1.143.506m1.143-.506-1.143.506m0 0-1.174.432m1.174-.432-1.174.432m0 0-1.198.356m1.198-.356-1.198.356m0 0-1.219.278m1.22-.278-1.22.278m0 0-1.234.2m1.234-.2-1.234.2m0 0-1.244.12m1.244-.12-1.244.12m0 0-1.25.04m1.25-.04-1.25.04m0 0h3.579m-3.578 0h3.578m0 0-1.25-.04m1.25.04c-.482-.016-.963-.032-1.25-.04m0 0-1.244-.12m1.245.12-1.245-.12m0 0-1.234-.2m1.234.2c-.36-.06-.722-.117-1.234-.2m0 0-1.218-.278m1.218.278-1.218-.278m0 0-1.199-.356m1.199.356-1.199-.356m0 0-1.173-.432m1.173.432-1.173-.432m0 0-1.143-.506m1.143.506-1.143-.506m0 0-1.108-.578m1.108.578-1.108-.578m0 0-1.069-.648m1.069.648-1.069-.648m0 0-1.025-.715m1.025.715-1.025-.715m0 0-.977-.78m.977.78-.977-.78m0 0-.926-.84m.926.84-.926-.84m0 0-.87-.898m.87.898-.87-.898m0 0-.81-.952m.81.952-.81-.952m0 0-.747-1.001m.747 1.001-.747-1.001m0 0-.682-1.048m.682 1.048-.682-1.048m0 0-.614-1.09m.614 1.09-.614-1.09m0 0-.542-1.125m.542 1.126-.542-1.126m0 0-.47-1.159m.47 1.159-.47-1.159m0 0-.393-1.186m.394 1.186-.394-1.186m0 0-.317-1.21m.317 1.21-.317-1.21m0 0c-.095-.488-.19-.977-.24-1.226m.24 1.227-.24-1.227m0 0c-.033-.266-.067-.531-.159-1.24m.16 1.24-.16-1.24m0 0L8 122.734m.08 1.248L8 122.734m0 0v-1.25m0 1.25v-1.25m0 0 .08-1.247M8 121.484l.08-1.247m0 0 .16-1.24m-.16 1.24.16-1.24m0 0 .239-1.227m-.24 1.227.24-1.227m0 0 .317-1.209m-.317 1.21.317-1.21m0 0 .394-1.186m-.394 1.186.394-1.186m0 0 .469-1.159m-.47 1.159.47-1.159m0 0 .542-1.126m-.542 1.126.542-1.126m0 0 .614-1.09m-.614 1.09.614-1.09m0 0 .682-1.047m-.682 1.048.682-1.048m0 0 .747-1.002m-.747 1.002.747-1.002m0 0 .81-.951m-.81.951.81-.951m0 0 .87-.898m-.87.898.87-.898m0 0 .926-.84m-.926.84.926-.84m0 0c.237-.19.474-.38.977-.78m-.977.78c.33-.265.661-.529.977-.78m0 0 1.025-.715m-1.025.715 1.025-.715m0 0 1.069-.648m-1.069.648 1.069-.648m0 0 1.108-.579m-1.108.579 1.108-.579m0 0c.258-.113.515-.227 1.143-.506m-1.143.506 1.143-.506m0 0 1.173-.431m-1.173.431 1.173-.431m0 0 1.199-.356m-1.199.356c.336-.1.67-.2 1.199-.356m0 0 1.218-.278m-1.218.278 1.218-.278m0 0 1.234-.2m-1.234.2c.343-.055.686-.11 1.234-.2m0 0 1.245-.12m-1.245.12 1.245-.12m0 0 1.249-.04m-1.25.04 1.25-.04m0 0"></path></g><g class="label" transform="translate(20.365 110.11)"><rect></rect><foreignObject width="10.672" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>A</p></span></div></foreignObject></g></g><g id="mermaid-2-flowchart-i1-3" class="node default" data-look="classic" transform="translate(103.707 122.11)"><circle r="10.305" class="basic label-container"></circle><g class="label" transform="translate(-2.805 -12)"><rect></rect><foreignObject width="5.609" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>•</p></span></div></foreignObject></g></g><g id="mermaid-2-flowchart-b1-5" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M62.795 215.61h-3.578c.333.01.666.02 1.249.04l1.244.12 1.234.199 1.219.278 1.198.356 1.173.431 1.143.506 1.109.579 1.068.648 1.026.715.977.78.925.84.87.898.81.951.748 1.002.682 1.048.613 1.089.543 1.126.469 1.159.394 1.186.317 1.21c.055.285.11.57.239 1.226.05.396.102.792.16 1.24l.08 1.247v1.25l-.08 1.248c-.055.421-.11.842-.16 1.24l-.24 1.227-.316 1.209-.394 1.186-.47 1.159-.542 1.126-.613 1.089-.682 1.048-.748 1.001-.81.952-.87.898-.925.84-.977.78-1.026.715-1.068.648-1.109.578-1.143.506-1.173.432-1.198.356-1.219.278-1.234.2c-.364.034-.728.07-1.244.12l-1.25.04h3.579l-1.25-.04-1.244-.12-1.234-.2-1.219-.278-1.198-.356-1.173-.432-1.143-.506-1.108-.578-1.07-.648-1.024-.715-.978-.78-.925-.84-.87-.898-.81-.952-.748-1.001-.682-1.048-.613-1.09c-.21-.433-.418-.867-.542-1.125l-.47-1.159-.393-1.186-.317-1.21-.24-1.226-.16-1.24-.08-1.248v-1.25l.08-1.247c.05-.378.098-.756.16-1.24l.24-1.227.317-1.209.394-1.186.469-1.159.542-1.126.613-1.09.682-1.047.748-1.002.81-.951.87-.898.925-.84.978-.78 1.025-.715 1.069-.648 1.108-.579 1.143-.506 1.173-.431 1.198-.356 1.22-.278 1.233-.2 1.244-.12 1.25-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M62.795 215.61h-3.578m3.578 0h-3.578m0 0 1.249.04m-1.25-.04 1.25.04m0 0 1.244.12m-1.244-.12c.436.041.871.083 1.244.12m0 0 1.234.199m-1.234-.2 1.234.2m0 0 1.219.278m-1.219-.278 1.219.278m0 0 1.198.356m-1.198-.356 1.198.356m0 0 1.173.431m-1.173-.431 1.173.431m0 0 1.143.506m-1.143-.506 1.143.506m0 0 1.109.579m-1.109-.579 1.109.579m0 0 1.068.648m-1.068-.648 1.068.648m0 0 1.026.715m-1.026-.715 1.026.715m0 0 .977.78m-.977-.78.977.78m0 0 .925.84m-.925-.84.925.84m0 0 .87.898m-.87-.898.87.898m0 0 .81.951m-.81-.951.81.951m0 0 .748 1.002m-.748-1.002.748 1.002m0 0 .682 1.048m-.682-1.048.682 1.048m0 0 .613 1.089m-.613-1.09.613 1.09m0 0 .543 1.126m-.543-1.126.543 1.126m0 0 .469 1.159m-.47-1.159.47 1.159m0 0 .394 1.186m-.394-1.186.394 1.186m0 0 .317 1.21m-.317-1.21.317 1.21m0 0 .239 1.226m-.24-1.227.24 1.227m0 0 .16 1.24m-.16-1.24.16 1.24m0 0 .08 1.247m-.08-1.247.08 1.247m0 0v1.25m0-1.25v1.25m0 0-.08 1.248m.08-1.248-.08 1.248m0 0-.16 1.24m.16-1.24c-.047.356-.093.713-.16 1.24m0 0-.24 1.227m.24-1.227-.24 1.227m0 0-.316 1.209m.317-1.21-.317 1.21m0 0-.394 1.186m.394-1.186-.394 1.186m0 0-.47 1.159m.47-1.159-.47 1.159m0 0-.542 1.126m.543-1.126-.543 1.126m0 0-.613 1.089m.613-1.09-.613 1.09m0 0-.682 1.048m.682-1.048-.682 1.048m0 0-.748 1.001m.748-1.001-.748 1.001m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898-.87.898m0 0-.925.84m.925-.84-.925.84m0 0-.977.78m.977-.78-.977.78m0 0-1.026.715m1.026-.715-1.026.715m0 0-1.068.648m1.068-.648-1.068.648m0 0-1.109.578m1.109-.578-1.109.578m0 0-1.143.506m1.143-.506-1.143.506m0 0-1.173.432m1.173-.432-1.173.432m0 0-1.198.356m1.198-.356-1.198.356m0 0-1.219.278m1.219-.278-1.219.278m0 0-1.234.2m1.234-.2-1.234.2m0 0-1.244.12m1.244-.12-1.244.12m0 0-1.25.04m1.25-.04c-.352.01-.703.022-1.25.04m0 0h3.579m-3.578 0h3.578m0 0-1.25-.04m1.25.04-1.25-.04m0 0-1.244-.12m1.244.12-1.244-.12m0 0-1.234-.2m1.234.2-1.234-.2m0 0-1.219-.278m1.22.278-1.22-.278m0 0-1.198-.356m1.198.356-1.198-.356m0 0-1.173-.432m1.173.432-1.173-.432m0 0-1.143-.506m1.143.506-1.143-.506m0 0-1.108-.578m1.108.578-1.108-.578m0 0-1.07-.648m1.07.648-1.07-.648m0 0-1.024-.715m1.025.715-1.025-.715m0 0-.978-.78m.978.78-.978-.78m0 0-.925-.84m.925.84-.925-.84m0 0-.87-.898m.87.898-.87-.898m0 0-.81-.952m.81.952-.81-.952m0 0-.748-1.001m.748 1.001-.748-1.001m0 0-.682-1.048m.682 1.048-.682-1.048m0 0-.613-1.09m.613 1.09-.613-1.09m0 0-.542-1.125m.542 1.126-.542-1.126m0 0-.47-1.159m.47 1.159-.47-1.159m0 0-.393-1.186m.394 1.186-.394-1.186m0 0-.317-1.21m.317 1.21-.317-1.21m0 0-.24-1.226m.24 1.227c-.096-.489-.19-.977-.24-1.227m0 0-.16-1.24m.16 1.24c-.061-.482-.124-.964-.16-1.24m0 0c-.022-.359-.045-.717-.08-1.248m.08 1.248-.08-1.248m0 0v-1.25m0 1.25v-1.25m0 0c.025-.377.05-.753.08-1.247m-.08 1.247.08-1.247m0 0 .16-1.24m-.16 1.24.16-1.24m0 0 .24-1.227m-.24 1.227.24-1.227m0 0 .317-1.209m-.317 1.21.317-1.21m0 0 .394-1.186m-.394 1.186.394-1.186m0 0 .469-1.159m-.47 1.159.47-1.159m0 0 .542-1.126m-.542 1.126.542-1.126m0 0 .613-1.09m-.613 1.09.613-1.09m0 0 .682-1.047m-.682 1.048.682-1.048m0 0 .748-1.002m-.748 1.002.748-1.002m0 0 .81-.951m-.81.951.81-.951m0 0 .87-.898m-.87.898.87-.898m0 0 .925-.84m-.925.84.925-.84m0 0 .978-.78m-.978.78.978-.78m0 0 1.025-.715m-1.025.715 1.025-.715m0 0 1.069-.648m-1.07.648 1.07-.648m0 0 1.108-.579m-1.108.579 1.108-.579m0 0c.432-.19.864-.382 1.143-.506m-1.143.506 1.143-.506m0 0 1.173-.431m-1.173.431 1.173-.431m0 0 1.198-.356m-1.198.356 1.198-.356m0 0 1.22-.278m-1.22.278 1.22-.278m0 0 1.233-.2m-1.234.2 1.234-.2m0 0 1.244-.12m-1.244.12 1.244-.12m0 0 1.25-.04m-1.25.04 1.25-.04m0 0"></path></g><g class="label" transform="translate(55.67 223.11)"><rect></rect><foreignObject width="10.672" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>B</p></span></div></foreignObject></g></g><g id="mermaid-2-flowchart-a2-7" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M148.197 215.61h-3.579l1.25.04 1.244.12 1.234.199 1.219.278 1.198.356 1.173.431 1.143.506 1.108.579 1.07.648 1.025.715.977.78.925.84.87.898.81.951.748 1.002.682 1.048.613 1.089.542 1.126.47 1.159.393 1.186.317 1.21.24 1.226.16 1.24.08 1.247v1.25l-.08 1.248-.16 1.24-.24 1.227c-.12.462-.242.924-.317 1.209l-.393 1.186-.47 1.159-.542 1.126-.613 1.089-.682 1.048-.748 1.001-.81.952-.87.898-.925.84-.977.78-1.026.715-1.069.648c-.296.155-.592.31-1.108.578l-1.143.506-1.173.432-1.198.356-1.219.278-1.234.2-1.244.12-1.25.04h3.579l-1.25-.04c-.445-.044-.89-.087-1.244-.12l-1.234-.2-1.219-.278-1.198-.356-1.173-.432-1.143-.506-1.108-.578-1.07-.648-1.025-.715-.977-.78-.925-.84c-.302-.31-.603-.622-.87-.898l-.81-.952-.748-1.001-.682-1.048-.613-1.09-.543-1.125-.469-1.159-.394-1.186-.317-1.21-.239-1.226-.16-1.24-.08-1.248v-1.25l.08-1.247.16-1.24.24-1.227.316-1.209.394-1.186.47-1.159.542-1.126.613-1.09.682-1.047.748-1.002.81-.951c.32-.33.638-.66.87-.898l.925-.84.977-.78 1.026-.715 1.069-.648 1.108-.579 1.143-.506 1.173-.431 1.198-.356 1.219-.278 1.234-.2 1.244-.12c.486-.015.972-.03 1.25-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M148.197 215.61h-3.579m3.579 0h-3.579m0 0 1.25.04m-1.25-.04 1.25.04m0 0 1.244.12m-1.244-.12 1.244.12m0 0 1.234.199m-1.234-.2 1.234.2m0 0 1.219.278m-1.219-.278 1.219.278m0 0 1.198.356m-1.198-.356 1.198.356m0 0 1.173.431m-1.173-.431 1.173.431m0 0 1.143.506m-1.143-.506 1.143.506m0 0 1.108.579m-1.108-.579 1.108.579m0 0 1.07.648m-1.07-.648 1.07.648m0 0 1.025.715m-1.026-.715 1.026.715m0 0 .977.78m-.977-.78.977.78m0 0c.289.261.577.524.925.84m-.925-.84.925.84m0 0 .87.898m-.87-.898.87.898m0 0 .81.951m-.81-.951.81.951m0 0 .748 1.002m-.748-1.002.748 1.002m0 0 .682 1.048m-.682-1.048.682 1.048m0 0 .613 1.089m-.613-1.09.613 1.09m0 0 .542 1.126m-.542-1.126.542 1.126m0 0 .47 1.159m-.47-1.159.47 1.159m0 0 .393 1.186m-.393-1.186.393 1.186m0 0 .317 1.21m-.317-1.21.317 1.21m0 0 .24 1.226m-.24-1.227.24 1.227m0 0 .16 1.24m-.16-1.24.16 1.24m0 0c.026.421.053.843.08 1.247m-.08-1.247c.026.423.054.846.08 1.247m0 0v1.25m0-1.25v1.25m0 0-.08 1.248m.08-1.248-.08 1.248m0 0c-.044.332-.086.664-.16 1.24m.16-1.24c-.06.46-.12.918-.16 1.24m0 0-.24 1.227m.24-1.227-.24 1.227m0 0-.317 1.209m.317-1.21-.317 1.21m0 0-.393 1.186m.393-1.186-.393 1.186m0 0-.47 1.159m.47-1.159-.47 1.159m0 0-.542 1.126m.542-1.126-.542 1.126m0 0-.613 1.089m.613-1.09-.613 1.09m0 0-.682 1.048m.682-1.048-.682 1.048m0 0-.748 1.001m.748-1.001-.748 1.001m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898-.87.898m0 0-.925.84m.925-.84-.925.84m0 0-.977.78m.977-.78-.977.78m0 0-1.026.715m1.026-.715-1.026.715m0 0-1.069.648m1.07-.648-1.07.648m0 0-1.108.578m1.108-.578-1.108.578m0 0-1.143.506m1.143-.506-1.143.506m0 0-1.173.432m1.173-.432-1.173.432m0 0-1.198.356m1.198-.356-1.198.356m0 0-1.219.278m1.219-.278-1.219.278m0 0-1.234.2m1.234-.2c-.44.07-.878.142-1.234.2m0 0c-.281.026-.562.053-1.244.12m1.244-.12c-.489.046-.978.094-1.244.12m0 0-1.25.04m1.25-.04-1.25.04m0 0h3.579m-3.579 0h3.579m0 0-1.25-.04m1.25.04-1.25-.04m0 0c-.45-.044-.9-.088-1.244-.12m1.244.12c-.424-.042-.847-.082-1.244-.12m0 0-1.234-.2m1.234.2-1.234-.2m0 0-1.219-.278m1.219.278-1.219-.278m0 0-1.198-.356m1.198.356-1.198-.356m0 0-1.173-.432m1.173.432-1.173-.432m0 0-1.143-.506m1.143.506-1.143-.506m0 0-1.108-.578m1.108.578-1.108-.578m0 0-1.07-.648m1.07.648-1.07-.648m0 0-1.025-.715m1.026.715-1.026-.715m0 0-.977-.78m.977.78-.977-.78m0 0-.925-.84m.925.84-.925-.84m0 0-.87-.898m.87.898-.87-.898m0 0-.81-.952m.81.952-.81-.952m0 0-.748-1.001m.748 1.001-.748-1.001m0 0-.682-1.048m.682 1.048-.682-1.048m0 0-.613-1.09m.613 1.09-.613-1.09m0 0-.543-1.125m.543 1.126-.543-1.126m0 0-.469-1.159m.47 1.159-.47-1.159m0 0-.394-1.186m.394 1.186-.394-1.186m0 0-.317-1.21m.317 1.21-.317-1.21m0 0-.239-1.226m.24 1.227-.24-1.227m0 0c-.06-.475-.122-.95-.16-1.24m.16 1.24-.16-1.24m0 0c-.03-.478-.06-.955-.08-1.248m.08 1.248c-.03-.49-.062-.98-.08-1.248m0 0v-1.25m0 1.25v-1.25m0 0 .08-1.247m-.08 1.247.08-1.247m0 0 .16-1.24m-.16 1.24c.047-.36.094-.72.16-1.24m0 0 .24-1.227m-.24 1.227.24-1.227m0 0 .316-1.209m-.317 1.21.317-1.21m0 0 .394-1.186m-.394 1.186.394-1.186m0 0 .47-1.159m-.47 1.159.47-1.159m0 0 .542-1.126m-.543 1.126.543-1.126m0 0 .613-1.09m-.613 1.09.613-1.09m0 0 .682-1.047m-.682 1.048.682-1.048m0 0 .748-1.002m-.748 1.002.748-1.002m0 0 .81-.951m-.81.951.81-.951m0 0 .87-.898m-.87.898.87-.898m0 0 .925-.84m-.925.84.925-.84m0 0 .977-.78m-.977.78.977-.78m0 0 1.026-.715m-1.026.715 1.026-.715m0 0 1.069-.648m-1.07.648 1.07-.648m0 0 1.108-.579m-1.108.579 1.108-.579m0 0 1.143-.506m-1.143.506 1.143-.506m0 0 1.173-.431m-1.173.431 1.173-.431m0 0 1.198-.356m-1.198.356 1.198-.356m0 0 1.219-.278m-1.219.278 1.219-.278m0 0c.25-.04.5-.08 1.234-.2m-1.234.2 1.234-.2m0 0 1.244-.12m-1.244.12 1.244-.12m0 0 1.25-.04m-1.25.04 1.25-.04m0 0"></path></g><g class="label" transform="translate(141.072 223.11)"><rect></rect><foreignObject width="10.672" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>A</p></span></div></foreignObject></g></g></g></g><defs><filter id="mermaid-2-drop-shadow" width="130%" height="130%"><feDropShadow dx="4" dy="4" flood-color="#000" flood-opacity=".06" stdDeviation="0"></feDropShadow></filter></defs><defs><filter id="mermaid-2-drop-shadow-small" width="150%" height="150%"><feDropShadow dx="2" dy="2" flood-color="#000" flood-opacity=".06" stdDeviation="0"></feDropShadow></filter></defs></svg>
<svg xmlns="http://www.w3.org/2000/svg" id="mermaid-3" width="100%" aria-roledescription="flowchart-v2" class="flowchart" style="font-family:arial,sans-serif;font-size:16px;fill:#333;max-width:179.01893615722656px" viewBox="0 -1.695 179.019 264.305"><style>@keyframes edge-animation-frame{0%{stroke-dashoffset:0}}@keyframes dash{to{stroke-dashoffset:0}}#mermaid-3 .edge-thickness-normal{stroke-width:1px}#mermaid-3 .edge-pattern-solid{stroke-dasharray:0}#mermaid-3 .marker{fill:#333;stroke:#333}#mermaid-3 .marker.cross{stroke:#333}#mermaid-3 svg{font-family:arial,sans-serif;font-size:16px}#mermaid-3 p{margin:0}#mermaid-3 .label{font-family:arial,sans-serif;color:#333}#mermaid-3 span{fill:#333;color:#333}#mermaid-3 .node circle,#mermaid-3 .node path,#mermaid-3 .node rect{fill:#ececff;stroke:#9370db;stroke-width:1px}#mermaid-3 .flowchart-link{stroke:#333;fill:none}#mermaid-3 .edgeLabel,#mermaid-3 .node .label{text-align:center}#mermaid-3 .edgeLabel,#mermaid-3 .edgeLabel p,#mermaid-3 .edgeLabel rect{background-color:rgba(232,232,232,.8)}#mermaid-3 .edgeLabel rect{opacity:.5;fill:rgba(232,232,232,.8)}#mermaid-3 .labelBkg{background-color:rgba(232,232,232,.5)}#mermaid-3 [data-look=neo].node circle,#mermaid-3 [data-look=neo].node rect{stroke:#9370db;filter:drop-shadow(1px 2px 2px #b9b9b9)}#mermaid-3 [data-look=neo].node path{stroke:#9370db;stroke-width:1px}#mermaid-3 [data-look=neo].node .outer-path{filter:drop-shadow(1px 2px 2px #b9b9b9)}#mermaid-3 :root{--mermaid-font-family:arial,sans-serif}</style><marker id="mermaid-3_flowchart-v2-pointEnd" class="marker flowchart-v2" markerHeight="8" markerUnits="userSpaceOnUse" markerWidth="8" orient="auto" refX="5" refY="5" viewBox="0 0 10 10"><path d="m0 0 10 5-10 5z" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></path></marker><marker id="mermaid-3_flowchart-v2-pointStart" class="marker flowchart-v2" markerHeight="8" markerUnits="userSpaceOnUse" markerWidth="8" orient="auto" refX="4.5" refY="5" viewBox="0 0 10 10"><path d="m0 5 10 5V0z" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></path></marker><marker id="mermaid-3_flowchart-v2-pointEnd-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="10.5" orient="auto" refX="11.5" refY="7" viewBox="0 0 11.5 14"><path d="m0 0 11.5 7L0 14z" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></path></marker><marker id="mermaid-3_flowchart-v2-pointStart-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="11.5" orient="auto" refX="1" refY="7" viewBox="0 0 11.5 14"><path d="m0 7 11.5 7V0z" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></path></marker><marker id="mermaid-3_flowchart-v2-circleEnd" class="marker flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="11" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-3_flowchart-v2-circleStart" class="marker flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="-1" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-3_flowchart-v2-circleEnd-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="14" orient="auto" refX="12.25" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-3_flowchart-v2-circleStart-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="14" orient="auto" refX="-2" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-3_flowchart-v2-crossEnd" class="marker cross flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="12" refY="5.2" viewBox="0 0 11 11"><path d="m1 1 9 9m0-9-9 9" class="arrowMarkerPath" style="stroke-width:2;stroke-dasharray:1,0"></path></marker><marker id="mermaid-3_flowchart-v2-crossStart" class="marker cross flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="-1" refY="5.2" viewBox="0 0 11 11"><path d="m1 1 9 9m0-9-9 9" class="arrowMarkerPath" style="stroke-width:2;stroke-dasharray:1,0"></path></marker><marker id="mermaid-3_flowchart-v2-crossEnd-margin" class="marker cross flowchart-v2" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="12" orient="auto" refX="17.7" refY="7.5" viewBox="0 0 15 15"><path d="m1 1 13 13M1 14 14 1" class="arrowMarkerPath" style="stroke-width:2.5"></path></marker><marker id="mermaid-3_flowchart-v2-crossStart-margin" class="marker cross flowchart-v2" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="12" orient="auto" refX="-3.5" refY="7.5" viewBox="0 0 15 15"><path d="m1 1 13 13M1 14 14 1" class="arrowMarkerPath" style="stroke-width:2.5;stroke-dasharray:1,0"></path></marker><g class="root"><g class="edgePaths"><path id="mermaid-3-L_o2_i2_0" marker-end="url(#mermaid-3_flowchart-v2-pointEnd)" d="m100.849 26.255-5.408 6.559c-5.408 6.559-16.223 19.677-21.631 33.269s-5.408 27.657-5.408 34.689v7.033" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_o2_i2_0" data-look="classic" data-points="W3sieCI6MTAwLjg0OTI2ODY1Mjc1MzE4LCJ5IjoyNi4yNTUzOTE5MjY4NDM1Mn0seyJ4Ijo2OC40MDE4MzYzOTUyNjM2NywieSI6NjUuNjA5Mzc1fSx7IngiOjY4LjQwMTgzNjM5NTI2MzY3LCJ5IjoxMTEuODA0Njg3NX1d"></path><path id="mermaid-3-L_o2_a3_0" marker-end="url(#mermaid-3_flowchart-v2-pointEnd)" d="m113.96 26.255 5.408 6.559c5.408 6.559 16.224 19.677 21.706 31.82 5.482 12.142 5.631 23.309 5.706 28.892l.074 5.584" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_o2_a3_0" data-look="classic" data-points="W3sieCI6MTEzLjk2MDAwOTgzNTQwNiwieSI6MjYuMjU1MzkxOTI2ODQzNTJ9LHsieCI6MTQ2LjQwNzQ0MjA5Mjg5NTUsInkiOjY1LjYwOTM3NX0seyJ4IjoxNDYuOTA3NDQyMDkyODk1NSwieSI6MTAzLjEwOTM3NX1d"></path><path id="mermaid-3-L_i2_a4_0" marker-end="url(#mermaid-3_flowchart-v2-pointEnd)" d="m62.189 130.33-6.082 8.047c-6.081 8.046-18.243 24.139-24.25 37.769s-5.858 24.797-5.784 30.38l.075 5.584" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_i2_a4_0" data-look="classic" data-points="W3sieCI6NjIuMTg4NzE1NjI0MTk1Mjc1LCJ5IjoxMzAuMzMwMzA2NTAxNzY4OH0seyJ4IjoyNS43MDA5MTgxOTc2MzE4MzYsInkiOjE3OC42MDkzNzV9LHsieCI6MjYuMjAwOTE4MTk3NjMxODM2LCJ5IjoyMTYuMTA5Mzc1fV0="></path><path id="mermaid-3-L_i2_b2_0" marker-end="url(#mermaid-3_flowchart-v2-pointEnd)" d="m74.615 130.33 6.081 8.047c6.082 8.046 18.244 24.139 24.4 37.769s6.305 24.797 6.379 30.38l.074 5.584" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_i2_b2_0" data-look="classic" data-points="W3sieCI6NzQuNjE0OTU3MTY2MzMyMDYsInkiOjEzMC4zMzAzMDY1MDE3Njg4fSx7IngiOjExMS4xMDI3NTQ1OTI4OTU1MSwieSI6MTc4LjYwOTM3NX0seyJ4IjoxMTEuNjAyNzU0NTkyODk1NTEsInkiOjIxNi4xMDkzNzV9XQ=="></path></g><g class="edgeLabels"><g class="edgeLabel"><foreignObject width="33.75" height="24" class="label" data-id="L_o2_i2_0" transform="translate(51.527 53.61)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>база</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="57.813" height="24" class="label" data-id="L_o2_a3_0" transform="translate(113.206 48.4)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>override</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="33.75" height="24" class="label" data-id="L_i2_a4_0" transform="translate(8.826 166.61)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>база</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="57.813" height="24" class="label" data-id="L_i2_b2_0" transform="translate(82.197 166.61)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>override</p></span></div></foreignObject></g></g><g class="nodes"><g id="mermaid-3-flowchart-o2-0" class="node default" data-look="classic" transform="translate(107.405 18.305)"><circle r="10.305" class="basic label-container"></circle><g class="label" transform="translate(-2.805 -12)"><rect></rect><foreignObject width="5.609" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>•</p></span></div></foreignObject></g></g><g id="mermaid-3-flowchart-i2-1" class="node default" data-look="classic" transform="translate(68.402 122.11)"><circle r="10.305" class="basic label-container"></circle><g class="label" transform="translate(-2.805 -12)"><rect></rect><foreignObject width="5.609" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>•</p></span></div></foreignObject></g></g><g id="mermaid-3-flowchart-a3-3" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M148.197 102.61h-3.579l1.25.04 1.244.12 1.234.199 1.219.278 1.198.356 1.173.431 1.143.506 1.108.579 1.07.648 1.025.715.977.78.925.84.87.898.81.951.748 1.002.682 1.048.613 1.089.542 1.126.47 1.159.393 1.186.317 1.21.24 1.226.16 1.24.08 1.247v1.25l-.08 1.248c-.052.394-.102.789-.16 1.24l-.24 1.227-.317 1.209-.393 1.186-.47 1.159-.542 1.126-.613 1.089-.682 1.048-.748 1.001-.81.952-.87.898-.925.84-.977.78-1.026.715-1.069.648-1.108.578-1.143.506-1.173.432-1.198.356-1.219.278-1.234.2-1.244.12-1.25.04h3.579l-1.25-.04-1.244-.12-1.234-.2-1.219-.278-1.198-.356-1.173-.432-1.143-.506-1.108-.578-1.07-.648-1.025-.715-.977-.78-.925-.84c-.205-.21-.409-.422-.87-.898l-.81-.952-.748-1.001-.682-1.048-.613-1.09-.543-1.125-.469-1.159-.394-1.186-.317-1.21-.239-1.226-.16-1.24-.08-1.248v-1.25c.02-.292.038-.585.08-1.247l.16-1.24.24-1.227.316-1.209.394-1.186.47-1.159.542-1.126.613-1.09c.27-.413.54-.827.682-1.047l.748-1.002.81-.951c.26-.27.521-.539.87-.898l.925-.84.977-.78 1.026-.715 1.069-.648 1.108-.579 1.143-.506 1.173-.431 1.198-.356 1.219-.278 1.234-.2 1.244-.12 1.25-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M148.197 102.61h-3.579m3.579 0h-3.579m0 0 1.25.04m-1.25-.04c.44.013.878.028 1.25.04m0 0 1.244.12m-1.244-.12 1.244.12m0 0 1.234.199m-1.234-.2 1.234.2m0 0 1.219.278m-1.219-.278 1.219.278m0 0 1.198.356m-1.198-.356 1.198.356m0 0 1.173.431m-1.173-.431 1.173.431m0 0 1.143.506m-1.143-.506 1.143.506m0 0 1.108.579m-1.108-.579 1.108.579m0 0 1.07.648m-1.07-.648 1.07.648m0 0 1.025.715m-1.026-.715 1.026.715m0 0c.24.19.479.382.977.78m-.977-.78.977.78m0 0c.188.17.377.341.925.84m-.925-.84.925.84m0 0 .87.898m-.87-.898.87.898m0 0 .81.951m-.81-.951.81.951m0 0 .748 1.002m-.748-1.002.748 1.002m0 0 .682 1.048m-.682-1.048.682 1.048m0 0 .613 1.089m-.613-1.09.613 1.09m0 0 .542 1.126m-.542-1.126.542 1.126m0 0 .47 1.159m-.47-1.159.47 1.159m0 0 .393 1.186m-.393-1.186.393 1.186m0 0 .317 1.21m-.317-1.21.317 1.21m0 0 .24 1.226m-.24-1.227.24 1.227m0 0c.06.476.122.952.16 1.24m-.16-1.24.16 1.24m0 0c.016.266.033.533.08 1.247m-.08-1.247c.02.337.043.673.08 1.247m0 0v1.25m0-1.25v1.25m0 0c-.017.257-.034.514-.08 1.248m.08-1.248-.08 1.248m0 0-.16 1.24m.16-1.24-.16 1.24m0 0-.24 1.227m.24-1.227-.24 1.227m0 0-.317 1.209m.317-1.21-.317 1.21m0 0-.393 1.186m.393-1.186-.393 1.186m0 0-.47 1.159m.47-1.159-.47 1.159m0 0-.542 1.126m.542-1.126-.542 1.126m0 0-.613 1.089m.613-1.09-.613 1.09m0 0-.682 1.048m.682-1.048-.682 1.048m0 0-.748 1.001m.748-1.001-.748 1.001m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898-.87.898m0 0-.925.84m.925-.84-.925.84m0 0-.977.78m.977-.78-.977.78m0 0-1.026.715m1.026-.715-1.026.715m0 0-1.069.648m1.07-.648-1.07.648m0 0-1.108.578m1.108-.578-1.108.578m0 0-1.143.506m1.143-.506-1.143.506m0 0-1.173.432m1.173-.432-1.173.432m0 0-1.198.356m1.198-.356-1.198.356m0 0-1.219.278m1.219-.278-1.219.278m0 0-1.234.2m1.234-.2c-.312.05-.624.1-1.234.2m0 0-1.244.12m1.244-.12-1.244.12m0 0c-.469.014-.937.03-1.25.04m1.25-.04-1.25.04m0 0h3.579m-3.579 0h3.579m0 0-1.25-.04m1.25.04-1.25-.04m0 0c-.4-.04-.8-.078-1.244-.12m1.244.12-1.244-.12m0 0-1.234-.2m1.234.2-1.234-.2m0 0-1.219-.278m1.219.278-1.219-.278m0 0-1.198-.356m1.198.356-1.198-.356m0 0-1.173-.432m1.173.432-1.173-.432m0 0c-.273-.12-.545-.24-1.143-.506m1.143.506-1.143-.506m0 0-1.108-.578m1.108.578-1.108-.578m0 0c-.25-.15-.498-.302-1.07-.648m1.07.648-1.07-.648m0 0-1.025-.715m1.026.715-1.026-.715m0 0-.977-.78m.977.78-.977-.78m0 0-.925-.84m.925.84-.925-.84m0 0-.87-.898m.87.898-.87-.898m0 0-.81-.952m.81.952-.81-.952m0 0-.748-1.001m.748 1.001-.748-1.001m0 0-.682-1.048m.682 1.048-.682-1.048m0 0-.613-1.09m.613 1.09-.613-1.09m0 0c-.19-.394-.38-.79-.543-1.125m.543 1.126-.543-1.126m0 0-.469-1.159m.47 1.159-.47-1.159m0 0-.394-1.186m.394 1.186-.394-1.186m0 0-.317-1.21m.317 1.21-.317-1.21m0 0-.239-1.226m.24 1.227-.24-1.227m0 0-.16-1.24m.16 1.24c-.06-.477-.122-.953-.16-1.24m0 0-.08-1.248m.08 1.248-.08-1.248m0 0v-1.25m0 1.25v-1.25m0 0 .08-1.247m-.08 1.247c.025-.379.05-.758.08-1.247m0 0 .16-1.24m-.16 1.24.16-1.24m0 0 .24-1.227m-.24 1.227.24-1.227m0 0 .316-1.209m-.317 1.21.317-1.21m0 0 .394-1.186m-.394 1.186.394-1.186m0 0 .47-1.159m-.47 1.159.47-1.159m0 0 .542-1.126m-.543 1.126.543-1.126m0 0 .613-1.09m-.613 1.09.613-1.09m0 0 .682-1.047m-.682 1.048.682-1.048m0 0 .748-1.002m-.748 1.002.748-1.002m0 0 .81-.951m-.81.951.81-.951m0 0 .87-.898m-.87.898.87-.898m0 0 .925-.84m-.925.84.925-.84m0 0c.286-.23.573-.457.977-.78m-.977.78.977-.78m0 0 1.026-.715m-1.026.715 1.026-.715m0 0c.319-.194.638-.388 1.069-.648m-1.07.648 1.07-.648m0 0 1.108-.579m-1.108.579 1.108-.579m0 0 1.143-.506m-1.143.506 1.143-.506m0 0 1.173-.431m-1.173.431 1.173-.431m0 0 1.198-.356m-1.198.356 1.198-.356m0 0 1.219-.278m-1.219.278 1.219-.278m0 0 1.234-.2m-1.234.2 1.234-.2m0 0 1.244-.12m-1.244.12 1.244-.12m0 0 1.25-.04m-1.25.04 1.25-.04m0 0"></path></g><g class="label" transform="translate(141.072 110.11)"><rect></rect><foreignObject width="10.672" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>A</p></span></div></foreignObject></g></g><g id="mermaid-3-flowchart-a4-5" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M27.49 215.61h-3.578l1.25.04 1.243.12 1.234.199 1.22.278 1.197.356 1.174.431 1.143.506c.227.12.454.238 1.108.579l1.069.648 1.025.715.977.78.926.84.87.898.81.951.747 1.002.682 1.048.614 1.089.542 1.126.469 1.159.394 1.186.317 1.21.239 1.226.16 1.24.08 1.247v1.25l-.08 1.248-.16 1.24-.24 1.227-.316 1.209-.394 1.186-.47 1.159-.541 1.126-.614 1.089-.682 1.048-.747 1.001-.81.952-.87.898-.926.84-.977.78-1.025.715-1.07.648-1.107.578-1.143.506-1.174.432-1.198.356-1.219.278-1.234.2-1.244.12-1.25.04h3.579l-1.25-.04c-.357-.035-.715-.07-1.244-.12l-1.234-.2-1.218-.278-1.199-.356-1.173-.432-1.143-.506-1.108-.578-1.069-.648-1.025-.715-.977-.78-.926-.84-.87-.898-.81-.952-.747-1.001-.682-1.048-.614-1.09-.542-1.125-.47-1.159-.393-1.186-.317-1.21-.24-1.226-.159-1.24-.08-1.248v-1.25l.08-1.247.16-1.24.239-1.227.317-1.209.394-1.186.469-1.159.542-1.126.614-1.09.682-1.047.747-1.002.81-.951.87-.898.926-.84c.261-.21.522-.418.977-.78l1.025-.715 1.069-.648 1.108-.579c.387-.17.774-.342 1.143-.506.247-.09.493-.18 1.173-.431l1.199-.356 1.218-.278 1.234-.2 1.245-.12 1.249-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M27.49 215.61h-3.578m3.578 0h-3.578m0 0 1.25.04m-1.25-.04c.333.01.666.02 1.25.04m0 0 1.243.12m-1.244-.12 1.244.12m0 0 1.234.199m-1.234-.2 1.234.2m0 0 1.22.278m-1.22-.278 1.22.278m0 0 1.197.356m-1.198-.356 1.198.356m0 0 1.174.431m-1.174-.431 1.174.431m0 0 1.143.506m-1.143-.506 1.143.506m0 0 1.108.579m-1.108-.579 1.108.579m0 0 1.069.648m-1.07-.648c.245.147.488.295 1.07.648m0 0 1.025.715m-1.025-.715 1.025.715m0 0 .977.78m-.977-.78.977.78m0 0c.285.257.57.516.926.84m-.926-.84c.203.183.405.366.926.84m0 0 .87.898m-.87-.898.87.898m0 0 .81.951m-.81-.951.81.951m0 0 .747 1.002m-.747-1.002.747 1.002m0 0 .682 1.048m-.682-1.048.682 1.048m0 0 .614 1.089m-.614-1.09.614 1.09m0 0 .542 1.126m-.542-1.126.542 1.126m0 0 .469 1.159m-.47-1.159.47 1.159m0 0 .394 1.186m-.394-1.186.394 1.186m0 0 .317 1.21m-.317-1.21.317 1.21m0 0 .239 1.226m-.24-1.227.24 1.227m0 0 .16 1.24m-.16-1.24.16 1.24m0 0 .08 1.247m-.08-1.247.08 1.247m0 0v1.25m0-1.25v1.25m0 0-.08 1.248m.08-1.248c-.03.46-.06.92-.08 1.248m0 0c-.055.422-.11.844-.16 1.24m.16-1.24-.16 1.24m0 0-.24 1.227m.24-1.227-.24 1.227m0 0-.316 1.209m.317-1.21-.317 1.21m0 0-.394 1.186m.394-1.186-.394 1.186m0 0-.47 1.159m.47-1.159-.47 1.159m0 0-.541 1.126m.542-1.126-.542 1.126m0 0-.614 1.089m.614-1.09-.614 1.09m0 0-.682 1.048m.682-1.048-.682 1.048m0 0-.747 1.001m.747-1.001-.747 1.001m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898-.87.898m0 0-.926.84m.926-.84-.926.84m0 0-.977.78m.977-.78-.977.78m0 0-1.025.715m1.025-.715-1.025.715m0 0-1.07.648m1.07-.648-1.07.648m0 0-1.107.578m1.108-.578-1.108.578m0 0-1.143.506m1.143-.506-1.143.506m0 0-1.174.432m1.174-.432-1.174.432m0 0-1.198.356m1.198-.356-1.198.356m0 0-1.219.278m1.22-.278-1.22.278m0 0-1.234.2m1.234-.2-1.234.2m0 0-1.244.12m1.244-.12-1.244.12m0 0c-.422.013-.845.026-1.25.04m1.25-.04-1.25.04m0 0h3.579m-3.578 0h3.578m0 0c-.357-.012-.714-.024-1.25-.04m1.25.04c-.482-.016-.963-.032-1.25-.04m0 0-1.244-.12m1.245.12-1.245-.12m0 0-1.234-.2m1.234.2c-.252-.042-.504-.082-1.234-.2m0 0-1.218-.278m1.218.278-1.218-.278m0 0-1.199-.356m1.199.356-1.199-.356m0 0-1.173-.432m1.173.432-1.173-.432m0 0-1.143-.506m1.143.506-1.143-.506m0 0-1.108-.578m1.108.578-1.108-.578m0 0-1.069-.648m1.069.648-1.069-.648m0 0-1.025-.715m1.025.715-1.025-.715m0 0-.977-.78m.977.78-.977-.78m0 0-.926-.84m.926.84c-.34-.307-.678-.614-.926-.84m0 0-.87-.898m.87.898-.87-.898m0 0-.81-.952m.81.952-.81-.952m0 0-.747-1.001m.747 1.001-.747-1.001m0 0-.682-1.048m.682 1.048c-.205-.314-.41-.628-.682-1.048m0 0-.614-1.09m.614 1.09-.614-1.09m0 0-.542-1.125m.542 1.126-.542-1.126m0 0-.47-1.159m.47 1.159-.47-1.159m0 0-.393-1.186m.394 1.186-.394-1.186m0 0-.317-1.21m.317 1.21-.317-1.21m0 0-.24-1.226m.24 1.227-.24-1.227m0 0-.159-1.24m.16 1.24-.16-1.24m0 0L8 235.734m.08 1.248L8 235.734m0 0v-1.25m0 1.25v-1.25m0 0 .08-1.247M8 234.484l.08-1.247m0 0 .16-1.24m-.16 1.24.16-1.24m0 0 .239-1.227m-.24 1.227c.056-.281.11-.563.24-1.227m0 0 .317-1.209m-.317 1.21.317-1.21m0 0 .394-1.186m-.394 1.186.394-1.186m0 0 .469-1.159m-.47 1.159.47-1.159m0 0 .542-1.126m-.542 1.126.542-1.126m0 0 .614-1.09m-.614 1.09.614-1.09m0 0c.185-.284.37-.57.682-1.047m-.682 1.048.682-1.048m0 0 .747-1.002m-.747 1.002.747-1.002m0 0 .81-.951m-.81.951.81-.951m0 0 .87-.898m-.87.898.87-.898m0 0 .926-.84m-.926.84c.318-.29.636-.578.926-.84m0 0 .977-.78m-.977.78.977-.78m0 0 1.025-.715m-1.025.715 1.025-.715m0 0 1.069-.648m-1.069.648 1.069-.648m0 0 1.108-.579m-1.108.579 1.108-.579m0 0 1.143-.506m-1.143.506c.352-.155.703-.31 1.143-.506m0 0 1.173-.431m-1.173.431 1.173-.431m0 0 1.199-.356m-1.199.356 1.199-.356m0 0 1.218-.278m-1.218.278 1.218-.278m0 0 1.234-.2m-1.234.2 1.234-.2m0 0 1.245-.12m-1.245.12 1.245-.12m0 0 1.249-.04m-1.25.04 1.25-.04m0 0"></path></g><g class="label" transform="translate(20.365 223.11)"><rect></rect><foreignObject width="10.672" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>A</p></span></div></foreignObject></g></g><g id="mermaid-3-flowchart-b2-7" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M112.892 215.61h-3.578c.253.007.506.016 1.25.04l1.243.12 1.234.199 1.219.278 1.198.356 1.173.431 1.143.506 1.109.579 1.069.648 1.025.715.977.78.926.84.87.898c.19.225.383.45.81.951l.747 1.002.682 1.048.613 1.089.543 1.126.469 1.159.394 1.186.317 1.21.239 1.226.16 1.24.08 1.247v1.25l-.08 1.248-.16 1.24-.24 1.227-.316 1.209-.394 1.186c-.125.31-.25.62-.47 1.159l-.542 1.126-.613 1.089-.682 1.048-.748 1.001-.81.952-.87.898-.925.84-.977.78-1.025.715-1.07.648-1.108.578-1.143.506-1.173.432-1.198.356-1.219.278-1.234.2c-.436.041-.872.083-1.244.12l-1.25.04h3.579c-.464-.016-.928-.03-1.25-.04-.476-.047-.952-.093-1.244-.12l-1.234-.2-1.218-.278-1.199-.356-1.173-.432-1.143-.506-1.108-.578-1.07-.648-1.024-.715-.978-.78-.925-.84-.87-.898-.81-.952-.748-1.001-.681-1.048-.614-1.09-.542-1.125-.47-1.159-.393-1.186-.317-1.21-.24-1.226c-.06-.47-.12-.938-.16-1.24l-.08-1.248v-1.25l.08-1.247.16-1.24.24-1.227.317-1.209.394-1.186.469-1.159.542-1.126.614-1.09.681-1.047.748-1.002.81-.951.87-.898.925-.84.978-.78 1.025-.715 1.069-.648 1.108-.579 1.143-.506 1.173-.431 1.199-.356 1.218-.278 1.234-.2 1.244-.12 1.25-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M112.892 215.61h-3.578m3.578 0h-3.578m0 0 1.25.04m-1.25-.04 1.25.04m0 0 1.243.12m-1.244-.12 1.244.12m0 0 1.234.199m-1.234-.2 1.234.2m0 0 1.219.278m-1.219-.278 1.219.278m0 0 1.198.356m-1.198-.356 1.198.356m0 0 1.173.431m-1.173-.431 1.173.431m0 0 1.143.506m-1.143-.506 1.143.506m0 0 1.109.579m-1.109-.579 1.109.579m0 0 1.069.648m-1.07-.648 1.07.648m0 0 1.025.715m-1.025-.715 1.025.715m0 0 .977.78m-.977-.78.977.78m0 0 .926.84m-.926-.84.926.84m0 0 .87.898m-.87-.898.87.898m0 0c.274.322.548.645.81.951m-.81-.951.81.951m0 0c.176.238.354.475.747 1.002m-.748-1.002.748 1.002m0 0 .682 1.048m-.682-1.048.682 1.048m0 0 .613 1.089m-.613-1.09.613 1.09m0 0 .543 1.126m-.543-1.126.543 1.126m0 0 .469 1.159m-.47-1.159.47 1.159m0 0 .394 1.186m-.394-1.186.394 1.186m0 0 .317 1.21m-.317-1.21.317 1.21m0 0 .239 1.226m-.24-1.227.24 1.227m0 0 .16 1.24m-.16-1.24.16 1.24m0 0 .08 1.247m-.08-1.247.08 1.247m0 0v1.25m0-1.25v1.25m0 0-.08 1.248m.08-1.248-.08 1.248m0 0-.16 1.24m.16-1.24-.16 1.24m0 0-.24 1.227m.24-1.227-.24 1.227m0 0-.316 1.209m.317-1.21-.317 1.21m0 0-.394 1.186m.394-1.186-.394 1.186m0 0-.47 1.159m.47-1.159-.47 1.159m0 0c-.17.356-.342.712-.542 1.126m.543-1.126-.543 1.126m0 0-.613 1.089m.613-1.09c-.24.43-.482.858-.613 1.09m0 0-.682 1.048m.682-1.048-.682 1.048m0 0-.748 1.001m.748-1.001-.748 1.001m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898-.87.898m0 0c-.288.263-.577.525-.925.84m.926-.84-.926.84m0 0-.977.78m.977-.78-.977.78m0 0-1.025.715m1.025-.715-1.025.715m0 0-1.07.648m1.07-.648-1.07.648m0 0-1.108.578m1.109-.578-1.109.578m0 0-1.143.506m1.143-.506-1.143.506m0 0c-.365.135-.73.27-1.173.432m1.173-.432-1.173.432m0 0-1.198.356m1.198-.356-1.198.356m0 0-1.219.278m1.219-.278-1.219.278m0 0-1.234.2m1.234-.2-1.234.2m0 0-1.244.12m1.244-.12-1.244.12m0 0c-.284.008-.568.018-1.25.04m1.25-.04c-.377.011-.754.023-1.25.04m0 0h3.579m-3.578 0h3.578m0 0-1.25-.04m1.25.04-1.25-.04m0 0-1.244-.12m1.244.12c-.367-.036-.734-.072-1.244-.12m0 0c-.429-.07-.857-.14-1.234-.2m1.234.2-1.234-.2m0 0c-.26-.06-.521-.12-1.218-.278m1.218.278-1.218-.278m0 0-1.199-.356m1.199.356-1.199-.356m0 0-1.173-.432m1.173.432-1.173-.432m0 0-1.143-.506m1.143.506-1.143-.506m0 0-1.108-.578m1.108.578-1.108-.578m0 0-1.07-.648m1.07.648-1.07-.648m0 0-1.024-.715m1.025.715-1.025-.715m0 0-.978-.78m.978.78-.978-.78m0 0-.925-.84m.925.84-.925-.84m0 0-.87-.898m.87.898-.87-.898m0 0-.81-.952m.81.952-.81-.952m0 0-.748-1.001m.748 1.001-.748-1.001m0 0-.681-1.048m.681 1.048-.681-1.048m0 0-.614-1.09m.614 1.09-.614-1.09m0 0-.542-1.125m.542 1.126-.542-1.126m0 0-.47-1.159m.47 1.159-.47-1.159m0 0-.393-1.186m.394 1.186-.394-1.186m0 0-.317-1.21m.317 1.21-.317-1.21m0 0-.24-1.226m.24 1.227-.24-1.227m0 0-.16-1.24m.16 1.24-.16-1.24m0 0c-.03-.478-.06-.956-.08-1.248m.08 1.248-.08-1.248m0 0v-1.25m0 1.25v-1.25m0 0 .08-1.247m-.08 1.247.08-1.247m0 0c.06-.458.12-.916.16-1.24m-.16 1.24.16-1.24m0 0 .24-1.227m-.24 1.227.24-1.227m0 0 .317-1.209m-.317 1.21.317-1.21m0 0 .394-1.186m-.394 1.186.394-1.186m0 0 .469-1.159m-.47 1.159.47-1.159m0 0 .542-1.126m-.542 1.126.542-1.126m0 0 .614-1.09m-.614 1.09.614-1.09m0 0 .681-1.047m-.681 1.048.681-1.048m0 0 .748-1.002m-.748 1.002.748-1.002m0 0 .81-.951m-.81.951.81-.951m0 0 .87-.898m-.87.898.87-.898m0 0 .925-.84m-.925.84.925-.84m0 0 .978-.78m-.978.78.978-.78m0 0 1.025-.715m-1.025.715 1.025-.715m0 0 1.069-.648m-1.07.648 1.07-.648m0 0 1.108-.579m-1.108.579 1.108-.579m0 0 1.143-.506m-1.143.506 1.143-.506m0 0c.33-.12.66-.242 1.173-.431m-1.173.431 1.173-.431m0 0 1.199-.356m-1.199.356 1.199-.356m0 0 1.218-.278m-1.218.278 1.218-.278m0 0 1.234-.2m-1.234.2 1.234-.2m0 0 1.244-.12m-1.244.12 1.244-.12m0 0 1.25-.04m-1.25.04 1.25-.04m0 0"></path></g><g class="label" transform="translate(105.767 223.11)"><rect></rect><foreignObject width="10.672" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>B</p></span></div></foreignObject></g></g></g></g><defs><filter id="mermaid-3-drop-shadow" width="130%" height="130%"><feDropShadow dx="4" dy="4" flood-color="#000" flood-opacity=".06" stdDeviation="0"></feDropShadow></filter></defs><defs><filter id="mermaid-3-drop-shadow-small" width="150%" height="150%"><feDropShadow dx="2" dy="2" flood-color="#000" flood-opacity=".06" stdDeviation="0"></feDropShadow></filter></defs></svg>
<p>И поведение тоже разное:</p>
<ul>
<li>Конструкция 1 (<code>A.wohs(B.wohs(A))</code>): у <code>A</code> halt уходит в обёртку «<code>B</code> с halt’ом в <code>A</code>». При исполнении walk такой: <code>A → B → A</code>.</li>
<li>Конструкция 2 (<code>A.wohs(B).wohs(A)</code>): обёртка «<code>A</code> с halt’ом в <code>B</code>» дальше обёрнута ещё раз — теперь halt уходит в <code>A</code>. При исполнении walk: <code>A → A</code>. Внутренний override <code>B</code> мёртв: на halt-стек попадает только override самой внешней <code>.wohs()</code>-операции.</li>
</ul>
<p>Имя одинаковое. Просто потому, что плоский разделитель <code>></code> не запоминает, <em>где</em> в дереве конструирования каждая стрелка стояла. А дерево там было.</p>
<h2 id="poteryannoe-derevo"><a class="heading-anchor" href="#poteryannoe-derevo">Потерянное дерево</a></h2>
<p>Композиция через <code>withOverrodeHaltState</code> — операция бинарная: левый аргумент (<code>this</code>), правый аргумент (<code>override</code>). Каждое её применение — это новый узел в дереве. У узла два ребёнка: база и override. Если ребёнок сам обёртка, дерево там продолжается ещё одним узлом; если нет, это исходное <code>State</code> — <em>лист</em>. После N применений в дереве N узлов-обёрток и N+1 лист — стандартное свойство бинарного дерева. Например, у <code>minusOne</code> выше: 3 применения (3 узла-обёртки) и 4 листа — <code>invertNumber</code>, <code>plusOne</code>, <code>invertNumber</code>, <code>normalizeNumber</code>. Вот это же дерево явно:</p>
<svg xmlns="http://www.w3.org/2000/svg" id="mermaid-4" width="100%" aria-roledescription="flowchart-v2" class="flowchart" style="font-family:arial,sans-serif;font-size:16px;fill:#333;max-width:416.73614501953125px" viewBox="0 -1.695 416.736 377.305"><style>@keyframes edge-animation-frame{0%{stroke-dashoffset:0}}@keyframes dash{to{stroke-dashoffset:0}}#mermaid-4 .edge-thickness-normal{stroke-width:1px}#mermaid-4 .edge-pattern-solid{stroke-dasharray:0}#mermaid-4 .marker{fill:#333;stroke:#333}#mermaid-4 .marker.cross{stroke:#333}#mermaid-4 svg{font-family:arial,sans-serif;font-size:16px}#mermaid-4 p{margin:0}#mermaid-4 .label{font-family:arial,sans-serif;color:#333}#mermaid-4 span{fill:#333;color:#333}#mermaid-4 .node circle,#mermaid-4 .node path,#mermaid-4 .node rect{fill:#ececff;stroke:#9370db;stroke-width:1px}#mermaid-4 .flowchart-link{stroke:#333;fill:none}#mermaid-4 .edgeLabel,#mermaid-4 .node .label{text-align:center}#mermaid-4 .edgeLabel,#mermaid-4 .edgeLabel p,#mermaid-4 .edgeLabel rect{background-color:rgba(232,232,232,.8)}#mermaid-4 .edgeLabel rect{opacity:.5;fill:rgba(232,232,232,.8)}#mermaid-4 .labelBkg{background-color:rgba(232,232,232,.5)}#mermaid-4 [data-look=neo].node circle,#mermaid-4 [data-look=neo].node rect{stroke:#9370db;filter:drop-shadow(1px 2px 2px #b9b9b9)}#mermaid-4 [data-look=neo].node path{stroke:#9370db;stroke-width:1px}#mermaid-4 [data-look=neo].node .outer-path{filter:drop-shadow(1px 2px 2px #b9b9b9)}#mermaid-4 :root{--mermaid-font-family:arial,sans-serif}</style><marker id="mermaid-4_flowchart-v2-pointEnd" class="marker flowchart-v2" markerHeight="8" markerUnits="userSpaceOnUse" markerWidth="8" orient="auto" refX="5" refY="5" viewBox="0 0 10 10"><path d="m0 0 10 5-10 5z" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></path></marker><marker id="mermaid-4_flowchart-v2-pointStart" class="marker flowchart-v2" markerHeight="8" markerUnits="userSpaceOnUse" markerWidth="8" orient="auto" refX="4.5" refY="5" viewBox="0 0 10 10"><path d="m0 5 10 5V0z" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></path></marker><marker id="mermaid-4_flowchart-v2-pointEnd-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="10.5" orient="auto" refX="11.5" refY="7" viewBox="0 0 11.5 14"><path d="m0 0 11.5 7L0 14z" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></path></marker><marker id="mermaid-4_flowchart-v2-pointStart-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="11.5" orient="auto" refX="1" refY="7" viewBox="0 0 11.5 14"><path d="m0 7 11.5 7V0z" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></path></marker><marker id="mermaid-4_flowchart-v2-circleEnd" class="marker flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="11" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-4_flowchart-v2-circleStart" class="marker flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="-1" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-4_flowchart-v2-circleEnd-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="14" orient="auto" refX="12.25" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-4_flowchart-v2-circleStart-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="14" orient="auto" refX="-2" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-4_flowchart-v2-crossEnd" class="marker cross flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="12" refY="5.2" viewBox="0 0 11 11"><path d="m1 1 9 9m0-9-9 9" class="arrowMarkerPath" style="stroke-width:2;stroke-dasharray:1,0"></path></marker><marker id="mermaid-4_flowchart-v2-crossStart" class="marker cross flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="-1" refY="5.2" viewBox="0 0 11 11"><path d="m1 1 9 9m0-9-9 9" class="arrowMarkerPath" style="stroke-width:2;stroke-dasharray:1,0"></path></marker><marker id="mermaid-4_flowchart-v2-crossEnd-margin" class="marker cross flowchart-v2" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="12" orient="auto" refX="17.7" refY="7.5" viewBox="0 0 15 15"><path d="m1 1 13 13M1 14 14 1" class="arrowMarkerPath" style="stroke-width:2.5"></path></marker><marker id="mermaid-4_flowchart-v2-crossStart-margin" class="marker cross flowchart-v2" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="12" orient="auto" refX="-3.5" refY="7.5" viewBox="0 0 15 15"><path d="m1 1 13 13M1 14 14 1" class="arrowMarkerPath" style="stroke-width:2.5;stroke-dasharray:1,0"></path></marker><g class="root"><g class="edgePaths"><path id="mermaid-4-L_n1_invertA_0" marker-end="url(#mermaid-4_flowchart-v2-pointEnd)" d="m120.613 24.662-8.705 6.825c-8.705 6.824-26.116 20.473-34.747 32.881s-8.483 23.575-8.408 29.158l.074 5.584" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_n1_invertA_0" data-look="classic" data-points="W3sieCI6MTIwLjYxMzQ4MzY1OTI1NTg4LCJ5IjoyNC42NjIyMTc3MzczOTU1NX0seyJ4Ijo2OC4zODA2MDc2MDQ5ODA0NywieSI6NjUuNjA5Mzc1fSx7IngiOjY4Ljg4MDYwNzYwNDk4MDQ3LCJ5IjoxMDMuMTA5Mzc1fV0="></path><path id="mermaid-4-L_n1_n2_0" marker-end="url(#mermaid-4_flowchart-v2-pointEnd)" d="m136.833 24.662 8.706 6.825c8.705 6.824 26.116 20.473 34.821 34.33 8.706 13.857 8.706 27.923 8.706 34.955v7.033" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_n1_n2_0" data-look="classic" data-points="W3sieCI6MTM2LjgzMzAyNjY1NTY4NTUzLCJ5IjoyNC42NjIyMTc3MzczOTU1NX0seyJ4IjoxODkuMDY1OTAyNzA5OTYwOTQsInkiOjY1LjYwOTM3NX0seyJ4IjoxODkuMDY1OTAyNzA5OTYwOTQsInkiOjExMS44MDQ2ODc1fV0="></path><path id="mermaid-4-L_n2_plus_0" marker-end="url(#mermaid-4_flowchart-v2-pointEnd)" d="m182.144 129.743-7.385 8.144c-7.386 8.145-22.156 24.434-29.467 38.161-7.31 13.728-7.162 24.895-7.087 30.478l.074 5.584" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_n2_plus_0" data-look="classic" data-points="W3sieCI6MTgyLjE0Mzg2ODc1NzI0MDk0LCJ5IjoxMjkuNzQyOTg2ODg2MjU5ODR9LHsieCI6MTM3LjgzMjYzMDE1NzQ3MDcsInkiOjE3OC42MDkzNzV9LHsieCI6MTM4LjMzMjYzMDE1NzQ3MDcsInkiOjIxNi4xMDkzNzV9XQ=="></path><path id="mermaid-4-L_n2_n3_0" marker-end="url(#mermaid-4_flowchart-v2-pointEnd)" d="m195.988 129.743 7.385 8.144c7.385 8.145 22.156 24.434 29.541 39.611s7.385 29.242 7.385 36.274v7.033" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_n2_n3_0" data-look="classic" data-points="W3sieCI6MTk1Ljk4NzkzNjY2MjY4MDk0LCJ5IjoxMjkuNzQyOTg2ODg2MjU5ODR9LHsieCI6MjQwLjI5OTE3NTI2MjQ1MTE3LCJ5IjoxNzguNjA5Mzc1fSx7IngiOjI0MC4yOTkxNzUyNjI0NTExNywieSI6MjI0LjgwNDY4NzV9XQ=="></path><path id="mermaid-4-L_n3_invertB_0" marker-end="url(#mermaid-4_flowchart-v2-pointEnd)" d="m231.494 240.462-14.022 8.525c-14.023 8.524-42.067 25.573-56.015 39.681s-13.799 25.275-13.725 30.858l.075 5.584" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_n3_invertB_0" data-look="classic" data-points="W3sieCI6MjMxLjQ5MzkxMDk1NzE2MzUsInkiOjI0MC40NjIzMDkyNDA4MzMwNn0seyJ4IjoxNDcuMzU5OTczOTA3NDcwNywieSI6MjkxLjYwOTM3NX0seyJ4IjoxNDcuODU5OTczOTA3NDcwNywieSI6MzI5LjEwOTM3NX1d"></path><path id="mermaid-4-L_n3_norm_0" marker-end="url(#mermaid-4_flowchart-v2-pointEnd)" d="m249.104 240.462 14.023 8.525c14.022 8.524 42.067 25.573 56.163 39.681 14.097 14.108 14.246 25.275 14.321 30.858l.074 5.584" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_n3_norm_0" data-look="classic" data-points="W3sieCI6MjQ5LjEwNDQzOTU2NzczODg0LCJ5IjoyNDAuNDYyMzA5MjQwODMzMDZ9LHsieCI6MzMzLjIzODM3NjYxNzQzMTY0LCJ5IjoyOTEuNjA5Mzc1fSx7IngiOjMzMy43MzgzNzY2MTc0MzE2NCwieSI6MzI5LjEwOTM3NX1d"></path></g><g class="edgeLabels"><g class="edgeLabel"><foreignObject width="33.75" height="24" class="label" data-id="L_n1_invertA_0" transform="translate(62.865 44.705)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>база</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="57.813" height="24" class="label" data-id="L_n1_n2_0" transform="translate(160.16 53.61)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>override</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="33.75" height="24" class="label" data-id="L_n2_plus_0" transform="translate(120.958 166.61)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>база</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="57.813" height="24" class="label" data-id="L_n2_n3_0" transform="translate(211.393 166.61)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>override</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="33.75" height="24" class="label" data-id="L_n3_invertB_0" transform="translate(156.529 263.777)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>база</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="57.813" height="24" class="label" data-id="L_n3_norm_0" transform="translate(304.332 279.61)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>override</p></span></div></foreignObject></g></g><g class="nodes"><g id="mermaid-4-flowchart-n1-0" class="node default" data-look="classic" transform="translate(128.723 18.305)"><circle r="10.305" class="basic label-container"></circle><g class="label" transform="translate(-2.805 -12)"><rect></rect><foreignObject width="5.609" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>•</p></span></div></foreignObject></g></g><g id="mermaid-4-flowchart-invertA-1" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M27.49 102.61h81.781l1.25.04 1.244.12 1.234.199 1.218.278 1.199.356 1.173.431 1.143.506 1.108.579 1.07.648 1.024.715.978.78.925.84.87.898.81.951.748 1.002c.168.26.337.518.682 1.048l.613 1.089.542 1.126.47 1.159.393 1.186.317 1.21c.06.301.118.603.24 1.226l.16 1.24.08 1.247v1.25l-.08 1.248c-.04.301-.079.602-.16 1.24l-.24 1.227-.317 1.209-.394 1.186-.469 1.159-.542 1.126-.613 1.089-.682 1.048-.748 1.001-.81.952-.87.898-.925.84-.978.78-1.025.715-1.069.648-1.108.578-1.143.506-1.173.432-1.199.356-1.218.278-1.234.2-1.244.12c-.297.009-.594.018-1.25.04H27.49l-1.25-.04c-.408-.04-.816-.08-1.244-.12l-1.234-.2-1.218-.278-1.199-.356-1.173-.432-1.143-.506-1.108-.578-1.069-.648-1.025-.715-.977-.78-.926-.84-.87-.898-.81-.952-.747-1.001-.682-1.048-.614-1.09-.542-1.125-.47-1.159-.393-1.186-.317-1.21-.24-1.226c-.06-.48-.123-.959-.159-1.24L8 122.734v-1.25l.08-1.247.16-1.24.239-1.227.317-1.209.394-1.186.469-1.159.542-1.126.614-1.09.682-1.047.747-1.002.81-.951.87-.898c.256-.233.511-.465.926-.84l.977-.78 1.025-.715 1.069-.648 1.108-.579 1.143-.506 1.173-.431 1.199-.356 1.218-.278 1.234-.2 1.245-.12 1.249-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M27.49 102.61h81.781m-81.781 0h81.781m0 0 1.25.04m-1.25-.04 1.25.04m0 0 1.244.12m-1.244-.12 1.244.12m0 0 1.234.199m-1.234-.2 1.234.2m0 0 1.218.278M113 102.97l1.218.278m0 0 1.199.356m-1.199-.356 1.199.356m0 0 1.173.431m-1.173-.431 1.173.431m0 0 1.143.506m-1.143-.506 1.143.506m0 0 1.108.579m-1.108-.579 1.108.579m0 0 1.07.648m-1.07-.648 1.07.648m0 0 1.024.715m-1.025-.715 1.025.715m0 0 .978.78m-.978-.78c.34.27.68.541.978.78m0 0 .925.84m-.925-.84.925.84m0 0 .87.898m-.87-.898.87.898m0 0 .81.951m-.81-.951.81.951m0 0 .748 1.002m-.748-1.002.748 1.002m0 0 .682 1.048m-.682-1.048.682 1.048m0 0 .613 1.089m-.613-1.09c.235.42.471.838.613 1.09m0 0 .542 1.126m-.542-1.126.542 1.126m0 0 .47 1.159m-.47-1.159.47 1.159m0 0 .393 1.186m-.394-1.186.394 1.186m0 0 .317 1.21m-.317-1.21.317 1.21m0 0 .24 1.226m-.24-1.227.24 1.227m0 0 .16 1.24m-.16-1.24.16 1.24m0 0 .08 1.247m-.08-1.247c.02.335.042.671.08 1.247m0 0v1.25m0-1.25v1.25m0 0-.08 1.248m.08-1.248c-.025.37-.048.741-.08 1.248m0 0-.16 1.24m.16-1.24-.16 1.24m0 0-.24 1.227m.24-1.227-.24 1.227m0 0-.317 1.209m.317-1.21-.317 1.21m0 0-.394 1.186m.394-1.186-.394 1.186m0 0-.469 1.159m.47-1.159-.47 1.159m0 0-.542 1.126m.542-1.126c-.16.33-.318.662-.542 1.126m0 0-.613 1.089m.613-1.09-.613 1.09m0 0-.682 1.048m.682-1.048-.682 1.048m0 0-.748 1.001m.748-1.001-.748 1.001m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898-.87.898m0 0-.925.84m.925-.84-.925.84m0 0-.978.78m.978-.78-.978.78m0 0-1.025.715m1.025-.715-1.025.715m0 0-1.069.648m1.07-.648-1.07.648m0 0-1.108.578m1.108-.578-1.108.578m0 0-1.143.506m1.143-.506-1.143.506m0 0-1.173.432m1.173-.432-1.173.432m0 0-1.199.356m1.199-.356-1.199.356m0 0-1.218.278m1.218-.278-1.218.278m0 0-1.234.2m1.234-.2-1.234.2m0 0-1.244.12m1.244-.12-1.244.12m0 0-1.25.04m1.25-.04-1.25.04m0 0H27.49m81.781 0H27.49m0 0-1.25-.04m1.25.04-1.25-.04m0 0c-.435-.043-.87-.085-1.244-.12m1.245.12-1.245-.12m0 0-1.234-.2m1.234.2-1.234-.2m0 0-1.218-.278m1.218.278-1.218-.278m0 0-1.199-.356m1.199.356-1.199-.356m0 0-1.173-.432m1.173.432-1.173-.432m0 0-1.143-.506m1.143.506-1.143-.506m0 0-1.108-.578m1.108.578-1.108-.578m0 0-1.069-.648m1.069.648-1.069-.648m0 0-1.025-.715m1.025.715-1.025-.715m0 0-.977-.78m.977.78-.977-.78m0 0-.926-.84m.926.84-.926-.84m0 0-.87-.898m.87.898-.87-.898m0 0-.81-.952m.81.952-.81-.952m0 0-.747-1.001m.747 1.001-.747-1.001m0 0-.682-1.048m.682 1.048-.682-1.048m0 0-.614-1.09m.614 1.09-.614-1.09m0 0-.542-1.125m.542 1.126-.542-1.126m0 0-.47-1.159m.47 1.159-.47-1.159m0 0-.393-1.186m.394 1.186-.394-1.186m0 0-.317-1.21m.317 1.21-.317-1.21m0 0-.24-1.226m.24 1.227-.24-1.227m0 0-.159-1.24m.16 1.24-.16-1.24m0 0L8 122.734m.08 1.248L8 122.734m0 0v-1.25m0 1.25v-1.25m0 0 .08-1.247M8 121.484l.08-1.247m0 0 .16-1.24m-.16 1.24.16-1.24m0 0 .239-1.227m-.24 1.227c.05-.247.097-.494.24-1.227m0 0 .317-1.209m-.317 1.21.317-1.21m0 0 .394-1.186m-.394 1.186.394-1.186m0 0 .469-1.159m-.47 1.159c.145-.356.289-.71.47-1.159m0 0 .542-1.126m-.542 1.126.542-1.126m0 0 .614-1.09m-.614 1.09.614-1.09m0 0 .682-1.047m-.682 1.048.682-1.048m0 0 .747-1.002m-.747 1.002.747-1.002m0 0 .81-.951m-.81.951.81-.951m0 0 .87-.898m-.87.898.87-.898m0 0 .926-.84m-.926.84c.28-.255.56-.51.926-.84m0 0 .977-.78m-.977.78c.255-.205.51-.408.977-.78m0 0 1.025-.715m-1.025.715 1.025-.715m0 0 1.069-.648m-1.069.648 1.069-.648m0 0 1.108-.579m-1.108.579 1.108-.579m0 0 1.143-.506m-1.143.506 1.143-.506m0 0 1.173-.431m-1.173.431 1.173-.431m0 0 1.199-.356m-1.199.356 1.199-.356m0 0 1.218-.278m-1.218.278 1.218-.278m0 0 1.234-.2m-1.234.2 1.234-.2m0 0 1.245-.12m-1.245.12 1.245-.12m0 0 1.249-.04m-1.25.04 1.25-.04m0 0"></path></g><g class="label" transform="translate(20.365 110.11)"><rect></rect><foreignObject width="96.031" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>invertNumber</p></span></div></foreignObject></g></g><g id="mermaid-4-flowchart-n2-3" class="node default" data-look="classic" transform="translate(189.066 122.11)"><circle r="10.305" class="basic label-container"></circle><g class="label" transform="translate(-2.805 -12)"><rect></rect><foreignObject width="5.609" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>•</p></span></div></foreignObject></g></g><g id="mermaid-4-flowchart-plus-5" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M115.16 215.61h45.345l1.249.04 1.244.12 1.234.199 1.219.278 1.198.356 1.173.431 1.143.506 1.108.579 1.07.648 1.025.715.977.78.925.84.87.898.81.951.748 1.002.682 1.048.613 1.089.543 1.126.469 1.159.394 1.186.317 1.21.239 1.226.16 1.24.08 1.247v1.25l-.08 1.248-.16 1.24-.24 1.227-.316 1.209-.394 1.186-.47 1.159-.542 1.126-.613 1.089-.682 1.048-.748 1.001-.81.952-.87.898-.925.84-.977.78-1.026.715-1.069.648c-.41.215-.822.43-1.108.578l-1.143.506-1.173.432-1.198.356-1.219.278-1.234.2-1.244.12c-.268.008-.536.016-1.25.04h-45.343l-1.25-.04c-.409-.04-.818-.08-1.244-.12l-1.234-.2c-.414-.095-.828-.19-1.219-.278l-1.198-.356-1.173-.432-1.143-.506-1.108-.578-1.07-.648-1.024-.715-.978-.78-.925-.84-.87-.898-.81-.952-.748-1.001c-.265-.408-.53-.817-.682-1.048l-.613-1.09-.542-1.125-.47-1.159-.393-1.186-.317-1.21-.24-1.226-.16-1.24-.08-1.248v-1.25l.08-1.247.16-1.24.24-1.227.317-1.209.394-1.186.469-1.159.542-1.126.613-1.09.682-1.047.748-1.002.81-.951.87-.898.925-.84.978-.78 1.025-.715 1.069-.648 1.108-.579c.287-.126.574-.253 1.143-.506l1.173-.431 1.198-.356 1.22-.278 1.233-.2 1.244-.12 1.25-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M115.16 215.61h45.345m-45.344 0h45.344m0 0 1.249.04m-1.25-.04 1.25.04m0 0 1.244.12m-1.244-.12 1.244.12m0 0 1.234.199m-1.234-.2 1.234.2m0 0 1.219.278m-1.219-.278 1.219.278m0 0 1.198.356m-1.198-.356 1.198.356m0 0 1.173.431m-1.173-.431 1.173.431m0 0 1.143.506m-1.143-.506 1.143.506m0 0 1.108.579m-1.108-.579 1.108.579m0 0 1.07.648m-1.07-.648 1.07.648m0 0 1.025.715m-1.026-.715 1.026.715m0 0 .977.78m-.977-.78.977.78m0 0 .925.84m-.925-.84c.257.232.513.465.925.84m0 0 .87.898m-.87-.898c.205.21.41.422.87.898m0 0 .81.951m-.81-.951.81.951m0 0 .748 1.002m-.748-1.002.748 1.002m0 0 .682 1.048m-.682-1.048.682 1.048m0 0 .613 1.089m-.613-1.09.613 1.09m0 0 .543 1.126m-.543-1.126.543 1.126m0 0 .469 1.159m-.47-1.159.47 1.159m0 0 .394 1.186m-.394-1.186.394 1.186m0 0 .317 1.21m-.317-1.21.317 1.21m0 0c.065.336.13.673.239 1.226m-.24-1.227.24 1.227m0 0 .16 1.24m-.16-1.24.16 1.24m0 0 .08 1.247m-.08-1.247c.02.33.042.658.08 1.247m0 0v1.25m0-1.25v1.25m0 0-.08 1.248m.08-1.248-.08 1.248m0 0-.16 1.24m.16-1.24-.16 1.24m0 0-.24 1.227m.24-1.227-.24 1.227m0 0-.316 1.209m.317-1.21-.317 1.21m0 0-.394 1.186m.394-1.186-.394 1.186m0 0-.47 1.159m.47-1.159-.47 1.159m0 0-.542 1.126m.543-1.126-.543 1.126m0 0-.613 1.089m.613-1.09-.613 1.09m0 0-.682 1.048m.682-1.048-.682 1.048m0 0-.748 1.001m.748-1.001-.748 1.001m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898c-.28.29-.56.58-.87.898m0 0-.925.84m.925-.84-.925.84m0 0-.977.78m.977-.78-.977.78m0 0-1.026.715m1.026-.715-1.026.715m0 0c-.39.237-.78.474-1.069.648m1.07-.648-1.07.648m0 0-1.108.578m1.108-.578-1.108.578m0 0-1.143.506m1.143-.506-1.143.506m0 0-1.173.432m1.173-.432-1.173.432m0 0-1.198.356m1.198-.356-1.198.356m0 0-1.219.278m1.219-.278-1.219.278m0 0-1.234.2m1.234-.2-1.234.2m0 0c-.27.025-.54.051-1.244.12m1.244-.12-1.244.12m0 0-1.25.04m1.25-.04-1.25.04m0 0h-45.343m45.344 0H115.16m0 0-1.25-.04m1.25.04-1.25-.04m0 0-1.244-.12m1.244.12-1.244-.12m0 0-1.234-.2m1.234.2-1.234-.2m0 0-1.219-.278m1.22.278-1.22-.278m0 0-1.198-.356m1.198.356-1.198-.356m0 0-1.173-.432m1.173.432-1.173-.432m0 0-1.143-.506m1.143.506-1.143-.506m0 0-1.108-.578m1.108.578-1.108-.578m0 0-1.07-.648m1.07.648-1.07-.648m0 0-1.024-.715m1.025.715-1.025-.715m0 0-.978-.78m.978.78-.978-.78m0 0-.925-.84m.925.84-.925-.84m0 0-.87-.898m.87.898-.87-.898m0 0-.81-.952m.81.952-.81-.952m0 0-.748-1.001m.748 1.001-.748-1.001m0 0-.682-1.048m.682 1.048-.682-1.048m0 0-.613-1.09m.613 1.09-.613-1.09m0 0-.542-1.125m.542 1.126-.542-1.126m0 0-.47-1.159m.47 1.159-.47-1.159m0 0-.393-1.186m.394 1.186-.394-1.186m0 0-.317-1.21m.317 1.21-.317-1.21m0 0-.24-1.226m.24 1.227-.24-1.227m0 0c-.034-.273-.07-.546-.16-1.24m.16 1.24c-.048-.377-.096-.753-.16-1.24m0 0-.08-1.248m.08 1.248-.08-1.248m0 0v-1.25m0 1.25v-1.25m0 0 .08-1.247m-.08 1.247.08-1.247m0 0 .16-1.24m-.16 1.24.16-1.24m0 0 .24-1.227m-.24 1.227.24-1.227m0 0 .317-1.209m-.317 1.21.317-1.21m0 0 .394-1.186m-.394 1.186.394-1.186m0 0 .469-1.159m-.47 1.159.47-1.159m0 0 .542-1.126m-.542 1.126.542-1.126m0 0 .613-1.09m-.613 1.09.613-1.09m0 0 .682-1.047m-.682 1.048.682-1.048m0 0 .748-1.002m-.748 1.002.748-1.002m0 0 .81-.951m-.81.951.81-.951m0 0 .87-.898m-.87.898.87-.898m0 0 .925-.84m-.925.84c.32-.292.642-.583.925-.84m0 0 .978-.78m-.978.78.978-.78m0 0c.25-.175.5-.35 1.025-.715m-1.025.715 1.025-.715m0 0 1.069-.648m-1.07.648c.25-.15.497-.301 1.07-.648m0 0 1.108-.579m-1.108.579 1.108-.579m0 0 1.143-.506m-1.143.506 1.143-.506m0 0 1.173-.431m-1.173.431 1.173-.431m0 0 1.198-.356m-1.198.356 1.198-.356m0 0 1.22-.278m-1.22.278 1.22-.278m0 0 1.233-.2m-1.234.2 1.234-.2m0 0 1.244-.12m-1.244.12 1.244-.12m0 0 1.25-.04m-1.25.04c.347-.01.694-.022 1.25-.04m0 0"></path></g><g class="label" transform="translate(108.036 223.11)"><rect></rect><foreignObject width="59.594" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>plusOne</p></span></div></foreignObject></g></g><g id="mermaid-4-flowchart-n3-7" class="node default" data-look="classic" transform="translate(240.3 235.11)"><circle r="10.305" class="basic label-container"></circle><g class="label" transform="translate(-2.805 -12)"><rect></rect><foreignObject width="5.609" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>•</p></span></div></foreignObject></g></g><g id="mermaid-4-flowchart-invertB-9" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M106.47 328.61h81.78c.486.015.972.03 1.25.04l1.244.12 1.234.199 1.219.278 1.198.356 1.173.431c.23.103.461.205 1.143.506l1.109.579 1.069.648 1.025.715.977.78.925.84.87.898.81.951.748 1.002.682 1.048.613 1.089.543 1.126.469 1.159.394 1.186.317 1.21.239 1.226.16 1.24.08 1.247v1.25c-.03.447-.058.893-.08 1.248l-.16 1.24-.24 1.227-.316 1.209-.394 1.186-.47 1.159-.542 1.126-.613 1.089-.682 1.048-.748 1.001-.81.952-.87.898-.925.84-.977.78-1.025.715-1.07.648-1.108.578-1.143.506-1.173.432-1.198.356-1.219.278-1.234.2c-.38.036-.758.072-1.244.12l-1.25.04h-81.78l-1.25-.04c-.456-.045-.912-.089-1.244-.12l-1.234-.2c-.436-.1-.872-.2-1.219-.278l-1.198-.356-1.173-.432-1.143-.506-1.109-.578-1.069-.648-1.025-.715-.977-.78-.925-.84-.87-.898-.81-.952-.748-1.001-.682-1.048-.613-1.09-.543-1.125-.469-1.159-.394-1.186-.317-1.21-.239-1.226-.16-1.24-.08-1.248v-1.25l.08-1.247.16-1.24.24-1.227.316-1.209.394-1.186.47-1.159.542-1.126.613-1.09.682-1.047.748-1.002.81-.951.87-.898.925-.84.977-.78 1.025-.715 1.07-.648 1.108-.579 1.143-.506 1.173-.431 1.198-.356 1.219-.278 1.234-.2 1.244-.12 1.25-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M106.47 328.61h81.78m-81.78 0h81.78m0 0 1.25.04m-1.25-.04c.282.008.563.017 1.25.04m0 0 1.244.12m-1.244-.12c.422.04.843.08 1.244.12m0 0 1.234.199m-1.234-.2 1.234.2m0 0 1.219.278m-1.219-.278 1.219.278m0 0 1.198.356m-1.198-.356 1.198.356m0 0 1.173.431m-1.173-.431 1.173.431m0 0 1.143.506m-1.143-.506 1.143.506m0 0 1.109.579m-1.109-.579 1.109.579m0 0 1.069.648m-1.07-.648 1.07.648m0 0 1.025.715m-1.025-.715 1.025.715m0 0 .977.78m-.977-.78.977.78m0 0 .925.84m-.925-.84.925.84m0 0 .87.898m-.87-.898.87.898m0 0 .81.951m-.81-.951.81.951m0 0 .748 1.002m-.748-1.002.748 1.002m0 0 .682 1.048m-.682-1.048.682 1.048m0 0 .613 1.089m-.613-1.09.613 1.09m0 0 .543 1.126m-.543-1.126.543 1.126m0 0 .469 1.159m-.47-1.159.47 1.159m0 0 .394 1.186m-.394-1.186.394 1.186m0 0 .317 1.21m-.317-1.21.317 1.21m0 0 .239 1.226m-.24-1.227.24 1.227m0 0 .16 1.24m-.16-1.24.16 1.24m0 0 .08 1.247m-.08-1.247.08 1.247m0 0v1.25m0-1.25v1.25m0 0-.08 1.248m.08-1.248-.08 1.248m0 0-.16 1.24m.16-1.24-.16 1.24m0 0c-.08.412-.16.824-.24 1.227m.24-1.227-.24 1.227m0 0-.316 1.209m.317-1.21-.317 1.21m0 0-.394 1.186m.394-1.186c-.08.237-.158.474-.394 1.186m0 0-.47 1.159m.47-1.159-.47 1.159m0 0-.542 1.126m.543-1.126-.543 1.126m0 0-.613 1.089m.613-1.09-.613 1.09m0 0-.682 1.048m.682-1.048-.682 1.048m0 0-.748 1.001m.748-1.001-.748 1.001m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898-.87.898m0 0-.925.84m.925-.84-.925.84m0 0-.977.78m.977-.78-.977.78m0 0-1.025.715m1.025-.715-1.025.715m0 0-1.07.648m1.07-.648-1.07.648m0 0-1.108.578m1.109-.578-1.109.578m0 0-1.143.506m1.143-.506-1.143.506m0 0-1.173.432m1.173-.432-1.173.432m0 0-1.198.356m1.198-.356-1.198.356m0 0-1.219.278m1.219-.278-1.219.278m0 0-1.234.2m1.234-.2c-.466.075-.932.15-1.234.2m0 0-1.244.12m1.244-.12-1.244.12m0 0c-.344.01-.688.021-1.25.04m1.25-.04-1.25.04m0 0h-81.78m81.78 0h-81.78m0 0-1.25-.04m1.25.04-1.25-.04m0 0-1.244-.12m1.244.12-1.244-.12m0 0-1.234-.2m1.234.2-1.234-.2m0 0-1.219-.278m1.219.278-1.219-.278m0 0-1.198-.356m1.198.356-1.198-.356m0 0-1.173-.432m1.173.432-1.173-.432m0 0-1.143-.506m1.143.506-1.143-.506m0 0L96.9 365.1m1.109.578L96.9 365.1m0 0-1.069-.648m1.07.648-1.07-.648m0 0-1.025-.715m1.025.715-1.025-.715m0 0-.977-.78m.977.78-.977-.78m0 0-.925-.84m.925.84-.925-.84m0 0-.87-.898m.87.898-.87-.898m0 0-.81-.952m.81.952-.81-.952m0 0-.748-1.001m.748 1.001-.748-1.001m0 0-.682-1.048m.682 1.048-.682-1.048m0 0-.613-1.09m.613 1.09-.613-1.09m0 0-.543-1.125m.543 1.126-.543-1.126m0 0-.469-1.159m.47 1.159-.47-1.159m0 0-.394-1.186m.394 1.186-.394-1.186m0 0-.317-1.21m.317 1.21-.317-1.21m0 0-.239-1.226m.24 1.227-.24-1.227m0 0-.16-1.24m.16 1.24-.16-1.24m0 0-.08-1.248m.08 1.248-.08-1.248m0 0v-1.25m0 1.25v-1.25m0 0 .08-1.247m-.08 1.247.08-1.247m0 0c.04-.301.078-.602.16-1.24m-.16 1.24.16-1.24m0 0 .24-1.227m-.24 1.227.24-1.227m0 0 .316-1.209m-.317 1.21.317-1.21m0 0 .394-1.186m-.394 1.186.394-1.186m0 0 .47-1.159m-.47 1.159.47-1.159m0 0 .542-1.126m-.543 1.126.543-1.126m0 0 .613-1.09m-.613 1.09.613-1.09m0 0 .682-1.047m-.682 1.048.682-1.048m0 0 .748-1.002m-.748 1.002.748-1.002m0 0 .81-.951m-.81.951.81-.951m0 0 .87-.898m-.87.898.87-.898m0 0 .925-.84m-.925.84.925-.84m0 0 .977-.78m-.977.78.977-.78m0 0 1.025-.715m-1.025.715 1.025-.715m0 0 1.07-.648m-1.07.648 1.07-.648m0 0 1.108-.579m-1.109.579 1.109-.579m0 0 1.143-.506m-1.143.506 1.143-.506m0 0 1.173-.431m-1.173.431 1.173-.431m0 0 1.198-.356m-1.198.356 1.198-.356m0 0 1.219-.278m-1.219.278 1.219-.278m0 0 1.234-.2m-1.234.2 1.234-.2m0 0c.325-.03.65-.062 1.244-.12m-1.244.12 1.244-.12m0 0 1.25-.04m-1.25.04 1.25-.04m0 0"></path></g><g class="label" transform="translate(99.344 336.11)"><rect></rect><foreignObject width="96.031" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>invertNumber</p></span></div></foreignObject></g></g><g id="mermaid-4-flowchart-norm-11" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M277.23 328.61h112.016l1.25.04 1.244.12 1.234.199 1.218.278 1.199.356 1.173.431 1.143.506 1.108.579 1.07.648 1.024.715.978.78c.29.262.579.525.925.84l.87.898.81.951.748 1.002.681 1.048.614 1.089.542 1.126.47 1.159.393 1.186.317 1.21.24 1.226.16 1.24c.016.258.032.516.08 1.247v1.25l-.08 1.248c-.052.395-.103.79-.16 1.24l-.24 1.227-.317 1.209-.394 1.186-.469 1.159-.542 1.126-.614 1.089-.681 1.048-.748 1.001-.81.952-.87.898-.925.84-.978.78-1.025.715-1.069.648c-.381.2-.763.398-1.108.578l-1.143.506-1.173.432-1.199.356-1.218.278-1.234.2c-.457.043-.914.087-1.244.12l-1.25.04H277.231c-.371-.013-.742-.024-1.25-.04l-1.244-.12-1.234-.2-1.219-.278-1.198-.356-1.173-.432-1.143-.506-1.108-.578-1.07-.648-1.025-.715-.977-.78-.925-.84-.87-.898-.81-.952-.748-1.001-.682-1.048-.613-1.09-.542-1.125-.47-1.159-.393-1.186-.318-1.21-.238-1.226-.16-1.24-.08-1.248v-1.25l.08-1.247.16-1.24.238-1.227.318-1.209.393-1.186.47-1.159.542-1.126.613-1.09c.21-.32.419-.643.682-1.047l.748-1.002.81-.951.87-.898.925-.84.977-.78 1.026-.715 1.069-.648 1.108-.579 1.143-.506 1.173-.431 1.198-.356 1.219-.278 1.234-.2 1.244-.12 1.25-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M277.23 328.61h112.016m-112.015 0h112.015m0 0 1.25.04m-1.25-.04c.267.008.533.016 1.25.04m0 0 1.244.12m-1.244-.12 1.244.12m0 0c.255.04.51.082 1.234.199m-1.234-.2 1.234.2m0 0 1.218.278m-1.218-.278 1.218.278m0 0 1.199.356m-1.199-.356 1.199.356m0 0 1.173.431m-1.173-.431 1.173.431m0 0 1.143.506m-1.143-.506 1.143.506m0 0 1.108.579m-1.108-.579 1.108.579m0 0 1.07.648m-1.07-.648 1.07.648m0 0 1.024.715m-1.025-.715 1.025.715m0 0 .978.78m-.978-.78.978.78m0 0 .925.84m-.925-.84.925.84m0 0 .87.898m-.87-.898.87.898m0 0 .81.951m-.81-.951.81.951m0 0 .748 1.002m-.748-1.002.748 1.002m0 0 .681 1.048m-.681-1.048.681 1.048m0 0 .614 1.089m-.614-1.09.614 1.09m0 0 .542 1.126m-.542-1.126.542 1.126m0 0 .47 1.159m-.47-1.159.47 1.159m0 0 .393 1.186m-.394-1.186c.13.388.258.777.394 1.186m0 0 .317 1.21m-.317-1.21.317 1.21m0 0 .24 1.226m-.24-1.227.24 1.227m0 0c.043.337.086.675.16 1.24m-.16-1.24.16 1.24m0 0 .08 1.247m-.08-1.247.08 1.247m0 0v1.25m0-1.25v1.25m0 0c-.025.378-.05.755-.08 1.248m.08-1.248-.08 1.248m0 0-.16 1.24m.16-1.24c-.038.286-.075.572-.16 1.24m0 0-.24 1.227m.24-1.227-.24 1.227m0 0-.317 1.209m.317-1.21-.317 1.21m0 0-.394 1.186m.394-1.186-.394 1.186m0 0-.469 1.159m.47-1.159-.47 1.159m0 0-.542 1.126m.542-1.126-.542 1.126m0 0-.614 1.089m.614-1.09-.614 1.09m0 0-.681 1.048m.681-1.048-.681 1.048m0 0-.748 1.001m.748-1.001-.748 1.001m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898-.87.898m0 0-.925.84m.925-.84-.925.84m0 0c-.275.22-.55.44-.978.78m.978-.78-.978.78m0 0-1.025.715m1.025-.715-1.025.715m0 0-1.069.648m1.07-.648-1.07.648m0 0-1.108.578m1.108-.578-1.108.578m0 0-1.143.506m1.143-.506-1.143.506m0 0-1.173.432m1.173-.432-1.173.432m0 0-1.199.356m1.199-.356-1.199.356m0 0-1.218.278m1.218-.278-1.218.278m0 0-1.234.2m1.234-.2-1.234.2m0 0c-.364.034-.727.07-1.244.12m1.244-.12-1.244.12m0 0-1.25.04m1.25-.04-1.25.04m0 0H277.231m112.015 0H277.231m0 0-1.25-.04m1.25.04-1.25-.04m0 0c-.296-.03-.593-.058-1.244-.12m1.244.12-1.244-.12m0 0-1.234-.2m1.234.2-1.234-.2m0 0-1.219-.278m1.219.278-1.219-.278m0 0-1.198-.356m1.198.356-1.198-.356m0 0c-.273-.1-.546-.2-1.173-.432m1.173.432-1.173-.432m0 0-1.143-.506m1.143.506-1.143-.506m0 0-1.108-.578m1.108.578-1.108-.578m0 0-1.07-.648m1.07.648-1.07-.648m0 0-1.025-.715m1.026.715-1.026-.715m0 0-.977-.78m.977.78-.977-.78m0 0-.925-.84m.925.84-.925-.84m0 0-.87-.898m.87.898-.87-.898m0 0-.81-.952m.81.952-.81-.952m0 0-.748-1.001m.748 1.001-.748-1.001m0 0-.682-1.048m.682 1.048-.682-1.048m0 0-.613-1.09m.613 1.09-.613-1.09m0 0-.542-1.125m.542 1.126-.542-1.126m0 0-.47-1.159m.47 1.159-.47-1.159m0 0-.393-1.186m.393 1.186-.393-1.186m0 0-.318-1.21m.318 1.21-.318-1.21m0 0-.238-1.226m.238 1.227-.238-1.227m0 0-.16-1.24m.16 1.24-.16-1.24m0 0-.08-1.248m.08 1.248-.08-1.248m0 0v-1.25m0 1.25v-1.25m0 0 .08-1.247m-.08 1.247.08-1.247m0 0 .16-1.24m-.16 1.24.16-1.24m0 0 .238-1.227m-.238 1.227.238-1.227m0 0 .318-1.209m-.318 1.21.318-1.21m0 0 .393-1.186m-.393 1.186.393-1.186m0 0 .47-1.159m-.47 1.159.47-1.159m0 0c.127-.265.254-.53.542-1.126m-.542 1.126.542-1.126m0 0 .613-1.09m-.613 1.09.613-1.09m0 0 .682-1.047m-.682 1.048.682-1.048m0 0 .748-1.002m-.748 1.002.748-1.002m0 0 .81-.951m-.81.951.81-.951m0 0 .87-.898m-.87.898c.242-.25.483-.5.87-.898m0 0 .925-.84m-.925.84.925-.84m0 0 .977-.78m-.977.78.977-.78m0 0 1.026-.715m-1.026.715 1.026-.715m0 0 1.069-.648m-1.07.648 1.07-.648m0 0 1.108-.579m-1.108.579 1.108-.579m0 0 1.143-.506m-1.143.506 1.143-.506m0 0 1.173-.431m-1.173.431 1.173-.431m0 0 1.198-.356m-1.198.356 1.198-.356m0 0 1.219-.278m-1.219.278 1.219-.278m0 0 1.234-.2m-1.234.2 1.234-.2m0 0 1.244-.12m-1.244.12 1.244-.12m0 0 1.25-.04m-1.25.04 1.25-.04m0 0"></path></g><g class="label" transform="translate(270.106 336.11)"><rect></rect><foreignObject width="126.266" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>normalizeNumber</p></span></div></foreignObject></g></g></g></g><defs><filter id="mermaid-4-drop-shadow" width="130%" height="130%"><feDropShadow dx="4" dy="4" flood-color="#000" flood-opacity=".06" stdDeviation="0"></feDropShadow></filter></defs><defs><filter id="mermaid-4-drop-shadow-small" width="150%" height="150%"><feDropShadow dx="2" dy="2" flood-color="#000" flood-opacity=".06" stdDeviation="0"></feDropShadow></filter></defs></svg>
<p>Имя же — <em>плоское</em> — конкатенация имён через <code>></code>. Из бинарного дерева получается строка, и обратно строка восстанавливает дерево только если соглашение об именовании <em>биективно</em>. У плоской <code>></code>-нотации оно <em>не</em> биективно.</p>
<p>Сколько разных деревьев соответствует одному имени с N разделителями? Стандартный ответ из комбинаторики: <a href="https://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D0%BB%D0%B0_%D0%9A%D0%B0%D1%82%D0%B0%D0%BB%D0%B0%D0%BD%D0%B0">число Каталана</a> <math><msub><mi>C</mi><mi>N</mi></msub></math>.</p>
<p><math><mrow><msub><mi>C</mi><mi>n</mi></msub><mo>=</mo><mfrac><mn>1</mn><mrow><mi>n</mi><mo>+</mo><mn>1</mn></mrow></mfrac><mrow><mo fence="true">(</mo><mfrac linethickness="0px"><mrow><mn>2</mn><mi>n</mi></mrow><mi>n</mi></mfrac><mo fence="true">)</mo></mrow></mrow></math> — это та самая последовательность, которая считает: число бинарных деревьев с n+1 листьями, число правильных скобочных последовательностей длины 2n, число путей Дика длины 2n, число триангуляций (n+2)-угольника, и ещё с десяток изоморфных комбинаторных объектов. Одно из тех чисел, которые «вылезают отовсюду».</p>
<p>В нашем случае самая близкая проекция — расстановка скобок: для произвольного бинарного оператора <code>•</code> число способов расставить скобки в выражении <code>a • b • c • ...</code> (n+1 операндов) равно <math><msub><mi>C</mi><mi>n</mi></msub></math>. <code>withOverrodeHaltState</code> — как раз такой оператор, и каждой расстановке скобок соответствует своё дерево композиции.</p>
<p>Первые восемь значений: <math><mrow><msub><mi>C</mi><mn>0</mn></msub><mo>=</mo><mn>1</mn></mrow></math>, <math><mrow><msub><mi>C</mi><mn>1</mn></msub><mo>=</mo><mn>1</mn></mrow></math>, <math><mrow><msub><mi>C</mi><mn>2</mn></msub><mo>=</mo><mn>2</mn></mrow></math>, <math><mrow><msub><mi>C</mi><mn>3</mn></msub><mo>=</mo><mn>5</mn></mrow></math>, <math><mrow><msub><mi>C</mi><mn>4</mn></msub><mo>=</mo><mn>14</mn></mrow></math>, <math><mrow><msub><mi>C</mi><mn>5</mn></msub><mo>=</mo><mn>42</mn></mrow></math>, <math><mrow><msub><mi>C</mi><mn>6</mn></msub><mo>=</mo><mn>132</mn></mrow></math>, <math><mrow style="font-weight:bold;"><msub><mi>𝑪</mi><mstyle style="font-style:italic;font-family:Cambria, &#x27;Times New Roman&#x27;, serif;font-weight:bold;"><mn>7</mn></mstyle></msub><mo>=</mo><mstyle style="font-style:italic;font-family:Cambria, &#x27;Times New Roman&#x27;, serif;font-weight:bold;"><mn>4</mn></mstyle><mstyle style="font-style:italic;font-family:Cambria, &#x27;Times New Roman&#x27;, serif;font-weight:bold;"><mn>2</mn></mstyle><mstyle style="font-style:italic;font-family:Cambria, &#x27;Times New Roman&#x27;, serif;font-weight:bold;"><mn>9</mn></mstyle></mrow></math>.</p>
<p>Для нашего <code>A>B>B>A>A>B>B>A</code> это значит: 429 различных деревьев композиции, у каждого по-своему расставлены родительско-дочерние связи, дают одну и ту же строку. И это не теоретическая верхняя оценка — это точное число несовпадающих структур, прячущихся за одним именем.</p>
<p>Пример поменьше — <code>A>B>C>D</code> (три разделителя). По формуле <math><mrow><msub><mi>C</mi><mn>3</mn></msub><mo>=</mo><mn>5</mn></mrow></math>; вот эти пять деревьев, сконструированные в коде:</p>
<pre class="shiki shiki-themes github-light github-dark language-javascript" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// 1. полностью левоассоциативное</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">((</span><span style="color:#005CC5;--shiki-dark:#79B8FF">A</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">wohs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">B</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">wohs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">C</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">wohs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">D</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// 2.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">A</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">wohs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">B</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">wohs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">C</span><span style="color:#24292E;--shiki-dark:#E1E4E8">))).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">wohs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">D</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// 3.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">A</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">wohs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">B</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">wohs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">C</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">wohs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">D</span><span style="color:#24292E;--shiki-dark:#E1E4E8">))</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// 4.</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">A</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">wohs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">((</span><span style="color:#005CC5;--shiki-dark:#79B8FF">B</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">wohs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">C</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">wohs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">D</span><span style="color:#24292E;--shiki-dark:#E1E4E8">))</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// 5. полностью правоассоциативное</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">A</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">wohs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">B</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">wohs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">C</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">wohs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">D</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)))</span></span></code></pre>
<p>Все пять схлопываются в одну и ту же плоскую строку <code>A>B>C>D</code>. В скобочной нотации, которая придёт в v7, каждое получит уникальное имя — об этом ниже.</p>
<h2 id="pochinka-skobkami"><a class="heading-anchor" href="#pochinka-skobkami">Починка скобками</a></h2>
<p>Если плоский разделитель теряет дерево, то скобки его сохраняют. v7 (<a href="https://github.com/mellonis/turing-machine-js/issues/148"><code>turing-machine-js#148</code></a>) меняет соглашение об именовании на:</p>
<pre class="shiki shiki-themes github-light github-dark language-javascript" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">state.#name </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">this</span><span style="color:#032F62;--shiki-dark:#9ECBFF">.</span><span style="color:#24292E;--shiki-dark:#E1E4E8">name</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}(${</span><span style="color:#24292E;--shiki-dark:#E1E4E8">override</span><span style="color:#032F62;--shiki-dark:#9ECBFF">.</span><span style="color:#24292E;--shiki-dark:#E1E4E8">name</span><span style="color:#032F62;--shiki-dark:#9ECBFF">})`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span></code></pre>
<p><code>A.withOverrodeHaltState(B)</code> теперь называется <code>A(B)</code>. Возвращаемся к нашим двум конструкциям <code>A>B>A</code>:</p>
<pre class="shiki shiki-themes github-light github-dark language-javascript" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Конструкция 1: A(B(A))</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> c1</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> A</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">withOverrodeHaltState</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">B</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">withOverrodeHaltState</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">A</span><span style="color:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Конструкция 2: A(B)(A) — в чистой скобочной нотации</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> c2</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> A</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">withOverrodeHaltState</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">B</span><span style="color:#24292E;--shiki-dark:#E1E4E8">).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">withOverrodeHaltState</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">A</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
<p>Те же буквы, разная расстановка скобок, разные деревья. Имена различимы. Биекция!</p>
<p>И это не <em>изобретённое</em> соглашение. Сбалансированные скобочные строки длины 2n — каноническая запись бинарных деревьев с n+1 листьями в комбинаторике. Это та самая биекция, на которой держится один из основных счётных результатов о числах Каталана. Лучший формат не был <em>придуман</em> — в нём просто будет <em>отражена</em> структурность, которую <code>></code>-нотация всё это время выбрасывала.</p>
<p>Стоимость для пользователя — запрет на скобки <code>(</code> и <code>)</code> в именах состояний: конструктор <code>State</code> бросает исключение. Скобки зарезервированы под композицию. Зато <code>></code> больше не зарезервирован — можно использовать в пользовательских именах свободно.</p>
<h2 id="vtoraya-polovina-istorii"><a class="heading-anchor" href="#vtoraya-polovina-istorii">Вторая половина истории</a></h2>
<p>Чистая скобочная биекция выше — это история про <em>именование</em>. v7 заодно подчищает и runtime. <a href="https://github.com/mellonis/turing-machine-js/issues/176"><code>turing-machine-js#176</code></a> добавляет коллапс цепочек: внутри <code>withOverrodeHaltState</code> теперь делается <code>bare = this.#bareState ?? this</code>. То есть <code>A.wohs(B).wohs(A)</code> распознаётся как «обёртывание уже-обёрнутой A», внутренний override (<code>B</code>) при этом всё равно мёртв во время исполнения (на halt-стек кладётся только override самой <em>внешней</em> обёртки), и вместо <code>A(B)(A)</code> получается просто <code>A(A)</code>.</p>
<p>Так что в реальном v7 наша Конструкция 2 не даст <code>A(B)(A)</code> — на этапе сборки она схлопывается в <code>A(A)</code>. Скобочное именование само по себе биективно (каждому дереву — своё имя); коллапс цепочек убирает деревья, неотличимые на runtime. В итоге имя в v7 описывает поведение машины, а не историю её построения.</p>
<p>И заодно: метод переименовывается. <code>withOverrodeHaltState</code> → <code>withOverriddenHaltState</code> (<a href="https://github.com/mellonis/turing-machine-js/issues/149"><code>turing-machine-js#149</code></a>). Past-tense (<code>overrode</code>) в этой идиоме читался криво — должен был быть past participle (<code>overridden</code>). Семилетняя грамматическая опечатка на публичном API. Чинится одним rename без алиаса.</p>
<h2 id="gde-yeshchyo-vstrechaetsya-podobnoe"><a class="heading-anchor" href="#gde-yeshchyo-vstrechaetsya-podobnoe">Где ещё встречается подобное</a></h2>
<p>Неинъективная сериализация — общий паттерн. Если в вашем коде есть «имя», которое склеивается плоским разделителем из чего-то структурно древовидного, шансы на C_n-баг ненулевые. Несколько примеров:</p>
<ul>
<li><strong>Цепочки расширений файлов</strong> — <code>.tar.gz</code>, <code>.spec.ts</code>, <code>.d.ts</code>. Работает по соглашению, потому что расширения известны заранее. Стоит поставить точку в имени файла — и сразу возникает неоднозначность: <code>my.app.tar.gz</code> — это базовое имя <code>my.app</code> с расширением <code>.tar.gz</code> или базовое имя <code>my.app.tar</code> с расширением <code>.gz</code>? Разные инструменты разрешают по-разному.</li>
<li><strong>Имена джоинов в БД</strong> через <code>_</code>: <code>user_address_country</code>. Если <code>user_address</code> уже сам по себе имя таблицы (подчёркивание — часть идентификатора), то <code>user_address_country</code> неоднозначно: то ли это <code>user.address_country</code>, то ли <code>user_address.country</code>. Починка — квалифицированное пространство имён или явные алиасы.</li>
<li><strong>Модульные пути в Java/Python</strong> — <code>com.example.foo.Bar</code>. Это может быть класс <code>Bar</code> в пакете <code>com.example.foo</code> или вложенный класс <code>foo.Bar</code> в пакете <code>com.example</code>. Разрешается по соглашению (заглавная буква — класс), а не самим именованием.</li>
</ul>
<p>Эвристика для код-ревью: <strong>если ваш «идентификатор» — это плоская конкатенация разделителем чего-то древовидного, поищите C_n-баг</strong>. Чаще всего он <em>скрытый</em>: имена случайно не пересекаются на тех данных, что в проде, но архитектурно дыра всегда там.</p>
<p>В моей библиотеке C_n-баг прятался семь лет, потому что никто не пытался поймать движок на коллизии имён. Чтобы её обнаружить, надо <em>догадаться</em> построить два структурно разных дерева и проверить, что имена у них различаются. Такой тест никто не пишет, пока не узнает, что коллизия в принципе возможна.</p>
<h2 id="chto-uezzhaet-v-v7"><a class="heading-anchor" href="#chto-uezzhaet-v-v7">Что уезжает в v7</a></h2>
<p>Три тикета, которые задумывались как три отдельных изменения, но при ближайшем рассмотрении — одна и та же мысль:</p>
<ul>
<li>
<p><strong>Скобочное именование</strong> (<a href="https://github.com/mellonis/turing-machine-js/issues/148"><code>turing-machine-js#148</code></a>). Биекция между деревьями композиции и именами.</p>
</li>
<li>
<p><strong>Чистая Mermaid-визуализация</strong> (<a href="https://github.com/mellonis/turing-machine-js/issues/138"><code>turing-machine-js#138</code></a>, <a href="https://github.com/mellonis/turing-machine-js/issues/174"><code>turing-machine-js#174</code></a>). Обёртка рендерится как отдельный узел <code>[[композитное-имя]]</code>, тело базы — как подграф <code>callable subtree of NAME</code>. Структурная ясность имени отражается в визуальной ясности диаграммы:</p>
<svg xmlns="http://www.w3.org/2000/svg" id="mermaid-5" width="100%" aria-roledescription="flowchart-v2" class="flowchart" style="font-family:arial,sans-serif;font-size:16px;fill:#333;max-width:706.8036499023438px" viewBox="0 0 706.804 226.898"><style>@keyframes edge-animation-frame{0%{stroke-dashoffset:0}}@keyframes dash{to{stroke-dashoffset:0}}#mermaid-5 .edge-thickness-normal{stroke-width:1px}#mermaid-5 .edge-pattern-solid{stroke-dasharray:0}#mermaid-5 .edge-pattern-dotted{stroke-dasharray:2}#mermaid-5 .marker{fill:#333;stroke:#333}#mermaid-5 .marker.cross{stroke:#333}#mermaid-5 svg{font-family:arial,sans-serif;font-size:16px}#mermaid-5 p{margin:0}#mermaid-5 .label{font-family:arial,sans-serif;color:#333}#mermaid-5 span{fill:#333;color:#333}#mermaid-5 .node circle,#mermaid-5 .node path,#mermaid-5 .node rect{fill:#ececff;stroke:#9370db;stroke-width:1px}#mermaid-5 .flowchart-link{stroke:#333;fill:none}#mermaid-5 .edgeLabel,#mermaid-5 .node .label{text-align:center}#mermaid-5 .edgeLabel,#mermaid-5 .edgeLabel p,#mermaid-5 .edgeLabel rect{background-color:rgba(232,232,232,.8)}#mermaid-5 .edgeLabel rect{opacity:.5;fill:rgba(232,232,232,.8)}#mermaid-5 .labelBkg{background-color:rgba(232,232,232,.5)}#mermaid-5 [data-look=neo].cluster rect,#mermaid-5 [data-look=neo].node circle,#mermaid-5 [data-look=neo].node rect{stroke:#9370db;filter:drop-shadow(1px 2px 2px #b9b9b9)}#mermaid-5 [data-look=neo].node path{stroke:#9370db;stroke-width:1px}#mermaid-5 [data-look=neo].node .outer-path{filter:drop-shadow(1px 2px 2px #b9b9b9)}#mermaid-5 :root{--mermaid-font-family:arial,sans-serif}</style><marker id="mermaid-5_flowchart-v2-pointEnd" class="marker flowchart-v2" markerHeight="8" markerUnits="userSpaceOnUse" markerWidth="8" orient="auto" refX="5" refY="5" viewBox="0 0 10 10"><path d="m0 0 10 5-10 5z" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></path></marker><marker id="mermaid-5_flowchart-v2-pointStart" class="marker flowchart-v2" markerHeight="8" markerUnits="userSpaceOnUse" markerWidth="8" orient="auto" refX="4.5" refY="5" viewBox="0 0 10 10"><path d="m0 5 10 5V0z" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></path></marker><marker id="mermaid-5_flowchart-v2-pointEnd-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="10.5" orient="auto" refX="11.5" refY="7" viewBox="0 0 11.5 14"><path d="m0 0 11.5 7L0 14z" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></path></marker><marker id="mermaid-5_flowchart-v2-pointStart-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="11.5" orient="auto" refX="1" refY="7" viewBox="0 0 11.5 14"><path d="m0 7 11.5 7V0z" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></path></marker><marker id="mermaid-5_flowchart-v2-circleEnd" class="marker flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="11" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-5_flowchart-v2-circleStart" class="marker flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="-1" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:1;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-5_flowchart-v2-circleEnd-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="14" orient="auto" refX="12.25" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-5_flowchart-v2-circleStart-margin" class="marker flowchart-v2" markerHeight="14" markerUnits="userSpaceOnUse" markerWidth="14" orient="auto" refX="-2" refY="5" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width:0;stroke-dasharray:1,0"></circle></marker><marker id="mermaid-5_flowchart-v2-crossEnd" class="marker cross flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="12" refY="5.2" viewBox="0 0 11 11"><path d="m1 1 9 9m0-9-9 9" class="arrowMarkerPath" style="stroke-width:2;stroke-dasharray:1,0"></path></marker><marker id="mermaid-5_flowchart-v2-crossStart" class="marker cross flowchart-v2" markerHeight="11" markerUnits="userSpaceOnUse" markerWidth="11" orient="auto" refX="-1" refY="5.2" viewBox="0 0 11 11"><path d="m1 1 9 9m0-9-9 9" class="arrowMarkerPath" style="stroke-width:2;stroke-dasharray:1,0"></path></marker><marker id="mermaid-5_flowchart-v2-crossEnd-margin" class="marker cross flowchart-v2" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="12" orient="auto" refX="17.7" refY="7.5" viewBox="0 0 15 15"><path d="m1 1 13 13M1 14 14 1" class="arrowMarkerPath" style="stroke-width:2.5"></path></marker><marker id="mermaid-5_flowchart-v2-crossStart-margin" class="marker cross flowchart-v2" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="12" orient="auto" refX="-3.5" refY="7.5" viewBox="0 0 15 15"><path d="m1 1 13 13M1 14 14 1" class="arrowMarkerPath" style="stroke-width:2.5;stroke-dasharray:1,0"></path></marker><g class="root"><g class="clusters"><g id="mermaid-5-frame" class="cluster" data-look="classic"><path d="M298.902 8h399.902v136.898H298.902z" style="fill:#ffffde;stroke:#aa3;stroke-width:1px"></path><foreignObject width="144.094" height="24" class="cluster-label" transform="translate(426.806 8)"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5"><span class="nodeLabel" style="color:#333"><p style="background-color:transparent">callable subtree of A</p></span></div></foreignObject></g></g><g class="edgePaths"><path id="mermaid-5-L_idle_W_0" marker-end="url(#mermaid-5_flowchart-v2-pointEnd)" d="m58.136 103.949 7.123-.083c7.122-.083 21.367-.25 35.112-.258 13.744-.007 26.989.144 33.612.22l6.622.075" class="edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_idle_W_0" data-look="classic" data-points="W3sieCI6NTguMTM2MjEyNDMxODI2NzEsInkiOjEwMy45NDkyMTg3NX0seyJ4IjoxMDAuODcwNTg2Mzk1MjYzNjcsInkiOjEwMy40NDkyMTg3NX0seyJ4IjoxNDQuNjA0OTYxMzk1MjYzNjcsInkiOjEwMy45NDkyMTg3NX1d"></path><path id="mermaid-5-L_W_A_0" marker-end="url(#mermaid-5_flowchart-v2-pointEnd)" d="m207.605 86.042 7.566-4.432c7.567-4.432 22.7-13.297 37.916-17.729s30.515-4.432 42.125-2.356c11.61 2.075 19.529 6.226 23.489 8.301l3.96 2.075" class="edge-pattern-solid edge-thickness-normal flowchart-link" data-edge="true" data-et="edge" data-id="L_W_A_0" data-look="classic" data-points="W3sieCI6MjA3LjYwNDk2MTM5NTI2MzY3LCJ5Ijo4Ni4wNDE4ODA1MDQzMTUxM30seyJ4IjoyNTMuMDAzMzk4ODk1MjYzNjcsInkiOjU5LjQ0OTIxODc1fSx7IngiOjI5OC45MDE4MzYzOTUyNjM3LCJ5Ijo1OS40NDkyMTg3NX0seyJ4IjozMjYuMjA0MjAzNDc2MDU1MSwieSI6NzMuNzU4MTAzOTEzNzQxMzd9XQ==" style="stroke-width:3.5px"></path><path id="mermaid-5-L_A_A1_0" marker-end="url(#mermaid-5_flowchart-v2-pointEnd)" d="m359.804 81.949 7.491-.083c7.49-.083 22.472-.25 36.871-.333 14.398-.084 28.213-.084 35.121-.084h6.907" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_A_A1_0" data-look="classic" data-points="W3sieCI6MzU5LjgwMzY3MzgyNzA5MTEsInkiOjgxLjk0OTIxODc1fSx7IngiOjQwNC43NDg5ODUyOTA1MjczNCwieSI6ODEuNDQ5MjE4NzV9LHsieCI6NDUwLjE5NDI5Nzc5MDUyNzM0LCJ5Ijo4MS40NDkyMTg3NX1d"></path><path id="mermaid-5-L_A1_h_0" marker-end="url(#mermaid-5_flowchart-v2-pointEnd)" d="M529.772 81.449h84.235" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_A1_h_0" data-look="classic" data-points="W3sieCI6NTI5Ljc3MjQyMjc5MDUyNzMsInkiOjgxLjQ0OTIxODc1fSx7IngiOjU3My44ODk2MTAyOTA1MjczLCJ5Ijo4MS40NDkyMTg3NX0seyJ4Ijo2MTguMDA2Nzk3NzkwNTI3MywieSI6ODEuNDQ5MjE4NzV9XQ=="></path><path id="mermaid-5-L_W_B_0" marker-end="url(#mermaid-5_flowchart-v2-pointEnd)" d="m191.835 123.449 10.195 12.658c10.194 12.659 30.584 37.975 48.428 50.633 17.845 12.658 33.144 12.658 44.378 12.729 11.233.07 18.4.21 21.983.281l3.584.07" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_W_B_0" data-look="classic" data-points="W3sieCI6MTkxLjgzNDgzOTY2NzQ2MTcsInkiOjEyMy40NDkyMTg3NX0seyJ4IjoyNTMuMDAzMzk4ODk1MjYzNjcsInkiOjE5OS4zOTg0Mzc1fSx7IngiOjI5OC45MDE4MzYzOTUyNjM3LCJ5IjoxOTkuMzk4NDM3NX0seyJ4IjozMjQuNDAxODM2Mzk1MjY4NywieSI6MTk5Ljg5ODQzNzV9XQ=="></path><path id="mermaid-5-L_frame_W_0" marker-end="url(#mermaid-5_flowchart-v2-pointEnd)" d="M298.902 90.14v2.218c0 2.219 0 6.655-7.65 8.873s-22.949 2.218-37.499 2.294c-14.549.076-28.349.228-35.249.304l-6.899.076" class="edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link" data-edge="true" data-et="edge" data-id="L_frame_W_0" data-look="classic" data-points="W3sieCI6MzI2LjIwNDIwMzQ3NjA1MjU2LCJ5Ijo5MC4xNDAzMzM1ODYyNTk5Nn0seyJ4IjoyOTguOTAxODM2Mzk1MjYzNywieSI6MTAzLjQ0OTIxODc1fSx7IngiOjI1My4wMDMzOTg4OTUyNjM2NywieSI6MTAzLjQ0OTIxODc1fSx7IngiOjIwNy42MDQ5NjEzOTUyNjM2NywieSI6MTAzLjk0OTIxODc1fV0="></path></g><g class="edgeLabels"><g class="edgeLabel"><foreignObject width="36.469" height="24" class="label" data-id="L_idle_W_0" transform="translate(83.136 91.455)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>enter</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="24.016" height="24" class="label" data-id="L_W_A_0" transform="translate(240.996 47.45)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>call</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="40.891" height="24" class="label" data-id="L_A_A1_0" transform="translate(384.304 69.45)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>x: y/R</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="38.234" height="24" class="label" data-id="L_A1_h_0" transform="translate(554.772 69.45)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>y: y/L</p></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="0" height="0" class="label" data-id="L_W_B_0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"></span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="41.797" height="24" class="label" data-id="L_frame_W_0" transform="translate(239.008 91.45)"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="edgeLabel"><p>return</p></span></div></foreignObject></g></g><g class="nodes"><g id="mermaid-5-flowchart-idle-0" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M27.49 83.95h10.656c.377.011.754.023 1.25.04l1.244.12 1.234.199c.355.08.71.162 1.218.278l1.199.356 1.173.431c.426.19.853.378 1.143.506l1.108.579 1.07.648 1.024.715.978.779.925.84.87.898.81.952.748 1.002.682 1.048.613 1.089.542 1.126.47 1.159.393 1.186.317 1.21.24 1.226.16 1.24.08 1.247v1.25c-.017.255-.034.51-.08 1.248-.036.27-.07.542-.16 1.24l-.24 1.226-.317 1.21-.394 1.186-.469 1.158-.542 1.127-.613 1.089-.682 1.047-.748 1.002-.81.952-.87.898-.925.84-.978.78-1.025.715-1.069.648-1.108.578-1.143.506-1.173.432-1.199.355-1.218.279c-.275.044-.55.088-1.234.2-.459.043-.918.088-1.244.12-.478.014-.956.03-1.25.04H27.49c-.359-.012-.718-.024-1.25-.04l-1.244-.12-1.234-.2-1.218-.279-1.199-.355-1.173-.432-1.143-.506-1.108-.578-1.069-.648-1.025-.715-.977-.78-.926-.84-.87-.898-.81-.952-.747-1.002-.682-1.047-.614-1.09-.542-1.126-.47-1.158q-.194-.59-.393-1.186l-.317-1.21-.24-1.227-.159-1.24-.08-1.247v-1.25l.08-1.247.16-1.24.239-1.227.317-1.21.394-1.185.469-1.16.542-1.125.614-1.09.682-1.047.747-1.002.81-.952.87-.898.926-.84.977-.78 1.025-.714 1.069-.648 1.108-.579 1.143-.506 1.173-.431 1.199-.356 1.218-.278 1.234-.2 1.245-.12 1.249-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M27.49 83.95h10.656m-10.656 0h10.656m0 0 1.25.04m-1.25-.04c.252.007.505.015 1.25.04m0 0 1.244.12m-1.244-.12 1.244.12m0 0 1.234.199m-1.234-.2 1.234.2m0 0 1.218.278m-1.218-.278 1.218.278m0 0 1.199.356m-1.199-.356 1.199.356m0 0 1.173.431m-1.173-.431 1.173.431m0 0 1.143.506m-1.143-.506 1.143.506m0 0 1.108.579m-1.108-.579 1.108.579m0 0 1.07.648m-1.07-.648 1.07.648m0 0 1.024.715m-1.025-.715 1.025.715m0 0 .978.779m-.978-.78.978.78m0 0 .925.84m-.925-.84.925.84m0 0 .87.898m-.87-.898.87.898m0 0 .81.952m-.81-.952c.175.206.35.413.81.952m0 0 .748 1.002m-.748-1.002.748 1.002m0 0 .682 1.048m-.682-1.048.682 1.048m0 0 .613 1.089m-.613-1.09.613 1.09m0 0 .542 1.126m-.542-1.126.542 1.126m0 0 .47 1.159m-.47-1.16.47 1.16m0 0 .393 1.186m-.394-1.186.394 1.186m0 0 .317 1.21m-.317-1.21.317 1.21m0 0 .24 1.226m-.24-1.227.24 1.227m0 0c.032.256.065.511.16 1.24m-.16-1.24.16 1.24m0 0 .08 1.247m-.08-1.247.08 1.247m0 0v1.25m0-1.25v1.25m0 0c-.025.371-.048.742-.08 1.248m.08-1.248c-.02.287-.038.573-.08 1.248m0 0c-.057.433-.113.865-.16 1.24m.16-1.24-.16 1.24m0 0-.24 1.226m.24-1.227-.24 1.227m0 0-.317 1.21m.317-1.21-.317 1.21m0 0c-.14.425-.282.85-.394 1.186m.394-1.186-.394 1.186m0 0-.469 1.158m.47-1.158-.47 1.158m0 0-.542 1.127m.542-1.127-.542 1.127m0 0-.613 1.089m.613-1.09-.613 1.09m0 0-.682 1.047m.682-1.047-.682 1.047m0 0-.748 1.002m.748-1.002-.748 1.002m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898-.87.898m0 0-.925.84m.925-.84-.925.84m0 0-.978.78m.978-.78-.978.78m0 0-1.025.715m1.025-.715-1.025.715m0 0-1.069.648m1.07-.648-1.07.648m0 0-1.108.578m1.108-.578-1.108.578m0 0-1.143.506m1.143-.506-1.143.506m0 0-1.173.432m1.173-.432-1.173.432m0 0-1.199.355m1.199-.355c-.32.095-.638.19-1.199.355m0 0-1.218.279m1.218-.279-1.218.279m0 0c-.375.06-.749.12-1.234.2m1.234-.2-1.234.2m0 0c-.429.04-.858.082-1.244.12m1.244-.12c-.475.045-.95.09-1.244.12m0 0c-.412.012-.824.026-1.25.04m1.25-.04-1.25.04m0 0H27.49m10.656 0H27.49m0 0c-.275-.01-.55-.018-1.25-.04m1.25.04-1.25-.04m0 0-1.244-.12m1.245.12c-.4-.04-.799-.078-1.245-.12m0 0c-.48-.079-.962-.156-1.234-.2m1.234.2c-.418-.069-.837-.136-1.234-.2m0 0-1.218-.279m1.218.279-1.218-.279m0 0-1.199-.355m1.199.355-1.199-.355m0 0-1.173-.432m1.173.432-1.173-.432m0 0-1.143-.506m1.143.506-1.143-.506m0 0-1.108-.578m1.108.578-1.108-.578m0 0-1.069-.648m1.069.648-1.069-.648m0 0-1.025-.715m1.025.715-1.025-.715m0 0-.977-.78m.977.78-.977-.78m0 0-.926-.84m.926.84-.926-.84m0 0-.87-.898m.87.898-.87-.898m0 0c-.322-.38-.645-.759-.81-.952m.81.952-.81-.952m0 0-.747-1.002m.747 1.002-.747-1.002m0 0-.682-1.047m.682 1.047-.682-1.047m0 0-.614-1.09m.614 1.09-.614-1.09m0 0-.542-1.126m.542 1.127-.542-1.127m0 0-.47-1.158m.47 1.158-.47-1.158m0 0-.393-1.186m.394 1.186-.394-1.186m0 0-.317-1.21m.317 1.21-.317-1.21m0 0-.24-1.227m.24 1.227-.24-1.227m0 0c-.034-.274-.07-.548-.159-1.24m.16 1.24-.16-1.24m0 0L8 104.075m.08 1.248L8 104.074m0 0v-1.25m0 1.25v-1.25m0 0 .08-1.247M8 102.824l.08-1.247m0 0 .16-1.24m-.16 1.24.16-1.24m0 0 .239-1.227m-.24 1.227.24-1.227m0 0 .317-1.21m-.317 1.21.317-1.21m0 0 .394-1.185M8.796 97.9l.394-1.186m0 0 .469-1.16m-.47 1.16.47-1.16m0 0 .542-1.125m-.542 1.126.542-1.126m0 0 .614-1.09m-.614 1.09.614-1.09m0 0 .682-1.047m-.682 1.048.682-1.048m0 0 .747-1.002m-.747 1.002.747-1.002m0 0 .81-.952m-.81.952.81-.952m0 0 .87-.898m-.87.898.87-.898m0 0 .926-.84m-.926.84.926-.84m0 0 .977-.78m-.977.78.977-.78m0 0 1.025-.714m-1.025.715 1.025-.715m0 0 1.069-.648m-1.069.648 1.069-.648m0 0 1.108-.579m-1.108.579 1.108-.579m0 0 1.143-.506m-1.143.506c.41-.18.818-.362 1.143-.506m0 0 1.173-.431m-1.173.431 1.173-.431m0 0 1.199-.356m-1.199.356 1.199-.356m0 0 1.218-.278m-1.218.278 1.218-.278m0 0 1.234-.2m-1.234.2 1.234-.2m0 0 1.245-.12m-1.245.12 1.245-.12m0 0 1.249-.04m-1.25.04 1.25-.04m0 0"></path></g><g class="label" transform="translate(20.365 91.45)"><rect></rect><foreignObject width="24.906" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>idle</p></span></div></foreignObject></g></g><g id="mermaid-5-flowchart-W-1" class="node default" data-look="classic"><path d="M0 0h47v-39H0V0h-8 63v-39H-8V0z" class="label-container" style="fill:#ececff;stroke:#9370db;stroke-width:1px" transform="translate(152.105 122.95)"></path><g class="label" transform="translate(159.605 91.45)"><rect></rect><foreignObject width="32" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>A(B)</p></span></div></foreignObject></g></g><g id="mermaid-5-flowchart-A-3" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M343.392 61.95h-3.578l1.25.04 1.243.12c.36.057.719.115 1.234.199l1.219.278 1.198.356 1.173.431 1.143.506 1.109.579 1.069.648 1.025.715.977.779.926.84c.178.185.357.37.87.898l.81.952.747 1.002.682 1.048.613 1.089.543 1.126.469 1.159.394 1.186.317 1.21.239 1.226.16 1.24.08 1.247v1.25l-.08 1.248-.16 1.24-.24 1.226-.316 1.21-.394 1.186-.47 1.158-.542 1.127-.613 1.089-.682 1.047-.748 1.002-.81.952-.87.898-.925.84-.977.78-1.025.715-1.07.648-1.108.578-1.143.506-1.173.432-1.198.355-1.219.279-1.234.2-1.244.12-1.25.04h3.579c-.4-.014-.801-.026-1.25-.04-.342-.034-.685-.067-1.244-.12-.313-.052-.627-.102-1.234-.2l-1.218-.279-1.199-.355-1.173-.432-1.143-.506-1.108-.578-1.07-.648-1.024-.715-.978-.78-.925-.84-.87-.898-.81-.952-.748-1.002-.681-1.047-.614-1.09-.542-1.126-.47-1.158-.393-1.186-.317-1.21-.24-1.227c-.042-.33-.084-.659-.16-1.24l-.08-1.247v-1.25l.08-1.247c.06-.457.119-.915.16-1.24l.24-1.227.317-1.21.394-1.185.469-1.16.542-1.125.614-1.09.681-1.047.748-1.002.81-.952.87-.898.925-.84.978-.78 1.025-.714 1.069-.648 1.108-.579 1.143-.506 1.173-.431 1.199-.356 1.218-.278 1.234-.2 1.244-.12 1.25-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M343.392 61.95h-3.578m3.578 0h-3.578m0 0 1.25.04m-1.25-.04 1.25.04m0 0 1.243.12m-1.244-.12 1.244.12m0 0 1.234.199m-1.234-.2 1.234.2m0 0 1.219.278m-1.219-.278 1.219.278m0 0 1.198.356m-1.198-.356 1.198.356m0 0c.248.09.496.182 1.173.431m-1.173-.431 1.173.431m0 0 1.143.506m-1.143-.506 1.143.506m0 0 1.109.579m-1.109-.579 1.109.579m0 0 1.069.648m-1.07-.648 1.07.648m0 0 1.025.715m-1.025-.715 1.025.715m0 0 .977.779m-.977-.78.977.78m0 0 .926.84m-.926-.84.926.84m0 0c.337.35.675.698.87.898m-.87-.898.87.898m0 0 .81.952m-.81-.952.81.952m0 0 .747 1.002m-.748-1.002.748 1.002m0 0 .682 1.048m-.682-1.048.682 1.048m0 0 .613 1.089m-.613-1.09.613 1.09m0 0 .543 1.126m-.543-1.126.543 1.126m0 0 .469 1.159m-.47-1.16.47 1.16m0 0 .394 1.186m-.394-1.186.394 1.186m0 0 .317 1.21m-.317-1.21.317 1.21m0 0 .239 1.226m-.24-1.227.24 1.227m0 0 .16 1.24m-.16-1.24.16 1.24m0 0 .08 1.247m-.08-1.247.08 1.247m0 0v1.25m0-1.25v1.25m0 0-.08 1.248m.08-1.248-.08 1.248m0 0-.16 1.24m.16-1.24-.16 1.24m0 0-.24 1.226m.24-1.227-.24 1.227m0 0-.316 1.21m.317-1.21-.317 1.21m0 0-.394 1.186m.394-1.186-.394 1.186m0 0-.47 1.158m.47-1.158-.47 1.158m0 0-.542 1.127m.543-1.127-.543 1.127m0 0-.613 1.089m.613-1.09-.613 1.09m0 0-.682 1.047m.682-1.047-.682 1.047m0 0-.748 1.002m.748-1.002-.748 1.002m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898-.87.898m0 0-.925.84m.926-.84-.926.84m0 0-.977.78m.977-.78-.977.78m0 0-1.025.715m1.025-.715-1.025.715m0 0-1.07.648m1.07-.648-1.07.648m0 0-1.108.578m1.109-.578-1.109.578m0 0-1.143.506m1.143-.506-1.143.506m0 0-1.173.432m1.173-.432-1.173.432m0 0-1.198.355m1.198-.355-1.198.355m0 0-1.219.279m1.219-.279-1.219.279m0 0c-.478.077-.957.154-1.234.2m1.234-.2-1.234.2m0 0-1.244.12m1.244-.12c-.457.043-.914.087-1.244.12m0 0c-.392.012-.785.024-1.25.04m1.25-.04c-.486.015-.972.03-1.25.04m0 0h3.579m-3.578 0h3.578m0 0c-.272-.01-.543-.018-1.25-.04m1.25.04c-.483-.016-.965-.032-1.25-.04m0 0c-.297-.03-.594-.058-1.244-.12m1.244.12c-.377-.037-.754-.074-1.244-.12m0 0-1.234-.2m1.234.2c-.262-.043-.524-.086-1.234-.2m0 0-1.218-.279m1.218.279-1.218-.279m0 0-1.199-.355m1.199.355-1.199-.355m0 0-1.173-.432m1.173.432-1.173-.432m0 0-1.143-.506m1.143.506-1.143-.506m0 0-1.108-.578m1.108.578-1.108-.578m0 0-1.07-.648m1.07.648-1.07-.648m0 0-1.024-.715m1.025.715-1.025-.715m0 0-.978-.78m.978.78-.978-.78m0 0-.925-.84m.925.84-.925-.84m0 0-.87-.898m.87.898-.87-.898m0 0-.81-.952m.81.952-.81-.952m0 0-.748-1.002m.748 1.002-.748-1.002m0 0-.681-1.047m.681 1.047-.681-1.047m0 0-.614-1.09m.614 1.09-.614-1.09m0 0-.542-1.126m.542 1.127-.542-1.127m0 0-.47-1.158m.47 1.158-.47-1.158m0 0-.393-1.186m.394 1.186-.394-1.186m0 0-.317-1.21m.317 1.21-.317-1.21m0 0-.24-1.227m.24 1.227-.24-1.227m0 0-.16-1.24m.16 1.24c-.058-.453-.116-.907-.16-1.24m0 0c-.03-.48-.06-.96-.08-1.247m.08 1.248-.08-1.248m0 0v-1.25m0 1.25v-1.25m0 0c.032-.481.063-.962.08-1.247m-.08 1.247.08-1.247m0 0c.042-.318.083-.635.16-1.24m-.16 1.24c.035-.263.069-.526.16-1.24m0 0c.05-.248.097-.497.24-1.227m-.24 1.227.24-1.227m0 0 .317-1.21m-.317 1.21.317-1.21m0 0 .394-1.185m-.394 1.186.394-1.186m0 0 .469-1.16m-.47 1.16.47-1.16m0 0 .542-1.125m-.542 1.126.542-1.126m0 0 .614-1.09m-.614 1.09.614-1.09m0 0 .681-1.047m-.681 1.048.681-1.048m0 0 .748-1.002m-.748 1.002.748-1.002m0 0c.18-.21.36-.422.81-.952m-.81.952.81-.952m0 0 .87-.898m-.87.898.87-.898m0 0 .925-.84m-.925.84.925-.84m0 0 .978-.78m-.978.78c.28-.222.558-.445.978-.78m0 0 1.025-.714m-1.025.715 1.025-.715m0 0 1.069-.648m-1.07.648 1.07-.648m0 0 1.108-.579m-1.108.579 1.108-.579m0 0 1.143-.506m-1.143.506 1.143-.506m0 0 1.173-.431m-1.173.431 1.173-.431m0 0 1.199-.356m-1.199.356 1.199-.356m0 0 1.218-.278m-1.218.278 1.218-.278m0 0 1.234-.2m-1.234.2 1.234-.2m0 0 1.244-.12m-1.244.12 1.244-.12m0 0 1.25-.04m-1.25.04 1.25-.04m0 0"></path></g><g class="label" transform="translate(336.267 69.45)"><rect></rect><foreignObject width="10.672" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>A</p></span></div></foreignObject></g></g><g id="mermaid-5-flowchart-A1-5" class="node default" data-look="classic"><path d="M450.194 54.45h79.578v54h-79.578z" class="basic label-container"></path><g class="label" transform="translate(480.194 69.45)"><rect></rect><foreignObject width="19.578" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>A1</p></span></div></foreignObject></g></g><g id="mermaid-5-flowchart-h-7" class="node default" data-look="classic"><g class="basic label-container" transform="translate(645.905 81.45)"><circle r="27.898" class="outer-circle"></circle><circle r="22.898" class="inner-circle"></circle></g><g class="label" transform="translate(633.007 69.45)"><rect></rect><foreignObject width="25.797" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>halt</p></span></div></foreignObject></g></g><g id="mermaid-5-flowchart-B-11" class="node default" data-look="classic"><g class="basic label-container outer-path"><path fill="#ececff" stroke-width="0" d="M343.392 179.898h-3.578l1.25.04c.46.045.92.09 1.243.12l1.234.2 1.219.278 1.198.356 1.173.432 1.143.506 1.109.578 1.069.648 1.025.715.977.78.926.84.87.898.81.951.747 1.002.682 1.048.613 1.089.543 1.126.469 1.159.394 1.186.317 1.21.239 1.226.16 1.24.08 1.247v1.25l-.08 1.248-.16 1.24-.24 1.227-.316 1.209-.394 1.186-.47 1.159-.542 1.126-.613 1.09-.682 1.047-.748 1.001-.81.952-.87.898c-.195.178-.39.356-.925.84-.21.169-.422.337-.977.78l-1.025.715-1.07.648-1.108.578-1.143.506-1.173.432-1.198.356-1.219.278-1.234.2-1.244.12-1.25.04h3.579l-1.25-.04c-.399-.04-.798-.078-1.244-.12l-1.234-.2-1.218-.278-1.199-.356-1.173-.432-1.143-.506-1.108-.578-1.07-.648-1.024-.715-.978-.78-.925-.84-.87-.898-.81-.952-.748-1.001-.681-1.048-.614-1.09-.542-1.125-.47-1.159-.393-1.186-.317-1.21-.24-1.226c-.052-.408-.104-.815-.16-1.24-.025-.407-.051-.813-.08-1.248v-1.25c.026-.396.052-.792.08-1.247l.16-1.24.24-1.227.317-1.209.394-1.186.469-1.159.542-1.126.614-1.09.681-1.047.748-1.002.81-.951.87-.898.925-.84.978-.78 1.025-.715 1.069-.648 1.108-.578 1.143-.506 1.173-.432 1.199-.356 1.218-.278 1.234-.2 1.244-.12 1.25-.04"></path><path fill="none" stroke="#9370db" stroke-dasharray="0 0" stroke-width="1.3" d="M343.392 179.898h-3.578m3.578 0h-3.578m0 0 1.25.04m-1.25-.04 1.25.04m0 0c.269.027.539.053 1.243.12m-1.244-.12 1.244.12m0 0 1.234.2m-1.234-.2 1.234.2m0 0 1.219.278m-1.219-.278 1.219.278m0 0 1.198.356m-1.198-.356 1.198.356m0 0 1.173.432m-1.173-.432 1.173.432m0 0 1.143.506m-1.143-.506 1.143.506m0 0 1.109.578m-1.109-.578 1.109.578m0 0 1.069.648m-1.07-.648c.29.175.58.35 1.07.648m0 0 1.025.715m-1.025-.715 1.025.715m0 0c.39.31.779.621.977.78m-.977-.78.977.78m0 0 .926.84m-.926-.84.926.84m0 0 .87.898m-.87-.898.87.898m0 0 .81.951m-.81-.951.81.951m0 0 .747 1.002m-.748-1.002.748 1.002m0 0 .682 1.048m-.682-1.048.682 1.048m0 0 .613 1.089m-.613-1.09.613 1.09m0 0 .543 1.126m-.543-1.126.543 1.126m0 0 .469 1.159m-.47-1.159.47 1.159m0 0 .394 1.186m-.394-1.186.394 1.186m0 0 .317 1.21m-.317-1.21.317 1.21m0 0 .239 1.226m-.24-1.227c.09.454.177.908.24 1.227m0 0 .16 1.24m-.16-1.24.16 1.24m0 0 .08 1.247m-.08-1.247.08 1.247m0 0v1.25m0-1.25v1.25m0 0-.08 1.248m.08-1.248c-.03.452-.058.903-.08 1.248m0 0-.16 1.24m.16-1.24-.16 1.24m0 0-.24 1.227m.24-1.227-.24 1.227m0 0-.316 1.209m.317-1.21-.317 1.21m0 0-.394 1.186m.394-1.186-.394 1.186m0 0-.47 1.159m.47-1.159-.47 1.159m0 0-.542 1.126m.543-1.126-.543 1.126m0 0-.613 1.09m.613-1.09-.613 1.09m0 0-.682 1.047m.682-1.048-.682 1.048m0 0-.748 1.001m.748-1.001-.748 1.001m0 0-.81.952m.81-.952-.81.952m0 0-.87.898m.87-.898-.87.898m0 0-.925.84m.926-.84-.926.84m0 0-.977.78m.977-.78-.977.78m0 0-1.025.715m1.025-.715-1.025.715m0 0-1.07.648m1.07-.648-1.07.648m0 0-1.108.578m1.109-.578-1.109.578m0 0-1.143.506m1.143-.506-1.143.506m0 0-1.173.432m1.173-.432-1.173.432m0 0-1.198.356m1.198-.356-1.198.356m0 0-1.219.278m1.219-.278-1.219.278m0 0-1.234.2m1.234-.2-1.234.2m0 0c-.281.026-.563.054-1.244.12m1.244-.12c-.265.025-.53.05-1.244.12m0 0-1.25.04m1.25-.04c-.491.015-.982.03-1.25.04m0 0h3.579m-3.578 0h3.578m0 0-1.25-.04m1.25.04-1.25-.04m0 0c-.407-.04-.814-.08-1.244-.12m1.244.12c-.466-.046-.933-.09-1.244-.12m0 0-1.234-.2m1.234.2-1.234-.2m0 0-1.218-.278m1.218.278-1.218-.278m0 0-1.199-.356m1.199.356-1.199-.356m0 0-1.173-.432m1.173.432-1.173-.432m0 0-1.143-.506m1.143.506-1.143-.506m0 0-1.108-.578m1.108.578-1.108-.578m0 0-1.07-.648m1.07.648-1.07-.648m0 0-1.024-.715m1.025.715-1.025-.715m0 0-.978-.78m.978.78-.978-.78m0 0-.925-.84m.925.84-.925-.84m0 0-.87-.898m.87.898-.87-.898m0 0-.81-.952m.81.952-.81-.952m0 0-.748-1.001m.748 1.001-.748-1.001m0 0-.681-1.048m.681 1.048-.681-1.048m0 0-.614-1.09m.614 1.09-.614-1.09m0 0-.542-1.125m.542 1.126-.542-1.126m0 0-.47-1.159m.47 1.159-.47-1.159m0 0-.393-1.186m.394 1.186-.394-1.186m0 0-.317-1.21m.317 1.21-.317-1.21m0 0-.24-1.226m.24 1.227-.24-1.227m0 0c-.031-.249-.063-.498-.16-1.24m.16 1.24c-.048-.377-.096-.754-.16-1.24m0 0c-.017-.275-.034-.55-.08-1.248m.08 1.248c-.02-.324-.04-.647-.08-1.248m0 0v-1.25m0 1.25v-1.25m0 0c.018-.263.035-.526.08-1.247m-.08 1.247c.032-.483.063-.967.08-1.247m0 0 .16-1.24m-.16 1.24c.058-.445.116-.89.16-1.24m0 0 .24-1.227m-.24 1.227.24-1.227m0 0 .317-1.209m-.317 1.21.317-1.21m0 0 .394-1.186m-.394 1.186.394-1.186m0 0 .469-1.159m-.47 1.159.47-1.159m0 0 .542-1.126m-.542 1.126.542-1.126m0 0 .614-1.09m-.614 1.09.614-1.09m0 0 .681-1.047m-.681 1.048.681-1.048m0 0 .748-1.002m-.748 1.002.748-1.002m0 0 .81-.951m-.81.951.81-.951m0 0 .87-.898m-.87.898.87-.898m0 0 .925-.84m-.925.84.925-.84m0 0 .978-.78m-.978.78.978-.78m0 0 1.025-.715m-1.025.715 1.025-.715m0 0 1.069-.648m-1.07.648 1.07-.648m0 0 1.108-.578m-1.108.578 1.108-.578m0 0 1.143-.506m-1.143.506 1.143-.506m0 0 1.173-.432m-1.173.432 1.173-.432m0 0 1.199-.356m-1.199.356 1.199-.356m0 0 1.218-.278m-1.218.278 1.218-.278m0 0 1.234-.2m-1.234.2 1.234-.2m0 0 1.244-.12m-1.244.12 1.244-.12m0 0 1.25-.04m-1.25.04 1.25-.04m0 0"></path></g><g class="label" transform="translate(336.267 187.398)"><rect></rect><foreignObject width="10.672" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center"><span class="nodeLabel"><p>B</p></span></div></foreignObject></g></g></g></g><defs><filter id="mermaid-5-drop-shadow" width="130%" height="130%"><feDropShadow dx="4" dy="4" flood-color="#000" flood-opacity=".06" stdDeviation="0"></feDropShadow></filter></defs><defs><filter id="mermaid-5-drop-shadow-small" width="150%" height="150%"><feDropShadow dx="2" dy="2" flood-color="#000" flood-opacity=".06" stdDeviation="0"></feDropShadow></filter></defs></svg>
<p>Обёртка — <em>call-site</em>; тело базы — <em>callable subtree</em>; halt — <em>return</em>. Композиция в движке буквально стала чтением как вызов функции.</p>
</li>
<li>
<p><strong><code>withOverrodeHaltState</code> → <code>withOverriddenHaltState</code></strong> (<a href="https://github.com/mellonis/turing-machine-js/issues/149"><code>turing-machine-js#149</code></a>). Грамматика на публичном API.</p>
</li>
</ul>
<p>Все три про одно: композиция — древовидная, представление должно сохранять дерево, а не размывать его.</p>
<h2 id="v-zaklyuchenie"><a class="heading-anchor" href="#v-zaklyuchenie">В заключение</a></h2>
<p>Чистка визуализации привела к перестройке именования. Это та же история, которую я уже описывал в <a href="https://mellonis.ru/ru/articles/dva-mazhora-odin-readme-odno-demo/">"Два мажора, один README, одно демо"</a>: дизайн-ревью, которое тесты провести не могут, ловит то, что тестам не видно. Там это были документация и первый потребитель API. Здесь — визуализация. Жанр один и тот же: ставишь себя перед задачей <em>показать структуру</em>, и структура — если она кривая — начинает торчать. Тесты этого не делают, потому что они смотрят на поведение, а кривизна структуры зачастую видна только в её <em>представлении</em>.</p>
<p>Семь стрел, 429 деревьев. Имя, которое выглядело как инструкция обхода, на деле было одной из 429 возможных. v7 даёт каждой её собственное имя — и оставляет за визуализацией право показать, как это дерево на самом деле росло. А что обёртка — это call-frame, и эту структуру стоит вытащить в отдельный тип, — уже другая история (<a href="https://github.com/mellonis/turing-machine-js/issues/213"><code>turing-machine-js#213</code></a>).</p>]]></content:encoded>
    </item>
    <item>
      <title>Пауза, двойственная по природе: контракт хука и протокол воркера</title>
      <link>https://mellonis.ru/ru/articles/pauza-dvojstvennaya-po-prirode/</link>
      <guid isPermaLink="false">https://mellonis.ru/ru/articles/pauza-dvojstvennaya-po-prirode/</guid>
      <pubDate>Wed, 20 May 2026 00:00:00 GMT</pubDate>
      <description>Реализация паузы внутри воркера — не одно решение, а два контракта одновременно: хук движка и протокол между ним и основным потоком.</description>
      <content:encoded><![CDATA[<p>Предположим, вы пишете интерпретатор машины Тьюринга, запущенный в Web Worker’е. UI должен показывать трейс — как машина шагает от состояния к состоянию, что пишется на ленту, как двигается каретка. Чтобы пользователь успевал считывать изменения в UI, между итерациями движка нужна короткая задержка — миллисекунды, регулярно, на каждом шаге. Это приостановка движка между итерациями — регулярная и предсказуемая, не «пауза» в смысле UI-кнопки «Пауза» (та останавливает машину до клика «Продолжить»).</p>
<p>Возникает вопрос: где именно в цикле итерации воркеру нужно реализовать приостановку? Кандидатов два, и выбор между ними фиксирует сразу два контракта: хуков движка и протокола между воркером и основным потоком. Выбрать точку — это спроектировать обе стороны сразу; промахнуться — испортить обе. Эта статья — про этот выбор.</p>
<figure><img src="https://mellonis.ru/articles/pauza-dvojstvennaya-po-prirode/dual-pause.webp?v=eeb1e2a2" alt="Скриншот demo.machines.mellonis.ru: машина Тьюринга в режиме RUNNING_AUTO, кнопка «Пауза» выделена зум-кругом, слева виден пошаговый трейс выполнения" width="3680" height="2392"><figcaption>Скриншот demo.machines.mellonis.ru: машина Тьюринга в режиме RUNNING_AUTO, кнопка «Пауза» выделена зум-кругом, слева виден пошаговый трейс выполнения</figcaption></figure>
<h2 id="vvedenie"><a class="heading-anchor" href="#vvedenie">Введение</a></h2>
<p><a href="https://github.com/mellonis/turing-machine-js"><code>turing-machine-js</code></a> — интерпретатор машины Тьюринга. У движка есть хук <code>onStep</code> — переданный обработчик вызывается после каждого шага машины. Этим вводным про движок ограничимся. Подробнее про устройство и предыдущие изменения API — в статьях <a href="https://mellonis.ru/ru/articles/three-majors-two-mistakes/">«Три мажора, две ошибки: проектирование API приостановки для интерпретатора машины Тьюринга»</a> (1) и <a href="https://mellonis.ru/ru/articles/dva-mazhora-odin-readme-odno-demo/">«Два мажора, один README, одно демо: два почти бесплатных дизайн-ревью»</a> (2).</p>
<p>Первый настоящий потребитель движка — <a href="https://demo.machines.mellonis.ru"><code>machines-demo</code></a>, интерактивный отладчик. Пользовательская машина исполняется в Web Worker’е; UI на основном потоке показывает трейс. То же самое демо, которое в <a href="https://mellonis.ru/ru/articles/dva-mazhora-odin-readme-odno-demo/">статье (2)</a> спровоцировало два мажора в движке (<code>#109</code>, <code>#108</code>). В этой статье — следующий эпизод из жизни того же движка: v6.2 → v6.3 → v6.4.</p>
<p>В демо есть режим RUNNING_AUTO: пользователь выбирает интервал, нажимает «Старт», машина шагает автоматически. Изначально режим был устроен прямолинейно — основной поток запускал машину в режиме пошагового выполнения (через <code>runner.step()</code> — обёртку над движковым <code>runStepByStep()</code> в воркере), между шагами вставлял <code>setTimeout(intervalMs)</code> для трейса. Просто и работает — до тех пор, пока пользователь не поставит брейкпоинт.</p>
<p>Брейкпоинты в движке реализованы внутри <code>machine.run()</code>: цикл итераций находится в самом движке, и при попадании в <code>debug</code>-помеченное состояние он сам себя останавливает. <code>runStepByStep()</code> же — генератор: цикл по нему крутит потребитель, и брейкпоинты он не видит. <a href="https://github.com/mellonis/machines-demo/issues/43"><code>machines-demo#43</code></a> и был этой дырой — пользователь ставит брейкпоинт в режиме RUNNING_AUTO, нажимает «Старт», но брейкпоинт игнорируется. Чтобы брейкпоинты заработали, RUNNING_AUTO должен перейти с <code>runStepByStep()</code> на <code>run()</code>.</p>
<p>Но <code>run()</code> крутит цикл итераций внутри движка, не передавая управление наружу. Раньше приостановка жила в паузах между вызовами <code>runner.step()</code> на основном потоке — то есть на стороне потребителя, который сам управлял интервалом между шагами. С переходом на <code>run()</code> этой паузы на стороне потребителя больше нет — приостановку нужно либо переносить внутрь движка (в существующий или новый хук), либо убирать. Это и стало дизайн-вопросом.</p>
<p>Ещё одна архитектурная деталь, которая нам пригодится: у основного потока есть сторожевой таймер на воркер. Если воркер молчит на сообщениях дольше <code>WORKER_TIMEOUT_MS</code>, основной поток считает его подвисшим (например, недостижимое halt-состояние пользовательской машины) и убивает воркер. Любое размещение приостановки должно с этим таймером уживаться.</p>
<h2 id="gde-mozhet-zhit-priostanovka"><a class="heading-anchor" href="#gde-mozhet-zhit-priostanovka">Где может жить приостановка</a></h2>
<p>Два кандидата:</p>
<ol>
<li><strong>Внутри <code>onStep</code></strong> — синхронного хука, который движок зовёт после каждого шага. Сейчас его сигнатура <code>(state) => void</code>. Можно расширить её до <code>(state) => void | Promise&#x3C;void></code> — и тогда обработчик может <code>await</code>-ить задержку прямо там.</li>
<li><strong>На границе итерации</strong> — добавить новый хук, специально для асинхронных ожиданий. Имя пока неважно; важно, что вызывается он один раз в конце каждой итерации движка, и движок ждёт возвращённый Promise.</li>
</ol>
<p>Оба варианта отвечают на вопрос «где живёт приостановка». Но они одновременно диктуют, как устроен протокол между воркером и основным потоком.</p>
<p>Если приостановка стоит в <code>onStep</code>, она оказывается посредине каждой итерации движка — в той точке, где зовётся <code>onStep</code>. К этой же точке приходится привязывать и всё остальное в протоколе: и сторожевой таймер, и обработку клика «Пауза» от пользователя — всё повисает на этой мид-итерационной задержке.</p>
<p>Если приостановка стоит на границе итерации, у воркера в конце каждой итерации появляется момент — движок не работает, ждёт Promise. В этот момент удобно помириться со сторожевым таймером и быть готовым к обработке клика «Пауза».</p>
<p>Дальше — оба контракта по очереди.</p>
<h2 id="kontrakt-vnutri-onstep"><a class="heading-anchor" href="#kontrakt-vnutri-onstep">Контракт «Внутри onStep»</a></h2>
<p>v6.2.0 пошла первым путём — расширила сигнатуру <code>onStep</code> с <code>(state) => void</code> до <code>(state) => void | Promise&#x3C;void></code>. Обработчик теперь мог <code>await</code>-ить задержку прямо там, где раньше только смотрел состояние. Со стороны воркера это выглядело красиво — один хук, одна точка ожидания, никаких новых сущностей.</p>
<p>Уже через несколько часов я пометил v6.2.0 в npm как deprecated. Не «вышла версия посвежее, обновляйтесь по случаю», а явное предупреждение: эта версия — <em>неправильная</em>.</p>
<p>v6.3.0 откатила расширение сигнатуры. Причина — простая: <code>onStep</code> задумывался как хук наблюдения, а не координации. Он работает синхронно, посредине каждой итерации движка, для того чтобы потребитель мог посмотреть на состояние и где-то его записать. Добавить ему <code>await</code> в сигнатуру — значит склеить две разные задачи в одну: и наблюдать, и удерживать движок. Читатель сигнатуры теперь не различает: этот хук — «посмотри на состояние» или «придержи здесь движок»? Назначение хука перестаёт быть видно из сигнатуры.</p>
<p>Симптом того, что что-то не так, проявился в документации. README должна была пояснить, как пользоваться расширенным <code>onStep</code>. Честный абзац начинался с оговорки: «<code>onStep</code> — это хук наблюдения, но вы можете возвращать из него Promise, и движок дождётся его перед следующей итерацией». Не получалось убрать ни «но», ни вторую половину фразы. Документация работала ровно так, как описано в <a href="https://mellonis.ru/ru/articles/dva-mazhora-odin-readme-odno-demo/">статье (2)</a>: прозой давила на форму — форма треснула.</p>
<p>v6.4.0 добавила отдельный хук — <code>onIter</code>. Сигнатура: <code>(state) => void | Promise&#x3C;void></code>. Контракт: вызывается один раз в конце каждой итерации, асинхронно, безусловно. Имя — <code>iteration boundary</code>, граница итерации — описывает <em>что</em> хук делает (даёт точку в конце итерации, в которой можно подождать), а не <em>зачем</em> потребитель собирается его использовать. А использовать его можно по-разному: для приостановки между шагами трейса, для синхронизации с UI, для прерывания прогона (например, по AbortSignal).</p>
<p>Контракт хуков движка теперь различает три задачи:</p>
<ul>
<li><strong><code>onStep</code></strong> — синхронный, посредине итерации. Наблюдение за состоянием.</li>
<li><strong><code>onPause</code></strong> — асинхронный, <em>условный</em> (срабатывает только на брейкпоинтах). Граница между ходом и остановом.</li>
<li><strong><code>onIter</code></strong> — асинхронный, <em>безусловный</em> (срабатывает на каждой итерации). Координация: удержание движка для асинхронной работы потребителя.</li>
</ul>
<p>Три хука — три разные задачи. Наблюдение не должно удерживать движок; для этого есть отдельный, асинхронный хук. Удержание не должно прятаться в хуке наблюдения; для этого есть <code>onIter</code>. Пауза по брейкпоинту — это вообще отдельный сценарий, и под него есть <code>onPause</code>, у которого событие срабатывания явно прописано в имени.</p>
<h2 id="kontrakt-na-granitse-iteratsii"><a class="heading-anchor" href="#kontrakt-na-granitse-iteratsii">Контракт «На границе итерации»</a></h2>
<p>Где живёт приостановка — теперь ясно: внутри обработчика <code>onIter</code>. Но обработчик <code>onIter</code> запускается <em>внутри</em> воркера, а пауза от пользователя приходит <em>снаружи</em> — с основного потока, по клику. Между ними нужен протокол.</p>
<p>Этот протокол содержит четыре механизма: пара сообщений <code>idle</code>/<code>busy</code>, флаг <code>stepRequested</code>, синтетическое событие <code>paused</code>, поле <code>intervalMs</code> в <code>resume</code>. Каждый закрывает одну конкретную проблему, которую расположение приостановки создаёт.</p>
<h3 id="soobshcheniya-idle-i-busy-priostanovka-storozhevogo-taimera"><a class="heading-anchor" href="#soobshcheniya-idle-i-busy-priostanovka-storozhevogo-taimera">Сообщения <code>idle</code> и <code>busy</code>: приостановка сторожевого таймера</a></h3>
<p>Упомянутый выше сторожевой таймер в <code>onIter</code> начинает мешать.</p>
<p>Приостановка в <code>onIter</code> на каждой итерации может легко превышать таймаут — особенно если пользователь выкрутил <code>intervalMs</code> на «медленно» (демо позволяет ставить интервал, скажем, в две секунды). Получается ложная тревога: воркер не подвис, он просто ждёт.</p>
<p>Решение — воркер сам сообщает основному потоку, в какой момент он «работает», а в какой «ждёт». Перед тем как <code>await</code>-ить приостановку в <code>onIter</code>, воркер посылает сообщение <code>idle</code>. После того как приостановка завершилась, посылает <code>busy</code>. Основной поток приостанавливает сторожевой таймер на промежутке между <code>idle</code> и <code>busy</code>. Таймер продолжает ловить настоящие зависания (например, недостижимое halt-состояние), но не путает их с легитимным замедлением трейса.</p>
<p>Без такой обёртки у сторожевого таймера два плохих варианта. Либо брать порог с запасом — тогда настоящие зависания не ловятся (или ловятся слишком поздно). Либо брать порог впритык — выше вероятность ложного срабатывания (воркер убивается на легитимной задержке). Сообщения <code>idle</code> / <code>busy</code> разрешают оба сценария одновременно.</p>
<h3 id="shag-flag-vnutri-vorkera-a-ne-sostoyanie-dvizhka"><a class="heading-anchor" href="#shag-flag-vnutri-vorkera-a-ne-sostoyanie-dvizhka">Шаг — флаг внутри воркера, а не состояние движка</a></h3>
<p>Когда пользователь нажимает «Шаг», основной поток посылает воркеру сообщение <code>resume</code> с флагом <code>step: true</code> — «продвинь машину на одну итерацию и снова поставь паузу». Реализовано это так: воркер выставляет флаг <code>stepRequested = true</code>. На следующем <code>onIter</code> обработчик видит флаг, сбрасывает его в <code>false</code>, синтезирует событие <code>paused</code> и снова ждёт. Машина прошла ровно один шаг.</p>
<p>Принципиально: «шаг» — это флаг <em>внутри воркера</em>, а не часть состояния движка. Движок не знает, что такое «шаг», — он просто на следующей итерации опять оказался в режиме ожидания. Координация лежит снаружи движка. <code>onIter</code> остаётся тонким <code>await</code>-хуком — он не мутирует движок, он только приостанавливает его выполнение. Это и сделало <code>onIter</code> жизнеспособным как контракт: в нём нет «специальной логики шага», есть только Promise, который воркер сам контролирует. Если бы шаг был частью состояния движка, контракт <code>onIter</code> пришлось бы расширять до «и ещё, дёрни вот этот флаг», и хук перестал бы быть тонким.</p>
<h3 id="pri-shage-zamedlenie-izlishne"><a class="heading-anchor" href="#pri-shage-zamedlenie-izlishne">При «Шаге» замедление излишне</a></h3>
<p>При нажатии на «Шаг» замедление излишне — пользователь сам играет роль таймера: кликает столько раз и так быстро, как ему нужно. Реализация это отражает: при «Шаге» <code>intervalMs</code> игнорируется, обработчик <code>onIter</code> не вставляет ожидание. Это самое чистое выражение разделения «наблюдение ≠ координация» внутри воркера: когда управляет пользователь, движок подчиняется.</p>
<h3 id="sinteticheskaya-pauza-po-kliku"><a class="heading-anchor" href="#sinteticheskaya-pauza-po-kliku">Синтетическая пауза по клику</a></h3>
<p>Сценарий: машина в RUNNING_AUTO — движок выполняется с приостановками по времени, между итерациями <code>onIter</code> ждёт <code>intervalMs</code>. Пользователь нажимает в UI кнопку «Пауза». Клик приходит на основной поток. Основной поток посылает воркеру сообщение <code>pause</code>. Воркер в этот момент удерживает движок в приостановке — Promise ещё не разрешился. Что делать?</p>
<p>Решение — синтезировать такую же паузу, как от брейкпоинта. У UI уже есть готовый обработчик: на <code>paused</code> из воркера он переключается в «остановлено». Если клик «Пауза» пойдёт по другому пути, появится вторая ветка с тем же поведением. Поэтому воркер и синтезирует <code>paused</code> — для UI клик «Пауза» неотличим от срабатывания брейкпоинта.</p>
<p>Воркер отменяет приостановку (разрешает её принудительно, заранее), выставляет флаг <code>stepRequested = false</code>, и из обработчика <code>onIter</code> после возврата из ожидания посылает наружу событие <code>paused</code> — <em>синтетическое</em>, а не возникшее в ходе нормальной работы движка. Под капотом два сценария различаются (один отменяет приостановку, другой ждёт её естественного завершения), но контракт снаружи — один сценарий «пауза».</p>
<h3 id="intervalms-parametr-resume-ne-run"><a class="heading-anchor" href="#intervalms-parametr-resume-ne-run"><code>intervalMs</code> — параметр <code>resume</code>, не <code>run()</code></a></h3>
<p>Приостановка реализуется в <code>onIter</code> по <code>intervalMs</code>. Откуда воркер берёт это значение? Не из параметров запуска <code>run()</code>. А из последнего сообщения <code>resume</code>: каждое из них приходит с актуальным <code>intervalMs</code> из основного потока.</p>
<p>Причина — пользователь может управлять величиной задержки между приостановками во время прогона. Значение в UI читается <em>в момент клика</em>, не в момент старта <code>run()</code>; следующее <code>resume</code> передаст новое значение, и со следующей итерации приостановка подстроится. Маленькая цена для протокола, большой выигрыш для UX.</p>
<h2 id="dvoistvennost-yavno"><a class="heading-anchor" href="#dvoistvennost-yavno">Двойственность, явно</a></h2>
<p>Приостановка живёт в одной точке итерации. С точки зрения движка эта точка — обработчик <code>onIter</code>: хук, который вызывается один раз в конце каждой итерации, асинхронно. С точки зрения протокола та же точка — это промежуток между сообщениями <code>idle</code> и <code>busy</code>, в котором сторожевой таймер приостановлен, клик «Пауза» может мягко отменить приостановку, а <code>intervalMs</code> берётся из последнего <code>resume</code>.</p>
<p>Одна точка, два API. От выбора зависят все последующие этапы проектирования. Положить её посредине хука наблюдения — это сразу поломать обе стороны: документация на расширенный <code>onStep</code> не пишется без «но», обёртку сообщениями <code>idle</code>/<code>busy</code> не к чему прикрутить (один и тот же хук обозначает и наблюдение, и ожидание), синтетическое событие <code>paused</code> приходится посылать из неестественного места. Без чистой границы итерации страдают и хук, и протокол.</p>
<h2 id="zaklyuchenie"><a class="heading-anchor" href="#zaklyuchenie">Заключение</a></h2>
<p>Разделять наблюдение и координацию — это то, ради чего контракт хуков движка различает три задачи, а не сливает в одну. Склеить их — соблазн, которому поддался v6.2.0. И единственное, что позволило осознать ошибочность подхода за несколько часов — README движка не получалось написать честно, и smoke-тест демо не получалось пройти. Два почти бесплатных дизайн-ревью (<a href="https://mellonis.ru/ru/articles/dva-mazhora-odin-readme-odno-demo/">статья (2)</a>).</p>
<hr>
<p><em>Код: <a href="https://github.com/mellonis/turing-machine-js"><code>turing-machine-js</code></a> (движок, v6.2 → v6.4) и <a href="https://demo.machines.mellonis.ru"><code>machines-demo</code></a> (демо).</em></p>]]></content:encoded>
    </item>
    <item>
      <title>Два мажора, один README, одно демо: два почти бесплатных дизайн-ревью</title>
      <link>https://mellonis.ru/ru/articles/dva-mazhora-odin-readme-odno-demo/</link>
      <guid isPermaLink="false">https://mellonis.ru/ru/articles/dva-mazhora-odin-readme-odno-demo/</guid>
      <pubDate>Mon, 18 May 2026 00:00:00 GMT</pubDate>
      <description>Из трёх мажоров, описанных в предыдущей статье, два не всплыли в тестах. Они всплыли в двух дизайн-ревью, которые тесты провести не могут.</description>
      <content:encoded><![CDATA[<blockquote>
<p>Именно работа над документацией вскрыла обе ошибки. Тут есть мета-урок: документация — самое дешёвое дизайн-ревью, которое можно у себя устроить, но это уже тема для другого поста.</p>
</blockquote>
<p>Это и есть тот самый «пост», другая статья — и первое, что ей нужно сделать, это поправить тизер. Тизер привирает. Документация поймала только одну из двух ошибок. Вторую поймал первый настоящий потребитель API, которого я собирал параллельно. Эти два ревью сработали в паре: документация смотрит на форму API, потребитель — на использование. Вместе они ловят то, чего не видят тесты.</p>
<p>Если вы выпускаете что угодно за интерфейсом — библиотеку, CLI, любую сущность за контрактом, — это два ревью, которые вы, скорее всего, не оставляете без внимания.</p>
<figure><img src="https://mellonis.ru/articles/dva-mazhora-odin-readme-odno-demo/bf-tiser.webp?v=78281bd0" alt="Скриншот demo.machines.mellonis.ru с запущенным Brainfuck UTM: многоленточное представление слева, исходник движка справа" width="3680" height="2392"><figcaption>Скриншот demo.machines.mellonis.ru с запущенным Brainfuck UTM: многоленточное представление слева, исходник движка справа</figcaption></figure>
<h2 id="napominanie"><a class="heading-anchor" href="#napominanie">Напоминание</a></h2>
<p><a href="https://mellonis.ru/ru/articles/three-majors-two-mistakes/">«Три мажора, две ошибки»</a> разбирает движок и v4-API паузы — <code>onStep</code>, <code>onDebugBreak</code>, флаги <code>debug</code> на состояниях. Здесь я опираюсь на неё, не повторяясь слишком. Траектория v4 → v6 включает три брейкинг-мажора: переименование хука, ужесточение семантики halt и схлопывание lifecycle-тиков. Первые два всплыли в демо. Третий — в документации.</p>
<h2 id="demo-v4-v5"><a class="heading-anchor" href="#demo-v4-v5">Демо: v4 → v5</a></h2>
<p>Пока v4 уезжала в релиз, я начал собирать <a href="https://demo.machines.mellonis.ru"><code>machines-demo</code></a> — интерактивный отладчик Turing-машины и первый не-тестовый клиент движка.</p>
<p>Демо — естественный первый потребитель: у него двойная цель. Продуктовая — публичное распространение и демонстрация движка в действии. Техническая — обкатка изменений и проверка концепций на живой API-поверхности. Обе цели делают разработку демо неотъемлемой частью релизного цикла, а не опциональной добавкой к нему.</p>
<p>Демо задействовало оба хука одновременно: <code>onStep</code> заполнял буфер команд для UI-трейса на каждой итерации; <code>onDebugBreak</code> управлял циклом «пауза / продолжение».</p>
<p>Демо собиралось. Тесты проходили. Но писать его было неудобно, по двум причинам.</p>
<p>Во-первых, after-сработка <code>onDebugBreak</code> приходила с данными предыдущего yield’а — теми же, что предыдущий <code>onStep</code> уже показал. Демо обрабатывало одно и то же дважды, и вопрос «почему два хука для одного события?» задавал не я, а как будто сам код. Я открыл <a href="https://github.com/mellonis/turing-machine-js/issues/109"><code>turing-machine-js#109</code></a> как RFC об отношениях этих двух хуков, перечислил четыре эскиза; резолюция сузилась до <em>именования</em>. <code>onDebugBreak</code> ставил целью использования «отладку», глагол же потребителя — «пауза». Переименование без зазрения совести и без алиаса. v5.</p>
<p>Во-вторых, в UI демо появился сценарий «приостановиться перед halt» — дать пользователю заглянуть в финальное состояние машины до того, как она завершится. Естественная реализация — флажок <code>debug</code> на самом <code>haltState</code>. Первый тест-кейс установил <code>haltState.debug = { before: true, after: true }</code>, потому что симметрия выглядела правильной. Срабатывал же только <code>before</code>. Хуже того: after-сработка на самой итерации, которая <em>привела</em> к halt, не доходила до обработки — цикл выходил, как только <code>state.isHalt</code> становилось <code>true</code>. <a href="https://github.com/mellonis/turing-machine-js/issues/108"><code>turing-machine-js#108</code></a> разбил это на две части: восстановить потерянную after-сработку (баг); бросать исключение при записи в <code>haltState.debug.after</code> (API).</p>
<p>Обе претензии пришли со стороны потребителя. Демо не показал баг в коде — он показал <em>формат взаимодействия с API</em>. Имена не подходили под применение. Снисходительность к входным данным не соответствовала тому, что пользователь пытался сделать. Тесты сверяли поведение с собственной внутренней моделью движка — ок, зе́лено. Демо сверило поведение с ментальной моделью потребителя — и выдало две конкретные претензии (не ок).</p>
<h2 id="dokumentatsiya-v5-v6"><a class="heading-anchor" href="#dokumentatsiya-v5-v6">Документация: v5 → v6</a></h2>
<p>v5 уехала. README требовал обновления. Новый раздел про порядок диспатча должен был объяснить — словами, — когда срабатывают <code>onPause(before)</code>, <code>onStep</code> и <code>onPause(after)</code> относительно той итерации, которую они описывают.</p>
<p>Первый честный абзац выглядел примерно так:</p>
<blockquote>
<p><code>onPause(after, K)</code> срабатывает на yield’е итерации K+1, с payload’ом, подставленным из снэпшота итерации K, до того как сработают <code>onPause(before, K+1)</code> или <code>onStep(K+1)</code>.</p>
</blockquote>
<p>Я какое-то время смотрел на это предложение. Короче не получалось. Предложение о подстановке — это не то, что читатель должен был встретить.</p>
<p>Код работал. Тесты проходили. Демо корректно потребляло хуки. Ошибка была не в этом — она была в <em>форме</em> диспатча, и эта форма становилась видна только с той стороны, где её приходилось формулировать словами.</p>
<p>Фикс схлопнул lifecycle: <code>before(K) → step(K) → after(K)</code> на одном и том же yield’е. Без подстановок. Без межитерационного планирования. Без финального добора для останавливающейся итерации (в «Трёх мажорах, двух ошибках» я перевёл это как «пост-цикловой добор»; сейчас, пожалуй, обошёлся бы покороче). Абзац README теперь звучит так:</p>
<blockquote>
<p>На yield’е итерации K хуки срабатывают в lifecycle-порядке.</p>
</blockquote>
<p>А такое предложение читатель проходит не задумываясь. <a href="https://github.com/mellonis/turing-machine-js/issues/119"><code>turing-machine-js#119</code></a> уехал как v6.</p>
<p>Прямая цитата из предыдущей статьи:</p>
<blockquote>
<p>Код работал, тесты проходили, документация была корректной. Просто дизайн был неправильным.</p>
</blockquote>
<p>К этому хочется добавить: документация была корректной только при условии, что читатель смирится с тремя объясняющими предложениями, которые ему читать не следовало. Это не «корректная документация» — это документация, извиняющаяся за форму.</p>
<p>Ревью документацией поймало <em>форму</em>, не <em>использование</em>. Демо работало. Тесты проходили. Только <em>давление прозы</em> в этот раз вскрыло внутренние проблемы реализации.</p>
<h2 id="tri-revyu-tri-sloya"><a class="heading-anchor" href="#tri-revyu-tri-sloya">Три ревью, три слоя</a></h2>
<p>Опишем, что проверяет каждое:</p>
<ul>
<li><strong>Тесты</strong> сверяют код с самим собой. Внутренняя консистентность. Движок выдаёт то, что движок должен выдавать. Зелёные тесты — это база.</li>
<li><strong>Первый настоящий потребитель</strong> сверяет код с ментальной моделью потребителя. <em>Совпадает ли формат API с тем, что пользователь пытается сделать?</em> Ревью реального взаимодействия с API привело к его изменению: переименованию и запрету halt-after.</li>
<li><strong>Документация в виде <em>прозы</em></strong> (не JSDoc, а связный README-нарратив) сверяет код с авторским объяснением. <em>Есть ли у дизайна честное однопараграфное описание?</em> Отсутствие такового заставило внимательно посмотреть на танец с подстановкой и отказаться от него.</li>
</ul>
<p>Каждое ревью ловит то, что не ловит слой ниже. Тесты не ловят форму; потребитель не ловит проблемы взаимодействия, которые он умело может обойти; документация не ловит UX-неровности, которые ей упоминать не пришлось.</p>
<p>Стоимость асимметричная. Разработка настоящего потребителя — самое дорогое из трёх ревью; написание документации — самое дешёвое. И всё-таки даже самое дорогое из них обходится дешевле, чем переделывать API после того, как проблемы вскроются у пользователей: отсюда и «почти бесплатные» в заголовке. Настоящего потребителя нужно разработать <em>до</em> мажорного релиза — только это ревью делает API комфортным в использовании. Документация после этого ограждает то, что осталось.</p>
<h2 id="evristiki-na-sleduyushchii-mazhor"><a class="heading-anchor" href="#evristiki-na-sleduyushchii-mazhor">Эвристики на следующий мажор</a></h2>
<ol>
<li><strong>Соберите первого настоящего потребителя до мажора.</strong> Не тест-фикстуру — а потребителя со своей ментальной моделью. Трудности, с которыми он сталкивается, — это те же трудности, с которыми потом столкнутся все ваши пользователи.</li>
<li><strong>Напишите параграф про порядок диспатча до того, как зафиксируете порядок диспатча.</strong> Если не получается одной фразой описать, что и когда срабатывает, — диспатч неправильный. Решения такого рода должны приниматься на этапе проектирования, а не документации, тогда когда не написано даже и строчки кода.</li>
<li><strong>Перечитайте документацию глазами незнакомца.</strong> Если параграф читается как извинение за форму — документация работает, она <em>явила</em> ошибку формы. Чинить надо форму, а не параграф.</li>
</ol>
<p>Три мажора. Один README. Одно демо. Тестам сказать было нечего.</p>
<hr>
<p><em>Код: <a href="https://github.com/mellonis/turing-machine-js"><code>turing-machine-js</code></a> (движок) и <a href="https://demo.machines.mellonis.ru"><code>machines-demo</code></a> (первый не-тестовый потребитель, на котором всплыли v4 → v5).</em></p>]]></content:encoded>
    </item>
    <item>
      <title>Три мажора, две ошибки: проектирование API приостановки для интерпретатора машины Тьюринга</title>
      <link>https://mellonis.ru/ru/articles/three-majors-two-mistakes/</link>
      <guid isPermaLink="false">https://mellonis.ru/ru/articles/three-majors-two-mistakes/</guid>
      <pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate>
      <description>Две ошибки в проектировании API приостановки для машины Тьюринга и три мажорных релиза, ушедшие на их вычищение</description>
      <content:encoded><![CDATA[<figure><img src="https://mellonis.ru/articles/three-majors-two-mistakes/tm.webp?v=4db093ab" alt="Скриншот demo.machines.mellonis.ru с запущенной машиной Тьюринга" width="3680" height="2392"><figcaption>Скриншот demo.machines.mellonis.ru с запущенной машиной Тьюринга</figcaption></figure>
<p>За последние две недели я выкатил подряд четыре ломающих мажорных релиза <a href="https://github.com/mellonis/turing-machine-js"><code>@turing-machine-js/machine</code></a> — v3, v4, v5, v6 — и самое интересное здесь было не в какой-то одной фиче. Интересно было смотреть, как один и тот же участок API (хук приостановки/брейкпоинта в управляющем цикле) переделывался дважды за три версии, и каждый раз потому, что в прошлый раз там выпирало то, что выпирать не должно.</p>
<p>Этот пост — разбор полётов. Если вы проектируете API приостановки/шага/брейкпоинта для интерпретатора на цикле-генераторе, для планировщика или рантайма DSL, ошибки, которые я допустил, нетрудно повторить — и полезнее сначала встретить их в чужом коде.</p>
<h2 id="postanovka-zadachi"><a class="heading-anchor" href="#postanovka-zadachi">Постановка задачи</a></h2>
<p>Движок исполняет машину Тьюринга. Внутри — генератор: каждый <code>yield</code> — один шаг (одно срабатывание перехода на одном символе ленты). Управляющий цикл выглядит примерно так:</p>
<pre class="shiki shiki-themes github-light github-dark language-javascript" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">async</span><span style="color:#D73A49;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> run</span><span style="color:#24292E;--shiki-dark:#E1E4E8">({ </span><span style="color:#E36209;--shiki-dark:#FFAB70">initialState</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">onStep</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> }) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">  for</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> m</span><span style="color:#D73A49;--shiki-dark:#F97583"> of</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> runStepByStep</span><span style="color:#24292E;--shiki-dark:#E1E4E8">({ initialState })) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> onStep</span><span style="color:#24292E;--shiki-dark:#E1E4E8">?.(m);</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (m.state.isHalt) </span><span style="color:#D73A49;--shiki-dark:#F97583">return</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<p><code>MachineState</code> (<code>m</code>) — снимок состояния машины на каждой итерации: какое состояние сейчас будет исполнено, текущий и следующий символы, движения, которые сделают головки. Потребитель — логгер, UI, тест — может подцепиться к <code>onStep</code> и наблюдать.</p>
<p>В v4 я хотел добавить способ <em>приостановить</em> выполнение в выбранных точках — как брейкпоинт в отладчике. У любого <code>State</code> должна быть возможность задать конфигурацию <code>debug</code>:</p>
<pre class="shiki shiki-themes github-light github-dark language-javascript" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">myState.debug </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { before: [symA], after: [symA] };</span></span></code></pre>
<p>...а управляющий цикл должен давать потребителю возможность заглянуть внутрь машины и решить, когда продолжить.</p>
<h2 id="v4-vikativaem"><a class="heading-anchor" href="#v4-vikativaem">v4: выкатываем</a></h2>
<p>Дизайн v4 был простым. Я сделал <code>run()</code> асинхронным, добавил хук <code>onDebugBreak</code> и спроектировал его так:</p>
<pre class="shiki shiki-themes github-light github-dark language-javascript" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> machine.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">run</span><span style="color:#24292E;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  initialState,</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  onStep</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: (</span><span style="color:#E36209;--shiki-dark:#FFAB70">m</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">=></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">/* каждый шаг */</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> },</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  onDebugBreak</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#D73A49;--shiki-dark:#F97583">async</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="color:#E36209;--shiki-dark:#FFAB70">m</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">=></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (m.debugBreak?.before) console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'before:'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, m.state.name);</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (m.debugBreak?.after)  console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'after:'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,  m.state.name);</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    // зависаем здесь до резолва промиса</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">});</span></span></code></pre>
<p>Поле <code>debug</code> у состояния — мутабельное. <code>state.debug = { before: true }</code> ставит брейк перед шагом; <code>before: [symA]</code> фильтрует по тому, что читает головка. Хук вызывается через <code>await</code>, поэтому любой потребитель может реализовать сценарий «приостановить машину, пока человек не нажмёт Продолжить» — просто не резолвить промис.</p>
<p>Также хотелось ставить брейкпоинты на halt:</p>
<pre class="shiki shiki-themes github-light github-dark language-javascript" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">haltState.debug </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { before: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> };  </span><span style="color:#6A737D;--shiki-dark:#6A737D">// приостановка перед выходом</span></span></code></pre>
<p>Это работало. Фильтры по списку символов на <code>haltState</code> молча игнорировались — у <code>haltState</code> нет символа под головкой, по которому можно было бы фильтровать. Ну и ладно, подумал я, — будем снисходительны к входным данным, главное, что универсальный <code>true</code> срабатывает.</p>
<p>В этом дизайне было два просчёта. Я не замечал ни одного, пока не сел писать документацию, а следом — UI.</p>
<h2 id="oshibka-1-khuk-opisivaet-dvizhok-a-ne-potrebitelya"><a class="heading-anchor" href="#oshibka-1-khuk-opisivaet-dvizhok-a-ne-potrebitelya">Ошибка № 1: хук описывает движок, а не потребителя</a></h2>
<p><code>onDebugBreak</code> на бумаге читается как вполне разумное имя. Движок выстреливает «debug break»; вы цепляете на него хук.</p>
<p>Но что потребитель в этом хуке <em>делает</em>? Приостанавливает. Заглядывает внутрь. Ждёт ввод. Возобновляет. Хук на самом деле не уведомляет о том, что произошло событие, — он предлагает точку сотрудничества.</p>
<p>Имя <code>onDebugBreak</code> несёт одну конкретную рамку — «отладка». Но тот же хук — это ровно то, что нужно для пошаговой визуализации, замедленного воспроизведения, плавной анимации, UI с «нажмите пробел, чтобы двинуться дальше». Ничто из этого не отладка. Это всё <em>приостановка</em>.</p>
<p>Кажется мелочью. Это не мелочь — потому что, увидев имя, потребители начинают проектировать вокруг него: их обработчик называется <code>handleDebugBreak</code>, в их стейт-машине заводится булева <code>debugging</code>, кнопка в UI подписана «Остановить отладку». Имя расползается по словарю каждого потребителя.</p>
<p>Я написал RFC (<a href="https://github.com/mellonis/turing-machine-js/issues/109">turing-machine-js#109</a>) и переименовал хук в v5. Жёсткое переименование, без алиаса с депрекацией:</p>
<pre class="shiki shiki-themes github-light github-dark language-diff" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">await machine.run({</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    initialState,</span></span>
<span class="line"><span style="color:#B31D28;--shiki-dark:#FDAEB7">-   onDebugBreak: (m) => { ... },</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">+   onPause: (m) => { ... },</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  });</span></span></code></pre>
<p>Поле пейлоада <code>m.debugBreak</code> осталось — это метаданные о том, <em>почему</em> сработал этот yield (<code>{ before: true }</code> или <code>{ after: true }</code>), и «break» внутри пейлоада нормально читается. Но имя хука — это контракт с потребителем: <code>onPause</code> описывает, что он делает, а не что делает движок.</p>
<p><strong>Правило на следующий раз:</strong> называя хук, называйте глагол потребителя, а не событие движка. Хуки — это точки сотрудничества, а не event listeners.</p>
<h2 id="ogranichenie-haltstate-debug-after-bessmislitsa"><a class="heading-anchor" href="#ogranichenie-haltstate-debug-after-bessmislitsa">Ограничение: <code>haltState.debug.after</code> — бессмыслица</a></h2>
<p>Вторая ошибка v4 пряталась под инстинктом быть снисходительным к входным данным. <code>haltState.debug.after</code> молча принимал запись. Просто не срабатывал.</p>
<p>Почему? Потому что семантика <code>after</code> в этом движке означает «сработать на итерации <em>после</em> той, что соответствовала фильтру». Для обычных состояний это логично: вы выходите из состояния K, исполняется следующая итерация (K+1), <em>и на её yield-е</em> потребителю сообщают: «кстати, фильтр after на K сработал на прошлом шаге». Но halt — терминальное. После halt никакого K+1 нет. Событию <code>after</code> не за что зацепиться.</p>
<p>В v4 я молча проглатывал <code>haltState.debug.after = true</code>. Хуже того, я пропускал также <code>{ before: true, after: true }</code> — половина присваивания была осмысленной, половина была no-op-ом, и потребитель об этом не узнавал.</p>
<p>В v5 я сделал так, чтобы обе формы бросали исключение при записи (<a href="https://github.com/mellonis/turing-machine-js/issues/108">turing-machine-js#108</a>, часть 2):</p>
<pre class="shiki shiki-themes github-light github-dark language-diff" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B31D28;--shiki-dark:#FDAEB7">- haltState.debug = { before: true, after: true }; // v5: бросает — 'after' на halt не за что зацепиться</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">+ haltState.debug = { before: true };</span></span></code></pre>
<p><strong>Правило на следующий раз:</strong> если у конфигурации нет семантики — кидайте исключение пораньше. Молчаливый no-op на входе <em>выглядит</em> юзерфрендли — ровно до момента, когда потребитель полдня выясняет, почему у него UI не срабатывает на halt. «Будь снисходителен» — ложная экономия, когда снисходительность глушит реальную ошибку.</p>
<p>Заодно я починил связанный баг: фильтр <code>after</code> <em>самой</em> останавливающейся итерации тоже не срабатывал (<a href="https://github.com/mellonis/turing-machine-js/issues/108">turing-machine-js#108</a>, часть 1). Управляющий цикл выходил, как только <code>state.isHalt</code> становилось <code>true</code>, — и after-событие от предыдущей итерации, которое в норме сработало бы на этом финальном yield-е, просто проваливалось. В v5 я добавил пост-цикловой добор, чтобы оно сработало.</p>
<p>К этому я ещё вернусь.</p>
<p>И один бонус v5: общий выключатель на <code>run()</code> для всей системы приостановки.</p>
<pre class="shiki shiki-themes github-light github-dark language-javascript" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> machine.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">run</span><span style="color:#24292E;--shiki-dark:#E1E4E8">({ initialState, debug: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">false</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, onPause: </span><span style="color:#D73A49;--shiki-dark:#F97583">...</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> });</span></span></code></pre>
<p>При <code>false</code> все срабатывания приостановки подавляются вне зависимости от того, что прописано в <code>state.debug</code> по всему графу (<a href="https://github.com/mellonis/turing-machine-js/issues/106">turing-machine-js#106</a>). Можно A/B-тестировать «режим отладки», не переписывая граф и не вычищая каждое поле <code>state.debug</code>. Флаг управляет диспатчем <code>onPause</code>; само поле полезной нагрузки <code>m.debugBreak</code> по-прежнему заполняется на yield-ах генератора (это свойство итерации, а не того, как <code>run()</code> решил его выставить наружу).</p>
<h2 id="oshibka-2-tanets-podstanovki"><a class="heading-anchor" href="#oshibka-2-tanets-podstanovki">Ошибка №2: танец подстановки</a></h2>
<p>Эту я поймал с особой гордостью — код работал, тесты проходили, документация была корректной. Просто дизайн был неправильным.</p>
<p>В v4 и v5 <code>after K</code> (срабатывание after для итерации K) на самом деле происходило на yield-е итерации <strong>K+1</strong>. Управляющий цикл проносил между yield-ами флажок <code>pendingAfterFromPrev</code>, и на yield-е следующей итерации <em>сначала</em> диспатчил хук <code>after</code>, потом хук <code>before</code>, потом <code>onStep</code>. Хук для after-срабатывания K должен был видеть состояние K, а не K+1. Поэтому я <em>подставлял</em> снимок предыдущего yield-а в поле <code>m.state</code> итерации K+1 — только на время диспатча <code>after</code>.</p>
<p>Это работало. Но у этого были три побочки:</p>
<ol>
<li>
<p><strong>Подстановка протекала.</strong> Потребители хотели доступ к неподставленному, «настоящему» состоянию итерации (тому, которое видит хук <code>step</code>), — см. <a href="https://github.com/mellonis/turing-machine-js/issues/107">turing-machine-js#107</a>. Часть пользователей читала <code>MachineState.debugBreak</code> напрямую из <code>runStepByStep</code> и удивлялась, что <code>m.state</code> относится к итерации, на которой <code>after</code> сработал, а не к той, что его породила.</p>
</li>
<li>
<p><strong>Halt требовал отдельной ветки.</strong> Как уже сказано: собственный <code>after</code> останавливающейся итерации срабатывает <em>после</em> выхода из цикла. В v5 для этого появился пост-цикловой добор. И у этого добора своя ветка кода, со своей подстановкой, отдельной от диспатча внутри цикла.</p>
</li>
<li>
<p><strong>Возвращаемый тип генератора расширился.</strong> Поскольку пост-цикловому добору надо было <em>вернуть</em> финальный yield-нутый объект наружу из итератора, тип возврата <code>runStepByStep</code> стал <code>Generator&#x3C;MachineState, MachineState | null></code> — yield-тип <em>и</em> return-тип. Канонический потребитель на <code>for..of</code> return-значения не видит, но всякий, кто читал сигнатуру типа, теперь должен был разбираться, почему слотов два.</p>
</li>
</ol>
<p>Всё это выросло из одного проектного решения: диспатчить <code>after K</code> на yield-е итерации K+1, а не на собственном yield-е итерации K.</p>
<p>В v6 я это схлопнул (<a href="https://github.com/mellonis/turing-machine-js/issues/119">turing-machine-js#119</a>). Жизненный цикл одной итерации теперь честно <code>before → step → after</code>. Срабатывание <code>after</code> приходится на тот же yield, что и итерация, которая его породила. Нет подстановки. Нет <code>pendingAfterFromPrev</code>. Нет пост-циклового добора. Тип возврата генератора сужается обратно до <code>Generator&#x3C;MachineState></code>.</p>
<pre class="shiki shiki-themes github-light github-dark language-javascript" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// v6</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> machine.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">run</span><span style="color:#24292E;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  initialState,</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  onStep</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: (</span><span style="color:#E36209;--shiki-dark:#FFAB70">m</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">=></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">/* без изменений */</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> },</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  onPause</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: (</span><span style="color:#E36209;--shiki-dark:#FFAB70">m</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">=></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (m.debugBreak?.before) console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'before:'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, m.state.name);</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (m.debugBreak?.after)  console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'after:'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,  m.state.name);</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">});</span></span></code></pre>
<p>Порядок диспатча <em>между</em> хуками поменялся (любой тест, утверждавший <code>pause(after K-1) → pause(before K) → step(K)</code>, пришлось перевернуть в <code>pause(before K) → step(K) → pause(after K)</code>), но множество вызванных диспатчей и семантика на каждой итерации не изменились. Потребители, относящиеся к хукам как к независимым наблюдателям, не видят никаких различий (<a href="https://github.com/mellonis/turing-machine-js/issues/107">turing-machine-js#107</a> — «отдать наружу неподставленное состояние» — исчез как проблема: подстановки больше нет, обходить нечего).</p>
<p><strong>Правило на следующий раз:</strong> если пейлоаду хука нужно подставлять состояние из другой итерации, а не из той, что сейчас yield-ит, — последовательность диспатча неверна. Фазы жизненного цикла (<code>before</code>, <code>step</code>, <code>after</code>) принадлежат той итерации, которую описывают. Подстановка была не хитрым приёмом, а протечкой — она говорила, что события стоят не на том такте.</p>
<h2 id="chto-ya-zaberu-v-sleduyushchii-api-priostanovki-breikpointov"><a class="heading-anchor" href="#chto-ya-zaberu-v-sleduyushchii-api-priostanovki-breikpointov">Что я заберу в следующий API приостановки/брейкпоинтов</a></h2>
<ol>
<li>
<p><strong>Называйте хуки глаголом потребителя, а не событием движка.</strong> <code>onPause</code>, а не <code>onDebugBreak</code>. <code>onProgress</code>, а не <code>onChunkReceived</code>. Имя расползается по ментальной модели каждого потребителя — выбирайте ту рамку, которую хотите им передать.</p>
</li>
<li>
<p><strong>Кидайте исключение при невозможных конфигурациях — и пораньше.</strong> «Снисходительность к входным данным» подходит для форм, которые <em>могут</em> быть осмысленными; это ловушка для форм, которые осмысленными быть <em>не могут</em>. У <code>haltState.debug.after = true</code> не было возможной семантики. Молчаливое проглатывание стоило одному пользователю полдня — пока я не заметил.</p>
</li>
<li>
<p><strong>Фазы жизненного цикла принадлежат той итерации, которую описывают.</strong> Если хочется диспатчить <code>after K</code> на yield-е K+1 — спросите, какой пейлоад приходится подставлять, чтобы это выглядело правильно. Подстановка — это и есть сигнал.</p>
</li>
<li>
<p><strong>Общий выключатель стоит дёшево и оправдывает себя.</strong> Булева на входной точке, которая выключает всю фичу целиком (без переписывания флажков на уровне графа), делает фичу безопасной для прода и удобной для тестов. <code>run({ debug: false })</code> был одной из самых мелких добавок v5 — и одной из самых востребованных среди зависимых проектов.</p>
</li>
<li>
<p><strong>Ломающие изменения стоят мажора.</strong> Три мажора за две недели звучат дорого. На самом деле — нет, если синхронный downstream — один репозиторий (в моём случае <a href="https://github.com/mellonis/post-machine-js"><code>@post-machine-js/machine</code></a>, который синхронно с движком обновил peer-зависимость <code>^4.0.0</code> → <code>^6.0.0</code> и переименовал свой внутренний <code>__onDebugBreak</code> → <code>__onPause</code>). Эргономика API копится у всех зависимых проектов на весь срок жизни библиотеки — заплатите цену миграции, пока API ещё молодой.</p>
</li>
</ol>
<p>Дизайн v6 — это то, что мне следовало выпустить ещё в v4. Тогда у меня не было нужного словаря — я думал «выставить наружу события движка», а не «спроектировать контракт потребителя». Именно работа над документацией вскрыла обе ошибки. Тут есть мета-урок: документация — самое дешёвое дизайн-ревью, которое можно у себя устроить, но это уже тема для другого поста.</p>
<hr>
<p><em>Код:</em> <a href="https://github.com/mellonis/turing-machine-js"><code>turing-machine-js</code></a> <em>(движок) и</em> <a href="https://github.com/mellonis/post-machine-js"><code>post-machine-js</code></a> <em>(машина Поста поверх него, версии синхронизированы). Интерактивное демо на</em> <a href="https://demo.machines.mellonis.ru">demo.machines.mellonis.ru</a> <em>использует v6-хук</em> <code>onPause</code> <em>для кнопок Step / Run / Stop.</em></p>]]></content:encoded>
    </item>
  </channel>
</rss>