Документация · архитектура · 01.09.2026
Четыре листа вместо одного графа зависимостей. Большой граф не отвечает ни на один вопрос — он показывает всё сразу и потому не помогает решить ничего. Здесь каждый лист держит ровно тот уровень детали, который нужен для одного решения, и молчит про остальное.
Числа на листах верны на один
коммит и стареют молча — за первый же день под атласом закрылись
#146 и #147. Поэтому у него есть свой сторож:
atlas-facts.py меряет их заново и говорит, на каком листе править.
Что каждый лист умышленно не показывает
Граф всех зависимостей «Ролльни» рисуется за минуту и бесполезен: двадцать файлов, полторы сотни связей, и ни одного места, где взгляд остановится. Полезен не полный чертёж, а чертёж под решение — и тогда половину связей надо не нарисовать, а сознательно убрать.
Поэтому у каждого листа записано, что он не показывает. Лист 1 не рисует ни одной обратной стрелки — он про то, как задумано. Лист 2 не рисует ни одной здоровой связи — он про то, что мешает. Лист 3 не рисует ветвлений — один путь и целиком. Лист 4 не пересказывает контракт — только порядок, цену и доказательство.
Что существует
Зона — это право вызова, а не папка на диске: catalog живёт в
play/model/, а state — один файл в корне play/.
Внутри зоны перечислены файлы с числом строк, чтобы было видно, где масса:
model/geometry.js — 2902 строк, вдвое больше любого соседа, и это ядро
продукта, а не разросшийся утиль.
С 05.09 зон одиннадцать. Появилась core-v2 — второй
движок, считающий то же самое в миллиметрах и заново (PR #172). Он не заменяет
geometry.js: тот по-прежнему считает игру. У зоны ровно два ребра —
раскладка игрока приходит в неё через domain/recipe-v2.js, а наружу
уходит снимок (срез и радиус) через adapter.js → bridge.js.
Внутрь play/model/** зона не смотрит вовсе: каталог ей передают
аргументом, а не импортом.
Две зоны стоят особняком. Фасад domain/roll.js
подключён, но боевого потребителя не имеет — его зовёт только собственный сторож.
Лаборатория sim/ в рантайме игры отсутствует вовсе:
числа оттуда переезжают в каталог руками, через доки, и это единственная связь
между ними.
Что мешает переезду
Цифры на листе считаны, а не оценены: grep -c по каждому файлу на
01.09.2026. Красным помечено ровно то, что просили — глобалы, порядок загрузки
classic-скриптов, связи «рендер → состояние» и временные подмены S.
Главное, что видно из цифр: вход модели не назван.
geometry.js читает S.base, S.shape,
S.turns, S.hand прямо внутри buildModel —
значит посчитать ролл, не тронув живую сессию, нельзя. Все шесть редакций подмены
S существуют ровно потому, что безымянный вход надо чем-то подсунуть.
Половина лечения уже стоит в коде: объект g в
geometry.js:2403 — «паспорт модели: всё, что геометрия раньше
подсматривала в S и B(), снято здесь один раз». Это готовый ModelInput,
только собранный внутри функции, а не переданный снаружи.
Одна поправка к первой редакции листа. Файлы были разнесены
на «писателей» и «читателей» — деление было моё, а не измеренное. На самом деле
ни один файл не является только тем или только другим: ui/actions.js
пишет 29 раз и читает 26. Это не смягчает картину, а делает её хуже: у объекта
нет ни одной границы — ни на запись, ни на чтение.
| Режем | Узел | Где живёт | Обращений | Чем гасится |
|---|---|---|---|---|
| 1-м | ① порядок скриптов | play/index.html — 16 тегов, ?v=226 |
16 | шаг 1 · ES-модули |
| 2-м | ② S | state.js:11 — 238 записей, 215 чтений |
453 | шаги 2 и 4 |
| 2-м | ③ B() | state.js:214 — ключ на два поля |
75 | шаг 2 · ModelInput |
| 3-м | ⑥ подмена S | album · roll · checks · puzzle · core-v2/legacy-probe | 6 редакций | шаг 3 · чистый домен |
| 4-м | ④ L в домене | domain/roll.js:259 |
1 | шаг 3 · удалением |
| 5-м | ⑤ touchModel() | render/slice.js:423 — 7 файлов |
96 | шаг 5 · распадается сам |
Протянул циновку → увидел срез
Пять фаз — это не пять экранов, а пять мест, где путь меняет владельца. Красная стрелка стоит там, где владелец меняется через глобал, а не через аргумент.
Лист показывает и хорошую новость. Длинная часть пути — фазы 3 и 4, где живёт вся
математика, — уже почти чистая: вся грязь собрана на входе в неё,
в двух красных стрелках к S. Поднять g в аргумент — и фазы
3–4 отрываются целиком, одним куском.
И плохую. Живой срез во время скрутки (#94, закрыт) — это был тот же
путь, вызванный на каждом кадре с другим rollP: «записать
в S.rollP, посчитать, вернуть обратно». Предсказание сбылось иначе:
новая редакция подмены пришла 05.09 не отсюда, а из ядра V2 —
core-v2/legacy-probe.js, файл движка, который от монолита как раз
уходит. Приём воспроизводится даже там, где его не хотели.
ES-модули → ModelInput → чистый домен → команды → снимки
node tools/check.js — правок игрового кода ноль.
Замер на 01.09, в момент сдачи: 187 строк, 12,0 с, ВСЁ ЦЕЛО, известно 32,
практика сошлось 32 при нуле провалов. Сегодняшние числа — в блоке итогового
состояния (190 строк, известно 37); здесь они намеренно не обновляются, иначе
запись перестанет говорить, чем задача закрывалась.
Вшит в end-of-day.sh. Единственный пункт, который сам ничего не чинил
и разблокировал всё. Числа про вёрстку по-прежнему только из
?check в браузере — холст и DPR там настоящие.
Сторож умеет умирать молча. 05.09 все headless-инструменты
падали на загрузке — state.js стал разбирать ?v2 через
URLSearchParams, которого нет в песочнице vm. Пока он
лежал, эти три отчёта разъехались с кодом и никто не увидел.baseline-data.js: числа обязаны совпасть побитово.S. Переносить по одной
команде, после каждой — полный прогон.Работа, которая идёт рядом и путается с миграцией, потому что тоже «про архитектуру». Разница одна и она проверяемая: пять шагов выше не меняют ни одного числа, а эти меняют модель.
#146 — топология кольца за имя — закрыт 01.09, пока
собирался атлас. Пять мест снаружи читали wd.rin[NB + b],
зная, что слой k = 1 — обёртка. Теперь намотка отдаёт наружу восемь вопросов
вместо массивов: внутр, внеш, есть,
толщина, витокЕсть, накрыт,
крайВитка, голый. Это и был настоящий блокер второй
ветки намотки — тот самый, которого стороннее ревью не назвало.
Следом закрыт #147, а в каталоге появилась база узумаки
с winding: 'spiral': #142 закрыт. Урамаки
(#3, #124) тоже закрыты. Из перечисленного открыт
только #74 — роль как свойство укладки.
У вопроса «какой шаг следующий» теперь два ответа. Рядом с этими
пятью шагами идёт второй план миграции — docs/decisions/ADR-001-core-v2-scope.md,
шесть шагов, логика обратная: не разбирать монолит изнутри, а написать ядро заново
в миллиметрах рядом и подключить флагом. Его шаг 4 уже сделан (PR #172, влит 05.09),
а с #176 флаг ?v2 считает раскладку игрока, а не заготовку. Пять шагов
ниже не меняют ни одного числа; ADR-001 меняет модель. Выбор между ними — решение
владельца, а не следствие схемы.
Исходники, пересборка, карта для обсуждения
Листы — не картинки, а текст: .d2 лежит рядом с документацией и
правится как код. SVG — производное, пересобирается одной командой.
# поставить d2 (один раз) brew install d2 # пересобрать все четыре листа cd docs/architecture && ./build.sh # живая правка: браузер сам обновляется на каждое сохранение d2 --watch 02-danger-edges.d2 # карта для обсуждения — пересобрать после правки листов python3 docs/architecture/build-atlas-excalidraw.py # эта страница python3 docs/architecture/build-atlas-page.py # не разошёлся ли атлас с кодом (и на каком листе править) python3 docs/architecture/atlas-facts.py python3 docs/architecture/atlas-facts.py --snapshot # после правки листов # сторож самой игры — теперь без браузера (#147) node tools/check.js
docs/architecture/svg/*.svg,
открывается любым браузером, масштабируется без потери резкости.docs/architecture/rollery-atlas.excalidraw: открыть
excalidraw.com → Open → выбрать файл.
Или расширение «Excalidraw» в VS Code, тогда файл открывается прямо в редакторе.
Данные никуда не уходят, всё локально.Листы отвечают каждый на своё решение и потому нарочно неполны — на них нельзя показать пальцем «вот это к вот этому». Карта наоборот: одна поверхность, где слева что есть, посередине что красное, справа куда едем, и зелёный пунктир справа налево читается как «этот шаг гасит этот узел». Её можно таскать мышью, дописывать и черкать — для этого она и сделана. Внизу оставлено пустое место под два вопроса, которые решает владелец, а не я.
Править .d2, а не SVG: SVG перезаписывается при следующей сборке.
Excalidraw-карта собирается скриптом, поэтому её тоже правит генератор, а не рука —
иначе следующая пересборка сотрёт правку.
Числа руками не считать. atlas-facts.py меряет их
сам — счётчики, адреса file:line, длины файлов, статусы задач — и
сверяет со слепком, показывая, на каком листе править каждое расхождение. Меряет
через git show, а не по рабочему каталогу: над «Ролльней» работает
вторая сессия, и замер с полусохранённого файла не соответствует ни одному
состоянию проекта.