112 lines
28 KiB
Markdown
112 lines
28 KiB
Markdown
# Faset Engine — архитектура
|
||
|
||
Редакция 1.1 · 18 сентября 2026 · **принятый проект, реализация MVP в процессе**.
|
||
|
||
Этот файл фиксирует решения пользователя. **Принято** означает выбранное направление реализации, а не автоматически завершённую возможность движка. Работающий код, выполненные проверки и текущие ограничения перечислены в [журнале реализации](IMPLEMENTATION.md). Этапы до MVP и после него, зависимости работ и критерии готовности находятся в [PLAN.md](../PLAN.md).
|
||
|
||
Исследовательская база: [Unreal Engine, Godot, Unity и Blender](studies/README.md). Исторические сравнения не отменяют принятые здесь решения. Подробности: [ECS](studies/11-ecs-and-ergonomics.md), [MCP и Blender](studies/12-mcp-and-blender-integration.md), [стек и экспорт](studies/13-build-pipeline-and-stack.md), [renderer](studies/15-renderer-implementation-notes.md), [метаданные](studies/16-native-gameplay-and-metadata.md), [импорт](studies/17-asset-pipeline-and-blender-roundtrip.md), [build/cook](studies/18-build-cook-and-delivery.md).
|
||
|
||
## 1. Назначение и границы — принято
|
||
|
||
**Faset Engine** предназначен для десктопных **2D- и 3D-игр под Linux и Windows**. Важны удобное ручное редактирование, полноценная работа с проектом через MCP, передовая графика, интеграция с обычным Blender и экспорт самостоятельной игры. Независимость означает собственные форматы и API на границах движка, контролируемые зависимости и отсутствие обязательных облачных сервисов; это не обещание реализации всех библиотек с нуля или отсутствия платформенных SDK.
|
||
|
||
**Основной язык движка — английский:** интерфейс редактора, встроенные команды, диагностические сообщения, идентификаторы публичного API и описания CLI/MCP tools. Пользовательский текст и содержимое проектов поддерживают Unicode; язык создаваемых игр не ограничивается английским. Локализация редактора относится к развитию после MVP. Текущие исследования и проектная документация могут оставаться на русском; публичный корневой README ведётся на английском.
|
||
|
||
**MCP существует только в Editor и его headless-сервисах.** Он работает с авторскими документами, импортом, сборкой, запуском/остановкой Play и журналами редактора. MCP не читает и не изменяет runtime world, entities, компоненты или physics state. В Player, экспортной игре и SchemaExporter нет MCP-сервера, адаптера, зависимостей или транзитивного обхода этой границы через Editor.
|
||
|
||
## 2. Принятый стек
|
||
|
||
- **C++** — ядро и первый способ написания игровой логики; **Lua** обязательно добавляется следующим языковым этапом отдельным модулем. Использование Lua конкретной игрой необязательно.
|
||
- **EnTT** — runtime ECS за API Faset. Свои: сцены, метаданные, lifecycle, команды, планирование систем и связь с инструментами.
|
||
- **Box2D / Box3D** — отдельные физические миры 2D/3D; Box3D — [erincatto/box3d](https://github.com/erincatto/box3d).
|
||
- **Собственный retained C++ UI** — основной редактор: сохраняемое дерево виджетов, декларативные layout/styles, сначала тёмная тема. **Dear ImGui** — диагностические инструменты, не основная оболочка редактора.
|
||
- **Vulkan 1.3**, собственные renderer и RenderGraph; **Slang** для шейдеров и совместимого с ним подмножества HLSL.
|
||
- **SDL3 за Faset Platform API** — окна, события и платформенные службы; типы SDL не становятся обязательными типами gameplay API.
|
||
- **CMake + Ninja**, **Clang на Linux**, **clang-cl на Windows** с согласованными Windows SDK и runtime-библиотеками.
|
||
- **JSON authoring**, версионированные **бинарные cooked-форматы**, стабильные идентификаторы данных.
|
||
|
||
Выбор библиотеки не фиксирует её версию. Точные releases/commits, минимальные версии ОС/драйверов, Linux runtime baseline и разрешённые GPU formats/limits закрепляются в dependency lock и профилях перед интеграцией. Vulkan 1.3 сам по себе не гарантирует RT, mesh shaders или любые дополнительные features.
|
||
|
||
## 3. Процессы, модули и версии — принято
|
||
|
||
**Editor** владеет AuthoringService, UI, asset/build services, editor plugins и MCP adapter. Его headless-режим предоставляет те же авторские операции без окон. **Player** — отдельный процесс с runtime, renderer, физикой и игровым кодом; сбой gameplay не должен завершать редакторскую сессию. Это не изолирует native-плагин, загруженный непосредственно в Editor.
|
||
|
||
Gameplay оформляется **отдельной статической библиотекой, связанной с Player, в development и release**. Исправление `.cpp` пересобирает затронутые файлы и перелинковывает Player; изменения общих заголовков учитываются по графу зависимостей. Цикл разработки: **stop → build → restart Play**. Горячая замена C++, перенос действующих vtables/layout и сохранение произвольного runtime-состояния в этот контракт не входят.
|
||
|
||
Editor plugins — **DLL/SO под конкретный Editor SDK/build**, загружаемые при запуске. Обновление требует перезапуска Editor. Manifest содержит module ID/version, зависимости, назначение `runtime/editor`, API version и build fingerprint: ОС, архитектуру, toolchain, стандартную библиотеку/CRT и существенные настройки. Зависимости проверяются на циклы; editor и MCP targets не попадают в Player. Статическая gameplay-библиотека не означает статической линковки всех системных зависимостей.
|
||
|
||
Entry function плагина согласует версию интерфейса; память освобождает создавшая её сторона, ошибки имеют явный контракт. Нативные плагины не получают обещания стабильного ABI между любыми компиляторами. EnTT registry и детали storage не экспортируются как универсальный plugin ABI. Панели custom Inspector принадлежат отдельному Editor module и изменяют документы через общий command API.
|
||
|
||
## 4. Метаданные и схема без запуска gameplay в Editor — принято
|
||
|
||
Используется **собственная явная регистрация C++** выбранных компонентов, полей, методов и событий. Типизированные helpers проверяют поддерживаемые accessors и сигнатуры. Универсальный parser/binding generator произвольного C++ не нужен для первой версии.
|
||
|
||
После сборки отдельный служебный **SchemaExporter** выполняет registration entrypoints и пишет декларативный schema manifest. Он связывается с теми же регистрациями gameplay, но не запускает мир, renderer, `OnStart` или игровой цикл. Editor получает `TypeId`, `FieldId`, version, переносимые value types, defaults, units, constraints и UI hints **без загрузки игрового native-кода в процесс редактора**. MCP в SchemaExporter отсутствует. Manifest связывается с build fingerprint; сбой/ошибка экспорта не заменяет последнюю корректную схему.
|
||
|
||
В exporter всё же исполняется C++ регистрация: отдельный процесс изолирует её сбой, но не является sandbox для недоверенного кода. Defaults объявляются явно, а не извлекаются созданием произвольного игрового мира. Декларативные ограничения валидируются AuthoringService; дополнительные исполняемые authoring-валидаторы требуют явного editor/helper-контракта, а не неявного вызова gameplay из Inspector.
|
||
|
||
`TypeId/FieldId` описывают смысл сохраняемых данных и не зависят от имени C++, offset, порядка членов или `typeid`. Layout fingerprint нужен для совместимости конкретной сборки. Переименование сохраняет ID; изменение смысла/единиц требует миграции. Отсутствующий плагин не уничтожает неизвестные записи. Lua позже использует явно экспортированный runtime API и проверяемые handles; наличие metadata не делает каждый native-метод доступным MCP или Lua.
|
||
|
||
## 5. Авторские данные, сцены и редактор — принято
|
||
|
||
Пользователь работает со сценами, объектами, компонентами и ресурсами. Сцены допускают **вложенные экземпляры шаблонов**. JSON хранит ссылку на исходный шаблон и **sparse overrides** — только локальные отличия, с устойчивыми адресами полей/объектов. Подписи и пути не являются идентичностью. Для добавления, удаления и изменения вложенного содержимого нужны явные операции; потерянная цель override становится диагностируемым конфликтом, а не молчаливым удалением данных.
|
||
|
||
AuthoringService владеет документами, схемами, validation, revisions, транзакциями и Undo. **GUI, CLI, MCP и editor extensions вызывают одни authoring-команды.** Команда получает явные document/entity/asset IDs. Ручной drag создаёт preview и один commit; MCP batch проверяется целиком и создаёт один шаг Undo. В первом контракте атомарность ограничена одним документом; конфликт revision не применяет половину изменений. Idempotency действует для одинакового key/payload в пределах объявленного scope и срока хранения журнала.
|
||
|
||
Scene compiler переводит авторские данные в runtime и сохраняет соответствие `SceneEntityId → RuntimeEntity[]`. Persistent SceneEntityId/AssetId отличаются от runtime handle с world/session/generation. Runtime storage не становится источником истины для JSON и не обязан повторять layout документа. Изменение игрового компонента не создаёт редакторский Undo и не сохраняется в authoring автоматически.
|
||
|
||
Retained UI получает состояние и уведомления от AuthoringService; состояние виджетов не заменяет состояние документа. Стили/layout отделены от команд; ключевые действия доступны из интерфейса и через editor API. Импорт, компиляция и cook не блокируют event loop. Необходимые текстовый ввод, focus, clipboard, DPI и платформенные поведения проверяются на обеих ОС как часть UI, а не считаются автоматически решёнными SDL3.
|
||
|
||
## 6. Runtime ECS, поведение и игровой цикл — принято
|
||
|
||
EnTT используется для компонентов и запросов за типизированным API Faset. Простое поведение и массовая система обращаются к одному состоянию. Собственная ECS не разрабатывается на первом этапе; выбор EnTT не обещает большей скорости или дешёвой замены backend. Системы объявляют фазу, чтение/запись компонентов и внешних ресурсов, зависимости и диагностическое имя.
|
||
|
||
Публичный lifecycle: **`OnStart`, `FixedUpdate`, `Update`, `LateUpdate`, `OnDestroy`**. `OnStart` вызывается один раз после создания и привязки компонентов, перед первым обновлением; `OnDestroy` — один раз при применении удаления, пока допустимые данные ещё доступны. После него подписки снимаются, handles инвалидируются. Начальная загрузка проходит тот же явный барьер инициализации.
|
||
|
||
Главный цикл собирает ввод, выполняет нужное число фиксированных ticks, вызывает `Update`, вычисляет интерполированные presentation transforms, затем вызывает `LateUpdate` для камеры и зависимых визуальных объектов, финализирует render snapshot и рисует кадр. Одноразовые события ввода потребляются один раз соответствующим tick, удерживаемое состояние доступно последующим ticks. `Update/LateUpdate` не переписывают рассчитанный physics transform динамического тела; движения физических тел задаются командами следующего шага.
|
||
|
||
**Fixed tick — 60 Гц по умолчанию, настраиваемый для проекта.** Порядок tick: применить ожидающие structural commands → выполнить `FixedUpdate` и системы до физики → применить команды физическим телам → завершить Box2D/Box3D step → перенести transforms и события → выполнить системы реакции после физики. События доставляются через явную очередь; произвольный gameplay/Lua не вызывается из потоков physics callbacks.
|
||
|
||
Spawn/despawn и add/remove компонентов записываются в **runtime structural command buffer** и вступают в силу **в начале следующего фиксированного tick**, после завершения прежних задач. У применения один владелец; очередь имеет заданный порядок. Ссылки на память компонентов не переживают этот барьер. `create/add/delete`, повторное удаление и порядок lifecycle обрабатываются по единому контракту, который проверяется отдельными сценариями.
|
||
|
||
Catch-up ограничен числом ticks за проход главного цикла; начальный профиль — максимум четыре, с возможностью настройки. При перегрузке локальный Player отбрасывает избыточные целые интервалы накопленного времени, сохраняет дробный остаток и показывает `dropped_time`; не увеличивает physics `dt` произвольно. После паузы накопитель сбрасывается. Это политика локальной игры, не обещание deterministic lockstep/rollback.
|
||
|
||
Подготовка presentation transforms перед `LateUpdate` интерполирует предыдущий и текущий завершённые ticks с `alpha = accumulator / fixed_dt`; результат не записывается обратно в физику. Такой путь добавляет до одного tick визуальной задержки. Spawn, teleport и новая сессия сбрасывают историю. Motion vectors используют предыдущий **отрисованный** transform, а не просто предыдущий physics tick.
|
||
|
||
Первый scheduler — **последовательные именованные фазы** с измерением времени. Затем распараллеливаются отдельные независимые проходы: storage создаётся заранее, read/write conflicts и shared resources явно учитываются, structural changes остаются за барьером. EnTT не делает registry и пользовательские данные автоматически thread-safe. Полный task graph и подключение physics task callbacks к согласованному пулу workers вводятся после корректного последовательного варианта и профилирования.
|
||
|
||
## 7. Физика и renderer — принято
|
||
|
||
2D и 3D имеют отдельные миры и spatial-типы. Для динамического тела итоговым transform владеет физика; teleport и кинематическое управление — отдельные операции. Физическое тело связано с entity через проверяемый handle. Миры 2D и 3D не сталкиваются автоматически. Внутренние substeps solver настраиваются отдельно от частоты gameplay ticks; начальная настройка — четыре substeps с проверкой на сценах проекта.
|
||
|
||
Собственный Vulkan 1.3 renderer получает подготовленный snapshot, не обходит изменяемый EnTT registry с render thread. RenderGraph описывает reads/writes ресурсов, порядок проходов, barriers и lifetimes. Первым нужен проверяемый raster-путь 2D/3D без обязательных RT/mesh shaders; GPU-driven visibility и другие передовые механизмы добавляются по измеримым этапам из [плана](../PLAN.md).
|
||
|
||
**Vulkan API вызывается напрямую внутри собственного backend Faset.** Он владеет Vulkan handles, созданием GPU-ресурсов и pipelines, записью команд, синхронизацией и отправкой в очереди. Renderer и RenderGraph используют небольшой внутренний интерфейс ресурсов и команд Faset; Vulkan-типы и вызовы `vk*` не входят в gameplay API или команды редактора. SDL3 обеспечивает окно и создание Vulkan surface, но не заменяет графический backend; Slang отвечает за компиляцию шейдеров. Универсальная абстракция нескольких графических API не является задачей MVP.
|
||
|
||
Slang компилирует шейдеры в SPIR-V и выдаёт сведения для согласования CPU/GPU данных. Совместимый HLSL проходит выбранный pipeline; поддержка любого существующего HLSL-кода не обещается. Cook учитывает compiler/version, includes, defines и GPU profile. Nanite/Lumen-подобные системы остаются исследовательскими направлениями, не готовыми возможностями MVP.
|
||
|
||
## 8. Blender и ассеты — принято
|
||
|
||
Используется **обычный Blender**, без обязательного форка или установленного MCP-плагина. Базовый обмен — **GLB и manifest со стабильными asset/subasset IDs**. Обычный экспорт принимается движком; удобный add-on для публикации, UUID и повторного экспорта остаётся необязательным помощником. Если источник не предоставляет устойчивые subasset IDs, importer не обещает надёжно угадать соответствие после произвольного rename/reparent: сохраняет mapping и показывает конфликты.
|
||
|
||
`.blend` и исходные изображения отделены от cooked/cache-данных. Геометрия и skeleton принадлежат источнику, placement/gameplay — сцене Faset, локальные замены материалов — overrides. Reimport сохраняет ссылки и локальные отличия; удалённые или конфликтующие outputs видны пользователю. Export recipe фиксирует units/axes, материалы и поддержанный animation profile. Blender nodes не превращаются автоматически в Slang shaders. Live link и расширенный roundtrip следуют после надёжного файлового импорта по плану.
|
||
|
||
## 9. Build, доставка и проверка — принято
|
||
|
||
Один сервис сборки для Editor/CLI/editor MCP разделяет компиляцию C++, schema export, компиляцию шейдеров, cook и упаковку. CMake/Ninja отвечают за native build graph. Inputs фиксируются snapshot, зависимости явные, worker пишет в свой build tree; cache учитывает content, recipe, toolchain и версии форматов. Candidate публикуется лишь после проверки, предыдущая успешная сборка остаётся доступной.
|
||
|
||
Development и release Player содержат runtime, выбранные игровые модули и бинарные cooked assets. **Editor UI, editor plugins, import/build services, SchemaExporter и MCP не входят в экспортную игру.** Lua включается только при зависимости проекта. JSON authoring, стабильные IDs, schema versions и миграции не заменяются дампом памяти C++.
|
||
|
||
Первый путь доставки — самостоятельный каталог/архив игры и отдельно symbols. Windows и Linux собираются и проверяются собственными native workers. Clang/clang-cl не устраняют необходимость платформенных SDK/runtime; кросс-экспорт из Linux в Windows не считается готовой возможностью. Полностью побайтово воспроизводимый executable и стабильный ABI любых компиляторов не обещаются наличием build manifest.
|
||
|
||
Критерии [плана](../PLAN.md) включают цикл сцена → Inspector → MCP authoring edit → Undo → save → build → отдельный Player, повторный импорт GLB с сохранением overrides, lifecycle/handle tests и запуск на обеих ОС без Editor/Blender/MCP/SDK. Эти проверки предстоит реализовать и выполнить.
|
||
|
||
## История решений
|
||
|
||
- **17.09.2026:** выбраны имя Faset Engine, Linux/Windows desktop 2D/3D, ручная работа и MCP, интеграция с Blender, Box2D и Box3D; подготовлены исследования.
|
||
- **18.09.2026:** принят порядок C++ → Lua, затем EnTT, собственный retained UI, SDL3, Vulkan 1.3/RenderGraph/Slang и CMake/Ninja/Clang.
|
||
- **18.09.2026:** утверждены процессы и linkage, явная metadata registration/schema export, JSON/binary formats, вложенные шаблоны/sparse overrides, lifecycle, фиксированный tick и последовательный scheduler; граница MCP ограничена Editor/headless authoring services.
|
||
- **18.09.2026:** английский принят основным языком интерфейса, диагностик и публичного API; уточнена граница прямых Vulkan-вызовов внутри собственного backend.
|
||
|
||
Реализация идёт по [PLAN.md](../PLAN.md). Последующие изменения принятых контрактов фиксируются здесь с причиной и способом проверки.
|