Схемы C4 до уровня компонентов и четыре критических пути через sequence: запуск, тик симуляции, действие игрока и кадр.
Житьё — симулятор жизни в браузере: район из десяти семей, потребности, карьера, отношения, режимы «Покупка» и «Стройка». Без сборки и сервера: страница, набор ES-модулей и three.js с CDN. Ниже схемы C4 до уровня компонентов и четыре пути, на которых всё держится.
C4: контекст и контейнеры
Уровень 1. Система и её окружение
Своего сервера нет: подойдёт любой статический хостинг.
Уровень 2. Контейнеры
main.js создаёт шину и состояние, собирает контексты и крутит цикл. Мозг, Мир и UI читают каталог из data/.
C4 уровня 3: компоненты контекстов
Контексты общаются через шину событий и один объект состояния. У каждого факта один хозяин: время и потребности ведёт Мозг, клетки и стены знает Мир, картинку строит Рендер.
Мозг
Мозг не знает ни DOM, ни three.js: его можно запускать в Node, так и устроены тесты.
Автономия оценивает предметы вокруг по кривым потребностей и делит выигрыш на расстояние.
Команда игрока встаёт в очередь выше автономной и вытесняет её.
Подсистемы подключаются к действиям через хуки завершения шагов, ядро цикла их не знает.
Мир
Мир хранит участок в состоянии и отвечает на вопросы: можно ли поставить, где пройти, что за комната.
Сетки проходимости и комнат считаются один раз и сбрасываются при правке участка.
Активный участок живёт в state.lot, остальные хранятся снимками внутри района.
Рендер
Рендер только читает состояние и слушает шину; сам ничего в нём не меняет.
Объекты и жители пересинхронизируются по событиям шины, а стены ещё и по хэшу раз в полсекунды.
Модель не нашлась: рисуется процедурная замена.
Шейдеры прогреваются при старте, чтобы не было рывков позже.
UI и звук
UI получает от main.js мир, мозг и рендер одним объектом и вызывает их напрямую.
Звук стартует по первому жесту игрока: браузер раньше не даёт.
Громкости и настройки хранятся в localStorage.
Четыре критических пути
1. Запуск и загрузка мира
Новая игра сеет генератор случайных чисел, ставит стартовый дом и двух жителей; с сохранением пропускает это.
2. Тик симуляции
За кадр проходит несколько под-шагов: потребности тикают редко, действия и ходьба каждый под-шаг.
3. Действие игрока и его эффект
Дальше действие выполняет тик: идёт, входит, делает, выходит; Рендер реагирует на события.
4. Кадр: рендер и звук
Один requestAnimationFrame ведёт всё: симуляция, интерфейс, картинка, звук и автосохранение.
Правила, на которых всё держится
Состояние одно. Всё, что нужно сохранить, лежит в одном объекте, который целиком превращается в JSON.
Шина событий связывает контексты. Ошибка в обработчике одного слушателя не роняет остальных.
Мозг и Мир без DOM и three.js. Их проверяют в Node без браузера.
Рендер ничего не решает. Он рисует то, что лежит в состоянии.
Игрок и автономия в одной очереди. Приказ игрока всегда важнее.
Слабые места
Где
Что не так
Хуки в одном файле
Все обработчики шагов действий собраны в sim/index.js на 450 строк.
UI знает про Рендер
Создание семьи берёт списки одежды прямо из файла манифеста рендера.
Мир через глобальную ссылку
Мозг получает мир параметром и ещё хранит его в общей переменной модуля.
Версия сохранения
Сохранение другой версии молча отбрасывается, миграций нет.
Вес загрузки
three.js приходит с внешнего CDN, а набор моделей glb грузится при старте.