commit 8f9d5c2969feeac848bd569e546f18e29ad50945 Author: Emil <65846814+emil28092005@users.noreply.github.com> Date: Fri Sep 18 02:21:40 2026 +0300 Document Faset architecture and roadmap through MVP diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6fa0d62 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Installed dependencies and generated output of the research map +/docs/studies/map/node_modules/ +/docs/studies/map/dist/ + +# Generated local code indexes +**/graphify-out/ + +# Local caches and logs +__pycache__/ +*.py[cod] +.DS_Store +*.log + +# Future local build outputs and per-user configuration +/build/ +/exports/ +/.cache/ +CMakeUserPresets.json +.env +.env.* +!.env.example diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..696a5b7 --- /dev/null +++ b/PLAN.md @@ -0,0 +1,221 @@ +# План разработки Faset Engine + +Версия 1.0 · 18 сентября 2026 года. + +**Статус:** базовая архитектура принята; реализация движка и MVP ещё не началась. Выполнены исследование исходников, выбор архитектуры и создание карты документации. Все пункты реализации ниже открыты. Этот документ определяет порядок работ; контракты подсистем находятся в [ARCHITECTURE.md](docs/ARCHITECTURE.md). + +## 1. Результат MVP + +MVP — редактор и runtime, на которых можно завершить два небольших десктопных проекта: 2D-сцену с управляемым персонажем и физикой Box2D и 3D-сцену с моделью из Blender, физикой Box3D, освещением и дверью с поведением на C++. + +Для обоих проектов работает полный путь: создать проект → собрать сцену руками или через MCP редактора → сохранить и открыть → запустить отдельный Player → изменить C++ и пересобрать → экспортировать → запустить самостоятельную игру на Linux и Windows. + +### Обязательные границы + +- MCP обслуживает только редактор и его сервисы авторинга, импорта и сборки. Play/Stop и логи — функции редактора; API чтения или изменения игрового мира через MCP не создаётся. Player и экспорт не содержат MCP. +- C++ gameplay входит в MVP. Lua — обязательный следующий этап, но не условие MVP и не обязательная зависимость каждой игры. +- Собственный retained-mode UI редактора имеет основную тёмную тему; Dear ImGui остаётся отладочным инструментом. +- Базовый renderer требует Vulkan 1.3 и явно проверяемый набор возможностей; аппаратный RT, mesh shaders и виртуализированная геометрия не обязательны. +- Blender используется без модификации исходников. Обычный импорт GLB работает без дополнения; дополнение обеспечивает удобный экспорт и устойчивые IDs. +- Gameplay статически линкуется с Player в development и release. Изменение C++ требует stop/build/restart; сохранение состояния при горячей замене C++ не входит в MVP. +- Windows-сборка первоначально выполняется и проверяется на Windows, Linux-сборка — на Linux. Кросс-компиляция не является условием MVP. +- Редактируемая сцена отделена от симуляции. Stop сохраняет авторский документ; изменения игры автоматически в него не переносятся. + +### За пределами MVP + +Lua, C++ hot reload, Blender live link, встроенное изображение Player, стабильный ABI между разными SDK, наследуемые варианты шаблонов, multiplayer/rollback, полноценные игровые UI и audio toolkit, advanced animation tools, виртуальные текстуры и аналоги Nanite/Lumen/VSM. Небольшой HUD или звук демонстрации не следует превращать в такие подсистемы. + +## 2. Правила реализации + +Этап закрывается работающим результатом и записанными проверками. Порядок зависит от готовности контрактов, а не от заранее обещанных календарных сроков. Внутри этапа задачи оформляются небольшими изменениями с подходящей проверкой. + +- Архитектура хранит принятые контракты, PLAN — статус и объём. Исследования сохраняют наблюдения об исходных проектах и явно обозначают историю альтернатив. +- Скриншот, API-заглушка или сборка только одной ОС не означают завершение этапа. +- Корректность, удобство и производительность проверяются отдельно. Числа публикуются вместе со сценой, оборудованием, настройками и методом измерения. +- Форматы, миграции и команды проверяются без GUI; GPU и оконное поведение — в соответствующей среде. +- Исходные данные и настройки хранятся в Git. Кэши восстанавливаются. Сбой не заменяет рабочие данные частично подготовленной версией. + +## 3. Этапы до MVP + +### M0. Воспроизводимый фундамент + +**Результат:** минимальный C++-проект собирается в согласованной среде Linux и Windows. + +- [ ] Создать targets Core, Runtime, Editor, Player, SchemaExporter, инструментов и примеров; запретить зависимости Runtime/Player на Editor/MCP. +- [ ] Настроить CMake presets, Ninja, Clang/clang-cl, development/release и базовые проверки CI обеих ОС. +- [ ] Зафиксировать стандарт C++, версии компиляторов, Windows SDK/runtime, библиотек и Slang; определить проверяемую матрицу ОС, архитектур и GPU. +- [ ] Закрепить EnTT, SDL3, Slang, Box2D, Box3D и отладочный ImGui с воспроизводимым получением, notices и возможностью локальной сборки. Research commits не считать автоматически dependency versions. +- [ ] Определить ошибки, журналирование, проверки инвариантов, владение ресурсами и формат диагностик. +- [ ] Выбрать необходимые библиотеки шрифтов/текста, изображений и glTF, зафиксировать их лицензии и владельцев интеграции. + +**Готово:** чистая сборка проходит на обеих ОС, пример запускается, версии видны в отчёте. Офлайн-сборка проверяется с заранее подготовленными инструментами и зависимостями; обязательных облачных сервисов нет. + +### M1. Документы, метаданные и команды + +**Зависит от:** M0. **Результат:** авторский документ корректно меняется и сохраняется без GUI. + +- [ ] Реализовать постоянные ID ресурсов, объектов, компонентов, типов и полей; временные runtime handles с world/session и generation хранить отдельно. +- [ ] Создать явную типизированную C++-регистрацию схем, декларативные constraints и подсказки Inspector; отделить описание данных от исполняемых callbacks. +- [ ] Ввести версионированные JSON-форматы проекта, сцены, материала и import settings; стабильную запись и сохранение неизвестных данных отсутствующего модуля. +- [ ] Создать AuthoringService: query, команды, транзакции, validation, revisions, Undo/Redo, dirty state, атомарное сохранение и recovery. +- [ ] Поддержать объекты, компоненты, изменение полей и иерархии, batch-команды и понятные ошибки. +- [ ] Добавить миграции с fixtures: переименование при прежнем FieldId, новый default, несовместимый тип и отсутствующая схема. + +**Готово:** round-trip сохраняет смысл и ID; неверная транзакция не применяется частично; Undo/Redo восстанавливает ссылки; устаревшая revision даёт конфликт. Документы не содержат EnTT handles или адресов памяти. + +### M2. Платформа и базовая графика + +**Зависит от:** M0; загрузка сцены подключается к M1. + +- [ ] Подключить SDL3 через Faset Platform: окно, ввод, текст, DPI и Vulkan surface. +- [ ] Реализовать Vulkan 1.3 backend с проверкой features/limits/formats, ресурсами, синхронизацией и освобождением после завершения GPU. +- [ ] Создать Render Graph на одной graphics queue с явными чтениями/записями и barriers; подключить validation и метки проходов. +- [ ] Компилировать Slang в SPIR-V с закреплёнными layout/binding conventions; выгружать reflection в собственный формат. Проверить совместимый HLSL-пример. +- [ ] Получить спрайты, прозрачность и слои для 2D; static meshes, текстуры, базовый PBR, свет и обычную shadow map для 3D. +- [ ] Использовать direct draws и CPU frustum culling как эталон; ввести CPU/GPU timings и счётчики ресурсов. +- [ ] Обновлять шейдер с безопасной заменой pipeline: ошибка сохраняет рабочий вариант, изменение layout требует проверки совместимости. + +**Готово:** корректны resize/minimize, пересоздание attachments и повторный запуск; validation не сообщает ошибок в проверяемых сценариях. В игру идут SPIR-V и метаданные без обязательного Slang compiler. Создание pipelines драйвером остаётся отдельным этапом. + +### M3. Runtime, gameplay, физика и Player + +**Зависит от:** M1 и M2 для визуального запуска. + +- [ ] Преобразовывать authoring-сцену в EnTT-мир с картой происхождения и удобным typed API объектов/компонентов. +- [ ] Создать gameplay static library и SchemaExporter с теми же регистрациями: экспорт схем без запуска gameplay lifecycle или графического окна. Editor читает проверяемый декларативный результат. +- [ ] Реализовать OnStart, Update, FixedUpdate, LateUpdate, OnDestroy и события физики; регистрировать только нужные обработчики. +- [ ] Зафиксировать tick (default 60 Гц): structural commands → ввод → FixedUpdate → физика → readback/events → реакции. Spawn/despawn и изменение состава компонентов вступают в силу со следующего tick. +- [ ] Вызывать Update один раз за игровой кадр, LateUpdate после подготовки отображаемых положений. Интерполяция не пишет обратно в симуляцию. +- [ ] Подключить независимые адаптеры Box2D/Box3D: тела и коллайдеры для демо, collision layers, события и debug drawing. Динамическим телом управляет физика; телепортация явная. +- [ ] Ограничить catch-up (начальный лимит четыре ticks за проход); лишнее время локального Player отбрасывать с диагностикой. После pause/reset не догонять паузу; teleport/spawn сбрасывает историю интерполяции. +- [ ] Запускать Player отдельным процессом/окном из текущего снимка сцены, включая несохранённые правки; реализовать Play/Stop, pause/single-step, логи и обработку завершения. + +**Готово:** C++-поведение появляется в Inspector через schema export, двигает объект и реагирует на физику. Stop сохраняет авторскую сцену. Ошибка сборки оставляет старую схему/сборку с явным статусом устаревания; старый результат не выдаётся за новый. Player не содержит MCP и не требует Editor. + +### M4. Собственная UI-основа + +**Зависит от:** M1, M2. + +- [ ] Построить retained tree, layout, clipping/scroll, события, focus, keyboard navigation, drag/drop и lifecycle widgets. +- [ ] Подключить шрифты/shaping по выбору M0; проверить кириллицу, выделение/редактирование текста, clipboard, IME и разный DPI. +- [ ] Разделить C++-поведение, декларативную компоновку и стили с тёмной темой; поддержать программное создание Inspector. +- [ ] Реализовать кнопки, текстовые/числовые поля, списки/дерево, выбор ресурса, вкладки, панели и разделители. +- [ ] Добавить минимальный docking в одном окне и сохранение раскладки; дополнительные системные окна редактора отложить. +- [ ] Перезагружать layout/styles с проверкой и сохранением рабочего состояния; ImGui использовать для диагностики. + +**Готово:** интерфейс доступен клавиатурой, текст и DPI работают, стили меняются без C++ rebuild. Правка widget вызывает AuthoringService; один drag создаёт один Undo. + +### M5. Ресурсы и Blender + +**Зависит от:** M1, M2; UI состояния подключается по M4. + +- [ ] Реализовать AssetId, import settings, dependency graph, artifacts и manifest готового поколения. +- [ ] Включить в ключ кэша входы, настройки, версии importer/toolchain и целевой профиль; показывать ошибки/устаревшие результаты. +- [ ] Импортировать профиль glTF/GLB и изображения для демо; описать PBR-подмножество, единицы, оси и collider policy. +- [ ] Поддержать обычный GLB без Blender add-on; явно ограничить matching после rename/restructure без устойчивых IDs. +- [ ] Создать минимальное необязательное дополнение обычного Blender: export button, сохраняемые IDs и manifest; записывать версию exporter и профиль. +- [ ] Обновлять только импортированную основу, сохраняя gameplay/physics settings и overrides Faset. Исчезновение цели создаёт конфликт. +- [ ] Публиковать GLB/manifest/зависимости как согласованное поколение; ошибка/отмена оставляет последний рабочий artifact. +- [ ] Предоставить одинаковый импорт через UI/CLI/MCP редактора; длительная операция имеет ID, progress и cancellation. + +**Готово:** изменение mesh в Blender обновляет несколько экземпляров без потери компонентов/overrides; rename со стабильным ID сохраняет связь, удаление создаёт понятный конфликт. Игра использует cooked assets без Blender. + +### M6. Редактор и повторно используемые сцены + +**Зависит от:** M1, M3, M4, M5. + +- [ ] Собрать Scene Tree, Inspector, Asset Browser, 2D/3D viewport, Console, Project Settings и команды Save/Play/Build. +- [ ] Реализовать selection, focus, gizmos, создание объектов/компонентов, поиск и основные shortcuts. +- [ ] Ввести шаблон как scene asset: InstanceId, исходные ObjectId/ComponentId, sparse overrides и вложенные экземпляры без циклов. +- [ ] Адресовать overrides по цепочке инстанцирования и IDs, независимо от имён/parenting; при duplicate remap внутренних ссылок, внешние сохранить. +- [ ] Поддержать локальные добавления, suppression, ограниченный reparent внутри экземпляра с явным local/world transform; массивы переопределять целиком. +- [ ] Показывать происхождение значения, Revert и открытие источника. Сохранять конфликтующие данные исчезнувших targets/types; Apply to template и inherited variants отложить. +- [ ] Обеспечить одинаковые операции и Undo/Redo для 2D/3D; восстановить проект с очищенным кэшем. + +**Готово:** пользователь создаёт сцену без ручного JSON, размещает два экземпляра, меняет один, обновляет источник, отменяет правки и переоткрывает проект без потери идентичности. + +### M7. MCP редактора и расширения + +**Зависит от:** M1, M5, M6; headless проверки возможны раньше GUI. + +- [ ] Подключить MCP к AuthoringService: документы/query/schema, create/delete/set, batches, Undo/Redo, import/build/export, Play/Stop и диагностика редактора. +- [ ] Ввести capabilities и структурированные ошибки. Headless authoring/build не требует GUI; screenshot editor viewport требует render backend и GPU. +- [ ] Проверять revisions и конфликты ручных/MCP-изменений; не повторять слепую запись поверх нового состояния. Повтор запроса не дублирует завершённую транзакцию. +- [ ] Предоставить jobs ID/progress/result/cancellation; отделить отмену задачи от Undo документа. +- [ ] Загружать editor DLL/SO при старте: manifest, exact SDK/build compatibility, зависимости и владельцы регистраций. Обновление — через перезапуск. +- [ ] Проверить extension-пакет с runtime-компонентом и editor-командой/панелью; правки документов проходят через command API. + +**Готово:** ручные и MCP-операции дают эквивалентные канонические документы и историю. Нет MCP runtime-entity read/write; MCP transport отсутствует в Player и SchemaExporter. Отключённый пакет не уничтожает данные неизвестного компонента; экспорт сообщает о нерешённой зависимости. + +### M8. Экспорт законченных примеров + +**Зависит от:** M3, M5, M6, M7. + +- [ ] Создать BuildService: validate → C++/schema build → resource/Slang cook → package → проверить результат. +- [ ] Разделить development/release manifests; исключить Editor, MCP, Blender, schema helpers и shader compiler из обязательного runtime игры. +- [ ] Подготовить две маленькие 2D/3D-игры с исходниками, понятным управлением и сценариями проверки. +- [ ] Проверить stop/build/restart, save/reopen, nested scenes, Blender reimport и shader compile failure. +- [ ] Собрать обе игры на каждой целевой ОС и запустить в чистой среде с поддерживаемым GPU-драйвером и документированными runtime-зависимостями. +- [ ] Сформировать notices, manifest ресурсов/toolchain, отчёт и инструкции разработчику/игроку. + +**Готово:** все четыре сочетания «2D/3D × Linux/Windows» запускаются без исходного дерева Faset и редактора. Недостающий asset или библиотека обнаруживаются до публикации пакета. + +### M9. Приёмка MVP + +**Зависит от:** M0–M8. + +- [ ] Пройти новую установку и создание проекта по документации на обеих ОС. +- [ ] Повторить authoring-сценарий руками и через MCP; проверить Undo/Redo и конфликт revision при одновременной правке. +- [ ] Проверить recovery после сбоя сохранения, импорта, сборки и завершения Player с ошибкой. +- [ ] Записать startup, edit/build/run, import, CPU/GPU frame time и memory с оборудованием/сценами; по результатам установить бюджеты следующего этапа. +- [ ] Устранить блокирующие дефекты UX, текста, DPI, форматов и exports; записать ограничения. +- [ ] Обновить docs, приложить проверенные результаты и только затем поставить первый MVP tag. + +## 4. Развитие после MVP + +### P1. Lua и скорость итераций + +Добавить Lua runtime/editor пакет поверх публичного API, проверяемых handles и схем. Предусмотреть диагностику, отладку, Inspector и явный lifecycle перезагрузки. Сохранение состояния при reload проектируется отдельно. Проверка: C++ и Lua используют одни данные/фазы; C++-only export не включает Lua. + +Улучшать schema/build cache, сообщения компилятора, шаблоны проектов, переход к коду, autosave и измеренное время «изменение → результат». Dynamic gameplay loading рассматривать при подтверждённой проблеме линковки. + +### P2. GPU-driven visibility и LOD + +Сохранить direct renderer как эталон. Порядок: GPU instance IDs → GPU frustum culling → fixed indirect batches → current HZB/debug view → two-pass occlusion с контролем истории → подготовленный mesh LOD с hysteresis. + +Проверять пустую сцену, рост/переполнение буферов, массовое удаление, открывающуюся дверь, исчезновение заслона, camera cut, teleport и resize. Не требовать синхронного GPU readback. Сравнивать полный кадр на закрытых и открытых сценах: HZB может быть дороже эталона. Основа — [исследование 15](docs/studies/15-renderer-implementation-notes.md). + +### P3. Освещение, тени и temporal reconstruction + +Расширить local lights, добавить clustered/Forward+ при измеренной необходимости, cascaded sun shadows и ограниченный local shadow atlas. Shadow views имеют собственную видимость и бюджеты. + +Затем: previous transforms, motion vectors, jitter, history rejection и TAA; temporal upscaling — после устойчивого TAA. Проверять тонкую геометрию, движение, disocclusion, camera cut и смену разрешения, сравнивать с режимом без temporal. У cache/pass видны затраты и причины обновления. + +### P4. Непрямой свет и продвинутая геометрия + +Начать с подготовленного непрямого света и reflection probes; затем исследовать динамическую GI с бюджетами памяти/обновления и screen/world-space данными. RT — дополнительный путь для подходящих GPU, с базовой альтернативой. Критерии: заслоны, невидимые камерой поверхности, утечки света, изменение освещения и время полного кадра. + +Cluster LOD, streaming/residency, visibility buffer, virtual shadow maps и virtual textures — самостоятельные проекты после измерения узких мест. Подобные техники не означают автоматического достижения масштаба Nanite/Lumen. Очередность GI и виртуализации геометрии зависит от демонстрационной игры и профилирования. + +### P5. Инструменты создания игр + +- Animation/skinning workflows, state machines, blending и root motion с согласованной фазой симуляции. +- Игровой UI и audio pipeline как отдельные задачи; повторное использование низкоуровневых сервисов без включения всего редактора в игру. +- Tilemaps, анимация спрайтов, 2D-свет, удобные коллизии и специализированный импорт. +- Material/node editor с общими схемами и shader diagnostics; расширенная post-processing pipeline. +- Variants, безопасное Apply to template, overrides коллекций и инструменты разрешения конфликтов. +- Task scheduler после профилирования: объявленные чтения/записи компонентов и внешних ресурсов, structural barriers и согласованные physics jobs. Сам EnTT не обеспечивает эту потокобезопасность. + +### P6. Доставка и экосистема + +Проверяемый native plugin ABI для выбранных сценариев, SDK/пакеты, миграции, доступность, локализация, несколько окон, встраиваемый вид Player и Blender live link. + +Другие graphics backends, cross-compilation, remote build workers и ОС оцениваются отдельно. Сеть, replay/rollback и multiplayer требуют собственного контракта; фиксированный timestep его не создаёт. MCP остаётся редакторской интеграцией, а не API экспортируемой игры. + +## 5. Зависимости и запись результата + +Критический путь: M0 → M1/M2 → M3/M4/M5 → M6 → M7 → M8 → M9. UI-прототип может использовать простую поверхность M2; сервисы M1/M5/M7 проверяются без готового GUI. Контракты данных/владения/форматов согласуются до параллельной реализации потребителей. + +P1–P3 могут развиваться параллельно после MVP при наличии проверок; P4 требует зрелого renderer и диагностики. P5/P6 выбираются по потребностям игр и не считаются обязательным содержимым ближайшего релиза. + +Для закрытого этапа записывать commit/tag, сценарии, платформы/оборудование, результаты, ограничения и ссылки на отчёты. До такой записи пункты остаются открытыми. diff --git a/README.md b/README.md new file mode 100644 index 0000000..cbec430 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# Faset Engine + +Faset — проект независимого движка для десктопных 2D- и 3D-игр под **Linux и Windows**. В центре — удобный собственный редактор, его управление через MCP, работа с Blender и развитие современных графических технологий. + +**Текущий статус: базовая архитектура согласована; реализация движка ещё не начата.** Здесь находятся принятые решения, план, исследования других движков и работающая браузерная карта документации. Наличие технологии в плане не означает, что она реализована или измерена. + +## Начать здесь + +- [PLAN.md](PLAN.md) — этапы до MVP, критерии готовности и развитие после него. +- [Архитектура](docs/ARCHITECTURE.md) — принятые решения и границы подсистем. +- [Документация](docs/README.md) — навигация и правила актуализации. +- [Исследования](docs/studies/README.md) — Unreal Engine, Godot, Unity, Blender, ECS, графика, импорт и сборка. +- [Зависимости и независимость](docs/DEPENDENCIES.md) — библиотеки, инструменты и происхождение материалов. + +## Принятый фундамент + +- **C++** для ядра и первой версии gameplay. **Lua** добавляется после C++ отдельным модулем и необязателен для конкретной игры. +- Объекты, компоненты и вложенные сцены для авторинга; **EnTT** для runtime ECS. JSON, устойчивые ID и подготовленные бинарные данные для экспорта. +- Собственные **Vulkan 1.3** backend, Render Graph и renderer; **Slang** для SPIR-V и совместимого HLSL-кода. RT не требуется базовому режиму. +- **SDL3** за платформенным интерфейсом Faset; **Box2D** и **Box3D** для физики. +- Собственный retained-mode UI редактора: C++-поведение, декларативная компоновка, отдельные стили, основная тёмная тема. **Dear ImGui** — для отладки. +- **CMake + Ninja + Clang**; Windows использует clang-cl, Windows SDK и библиотеки MSVC. Первые сборки проверяются на целевой ОС. +- Отдельный **Player**, статически связанный с gameplay. Рабочий цикл C++ — остановка, инкрементальная сборка, повторный запуск. +- **MCP только в редакторе:** авторинг, ресурсы, импорт, сборка, экспорт, Play/Stop и диагностика редактора. В Player и экспортируемой игре MCP отсутствует. +- Обычный **Blender без изменения исходников**, glTF/GLB-импорт и дополнительный плагин для экспорта и устойчивых ID. + +MVP заканчивается двумя небольшими 2D/3D-проектами, которые можно создать, сохранить, запустить и экспортировать под обе ОС. GPU-driven rendering, HZB, расширенные тени, temporal reconstruction и динамическая GI развиваются после базового результата. + +## Открыть карту исследований + +Нужен Node.js с npm. Из корня репозитория: + +```sh +cd docs/studies/map +npm ci +npm test +npm run build +npm start +``` + +Карта доступна на [localhost:4178](http://localhost:4178). Это просмотрщик документации, а не веб-редактор Faset. Подробности — в [README карты](docs/studies/map/README.md). + +## Публичное содержимое + +Документация, исследования, манифест источников и код карты хранятся в Git. Сторонние движки, установленные зависимости, результаты сборки и кэши в репозиторий не включены. Ссылки на изученный код закреплены на research commits; Unreal может требовать доступа Epic. + +Состав публикации и статус лицензии собственного содержимого описаны в [публикационных заметках](docs/PUBLICATION.md). У сторонних проектов сохраняются их собственные условия. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..01af419 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,106 @@ +# Faset Engine — архитектура + +Редакция 1.0 · 18 сентября 2026 · **принятый проект, реализация ещё не начата**. + +Этот файл фиксирует решения пользователя. **Принято** означает выбранное направление реализации, а не существующую или проверенную возможность движка. Код Faset Engine ещё не создан, тесты и измерения движка не проводились. Этапы до 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. + +**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). + +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. + +Реализация идёт по [PLAN.md](../PLAN.md). Последующие изменения принятых контрактов фиксируются здесь с причиной и способом проверки. diff --git a/docs/DEPENDENCIES.md b/docs/DEPENDENCIES.md new file mode 100644 index 0000000..2940cfe --- /dev/null +++ b/docs/DEPENDENCIES.md @@ -0,0 +1,46 @@ +# Зависимости, инструменты и независимость + +Обновлено 18.09.2026. Здесь перечислены **выбранные направления интеграции**, а не уже подключённые библиотеки движка. Точные версии закрепляются на [M0](../PLAN.md); commits [исследовательского манифеста](studies/source-manifest.json) не заменяют dependency lock. + +## Runtime и инструменты Faset + +- **EnTT:** runtime ECS, за публичным API Faset. [MIT](https://github.com/skypjack/entt/blob/master/LICENSE). +- **SDL3:** окна, ввод и платформенные службы; Vulkan renderer остаётся собственным. [zlib](https://www.libsdl.org/license.php). +- **Box2D / Box3D:** независимые физические миры. [Box2D MIT](https://github.com/erincatto/box2d/blob/main/LICENSE), [Box3D MIT](https://github.com/erincatto/box3d/blob/main/LICENSE). +- **Slang:** компилятор шейдеров для редактора/cook, SPIR-V и собственные метаданные для Player. [Apache-2.0 WITH LLVM-exception](https://github.com/shader-slang/slang/blob/master/LICENSE). Зависимости конкретного compiler package учитываются отдельно. +- **Dear ImGui:** отладка, профилирование и диагностические инструменты. [MIT](https://github.com/ocornut/imgui/blob/master/LICENSE.txt). +- **Lua:** обязательный последующий модуль, необязательный в каждой игре. Конкретная реализация и версия выбираются при интеграции; выбор Lua не означает автоматически выбор LuaJIT. [Лицензия Lua](https://www.lua.org/license.html). + +Вспомогательные библиотеки для текста/shaping, изображений, glTF и упаковки ещё не интегрированы. Их выбор — задача реализации M0/M5, с учётом состава распространяемого пакета и транзитивных лицензий. + +## Сборочная среда + +CMake и Ninja управляют native build graph; Clang компилирует C++, а Slang — шейдеры. Windows-конфигурация clang-cl использует Windows SDK, UCRT и Visual C++ Runtime; установка одного LLVM не заменяет эти компоненты. Версии и допустимое распространение каждого комплекта фиксируются перед подготовкой SDK. + +- [CMake: лицензирование](https://cmake.org/licensing/). +- [Ninja: Apache 2.0](https://github.com/ninja-build/ninja/blob/master/COPYING). +- [LLVM: лицензия и исключения](https://llvm.org/docs/DeveloperPolicy.html#copyright-license-and-patents). +- [Clang: Windows system headers and libraries](https://clang.llvm.org/docs/UsersManual.html#windows-system-headers-and-library-lookup). + +Первоначально Windows и Linux имеют собственные сборочные и проверочные workers. Платформенные SDK и runtime — реальные зависимости. Офлайн-сборка означает доступный локальный набор инструментов/исходников, а не отсутствие первоначальной установки. + +## Исследуемые движки не являются зависимостями Faset + +Unreal Engine, Godot, UnityCsReference и Blender изучались как источники архитектурных идей и наблюдений. Их source tree не входит в этот репозиторий; ссылки указывают на upstream snapshots. Реализация Faset должна учитывать происхождение любого фактически добавляемого кода, а не считать изучение разрешением на копирование. + +- [Unreal Engine EULA](https://www.unrealengine.com/eula/unreal) — условия Epic; source-доступ может требовать авторизации. +- [Godot MIT](https://github.com/godotengine/godot/blob/master/LICENSE.txt). +- [UnityCsReference: reference-source terms](https://github.com/Unity-Technologies/UnityCsReference/blob/master/LICENSE.md). +- [Blender: лицензирование](https://www.blender.org/about/license/). + +Blender остаётся отдельной официальной программой. Файловый обмен GLB и metadata не требует включения Blender в Player. У будущего дополнения Blender будет явно указанная лицензия и проверенный способ распространения; лицензия Faset не переопределяет условия API/кода Blender. + +## Текущая браузерная карта + +Карта использует React, React DOM, react-markdown, remark-gfm и esbuild; точный npm-граф закреплён в [package-lock.json](studies/map/package-lock.json). Это зависимости просмотрщика исследований, не выбранная технология UI игрового редактора. Их код не vendored в Git: node_modules и dist исключены. При отдельном распространении собранного приложения нужно сохранить notices включённых компонентов. + +## Правила независимости + +Собственные форматы, schema IDs и API; закреплённые версии; source-доступ к ключевым зависимостям; воспроизводимая интеграция; возможность исправить или fork-нуть библиотеку; отсутствие обязательной облачной активации. Замена библиотеки всё равно имеет техническую стоимость. + +Для каждого поставляемого компонента хранить исходный URL, pin, лицензию, notices, местные изменения, назначение и способ попадания в runtime/SDK. Наличие permissive license не освобождает от её уведомлений и условий. Лицензия собственного содержимого Faset описана отдельно в [PUBLICATION.md](PUBLICATION.md). diff --git a/docs/PUBLICATION.md b/docs/PUBLICATION.md new file mode 100644 index 0000000..e3a22e7 --- /dev/null +++ b/docs/PUBLICATION.md @@ -0,0 +1,21 @@ +# Публичный репозиторий и происхождение материалов + +Срез документации: 18.09.2026. + +## Что публикуется + +Собственные архитектурные описания и планы Faset, исследования со ссылками на первичные источники, манифест исследованных snapshots и исходники локальной карты документации. Утверждённый план не выдаётся за готовую реализацию движка. + +Сторонние source checkouts, локальные индексы graphify, node_modules, dist, логи, credentials и пользовательские рабочие каталоги не входят в публикацию. Браузерный source viewer предназначен для локального использования и не является сервисом публичной раздачи стороннего кода. + +Исходники Unreal требуют соответствующего доступа Epic; публикация ссылки на commit не предоставляет этот доступ. У Godot, UnityCsReference, Blender и остальных источников остаются их собственные условия. Подробности — в [DEPENDENCIES.md](DEPENDENCIES.md). + +## Лицензия собственного содержимого + +Лицензия на повторное использование собственного кода и документации Faset пока не выбрана. Публичная доступность репозитория не должна описываться как автоматически предоставленная MIT/Apache-лицензия. Файл LICENSE будет добавлен после выбора владельца. + +## Проверки публикации + +Проверяются состав Git, отсутствие локальных секретов и чужих source trees, внутренние ссылки, source pins, запуск/сборка карты и границы её файлового API. Проверки карты и документов не являются тестами будущего C++-движка, Blender integration или графической производительности. + +Локальные source paths в манифесте являются необязательными относительными подсказками. Публичные Markdown-ссылки на исходники закреплены на upstream commits и не зависят от имени пользователя или расположения Desktop. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..e4444ab --- /dev/null +++ b/docs/README.md @@ -0,0 +1,30 @@ +# Документация Faset Engine + +Актуализировано 18.09.2026 по принятым решениям. Репозиторий пока содержит проектирование и исследования, а не реализацию движка. + +## Канонические документы + +- [PLAN.md](../PLAN.md) — порядок реализации, критерии приёмки MVP и развитие после него; здесь ведётся статус работ. +- [ARCHITECTURE.md](ARCHITECTURE.md) — принятый стек, контракты и владение данными. +- [DEPENDENCIES.md](DEPENDENCIES.md) — независимость, выбранные зависимости и версии. +- [PUBLICATION.md](PUBLICATION.md) — состав публичного репозитория и происхождение материалов. + +## Исследовательская база + +- [Индекс исследований](studies/README.md). +- [Манифест источников](studies/source-manifest.json) — commits, версии и границы изучения. +- [Карта документации](studies/map/README.md) — локальный интерактивный просмотр, исходники которого входят в Git. + +Архитектура определяет принятые контракты, PLAN — объём и порядок реализации. Исследования объясняют происхождение решений; отвергнутые варианты остаются историей, неподтверждённые идеи — гипотезами. Исправление противоречия должно затрагивать все текущие описания, включая карту. + +## Что уже выбрано + +C++ с последующим Lua; EnTT; Vulkan 1.3, Render Graph и Slang; SDL3; Box2D/Box3D; собственный retained UI редактора с тёмной темой; ImGui для отладки; CMake/Ninja/Clang; JSON authoring и cooked binary; отдельный Player; статический gameplay и DLL/SO-плагины редактора под согласованный SDK; вложенные сцены и overrides; fixed tick и Update/FixedUpdate/LateUpdate. + +**MCP принадлежит только редактору и его сервисам.** Обычный Blender остаётся внешним инструментом; дополнительный плагин не является модификацией Blender. Сборка первой версии под каждую ОС проверяется на этой ОС. + +## Дальнейшие изменения + +У изменения контракта должны быть причина, влияние на данные/API и проверяемый результат. Версии библиотек, стандарт C++, минимальные версии ОС и вспомогательные библиотеки фиксируются на M0 как реализационные параметры, а не повторный выбор архитектуры. + +Различать «изучено в чужом движке», «принято для Faset», «реализовано» и «измерено». Статический анализ исходников не доказывает производительность будущего Faset. Возможности сначала проходят критерии PLAN, затем описываются как реализованные. diff --git a/docs/studies/01-architecture.md b/docs/studies/01-architecture.md new file mode 100644 index 0000000..4abbb9a --- /dev/null +++ b/docs/studies/01-architecture.md @@ -0,0 +1,341 @@ +# 01. Архитектурные концепции + +**Статус на 18.09.2026.** Исследование и проектные решения, не описание реализованного движка. Канонические решения — [ARCHITECTURE.md](../ARCHITECTURE.md), порядок до и после MVP — [PLAN.md](../../PLAN.md). Сведения о других движках сохраняются как исследовательский контекст; прежние рекомендации, помеченные **superseded**, не задают стек Faset. + +Принято: C++ и EnTT runtime; собственный retained UI на C++ с декларативными layout/styles и тёмной темой по умолчанию, ImGui для отладки; SDL3, Vulkan 1.3, собственный renderer/RenderGraph, Slang; CMake + Ninja, Clang на Linux и clang-cl на Windows; Box2D/Box3D. Lua — обязательный следующий этап развития, необязательная зависимость конкретной игры. Первое MVP доводит маленькие 2D- и 3D-демо до самостоятельного экспорта на обе ОС; advanced graphics — после MVP. + +## 1. Архитектура должна следовать пользовательским действиям + +У движка есть два связанных, но не одинаковых продукта: + +- **runtime** — запускает игру, симулирует мир и выводит кадр; +- **authoring environment** — помогает человеку (и при необходимости automation-инструментам) строить, исследовать и исправлять мир. + +Авторские документы и работающая игра имеют разные владельцы состояния. Общая схема описывает сериализуемые данные, но редакторские транзакции не перехватывают каждое изменение компонента в игре. + +```text +Editor UI / CLI / MCP / editor plugins + ↓ +AuthoringService: JSON documents, schema, revisions, transactions, Undo + ↓ validated scene snapshot + cooked assets +Separate Player: statically linked C++ gameplay → EnTT runtime + ↓ +Physics / render extraction / renderer / runtime diagnostics +``` + +MCP работает только на стороне редактора: authoring, импорт, сборка, Play/Stop и editor logs. Он не читает и не редактирует runtime world, отсутствует в Player и экспортированных играх. Игровой C++ использует runtime API и command buffer; редакторские расширения используют AuthoringService. Snapshot запуска не меняется вслед за последующими правками исходной сцены. + +## 2. Рекомендуемые слои + +Не делать одну гигантскую `Engine`-сборку. Полезная граница модулей: + +```text +Engine.Editor # окна, selection, gizmos, asset browser, commands +Engine.Authoring # scenes, prefabs, transactions, reflection, validation +Engine.Runtime # world, systems, events, scheduling, time, services +Engine.Spatial # transforms, bounds, hierarchy, queries, cameras +Engine.Physics # 2D/3D adapters, fixed simulation, contacts +Engine.Graphics # render extraction, render graph interface, resources +Engine.Assets # IDs, importer, cache, dependency graph, hot reload +Engine.Input # devices -> actions -> gameplay/UI contexts +Engine.Scripting # public scripting API, reload boundary, diagnostics +Engine.Platform # window, filesystem, threads, clocks, processes +Engine.Diagnostics # typed logs/traces; editor and runtime boundaries +Engine.Automation # editor-only MCP; authoring/import/build/PlayStop/logs +Engine.App # composition root and game-specific code +``` + +Направление зависимостей — сверху вниз только через стабильные interfaces/data contracts. `Editor` может ссылаться на `Authoring`, но runtime не должен зависеть от ImGui или конкретного editor UI. `Graphics.Vulkan` — backend `Graphics`, а не центр, от которого зависит gameplay. + +### Правило владения + +Каждый объект имеет одного явного владельца: + +- authoring document владеет сохраняемыми объектами; runtime `World` — своими EnTT entities и компонентами; +- `AssetRegistry` владеет жизненным циклом загруженных ресурсов; +- `TransformGraph` владеет локальными/мировыми transform-вычислениями; +- physics backend владеет native body и рассчитанным transform динамического тела; teleport и kinematic movement задаются явно; +- renderer владеет GPU-копиями, но не авторским asset; +- editor transaction владеет историей изменений. + +Ссылки между подсистемами — стабильные IDs/handles, а не сохраняемые указатели на C++-объекты. Это облегчает hot reload, сохранение, remote tools и диагностику dangling references. + +## 3. World model: composition, а не одна иерархия + +### Entity + components + +Авторский объект имеет постоянный `ObjectId`; EnTT entity — временный handle внутри конкретного runtime world. Components — данные и явно определённые lifecycle hooks. Systems читают и изменяют наборы компонентов. Это дает композицию: объект может иметь `Transform2D`, `Sprite`, `Collider2D`, `AudioSource`, `Script` или их 3D-аналоги без дерева наследования. + +O3DE описывает тот же принцип как «has-a», а не «is-a», и разделяет runtime/editor/system components ([O3DE ECS overview](https://www.docs.o3de.org/docs/user-guide/programming/components/overview)). Это полезное различие для нового движка: + +- `TransformComponent` runtime существует в игре; +- `TransformEditorComponent` добавляет gizmo и authoring metadata; +- `AssetSystem`/`UndoSystem` — system services, не компоненты сущности. + +### Отдельный transform graph + +Не смешивать entity hierarchy с универсальным механизмом поведения. Нужна отдельная иерархия parent/child только там, где она имеет смысл: + +- локальные transform для руки, камеры, дочернего объекта; +- UI layout; +- skeleton/кости; +- 2D-вложенность и canvas layers. + +Логика gameplay, ownership ресурсов, physics constraints и spatial partition — другие графы. Один parent не должен неявно означать все типы связи. Это предотвращает циклы и неожиданное каскадное изменение. + +### Авторская сущность и runtime entity + +Сохраненная сцена содержит authoring IDs и компоненты. При запуске может появиться runtime entity mapping: + +```text +AuthoringId 42 -> RuntimeEntity 918 +PrefabId -> instance overrides +AssetId -> loaded resource handle +``` + +Это позволяет Player менять runtime без порчи исходной сцены. Mapping полезен внутренней диагностике, но не создаёт runtime MCP API; обратное применение произвольного состояния игры в документы не входит в MVP. + +## 4. Scenes, prefabs и resources + +Godot делает сцену универсальной композиционной единицей: сценой может быть weapon, character, door или уровень, а вложенные сцены ведут себя как переиспользуемые композиции ([design philosophy](https://docs.godotengine.org/en/stable/getting_started/introduction/godot_design_philosophy.html), [key concepts](https://docs.godotengine.org/en/stable/getting_started/introduction/key_concepts_overview.html)). Unity решает близкую задачу через prefab: GameObject с компонентами можно сохранять, вкладывать и делать вариации ([Prefabs](https://docs.unity3d.com/6000.1/Documentation/Manual/Prefabs.html)). + +Для нового движка полезно объединить сильные стороны: + +- **Scene** — редактируемое дерево/граф entities, которое можно открыть и запустить отдельно; +- **Prefab** — сцена как reusable asset с instance overrides; +- **Resource** — именованный типизированный asset (mesh, texture, material, animation, sound, script, data asset); +- **Spawnable** — подготовленная runtime-последовательность создания prefab/scene с dependency manifest. + +### Принятые правила экземпляров и overrides + +1. Шаблон — отдельный scene resource с постоянными `ObjectId` и `ComponentId`. Размещение хранит `TemplateAssetId`, собственный `InstanceId` и override layer; имя — только подпись. +2. Идентичность унаследованного объекта определяется цепочкой `InstanceId` вложенных размещений и исходным `ObjectId` в документе шаблона. Это цепочка инстанцирования, не путь transform-родителей: допустимый reparent не меняет ID. +3. Patch адресуется через instance chain, `ObjectId`, `ComponentId`, `FieldId`; `TypeId` проверяет совместимость. В v1 массив изменяется целиком, без хрупких index/name-based patches. +4. Порядок разрешения: данные шаблона → overrides вложенного экземпляра в содержащем шаблоне → overrides внешнего экземпляра в сцене. Последний явный override побеждает, включая намеренное совпадение с текущим default; неизменённые поля не записываются. +5. Добавленный объект или компонент получает новый ID в документе-владельце override. Сохраняются provenance и ссылка на родителя. Дублирование экземпляра создаёт новый `InstanceId`, переназначает внутренние ссылки и сохраняет внешние. +6. Reparent в v1 разрешён внутри одного экземпляра; циклы и перенос через границу вложенного экземпляра отклоняются. Перемещение экземпляра целиком допустимо. Команда явно выбирает сохранение local или world transform. +7. Удаление унаследованного объекта записывает suppression объекта и его разрешённого поддерева; шаблон остаётся прежним. Локально добавленный объект удаляется из документа-владельца. Ссылки и другие patches на подавленные цели требуют разрешения конфликта, а не молчаливого удаления. +8. Новая версия шаблона обновляет поля без overrides. Совместимые явные overrides сохраняются. Исчезнувшая цель, несовместимый тип, цикл и повреждённая обязательная ссылка дают конфликт; активной остаётся последняя согласованная версия до исправления. +9. Структурный batch проверяет revision, строит кандидат, валидирует итоговые связи и публикует один результат с одним Undo. `Revert` удаляет выбранный override и возвращает нижележащее значение; UI и MCP используют один authoring contract. +10. В MVP входят обычные и ациклично вложенные экземпляры, overrides полей, добавление объектов/компонентов, suppression и ограниченный reparent. Variant inheritance, `Apply overrides to template`, перенос через границы экземпляров и поэлементное слияние массивов — после MVP. + +Inspector показывает источник и локальные отличия, переход к шаблону, Revert и конкретные конфликтующие targets. Импортированный шаблон использует тот же принцип разделения baseline и пользовательского слоя; детали публикации поколения — в [исследовании импорта](17-asset-pipeline-and-blender-roundtrip.md). + +### Два режима связи + +- **strong reference** — asset должен присутствовать; ошибка блокирует export или явно видна; +- **soft reference** — путь/ID загружается по требованию; полезен для больших уровней и optional content. + +References хранятся по `AssetId`, а путь — метаданные/alias. Переименование файла не должно ломать проект. + +## 5. 2D и 3D: единое ядро, специализированные представления + +Не выбирать между «два полностью отдельных движка» и «2D — это 3D с нулевой координатой». Нужны три уровня: + +### Общие концепции + +- entity, components, resources, scenes, prefabs; +- transform hierarchy и local/world conversion; +- camera, visibility, layers/masks; +- input actions, animation, audio, particles; +- fixed/variable time, events, diagnostics; +- serialization, asset references, editor commands. + +### Специализированные типы + +| Общее | 2D | 3D | +|---|---|---| +| Transform | `Vector2`, `Rotation2D`, depth/layer | `Vector3`, quaternion, parent basis | +| Renderable | Sprite, tilemap, canvas item, 2D text | mesh, skinned mesh, terrain, 3D text | +| Camera | orthographic + canvas transform | perspective/orthographic + frustum | +| Physics | 2D bodies/shapes/joints | 3D bodies/shapes/joints | +| Spatial query | rect/grid/quadtree | AABB/BVH/octree/grid | +| Lighting | 2D lights/masks, optional normal maps | lights/shadows/environment | + +Godot прямо отмечает, что APIs и tutorial patterns 2D/3D во многом аналогичны, но имеет отдельные Node2D/Node3D и физические пространства ([Introduction to 3D](https://docs.godotengine.org/en/stable/tutorials/3d/introduction_to_3d.html), [physics introduction](https://docs.godotengine.org/en/stable/tutorials/physics/physics_introduction.html)). Это хороший UX-паттерн: одинаковые названия и операции, но не скрывать важные различия. + +### Гибридные игры — критерий архитектуры + +После двух базовых MVP-демо проверять расширенные сочетания: + +- 2D gameplay на 3D backdrop; +- 3D world-space UI и 2D overlay; +- orthographic camera в 3D world; +- Sprite/quad в 3D; +- particles и audio независимо от dimensionality. + +Это заставляет правильно отделить world transform, camera projection и render representation. + +## 6. Game loop и расписание систем + +Минимально понятный цикл: + +```text +FrameStart + input.poll_and_buffer() + fixed_accumulator += real_delta + ticks = 0 + while fixed_accumulator >= fixed_dt and ticks < max_catch_up_ticks: + runtime_commands.apply_structural_barrier() + input.consume_for_tick() + gameplay.fixed_update(fixed_dt) + physics.apply_body_commands() + physics.step_and_wait(fixed_dt) + physics.readback_transforms_and_queue_events() + gameplay.process_physics_events_and_reactions() + fixed_accumulator -= fixed_dt + ticks += 1 + discard_excess_whole_ticks_with_diagnostic(fixed_accumulator) + gameplay.update(variable_delta) + display_state.prepare_interpolated(previous_tick, current_tick, + fixed_accumulator / fixed_dt) + gameplay.late_update(display_state, variable_delta) + render.extract_snapshot(display_state) + render.submit() + diagnostics.end_frame() +FrameEnd +``` + +Structural commands, созданные во время tick, применяются только на барьере следующего tick, включая несколько ticks одного кадра. Начальная загрузка проходит отдельный явный барьер инициализации. Начальный лимит catch-up — четыре ticks; лишние целые интервалы отбрасываются с диагностикой, дробный остаток сохраняется. После pause/reset накопитель сбрасывается. Interpolated display state отделён от симуляции: Update/LateUpdate не переписывают рассчитанный physics transform динамического тела. + +Physics и код, зависящий от столкновений, должны идти на fixed step; render/UI — variable step. Godot документирует отдельные `_physics_process` и `_process`, причем physics rate независим от framerate ([Idle and Physics Processing](https://docs.godotengine.org/en/stable/tutorials/scripting/idle_and_physics_processing.html)). Unity также подчеркивает, что `FixedUpdate` может вызываться несколько раз за кадр или не вызываться между кадрами и что physics simulation имеет определенное место в loop ([execution order](https://docs.unity3d.com/6000.5/Documentation/Manual/execution-order.html)). + +### Не обещать ложную determinism + +Fixed timestep стабилизирует расписание, но не делает автоматически всю физику детерминированной. Godot прямо предупреждает, что физика не гарантированно детерминирована ([physics introduction](https://docs.godotengine.org/en/stable/tutorials/physics/physics_introduction.html)). Для Faset fixed physics и variable frame являются базой; следующие режимы описывают дальнейшие возможности: + +- `Realtime` — максимально плавный runtime; +- `Fixed` — фиксированный simulation step; +- `Record/Replay` — записывать input и seed, а не обещать byte-for-byte replay без теста backend; +- `Lockstep` — отдельная будущая возможность с жесткими ограничениями. + +Системы имеют декларативные зависимости (`reads/writes`, `before/after`), а не неявный порядок регистрации. Unreal показывает практическую ценность tick groups и tick dependencies для physics/gameplay ordering ([Actor Ticking](https://dev.epicgames.com/documentation/unreal-engine/actor-ticking-in-unreal-engine)). Для нового движка достаточно сначала детерминированных фаз, затем parallel scheduling. + +## 7. ECS, объектный код и data-oriented design + +ECS дает cache-friendly iteration и уменьшает связанность при больших однородных наборах. Но это не религия и не универсальный public API. Data-oriented design — выбор layout по access patterns, а не обязательное использование archetype ECS ([ECS FAQ](https://github.com/SanderMertens/ecs-faq)). Game Programming Patterns отдельно рассматривает Component, Event Queue, Data Locality, Game Loop и другие паттерны ([catalog](https://gameprogrammingpatterns.com/contents.html)). + +**Принято для Faset:** EnTT runtime, typed C++ gameplay API, явная metadata schema с постоянными `TypeId`/`FieldId`, удобные object/component descriptors в редакторе. Layout горячих данных и batching выбираются по измерениям. EnTT handles не записываются в JSON. + +**Superseded, история сравнения:** первоначально предлагались Flecs/archetype queries и C# façade. Они не входят в выбранный стек. Lua добавляется после C++-основы отдельным модулем; наличие схемы само по себе не реализует Lua bindings. + +### Сигналы и события + +Не вызывать произвольные методы десятка объектов во время системного прохода. Использовать typed events/commands: + +- события контактов, input actions, asset loaded; +- command buffer для structural changes; +- double-buffered event queues, чтобы producer и consumer имели понятный кадр доставки; +- логирование origin/target/sequence для отладки. + +Event Queue и Double Buffer — проверенные декомпозирующие паттерны, но их нужно ограничивать schema и lifetime ([Game Programming Patterns](https://gameprogrammingpatterns.com/contents.html)). + +## 8. Asset pipeline: source не равен runtime + +Pipeline должен быть видимым графом: + +```text +source file + importer settings + engine version + │ + ▼ + importer/cache + │ + typed artifact + dependencies + │ + ▼ + cooked platform package +``` + +Godot импортирует файлы в скрытые внутренние ресурсы и предупреждает, что ResourceLoader учитывает импорт, тогда как прямой FileAccess может работать в editor, но сломаться в export ([import process](https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/import_process.html)). Unity разделяет source asset, `.meta` с идентичностью и кэшируемые artifacts, которые можно регенерировать ([Asset Workflow](https://docs.unity3d.com/2019.3/Documentation/Manual/AssetWorkflow.html), [Asset Database](https://docs.unity3d.com/2020.3/Documentation/Manual/AssetDatabaseCustomizingWorkflow.html)). O3DE называет преобразование source в optimized product assets Asset Pipeline и выполняет его Asset Processor ([key concepts](https://docs.o3de.org/docs/welcome-guide/key-concepts)). + +### Минимальный контракт importer + +```text +ImporterId +Input extensions +Settings schema + defaults +Output asset type(s) +Dependency discovery +Content hash / importer version +Diagnostics (error/warning/info + source span) +Cancel/progress +Deterministic output +``` + +Нужно хранить рядом или в проектном manifest: + +- стабильный `AssetId`; +- importer settings, не только результат; +- dependency graph; +- source hash и importer version; +- platform variants; +- last import diagnostics. + +Ошибка импорта — first-class asset state: красный badge, понятная причина, путь к offending dependency и кнопка retry/open log. + +## 9. Serialization и эволюция формата + +Авторские сцены и data assets должны быть: + +- текстовыми и diff-friendly; +- с явными типами и версиями schema; +- устойчивыми к reorder полей; +- способными сохранять неизвестные поля хотя бы в compatibility window; +- защищенными от случайного хранения runtime handles, GPU pointers и absolute paths. + +Принят JSON с постоянными object/resource IDs, `TypeId`, `FieldId` и schema versions. Имена типов и полей могут сопровождать данные для чтения человеком, но не являются ключами идентичности. Конкретный envelope определяется реализацией и fixtures миграции; прежний YAML-пример с name-based references **superseded**. + +Binary artifacts допустимы для cooked output и больших blobs, но исходный truth должен быть читаемым. Autosave пишет recovery snapshot, а не перезаписывает source без undo/history. + +Миграция schema — отдельная команда, dry-run preview и резервный backup. Версию формата не связывать жестко с версией renderer. + +## 10. Потоки и async + +Безопасная базовая модель для личного engine: + +- AuthoringService сериализует изменения документов; runtime world отдельно владеет structural changes EnTT в разрешённых фазах; +- worker threads импортируют assets, компилируют shaders, готовят jobs; +- physics/render native calls выполняются на thread, который требует backend; +- cross-thread изменения приходят через typed queue и применяются в определенной фазе; +- cancellation и progress обязательны для долгих задач. + +Не делать «все async» API без ясной точки commit. Loader может background-load, но переход `Loading → Ready` должен быть наблюдаемым и происходить в transaction-safe фазе. + +## 11. Расширения и backend abstraction + +Редакторские C++ plugins собираются как DLL/SO под точную версию SDK/toolchain; стабильный произвольный C++ ABI и hot-unload не обещаются. Gameplay статически включается в отдельный Player. Регистры расширений могут добавлять: + +- component/schema; +- system; +- importer; +- editor panel/tool; +- export step; +- render feature/backend. + +Плагин не должен подменять core ID/lifetime/serialization conventions. Registry должен проверять версии API, зависимости и capability. + +Backend interface абстрагирует window/surface/device/resource submission, но не должен прятать концепции, которые нужны debugging. Например, render graph может сообщать pass/resource barriers в trace, даже если gameplay видит только `Renderable`. + +## 12. Диагностика и тестируемость — не надстройка + +Минимальная observability schema: + +- structured log: category, severity, entity/asset ID, frame, correlation ID; +- frame trace: systems, durations, allocations, render passes; +- asset trace: importer, dependencies, cache hit/miss; +- authoring snapshot/diff; runtime traces остаются отдельной внутренней диагностикой и не публикуются через MCP; +- command journal: кто, что, когда и почему изменил; +- validation: missing resources, invalid transforms, duplicate IDs, unowned native handles. + +Тестовые уровни: + +1. pure math/serialization/schema migrations; +2. system tests на headless world; +3. asset importer golden tests; +4. physics adapter tests с tolerance; +5. render extraction tests без GPU; +6. GPU/backend smoke tests; +7. editor command/undo integration tests; +8. маленькие end-to-end projects: 2D, 3D, hybrid. + +Главная цель — ошибка из графического кадра должна быть трассируема обратно к asset/entity/component/command. diff --git a/docs/studies/02-editor-ux.md b/docs/studies/02-editor-ux.md new file mode 100644 index 0000000..98b13ec --- /dev/null +++ b/docs/studies/02-editor-ux.md @@ -0,0 +1,290 @@ +# 02. UX редактора и повседневный workflow + +**Статус на 18.09.2026.** Исследование и проектные решения, не описание реализованного движка. Канонические решения — [ARCHITECTURE.md](../ARCHITECTURE.md), порядок до и после MVP — [PLAN.md](../../PLAN.md). Сведения о других движках сохраняются как исследовательский контекст; прежние рекомендации, помеченные **superseded**, не задают стек Faset. + +Принята собственная retained UI-система на C++: декларативные layout/styles, тёмная тема по умолчанию, schema-driven Inspector. ImGui используется для debug tools. Ни UI Toolkit Unity, ни Qt/web UI не являются выбранной основой. Описанные ниже расширенные инструменты — backlog; граница MVP определяется [планом](../../PLAN.md). + +## 1. Основная UX-гипотеза + +Пользователь движка не хочет «управлять ECS». Он хочет ответить на вопросы: + +- где находится объект; +- из чего он состоит; +- почему он выглядит/движется именно так; +- что произойдет, если я изменю параметр; +- как вернуться назад; +- что попадет в экспорт. + +Поэтому хороший UX строится вокруг **объекта, намерения и обратной связи**, а не вокруг внутренней терминологии engine. + +## 2. Главный цикл: observe → edit → run → inspect → repeat + +Время цикла — главный product metric личного движка: + +```text +1. Open project +2. Find/create scene +3. Add entity or prefab +4. Select it +5. Change one property +6. Press Play / run current scene +7. Observe game +8. Read editor/build diagnostics or debug the Player separately +9. Stop safely; edit scene remains independent +10. Save and export +``` + +Каждый шаг должен иметь одну очевидную точку входа. Если для добавления Sprite нужно вручную создать entity, зарегистрировать component type, загрузить texture и прописать renderer binding — это внутренний API, а не UX. + +### Метрики + +Отслеживать без телеметрии пользователя, локально: + +- first pixel: от Create Project до первого кадра; +- first interaction: до управляемого объекта; +- edit-to-visible: от изменения Inspector до результата; +- error-to-cause: от красной ошибки до исправления; +- clean export: от проекта до запускаемого build; +- recovery: насколько легко отменить ошибку. + +Целевые значения для MVP лучше задать позже после baseline, но каждый релиз должен уменьшать хотя бы один цикл. + +## 3. Информационная архитектура редактора + +Стартовый layout: + +```text +┌ Project / Scene tabs / Play / Stop / Build ┐ +├────────────┬──────────────────────────┬─────────────┤ +│ Scene Tree │ Scene View │ Inspector │ +│ │ 2D | 3D | Game | Debug │ │ +├────────────┴──────────────────────────┴─────────────┤ +│ Asset Browser | Console | Profiler | History │ +└──────────────────────────────────────────────────────┘ +``` + +Панели не должны быть обязательным лабиринтом: сохранить workspace per role (programmer, designer, debugger), tabs и reset layout. Главные действия доступны меню и keyboard shortcuts; mouse-only workflow недопустим. + +Полезные концепции из Godot: + +- Scene Tree и FileSystem дают две разные оси: composition и assets; +- Inspector автоматически показывает свойства выбранного объекта, поддерживает поиск, секции, revert и sub-resources ([Inspector Dock](https://docs.godotengine.org/en/stable/tutorials/editor/inspector_dock.html)); +- Project Settings группирует общие настройки, Input Map, Localization, Globals, Plugins и Import Defaults, имеет поиск и reset ([Project Settings](https://docs.godotengine.org/en/stable/tutorials/editor/project_settings.html)). + +Новый движок должен повторить эти **mental models**, но сделать их более согласованными с command journal и automation tools. + +## 4. Scene Tree и selection + +Scene Tree — не просто список entity IDs. Он должен показывать: + +- display name + stable ID по запросу; +- prefab/scene boundary; +- disabled/hidden/locked state; +- missing component/resource badges; +- parent/child transform relation; +- только authoring objects; runtime hierarchy не является частью MCP/Scene Tree MVP; +- search by name, type, tag, component, asset reference. + +Selection — глобальный контекст editor. Выделение в Scene View, Tree, Asset Browser и Inspector синхронно. Multi-select должен позволять массовое изменение через transaction с preview. + +Не полагаться только на hierarchy: для больших сцен нужен flat search/results view и reverse reference view «кто использует этот asset/entity». Hierarchy удобна для transform и чтения композиции, но не является полноценной базой данных. + +## 5. Inspector: главный ergonomic leverage + +Inspector строится по общей явной C++ metadata schema (`TypeId`/`FieldId`) для компонента, ресурса и editor-only данных. + +Каждое поле имеет: + +- label + tooltip с единицами измерения; +- valid range/enum и validation message; +- default, current и inherited value; +- source: template/scene/instance и provenance импортированной основы; +- reset/revert; +- copy path / copy value; +- searchable category; +- optional advanced section; +- link to references and documentation. + +Важно отличать: + +```text +invalid value → не принять или показать inline error +valid but risky → принять, warning и explain +valid override → показать source и revert +Player state → отдельная сессия; source меняется только authoring-командой +``` + +Godot показывает revert icon для измененных относительно оригинала значений и умеет делать sub-resources unique ([Inspector Dock](https://docs.godotengine.org/en/stable/tutorials/editor/inspector_dock.html)). Unity Prefab Mode использует context/isolation и breadcrumb, чтобы автор понимал, редактирует ли asset или instance ([Editing Prefab Mode](https://docs.unity3d.com/6000.5/Documentation/Manual/EditingInPrefabMode.html)). Для нового движка критичен аналогичный `source breadcrumb`: + +```text +Level01 > Player instance > Weapon prefab > Transform +``` + +## 6. Scene View и gizmos + +### Общие требования + +- frame selected, focus, orbit/pan/zoom; +- snapping и привязки с понятным modifier; +- local/world pivot; +- визуальные bounds/colliders/nav/audio; +- solo/isolate, hide, lock; +- orthographic/perspective toggle; +- camera bookmarks; +- safe preview before commit; +- одинаковые shortcuts для 2D и 3D, где это возможно. + +### 2D + +- canvas coordinates, pixels/world units и origin видимы; +- zoom не меняет смысл snapping; +- tilemap, sprite pivot, nine-patch и z/layer order редактируются в контексте; +- draw order объясним: layer, z-index, material/order override. + +### 3D + +- transform gizmo и numeric entry; +- grid и axis labels; +- frustum/camera preview; +- selection outline и object bounds; +- light/physics probes как debug overlays, не как неявные runtime objects. + +### Hybrid + +Переключатель должен быть не «2D editor против 3D editor», а режимом представления: 2D camera view, 3D perspective, game preview. Один world может иметь Sprite2D и Mesh3D. + +## 7. Commands, undo/redo и история + +Любое изменение через UI — команда: + +```text +SetProperty(documentId, objectAddress, componentId, fieldId, newValue, expectedRevision) +AddComponent(objectAddress, componentId, typeId, initialData) +Instantiate(prefabId, parentId, instanceId) +Delete(selection, reversiblePayload) +``` + +Команды группируются в transaction («переместить объект мышью» — один undo, а не сотни micro-steps). История содержит timestamp, source (`Inspector`, `MCP`, `script`, `importer`) и human-readable summary. + +Требования: + +- undo/redo работает в Scene Tree, Inspector, import settings и material editor; +- destructive action имеет preview/confirmation или reversible trash; +- изменения Player не попадают в edit history; обратный Apply из runtime не входит в MVP; +- autosave и recovery snapshot не уничтожают undo history; +- command journal экспортируется для воспроизведения bug. + +## 8. Play Mode и hot reload + +Принятый Play запускает отдельный Player со snapshot редактируемой сцены и статически собранным gameplay: + +```text +Edit document → validated snapshot → separate Player → Stop + ↓ ↓ +Further authoring edits temporary runtime state +``` + +Сцена может продолжать редактироваться независимо от snapshot запуска; для применения её новой версии к игре выполняется новый запуск. Начальный цикл C++: Stop → incremental build → Play. Редактор сохраняет контекст проекта и показывает ошибки компиляции с файлом и строкой. Кнопки MVP — Play, Stop, Restart, Pause и single-step; специальные runtime debugger UI относятся к дальнейшим инструментам. Управление сессией не предоставляет MCP доступ к runtime world. + +MCP вызывает editor Play/Stop, импорт и build, читает authoring state и editor logs. Он не получает runtime hierarchy, значения компонентов Player, runtime mutation tools или MCP endpoint в игре. Отдельная диагностика Player не превращается в MCP bridge. + +**Историческое сравнение, superseded для MVP Faset:** Godot поддерживает remote scene inspection, изменение параметров живой игры и reload ([debugging overview](https://docs.godotengine.org/en/stable/tutorials/scripting/debug/overview_of_debugging_tools.html)). Ранее здесь предлагались live edits и Apply выбранных runtime changes обратно в source. Эти сценарии не входят в принятый первый цикл; универсальный state-preserving native hot reload также отложен. + +Shader/data reload можно исследовать отдельно после надёжного жизненного цикла ресурсов. Lua — обязательный последующий этап, его reload требует собственного контракта. + +## 9. Debugging UX: от симптома к причине + +Следующие вопросы задают направление будущих локальных debug tools. Это исследовательский backlog после базовой диагностики MVP; runtime-инспекция не предоставляется через MCP. + +### «Почему объекта нет?» + +Один action `Explain Visibility` показывает: + +- entity active/disabled; +- parent visibility/layer; +- camera/frustum result; +- renderer extraction status; +- missing/failed asset; +- material/shader failure; +- draw pass and culling reason. + +### «Почему объект движется неправильно?» + +Показать: + +- owning systems; +- last writer per property; +- fixed vs variable tick; +- physics body mapping; +- incoming commands/events; +- transform parent chain; +- timeline of values. + +### «Почему frame slow?» + +Показать hierarchy: + +```text +Frame 16.6 ms + Systems 4.1 ms + Physics 2.8 ms + Render extraction 1.2 ms + GPU passes 7.4 ms + Asset/IO 0.3 ms +``` + +Нужны toggles для collision shapes, bounds, nav, lights, overdraw/draw calls и entity IDs. Godot предоставляет debugger/profilers, remote inspection и визуализацию collision/navigation ([debugging tools](https://docs.godotengine.org/en/stable/tutorials/scripting/debug/overview_of_debugging_tools.html)); Unity Frame Debugger умеет остановить кадр и пошагово показывать render events ([Frame Debugger](https://docs.unity3d.com/Manual/FrameDebugger.html)); Unreal Insights строит трассу CPU/gameplay/cook channels ([Insights reference](https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-insights-reference-in-unreal-engine-5)). Новому движку стоит взять эти идеи с единым correlation ID. + +## 10. Asset Browser и project hygiene + +Asset Browser должен показывать не только thumbnails: + +- тип, размер, импортированный статус; +- dependency/reverse-dependency graph; +- source path и AssetId; +- importer settings; +- platform variants; +- cache status; +- errors/warnings; +- usages и open-in-context. + +Drag-and-drop создает предсказуемый результат: texture в Sprite добавляет/заменяет компонент или resource, а не молча копирует файл. При rename сохраняется AssetId и обновляется registry path; устойчивые ссылки не переписываются ради нового имени. Редактор показывает реально изменённые файлы. + +Source control awareness полезна и в solo project: modified/untracked/generated/conflict badges. Unreal встроенно показывает checkout/history/diff и может checkout on save ([Source Control](https://dev.epicgames.com/documentation/en-us/unreal-engine/source-control-in-unreal-engine)); Новый движок может начать с простого read-only Git status и текстовых diff. + +## 11. Input UX + +В gameplay коде не использовать raw `Key.W` как единственный API. Использовать named actions: + +```text +Move (Vector2) +Look (Vector2) +Jump (Button) +Interact (Button) +``` + +Action Map хранит bindings для keyboard, mouse, gamepad, touch и rebinding. UI navigation — отдельный context с приоритетом над gameplay. Godot InputMap управляет actions и несколькими InputEvents, включая deadzone ([InputMap](https://docs.godotengine.org/en/stable/classes/class_inputmap.html)); Unity Input System строит named actions и UI events поверх action asset ([Runtime UI event system](https://docs.unity3d.com/6000.2/Documentation/Manual/UIE-Runtime-Event-System.html)). + +Editor должен показывать live input monitor: action, raw device event, resolved value, active context и deadzone. Это устраняет классическую ошибку «клавиша работает в editor, но не в game». + +## 12. Accessibility и cognitive load + +Даже internal editor должен иметь: + +- keyboard-first command palette; +- все действия с menu/shortcut и discoverability; +- масштабируемый UI и high contrast; +- не использовать цвет как единственный сигнал; +- readable error text и copyable diagnostics; +- focus order и screen-reader labels для важных controls; +- reduced motion в editor preview; +- единые единицы/локализация чисел; +- сохраненные workspace и пользовательские shortcuts. + +UX «удобного личного движка» — не меньше функций, а меньше скрытого состояния, модальных ловушек и неочевидных разрушительных операций. + +## 13. Шаблоны и Blender: принятые границы UX + +Вложенный экземпляр явно отделён в дереве; Inspector показывает источник, локальный override и Revert. В MVP доступны field overrides, добавление объектов/компонентов, suppression и reparent внутри одного экземпляра; перенос через nested boundary, variant inheritance и Apply to template отложены. Patch использует instance chain/ObjectId/ComponentId/FieldId; display name и transform path не заменяют ID. Удалённая цель и несовместимая схема дают конфликт с сохранением пользовательских данных. [Полные правила](01-architecture.md). + +Обычный официальный Blender не модифицируется. Стандартный импорт glTF/GLB работает без дополнений. Необязательный Python add-on упрощает экспорт, сохраняет source IDs и manifest; без устойчивых IDs нельзя гарантировать matching внутренних частей после rename/reorder. Gameplay, physics settings и instance overrides принадлежат Faset и не перезаписываются новым экспортом. diff --git a/docs/studies/03-comparison.md b/docs/studies/03-comparison.md new file mode 100644 index 0000000..56f7b64 --- /dev/null +++ b/docs/studies/03-comparison.md @@ -0,0 +1,128 @@ +# 03. Сравнение существующих подходов + +**Статус на 18.09.2026.** Исследование и проектные решения, не описание реализованного движка. Канонические решения — [ARCHITECTURE.md](../ARCHITECTURE.md), порядок до и после MVP — [PLAN.md](../../PLAN.md). Сведения о других движках сохраняются как исследовательский контекст; прежние рекомендации, помеченные **superseded**, не задают стек Faset. + +Матрица ниже — история оценки чужих подходов, не обещание реализовать все перечисленные возможности. Выбор Faset уже сделан: C++/EnTT, собственные retained C++ UI и Vulkan 1.3 renderer/RenderGraph, Slang/SDL3, CMake/Ninja/Clang и clang-cl, Box2D/Box3D; Lua добавляется отдельным обязательным этапом и остаётся необязательным для конкретной игры. MCP ограничен редакторским authoring/import/build/PlayStop/logs, отсутствует в Player и не инспектирует runtime world. + +Это не таблица «лучший engine». У каждого проекта другая целевая цена сложности. Вопрос: какую идею забрать в новый движок, а какую не импортировать. + +## 1. Сводная матрица + +| Движок/подход | Сильный mental model | Что особенно полезно | Цена/риск | Вывод для нового движка | +|---|---|---|---|---| +| Godot | Node + Scene + Resource | композиция сцен, единый editor, простая project structure, remote scene/debug | Node легко превращается в object soup; 2D/3D имеют отдельные типы; физика не обещает determinism | взять scene/resource UX и explicit 2D/3D parity; remote inspection оставить сравнением, без runtime MCP | +| Unity | GameObject + Component + Prefab + Asset Database | быстрый authoring, prefab variants, package model, большой ecosystem | скрытое состояние import/cache, сложный execution order, heavy editor/runtime coupling | взять prefab overrides, actions, frame debugger; сделать source/cache boundaries более прозрачными | +| Unreal | Actor/Component + Level/Asset + отражение | мощные editor tools, world/level authoring, Details/Outliner, trace/Insights, source-control UX | высокая когнитивная и build complexity, tick/GC/reflection pitfalls | взять details/outliner/trace patterns; не следовать enterprise-scale breadth | +| O3DE | Entity Component System + Editor/Runtime/System components + Gems | четкая component composition, Asset Processor, modular Gems, editor/runtime split | большой surface area, много abstractions, тяжелый onboarding | взять component categories, asset pipeline, plugins; держать API меньше | +| Bevy | ECS World + Resources + Systems + plugins | data-driven composition, explicit schedules, modular render/asset systems, code-first ergonomics | code-first UX, evolving APIs, ECS complexity для новичка | взять explicit schedule/resources/plugin boundaries; добавить визуальный authoring layer | + +## 2. Godot: композиция и маленький проект + +Godot явно строит философию вокруг object-oriented composition, scenes как reusable units и возможности сочетать editor и code ([design philosophy](https://docs.godotengine.org/en/stable/getting_started/introduction/godot_design_philosophy.html)). Scene Tree и FileSystem легко объяснить новичку. Inspector, Project Settings и editor plugins превращают metadata в UX surface ([Inspector](https://docs.godotengine.org/en/stable/tutorials/editor/inspector_dock.html), [Project Settings](https://docs.godotengine.org/en/stable/tutorials/editor/project_settings.html), [EditorPlugin](https://docs.godotengine.org/en/stable/classes/class_editorplugin.html)). + +**Перенять:** + +- любой node/subtree может стать сценой; +- сцена открывается и запускается независимо; +- ресурсы имеют собственное редактирование; +- import errors видны рядом с ассетами; remote scene Godot сохраняется здесь как сравнение, не требование runtime MCP Faset. + +**Не перенять без критики:** + +- смешивание lifecycle, hierarchy и behaviour в огромном количестве Node types; +- неявные глобальные singletons как основной способ коммуникации; +- отдельные 2D/3D API там, где общий concept был бы удобнее. + +## 3. Unity: скорость результата и prefab contract + +Unity показывает, насколько powerful один consistent loop `GameObject → Component → Inspector → Prefab → Play`. Prefab поддерживает nested instances и variations ([Prefabs](https://docs.unity3d.com/6000.1/Documentation/Manual/Prefabs.html)); контекстный Prefab Mode и breadcrumbs уменьшают риск редактировать не тот уровень ([Prefab Mode](https://docs.unity3d.com/6000.5/Documentation/Manual/EditingInPrefabMode.html)). Asset Database разделяет source, meta identity и cached artifacts ([Asset Workflow](https://docs.unity3d.com/2019.3/Documentation/Manual/AssetWorkflow.html)). + +**Перенять:** + +- inspector-driven authoring; +- prefab instance source/override visualization; +- named action input вместо raw keys; +- Frame Debugger, который делает render pipeline пошагово обозримым ([Frame Debugger](https://docs.unity3d.com/Manual/FrameDebugger.html)). + +**Не перенять без критики:** + +- dependency на opaque generated Library/cache; +- «магический» execution order; scheduling should be explicit; +- authoring-правки компонентов в обход общего command/history layer. Gameplay изменяет runtime-компоненты через runtime API без редакторского Undo; structural changes применяются через отдельный runtime command buffer. + +## 4. Unreal: масштабируемая authoring-система + +Unreal показывает ценность богатого Outliner + Details panel: actor можно найти в иерархии и редактировать свойства в контексте ([Level Editor](https://dev.epicgames.com/documentation/en-us/unreal-engine/level-editor-in-unreal-engine), [Details panel](https://dev.epicgames.com/documentation/en-us/unreal-engine/level-editor-details-panel-in-unreal-engine)). Source Control встроен в Content Browser и показывает checkout/history/diff ([Source Control](https://dev.epicgames.com/documentation/en-us/unreal-engine/source-control-in-unreal-engine)). Tick groups и dependencies делают порядок работы подсистем явным ([Actor Ticking](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-ticking-in-unreal-engine)). Unreal Insights демонстрирует, что trace должен включать gameplay, objects, physics и cook, а не только FPS ([Insights](https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-insights-reference-in-unreal-engine-5)). + +**Перенять:** + +- Outliner/Details split; +- human-readable reflection metadata; +- explicit runtime/editor distinction; +- trace channels и frame/object history; +- asset source-control status. + +**Не перенять:** + +- broad enterprise feature set; +- повсеместный per-actor ticking; +- сложную рефлексию без генерации schema и compile-time validation. + +## 5. O3DE: component boundaries и pipeline + +O3DE формулирует entity/component model, причем компоненты могут объявлять required/provided services, что предотвращает невалидное состояние entity. Отдельные editor components и system components — полезная архитектурная граница ([ECS overview](https://www.docs.o3de.org/docs/user-guide/programming/components/overview)). O3DE также явно выделяет Asset Processor, authoring tools, plugins и build system ([Key Concepts](https://docs.o3de.org/docs/welcome-guide/key-concepts)). + +**Перенять:** + +- schema-level component dependencies; +- editor component, runtime component, system service; +- importer/processors as first-class pipeline; +- modules/plugins with declared dependencies. + +**Осторожно:** + +- dependency graph component-услуг не должен быть непостижимым; показывать его в Inspector; +- не создавать micro-service архитектуру внутри маленького движка. + +## 6. Bevy: explicit data flow и systems + +Bevy предлагает code-first ECS, typed Assets и plugin composition. В релизных материалах подчеркиваются modularity, render phases, entity/component-driven draw functions и explicit resources ([Bevy 0.6](https://bevy.org/news/bevy-0-6)); новый scene system делает сцены composable, patchable и dependency-aware ([Bevy 0.19](https://bevy.org/news/bevy-0-19)). + +**Перенять:** + +- schedule phases и typed `Res`/components; +- asset handles + async loading; +- plugins as composition units; +- render extraction вместо обращения gameplay напрямую к GPU. + +**Не перенять:** + +- code-only authoring как единственный путь; +- нестабильную публичную surface без compatibility policy; +- предположение, что ECS автоматически делает every workload faster. + +## 7. Синтез + +Сильнейший общий паттерн не «ECS» и не «scene graph», а наличие **понятного authoring contract**: + +```text +Object in editor + = stable identity + + inspectable schema + + composable parts + + explicit references + + reversible changes + + runtime mapping +``` + +Для нового движка разумно выбрать: + +- Godot-подобную простоту сцен; +- Unity-подобный prefab/Inspector loop с nested composition и явными overrides; variants и Apply to template — после MVP; +- Unreal-подобные trace/Outliner/Details; +- O3DE-подобное разделение editor/runtime/system и Asset Processor; +- Bevy-подобные schedules/plugins/render extraction. + +И сознательно не переносить их масштаб, legacy и неявные соглашения. + +Прежние варианты C#/Flecs, готовый UI toolkit и прямое редактирование Player через automation **superseded**. Blender используется в официальной сборке: обычный glTF/GLB импорт самодостаточен; optional Python add-on добавляет устойчивые IDs и удобную кнопку экспорта. Его отсутствие не блокирует импорт, но ограничивает гарантии reimport после rename частей. MVP — два маленьких 2D/3D-демо с baseline PBR/тенями и самостоятельным экспортом под Linux/Windows; передовые графические технологии развиваются после него. diff --git a/docs/studies/04-engine-recommendations.md b/docs/studies/04-engine-recommendations.md new file mode 100644 index 0000000..588828d --- /dev/null +++ b/docs/studies/04-engine-recommendations.md @@ -0,0 +1,72 @@ +# 04. Принятые рекомендации для Faset Engine + +**Редакция 18.09.2026.** Здесь собраны выводы исследования после утверждения пользователем. Это проектные решения, не описание уже реализованных возможностей. Основной контракт — [ARCHITECTURE.md](../ARCHITECTURE.md); этапы и критерии до/после MVP — [PLAN.md](../../PLAN.md). Разбор других движков остаётся в исследованиях как обоснование. + +Faset — движок для десктопных 2D- и 3D-игр под Linux и Windows. Он должен давать понятный ручной authoring workflow и те же операции над проектом через MCP. Первый результат — маленькие 2D- и 3D-демо, созданные в редакторе и экспортированные в самостоятельные игры на обеих ОС. + +## 1. Принятый стек + +- Ядро и gameplay сначала на C++; EnTT для runtime ECS. Автор работает с объектами и компонентами, не с устройством ECS storage. +- Поддержка Lua обязательно добавляется после C++-основы отдельным модулем. Использование Lua конкретным проектом необязательно; C++ остаётся полноценным способом писать игру. +- SDL3, Vulkan 1.3, собственный renderer и RenderGraph, Slang. Baseline 3D — обычные PBR-материалы и тени; передовые технологии из UE развиваются после MVP. +- Собственный retained UI редактора на C++, декларативные layout/styles и тёмная тема по умолчанию. ImGui — инструмент отладки, не основа пользовательского интерфейса. +- CMake + Ninja; Clang на Linux, clang-cl на Windows. Gameplay статически включается в отдельный Player. Редакторские plugins — DLL/SO под точную версию SDK/toolchain. +- Box2D и Box3D, отдельные миры и типы для 2D/3D с общими правилами lifecycle и диагностики. Конкретные dependency pins закрепляются при интеграции. + +## 2. Источник истины и границы процессов + +AuthoringService владеет JSON-документами сцен/ресурсов, metadata schema, стабильными IDs, validation, revisions, транзакциями и Undo. UI, CLI, редакторские plugins и MCP вызывают его операции. Runtime world создаётся отдельно из проверенного snapshot; EnTT handles временные и не сериализуются. + +Play запускает отдельный Player со snapshot редактируемой сцены. Дальнейшие изменения документа не меняют этот snapshot. Начальный цикл C++: Stop → incremental build → Play; редактор сохраняет рабочий контекст. Состояние Player не записывается обратно в сцену автоматически. Универсальный native hot reload и обратный Apply runtime state не входят в MVP. + +**Граница MCP принята явно:** редактор/authoring, импорт, build/export, Play/Stop и editor logs. MCP не инспектирует и не редактирует runtime world. Player и экспортированные игры не содержат MCP server/client; управление запуском остаётся редакторской операцией. + +## 3. Постоянные идентификаторы и метаданные + +Различаются `ObjectId` авторского объекта, `ComponentId` конкретного компонента, `TypeId` его схемы, `FieldId` поля, `AssetId` ресурса, `InstanceId` размещения шаблона и временный runtime handle. Имя типа, подпись объекта и путь файла не подменяют идентичность. Переименование поля сохраняет `FieldId`; несовместимое изменение формата требует schema migration. + +Явные metadata содержат тип, defaults, units/ranges, enum values, Inspector grouping, ссылки, schema version и доступность операции. Inspector, сериализация, validation и authoring MCP используют одну схему. Она не означает автоматическую рефлексию произвольного C++ и не заменяет будущие Lua bindings. + +JSON содержит стабильные IDs и версии схем. Cooked данные — отдельный двоичный формат. Native pointers, GPU IDs и EnTT handles в authoring source недопустимы. Неизвестные компоненты сохраняются с диагностикой, чтобы отсутствие editor plugin не уничтожало данные. + +## 4. Сцены, шаблоны и nested instances + +Scene — открываемый документ объектов/компонентов; template/prefab — та же модель как повторно используемый ресурс. Экземпляр хранит ссылку на шаблон и override layer. Вложения ацикличны. Идентичность унаследованного объекта — instance chain плюс исходный `ObjectId`; цепочка инстанцирования независима от transform parenting. + +Patch адресует instance chain, `ObjectId`, `ComponentId`, `FieldId` и проверяет `TypeId`. В v1 массив заменяется целиком. Порядок значений: шаблон → overrides вложенного размещения в содержащем шаблоне → overrides внешнего экземпляра. Последний явный override выигрывает; Revert удаляет его и возвращает нижележащее значение. + +Добавления получают новые IDs в документе-владельце. Дублирование экземпляра создаёт новый `InstanceId`, переназначает внутренние ссылки и сохраняет внешние. Reparent разрешён внутри одного экземпляра с проверкой циклов; перенос через nested boundary запрещён. Перемещение экземпляра целиком допустимо; сохранение local/world transform задаётся явно. + +Удаление унаследованного объекта — suppression объекта и разрешённого поддерева. Удаление локального добавления изменяет документ-владелец. Ссылки и patches на исчезнувшую/подавленную цель, несовместимый тип и цикл дают конфликт; пользовательские данные не отбрасываются. Пока новая версия не согласована, сохраняется последняя рабочая. [Подробные правила](01-architecture.md). + +В MVP входят композиция, field overrides, добавление объектов/компонентов, suppression и ограниченный reparent. **После MVP:** template variants/наследование, Apply overrides to template, перенос между экземплярами, поэлементное слияние массивов. + +## 5. Транзакции и Undo + +Любая authoring-команда явно задаёт документ, targets и ожидаемую revision. Сначала строится и проверяется кандидат, затем публикуется одно изменение; конфликт revision не даёт частичного результата. Ручной drag использует временный preview и один commit/Undo; MCP batch также создаёт одну согласованную authoring-транзакцию. Это не глобальная транзакция поверх всех файлов проекта. + +Команды возвращают новые IDs, revision, изменения и структурированную диагностику. Долгие операции получают operation ID, progress/status/cancel. Повтор с тем же idempotency key и payload возвращает сохранённый результат в пределах определённого срока журнала. Runtime structural command buffer решает другую задачу и не создаёт editor Undo на каждый gameplay tick. + +## 6. Ассеты и официальный Blender + +Источник истины — исходные ресурсы, настройки импорта и постоянные IDs. Derived cache восстанавливается; importer version, recipe, зависимости и target profile участвуют в invalidation. Публикация нового поколения происходит после полной проверки; error/cancel сохраняют последнюю рабочую версию. + +Используется обычный официальный Blender без патчей или отдельного fork. Стандартные glTF/GLB импортируются самостоятельно. Необязательный Python add-on добавляет удобную кнопку, устойчивые IDs частей и manifest для более надёжного roundtrip. Без source IDs нельзя гарантировать сопоставление после rename/reorder; при неоднозначности нужны diagnostic и явный remap, а не угадывание по имени. + +Blender принадлежит геометрия, rig и animation; gameplay, physics settings и overrides принадлежат Faset. Reimport обновляет baseline и сохраняет совместимый пользовательский слой. Произвольные Blender material graphs не переносятся автоматически: поддерживается оговорённый glTF PBR-профиль, процедурные материалы требуют bake/ручного соответствия. Pixel-perfect совпадение разных renderer не обещается. [Разбор импорта](17-asset-pipeline-and-blender-roundtrip.md). + +## 7. Редактор и доступность действий + +Обязательный первый интерфейс: Project/Asset Browser, Scene Tree, schema Inspector, 2D/3D viewport, выбор/transform, Undo/Redo, open/save, Play/Stop и Build. Главные действия доступны через меню, shortcuts и command palette; ошибки показывают target и причину. Состояния template, instance override и conflict различимы без одного только цвета. + +Custom widgets и plugins используют общий binding/transaction слой. DLL/SO загружается только при точном соответствии SDK; registrations принадлежат plugin scope и снимаются при завершении его жизненного цикла. Gameplay Player не зависит от редакторского UI или plugins. Наличие декларативного layout не требует собственного визуального UI Builder в MVP. + +## 8. До и после MVP + +До MVP нужны identity/schema, документы/транзакции, EnTT mapping, базовые 2D/3D rendering и physics, импорт glTF/GLB, экземпляры/overrides, retained editor UI, editor-only MCP, отдельный Player и воспроизводимый build/cook/export. Интеграция проверяется на двух небольших демо под Linux и Windows. Сборка на одной ОС не доказывает работу другой. + +После MVP развиваются обязательный Lua-модуль, графика по выбранным измеряемым направлениям, advanced profiling/tools, расширенный Blender live link, variants и более сложные операции над шаблонами. Nanite/Lumen/VSM/TSR-подобные механизмы не являются условием первого самостоятельного экспорта. Точная последовательность и проверки собраны в [PLAN.md](../../PLAN.md). + +## 9. История альтернатив — superseded + +Первоначальный обзор предлагал выбрать язык позже, сравнивал C#/Flecs, готовые native/web UI и ImGui, допускал remote runtime world в automation, ранние prefab variants и один основной dimensionality slice. Эти варианты сохраняются в историческом сравнении, но не являются текущими рекомендациями Faset. Приняты C++/EnTT и собственный retained UI, editor-only MCP, nested composition без inheritance в MVP и два небольших сквозных 2D/3D-демо. diff --git a/docs/studies/05-roadmap.md b/docs/studies/05-roadmap.md new file mode 100644 index 0000000..21f3d2d --- /dev/null +++ b/docs/studies/05-roadmap.md @@ -0,0 +1,24 @@ +# 05. Дорожная карта реализации + +Актуализировано 18.09.2026. **Единственный актуальный план реализации — [PLAN.md](../../PLAN.md)**. Принятые контракты находятся в [архитектуре](../ARCHITECTURE.md). + +Старый план предполагал уже существующие runtime, демонстрационные сцены и совместимость с другим проектом. Эти предпосылки не применяются к Faset: реализация начинается заново. Прежние идеи runtime MCP и remote world editing также заменены: MCP относится только к редактору. + +## До MVP + +1. M0 — воспроизводимый C++-фундамент и зависимости Linux/Windows. +2. M1 — документы, метаданные, команды, revisions и Undo/Redo. +3. M2 — SDL3, Vulkan 1.3, Slang, Render Graph и базовая 2D/3D-отрисовка. +4. M3 — EnTT, C++ gameplay, schema export, Box2D/Box3D и отдельный Player. +5. M4 — собственный retained UI, тёмная тема, текст, ввод и панели. +6. M5 — asset pipeline, GLB и повторный импорт из обычного Blender. +7. M6 — рабочий редактор, вложенные сцены и переопределения. +8. M7 — MCP редактора и нативные editor-плагины под согласованный SDK. +9. M8 — две демонстрационные игры и standalone exports Linux/Windows. +10. M9 — приёмка MVP по полному пользовательскому сценарию. + +## После MVP + +Lua-модуль и ускорение итераций; GPU-driven visibility, HZB и LOD; расширенные тени и освещение; temporal reconstruction; динамическая GI и исследование виртуализированной геометрии; расширенные инструменты создания игр и доставки. + +Подробный состав, зависимости, границы, проверки и статус этапов ведутся только в PLAN.md, чтобы эта страница не стала второй расходящейся версией плана. diff --git a/docs/studies/06-sources.md b/docs/studies/06-sources.md new file mode 100644 index 0000000..0398ed8 --- /dev/null +++ b/docs/studies/06-sources.md @@ -0,0 +1,74 @@ +# 06. Источники и аннотации + +Актуализировано 18.09.2026. Принятый стек описан в [архитектуре](../ARCHITECTURE.md), этапы реализации — в [PLAN.md](../../PLAN.md). Версии изученных исходников закреплены в [манифесте](source-manifest.json). Сравнительная библиография ниже сохраняет историю выбора; наличие ссылки не означает зависимость Faset. + +Ссылки ниже использованы как проверяемые опорные материалы. Приоритет отдан официальной документации проектов и оригинальным справочным материалам; они описывают конкретные решения, но не доказывают, что это единственный или лучший дизайн для нового движка. + +## Godot + +1. [Godot design philosophy](https://docs.godotengine.org/en/stable/getting_started/introduction/godot_design_philosophy.html) — сцены как композиционные/reusable units, сочетание editor и code, object-oriented composition. +2. [Overview of Godot key concepts](https://docs.godotengine.org/en/stable/getting_started/introduction/key_concepts_overview.html) — Scene Tree, Nodes, Scenes, Resources и единый mental model для gameplay/UI. +3. [Introduction to 3D](https://docs.godotengine.org/en/stable/tutorials/3d/introduction_to_3d.html) — Node2D/Node3D, похожие APIs, orthographic 2D-in-3D и гибридные сценарии. +4. [Idle and physics processing](https://docs.godotengine.org/en/stable/tutorials/scripting/idle_and_physics_processing.html) — разделение variable per-frame processing и fixed physics processing. +5. [Physics introduction](https://docs.godotengine.org/en/stable/tutorials/physics/physics_introduction.html) — fixed-rate physics и важное предупреждение: engine physics не гарантирует determinism. +6. [Import process](https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/import_process.html) — source files, hidden imported resources, import settings и различие ResourceLoader/FileAccess в export. +7. [Inspector Dock](https://docs.godotengine.org/en/stable/tutorials/editor/inspector_dock.html) — searchable property inspector, sections, revert icon, sub-resources и resource editing. +8. [Project Settings](https://docs.godotengine.org/en/stable/tutorials/editor/project_settings.html) — категории, search/reset, Input Map, Localization, Plugins, Import Defaults и human-readable `project.godot`. +9. [EditorPlugin API](https://docs.godotengine.org/en/stable/classes/class_editorplugin.html) — расширение inspector, import/export/scene format plugins. +10. [Debugging tools overview](https://docs.godotengine.org/en/stable/tutorials/scripting/debug/overview_of_debugging_tools.html) — remote scene inspection, profiler/debugger, collision/navigation visualization и reload workflow. +11. [InputMap](https://docs.godotengine.org/en/stable/classes/class_inputmap.html) — named actions, multiple input events и deadzone. +12. [Default editor shortcuts](https://docs.godotengine.org/en/4.3/tutorials/editor/default_key_mapping.html) — discoverable keyboard-first operations and configurable shortcuts. + +## Unity + +13. [Prefabs](https://docs.unity3d.com/6000.1/Documentation/Manual/Prefabs.html) — GameObject + Components, nested prefabs и variations как reusable authoring contract. +14. [Editing Prefab Mode](https://docs.unity3d.com/6000.5/Documentation/Manual/EditingInPrefabMode.html) — context/isolation, breadcrumbs и визуальное отделение prefab contents. +15. [Asset Workflow](https://docs.unity3d.com/2019.3/Documentation/Manual/AssetWorkflow.html) — `.meta`, processing и превращение одного source file в несколько imported assets. +16. [Customizing Asset Database workflow](https://docs.unity3d.com/2020.3/Documentation/Manual/AssetDatabaseCustomizingWorkflow.html) — source/meta/artifact separation, cache regeneration и importer settings. +17. [Event function execution order](https://docs.unity3d.com/6000.5/Documentation/Manual/execution-order.html) — конкретный execution loop и место physics simulation. +18. [Runtime UI event system and input handling](https://docs.unity3d.com/6000.2/Documentation/Manual/UIE-Runtime-Event-System.html) — action-based input и UI navigation events. +19. [Frame Debugger](https://docs.unity3d.com/Manual/FrameDebugger.html) — остановка кадра и пошаговое исследование render events/state. + +## Unreal Engine + +20. [Level Editor](https://dev.epicgames.com/documentation/en-us/unreal-engine/level-editor-in-unreal-engine) — Outliner как hierarchical scene view и selection/editing в контексте. +21. [Level Editor Details Panel](https://dev.epicgames.com/documentation/en-us/unreal-engine/level-editor-details-panel-in-unreal-engine) — Details panel как schema-driven property surface для Actor. +22. [Actor ticking](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-ticking-in-unreal-engine) — tick groups и dependencies для упорядочивания gameplay/physics. +23. [Unreal Insights reference](https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-insights-reference-in-unreal-engine-5) — trace channels для CPU, gameplay, objects, physics и cook. +24. [Source Control in Unreal](https://dev.epicgames.com/documentation/en-us/unreal-engine/source-control-in-unreal-engine) — checkout, history, diff и asset-aware source control. +25. [Using Source Control in the Unreal Editor](https://dev.epicgames.com/documentation/en-us/unreal-engine/using-source-control-in-the-unreal-editor) — checkout/add-on-save и status feedback в editor workflow. + +## O3DE + +26. [Overview of Entities and Components](https://www.docs.o3de.org/docs/user-guide/programming/components/overview) — composition, component services/dependencies, editor/runtime/system components. +27. [Key Concepts: How O3DE Works](https://docs.o3de.org/docs/welcome-guide/key-concepts) — ECS, editor tools, Gems/plugins, Asset Pipeline, Asset Processor и build system. +28. [Material Editor](https://docs.o3de.org/docs/atom-guide/look-dev/tools/material-editor) — undo/redo, parent/child materials и automatic asset processing. + +## Bevy и общие паттерны + +29. [Bevy 0.6: modular render architecture](https://bevy.org/news/bevy-0-6) — plugin composition, render phases, entity/component-driven draw functions и render graph lessons. +30. [Bevy 0.19: scenes and app settings](https://bevy.org/news/bevy-0-19) — composable/patchable/dependency-aware scenes, typed assets/resources. +31. [Game Programming Patterns — contents](https://gameprogrammingpatterns.com/contents.html) — Game Loop, Component, Event Queue, Data Locality, Double Buffer, Command и другие patterns с компромиссами. +32. [Entity Component System FAQ](https://github.com/SanderMertens/ecs-faq) — data-oriented design как подбор layout по access patterns, а не обязательная идеология archetype ECS. +33. [The Essence of Entity Component System](https://arxiv.org/html/2606.14919v1) — технический обзор archetype ECS, SoA и cache locality; использовать как исследовательский материал, проверяя актуальность и peer-review status. + +## Как проверять выводы + +- Документация engine описывает intended behavior, но не гарантирует одинаковые performance/UX outcomes в другом проекте. +- Claims о determinism, hot reload и производительности проверять экспериментом в новом движке. +- Любую выбранную abstraction оценивать по стоимости сопровождения, debugging и schema migration, а не только по benchmark. +- Перед принятием dependency проверить license, поддерживаемые платформы, ABI/runtime requirements и возможность заменить backend. + +## Официальные источники принятого стека + +- [EnTT: ECS и многопоточность](https://github.com/skypjack/entt/wiki/Entity-Component-System) — storage/views, ограничение thread safety и organizer; собственный scheduler остаётся задачей Faset. +- [SDL3](https://wiki.libsdl.org/SDL3/FrontPage), [Vulkan surface](https://wiki.libsdl.org/SDL3/CategoryVulkan), [DPI](https://wiki.libsdl.org/SDL3/README-highdpi) — граница платформенного слоя. +- [Vulkan versions](https://docs.vulkan.org/guide/latest/versions.html), [features/limits](https://docs.vulkan.org/guide/latest/querying_extensions_features.html) — Vulkan 1.3 и отдельная проверка возможностей устройства. +- [Slang introduction](https://docs.shader-slang.org/en/stable/external/slang/docs/user-guide/00-introduction.html), [reflection](https://docs.shader-slang.org/en/stable/external/slang/docs/user-guide/09-reflection.html) — модульность, SPIR-V и параметры шейдеров; совместимость с большей частью HLSL, а не со всеми engine-specific shaders. +- [Box2D simulation](https://box2d.org/documentation/md_simulation.html), [Box3D](https://github.com/erincatto/box3d) — физические миры, handles и fixed-step интеграция. +- [CMake presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html), [Ninja manual](https://ninja-build.org/manual.html), [Clang toolchain](https://clang.llvm.org/docs/Toolchain.html) — роли инструментов сборки. +- [clang-cl и Windows SDK/runtime](https://clang.llvm.org/docs/UsersManual.html#windows-system-headers-and-library-lookup), [cross-compilation](https://clang.llvm.org/docs/CrossCompilation.html) — платформенные зависимости сохраняются. +- [Blender glTF exporter](https://github.com/KhronosGroup/glTF-Blender-IO) — внешний экспортёр; profile и устойчивые IDs задаются интеграцией Faset. +- [Dear ImGui](https://github.com/ocornut/imgui) — диагностический UI, не выбранная основа редактора. + +Лицензии и границы публикации собраны в [DEPENDENCIES.md](../DEPENDENCIES.md) и [PUBLICATION.md](../PUBLICATION.md). URL официальных руководств могут развиваться; findings по исходникам привязаны к commits. Ни один источник не заменяет будущие приёмочные проверки Faset. diff --git a/docs/studies/07-unreal-graphics-source-study.md b/docs/studies/07-unreal-graphics-source-study.md new file mode 100644 index 0000000..a49e0a8 --- /dev/null +++ b/docs/studies/07-unreal-graphics-source-study.md @@ -0,0 +1,290 @@ +# Что перенять из графического стека Unreal Engine 5.8.2 + +Исследование исходников от 17 сентября 2026 года. Репозиторий: `../../../UnrealEngine`. Версия проверена в [Build.version](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Build/Build.version#L1); commit `16d75d84714512edfb744e1fd0a59e9c74d57873`. Рабочее дерево UE при начале исследования было чистым. + +Фокус — передовая графика для Faset Engine. Прочитаны выбранные C++-реализации и `.usf`/`.ush`-шейдеры, включая тела алгоритмов и места их подключения. Это выборочное исследование архитектуры, а не аудит всего UE. Движок не собирался, сцены и GPU-бенчмарки не запускались. Приоритеты и варианты прототипов ниже — инженерная оценка; чисел ускорения по результатам измерений здесь нет. + +## Главный вывод + +Из UE стоит переносить **способы ограничивать работу**, а затем объединять их в систему. Nanite ограничивает и организует обработку геометрии, VSM — обновление теневых страниц, Lumen — трассировку и обновление освещения, MegaLights — число дорогих выборок света, Substrate — сложность обработки материалов. Результат зависит от хороших приближений, кэшей и проверки их актуальности. + +После завершения MVP первый серьёзный графический эксперимент — **GPU-driven visibility: indirect rendering + HZB + повторная проверка окклюзии**. Это полезная часть подхода Nanite, которая не требует сразу строить виртуализированную геометрию. Для первого небольшого визуального эффекта — **glints**. Для наиболее заметного изменения освещения — **гибридная GI с пространственным radiance cache**, если уже есть рабочая трассировка и temporal pipeline. + +## Что выбрать в зависимости от цели + +1. **Много объектов и высокая геометрическая плотность.** GPU culling → indirect draw → two-pass HZB → обычный mesh LOD; затем meshlet/cluster LOD → visibility buffer и сортировка shading work. Стриминг геометрии и software rasterizer добавлять после измерений на субпиксельных треугольниках. Первые этапы полезны самостоятельно. +2. **Динамическое непрямое освещение.** Гибридные screen/world traces, отдельное переиспользуемое представление освещения и бюджет обновления probes. Сначала ограничить прототип статической геометрией и diffuse GI. Полный Surface Cache Lumen с карточками не является обязательной первой ступенью собственной GI. +3. **Много динамических источников с мягкими тенями.** Подход MegaLights: выбирать небольшое число важных источников, трассировать visibility и восстанавливать изображение. Начинать после clustered light lists, motion vectors и базового denoiser. При 2–4 источниках общая инфраструктура может оказаться дороже обычного освещения. +4. **Чёткие тени на больших расстояниях.** VSM: виртуальные адреса, физический пул страниц, запросы от видимых поверхностей, кэширование и инвалидирование. Первая версия — один directional light и ограниченная сцена. Не начинать с большого числа локальных источников. +5. **Высокая детализация при меньшем внутреннем разрешении.** Идеи TSR: реконструкция с проверкой истории, обработка вновь открывшихся поверхностей и изменения шейдинга. Сначала правильные motion vectors и обычный temporal resolve; сложные эвристики TSR — следующий этап. +6. **Более богатые материалы.** Из Substrate взять ограниченные классы сложности и обработку по тайлам; из glints — фильтруемое распределение микробликов. Небольшой фиксированный clear-coat/двухслойный набор часто разумнее произвольного графа BSDF. +7. **Большие наборы текстур.** Virtual Texturing полезна, когда именно residency и объём текстур стали проблемой. Стриминг целых mip-уровней проще; переход на страницы имеет смысл по измеренному рабочему набору. + +## Принятый план Faset — реализация ещё не начата + +Решения обновлены 18 сентября 2026. Подробные критерии продукта — в [PLAN.md](../../PLAN.md), технические границы — в [архитектуре](../ARCHITECTURE.md). Результаты чтения UE ниже сохраняются; они не означают, что эти системы уже есть в Faset, входят в MVP или обязательно будут воспроизведены целиком. + +**Принято:** собственный Vulkan 1.3 renderer/backend и Render Graph, SDL3, Slang → SPIR-V с совместимым HLSL; базовый путь без обязательного RT. Compiler работает в editor/cook, Player получает готовые shaders и metadata. Готовый SPIR-V не отменяет создания pipelines драйвером. Editor и Player — разные процессы; MCP доступен строго в редакторе, включая headless authoring/import/build, Play/Stop и логи редактора, без чтения или изменения runtime worlds/сессий игры. + +### MVP (M2, M8–M9). Две демки и измеримый direct renderer + +Небольшая 2D-игра со спрайтами, прозрачностью, слоями и HUD; небольшая 3D-игра со статическими мешами, текстурами, простым PBR, солнцем и обычной shadow map. Обе проходят editor → save → Play → сборка самостоятельной игры на Linux и Windows. Начальный renderer: CPU frustum culling, direct draws, одна GPU queue, явные reads/writes, корректные barriers и отложенное освобождение ресурсов. Нужны имена проходов, validation, CPU/GPU timings и счётчики памяти; оптимизации graph culling, aliasing и async compute можно отложить. + +Готовность: предсказуемые save/build/Play, корректный resize/minimize, отсутствие ошибок validation, самостоятельный Player без редактора и MCP. Direct-путь остаётся эталоном для следующих этапов. Версия API не заменяет проверки features, formats, limits и драйверов конкретных GPU. + +### P2. GPU visibility и обычный LOD + +Resident instance buffer → GPU frustum culling → fixed indirect bins → current HZB visualization → two-pass HZB с history reset → обычный mesh LOD по экранному размеру и hysteresis. Детальный контракт разобран в [исследовании 15](15-renderer-implementation-notes.md). Начальный материал — opaque, обычная hardware rasterization; 2D/прозрачность сохраняют отдельный порядок. + +Готовность: финальная depth/ID-картинка совпадает с baseline с учётом допустимых ties, нет пропавших объектов при открытии двери/camera cut, проверены пустые и предельно заполненные очереди. Измерять полный кадр и открытой, и закрытой сцены; HZB может добавить стоимость без выигрыша. Motion-vector target и TAA не являются предпосылкой первого HZB. + +### P3, первая часть. Освещение и тени + +Расширить число локальных источников; вводить clustered light lists при подтверждённой необходимости. Для солнца — cascaded shadow maps, затем ограниченный atlas локальных теней. У shadow views собственные списки видимости: скрытый от основной камеры объект может отбрасывать видимую тень. Готовность — управляемые бюджеты, стабильность при движении камеры и понятный профиль обновлений. VSM остаётся отдельным дальнейшим экспериментом после обычных теней. + +### P3, вторая часть. История и temporal + +Motion vectors камеры/объектов, согласованные jitter/unjitter transforms, reset при camera cut/resize, reprojection и rejection → TAA → позднее temporal upscaling. Проверять disocclusion, тонкую геометрию, движение и экспозицию, сравнивая с режимом без temporal. TSR не заменяет denoiser GI или direct lighting: сигнал и критерии доверия различаются. + +### P4. Непрямой свет и исследовательские ветки + +Сначала запечённый непрямой свет и reflection probes, затем ограниченные screen-space дополнения с явным fallback. Динамическая GI без обязательного RT — самостоятельный исследовательский этап с бюджетами обновления/памяти, проверками света за камерой, утечек через стены и изменяющихся заслонов. Полный эквивалент Lumen не обещается. + +Дальнейшие ветки выбираются по измеренной проблеме, по одной: кластерный LOD/streaming и visibility buffer; виртуальные теневые страницы; стохастический direct lighting; VT; сложные материалы. Glints допустимы как изолированный BRDF-эксперимент. Это исследовательские возможности, не дополнительные обязательства MVP. На каждом этапе фиксируются сцена, путь камеры, настройки, GPU/driver, изображение, время и память; GPU-бенчмарки ещё не выполнены. + +## Общие ошибки, которых помогают избежать исходники + +- **«Nanite — это просто mesh shaders».** Его полезность складывается из preprocessing, выбора детализации, culling, rasterization, material scheduling и residency. Не нужно делать mesh shaders условием первого прототипа. +- **«Lumen — один ray tracing shader».** Это несколько представлений сцены, способов трассировки и кэшей, соединённых бюджетами и фильтрацией. Недостающий screen-space hit не равнозначен отсутствию геометрии в мире. +- **«MegaLights делает любое число lights бесплатным».** Ограничивается дорогая выборочная оценка; выбор кандидатов, scene structures, денойзинг и качество также имеют цену. +- **«Виртуальная память автоматически уменьшает работу».** Требуются реальные locality и повторное использование. Движущаяся геометрия и источники могут часто инвалидировать страницы, а резкое перемещение камеры создаёт всплеск запросов. +- **«Temporal накопление скрывает все ошибки».** Неверные velocity и слишком доверчивая история дают ghosting. Если история отвергается почти всегда, сигнал остаётся шумным. Проверять следует видео и контролируемые движения. +- **«Самая сложная версия UE — лучший старт».** Минимальная версия должна иметь собственный полезный результат и сравнимый baseline. Наличие множества permutations в UE показывает реальные ограничения интеграции, но не требует воспроизводить все варианты. + +## Как читать дальнейший разбор + +В следующих разделах **«в исходниках»** означает подтверждённую реализацию этой ревизии, а **«прототип / перенять / проверка»** — предложение для собственного движка. Ссылки на исходники относятся к указанному commit; локальные checkout являются внешними необязательными материалами исследования и не входят в Faset. Термины: HZB — иерархическая пирамида глубины; closure — отдельная составляющая модели рассеяния; residency — какие страницы сейчас находятся в GPU-памяти; disocclusion — появление ранее закрытой поверхности; radiance cache — переиспользуемые оценки входящего света. + +## Nanite / GPU Scene: что перенести в собственный renderer + +Основание: локальные тела C++/HLSL из `../../../UnrealEngine`, commit `16d75d84714512edfb744e1fd0a59e9c74d57873` (UE 5.8.2). Это исследование исходников, не выполненный GPU-профиль. Приоритеты, минимальные реализации и сценарии проверки ниже — инженерная оценка; чисел ускорения не измерял. Сосредоточился на треугольном пути: в этой ревизии уже есть отдельные ветки Nanite curves и voxels, поэтому нельзя автоматически приписывать всем путям свойства классического triangle Nanite. + +### 1. GPU-driven visibility с двухфазным HZB — начать здесь + +**Что интересно.** Предыдущая глубина используется как предположение, которое исправляется в том же кадре. Main pass проверяет bounds в предыдущих transforms/view относительно предыдущего HZB; предполагаемо закрытые объекты отправляются в отдельную очередь. Main raster строит текущую глубину, затем новый HZB и post pass повторно проверяют отложенную очередь. В результате temporal occlusion не требует ждать CPU query и имеет путь восстановления для открывшихся объектов. + +**Доказательства.** `FBoxCull::HZB` строит `PrevFrustumCull` из `PrevLocalToTranslatedWorld` и `PrevTranslatedWorldToClip`, записывая результат в `bWasOccluded`: [NaniteCullingCommon.ush:623](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteCullingCommon.ush#L623). В post permutation используется текущий rect и текущий HZB: [там же:646](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteCullingCommon.ush#L646). Запись отложенного instance через wave-aggregated atomic и чтение количества для post — [NaniteInstanceCulling.usf:169](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteInstanceCulling.usf#L169), [243](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteInstanceCulling.usf#L243). Реальный порядок C++: main cull/raster [NaniteCullRaster.cpp:7008](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Nanite/NaniteCullRaster.cpp#L7008), `BuildHZBFurthest` из scene depth и Nanite rasterized depth [7053](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Nanite/NaniteCullRaster.cpp#L7053), post cull/raster [7071](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Nanite/NaniteCullRaster.cpp#L7071). Это не просто декларация feature flag. + +**Не менее полезная основа — постоянная GPU Scene.** Primitive/instance данные адресуются ID, изменения накапливаются без повторного добавления primitive в dirty list, затем обновляются scatter upload. Видно в `FGPUScene::AddPrimitiveToUpdate` [GPUScene.cpp:1755](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/GPUScene.cpp#L1755), создании uploader [1218](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/GPUScene.cpp#L1218) и записи instance элементов в вычисленные scatter offsets [1358](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/GPUScene.cpp#L1358). Выигрыш архитектуры — culling, material lookup, тени и motion получают общие ID и таблицы; не нужно заново собирать данные каждого объекта на каждый draw. + +**Минимальный перенос.** Stable instance ID; GPU tables `{bounds, current/previous transform, mesh, material}`; dirty-range uploads; compute frustum/HZB; append-buffer видимых ID и indirect draw arguments. Сначала использовать обычные meshes/meshlets и hardware raster. Добавить очередь rejected, rebuild HZB и второй indirect draw. Для fixed-material opaque сцены это самостоятельный проект без Nanite builder и virtual geometry. + +**Ловушки.** Near-plane и деформация требуют консервативных bounds: сам UE пропускает HZB при пересечении near plane [NaniteCullingCommon.ush:635](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteCullingCommon.ush#L635). HZB-rect у UE привязан к центрам единственного sample; комментарий прямо предупреждает, что MSAA/conservative raster требуют другой формулы [NaniteHZBCull.ush:80](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteHZBCull.ush#L80). Reverse-Z требует minimum reduction и соответствующего сравнения [195](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteHZBCull.ush#L195). Добавить overflow counters для очередей и корректное сохранение previous transforms при переиспользовании ID. + +**Проверка.** 100 тыс. instances за крупными occluders: статичная камера, быстрый поворот, телепорт, движущаяся дверь. Сравнивать итоговую depth/ID-картинку с HZB-off reference, отдельно замерять main/post survivors, HZB build, culling и total GPU frame. Критерий успеха — отсутствие пропавшей видимой геометрии и выигрыш total frame на закрытой сцене, а не только меньше draw calls. На открытой сцене второй проход может оказаться чистой дополнительной стоимостью. + +### 2. Visibility buffer + группировка shading по материалу — сильная архитектурная идея отдельно от Nanite + +**Что интересно.** Геометрический проход сохраняет адрес поверхности, затем shading восстанавливает атрибуты победившего треугольника и группирует пиксели по материалам. Geometry scheduling отделяется от дорогого material evaluation. Это позволяет иметь очень много кластеров без пропорционального количества CPU material draws; потенциальный выигрыш особенно интересен при дорогих материалах и overdraw. + +**Доказательства.** `PackVisPixelX` кодирует visible-cluster ID и triangle ID, `UnpackVisPixel` выделяет второй компонент depth: [NaniteDataDecode.ush:890](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteDataDecode.ush#L890). `WritePixel` пакует ID+depth в 64-bit value и выполняет `ImageInterlockedMaxUInt64`, согласованно выбирая depth и идентификатор: [NaniteWritePixel.ush:20](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteWritePixel.ush#L20). Material evaluation реально читает этот ID, получает visible cluster и page/cluster data [NaniteVertexFactory.ush:1187](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteVertexFactory.ush#L1187); ветка shading читает VisBuffer64 и запрашивает пересчёт barycentrics [1240](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteVertexFactory.ush#L1240). + +Binning состоит из count, reserve, scatter: wave count группирует material-bin matches [NaniteShadeBinning.usf:397](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteShadeBinning.usf#L397), выбор count/scatter shader path [861](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteShadeBinning.usf#L861), резервирование диапазонов [959](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteShadeBinning.usf#L959). Особенно ценная деталь: материалам без derivative ops выделяются отдельные pixels, остальным — quads; проверка `bNoDerivativeOps` находится [968](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteShadeBinning.usf#L968). Это не универсальная сортировка всех пикселей независимо от требований shader. + +**Минимальный перенос.** Обычный hardware depth+ID pass, таблицы vertex/index/material, один compute resolve для нескольких фиксированных PBR-material classes. Затем material bins и indirect compute dispatch. Для такого варианта не обязательно воспроизводить Nanite 64-bit atomics: обычные depth attachment и integer ID target подходят, если пишет только hardware path. Общая атомарная depth+ID операция нужна при независимых конкурентных software/hardware writers. + +**Ловушки.** UV derivatives, texture LOD, tangent handedness, degenerate triangles и deformation должны восстанавливаться согласованно с geometry pass. UE вычисляет triangle barycentrics [NaniteVertexFactory.ush:1043](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteVertexFactory.ush#L1043) и интерполирует UV/position/tangent данные [717](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteVertexFactory.ush#L717). «Shading once» не означает отсутствие helper lanes или повторного alpha-test: masked/WPO поверхности усложняют raster. Полный G-buffer не обязательно исчезает: visibility может быть только первым этапом material evaluation. Не рассчитывать на экономию памяти без измерения всех вспомогательных buffers и output targets. + +**Проверка.** Одинаковая сцена в G-buffer и visbuffer вариантах: число material classes 4/32/256, overlap слоёв 1/4/16, subpixel triangles, checker UV. Считать geometry+binning+resolve вместе; проверять mip selection и края, shader invocations, bandwidth и p95 frame time. При простых материалах расходы binning могут перевесить пользу. + +### 3. Virtual geometry: согласованный LOD-срез, bounded residency и feedback + +**Что интересно.** Nanite объединяет offline и runtime алгоритмы: группы соседних кластеров упрощаются вместе, runtime выбирает достаточный уровень по screen-space error, GPU запрашивает недостающую детализацию, а резидентный более грубый уровень остаётся корректным drawable fallback. Ценная мысль — streaming и LOD составляют один протокол допустимых представлений поверхности. + +**Доказательства.** Builder объединяет children и упрощает triangles [ClusterDAG.cpp:1467](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Developer/NaniteBuilder/Private/ClusterDAG.cpp#L1467), заново делит результат в parent clusters [1558](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Developer/NaniteBuilder/Private/ClusterDAG.cpp#L1558), назначает всем parents группы одинаковые bounds/error [1587](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Developer/NaniteBuilder/Private/ClusterDAG.cpp#L1587). В simplifier позиции концов external edges блокируются [Cluster.cpp:1021](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Developer/NaniteBuilder/Private/Cluster.cpp#L1021). Поэтому независимое упрощение каждого meshlet не воспроизводит согласованность Nanite. + +В GPU traversal `ShouldVisitChildInternal` учитывает parent/min LOD errors и projected edge scales [NaniteClusterCulling.usf:258](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteClusterCulling.usf#L258); cluster cut использует `SmallEnoughToDraw` [287](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteClusterCulling.usf#L287). Ключ к graceful streaming: кластер принимается также при `NANITE_CLUSTER_FLAG_STREAMING_LEAF`, даже если критерий детализации ещё не достигнут [842](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteClusterCulling.usf#L842). Leaf traversal формирует page-range request с priority [579](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteClusterCulling.usf#L579). + +Менеджер рекурсивно добавляет page dependencies с повышенным приоритетом [NaniteStreamingManager.cpp:2579](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Engine/Private/Rendering/NaniteStreamingManager.cpp#L2579), увеличивает их refcount при регистрации [950](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Engine/Private/Rendering/NaniteStreamingManager.cpp#L950), при eviction выбирает только unreferenced LRU pages с нулевым refcount [2904](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Engine/Private/Rendering/NaniteStreamingManager.cpp#L2904). То есть простой LRU, игнорирующий зависимости, разрушает предпосылки traversal/decoding. + +**Минимальный перенос.** Сначала meshlet LOD hierarchy целиком в памяти, hardware raster и корректный cut. Затем фиксированный page pool, постоянно доступный coarse mesh, page table, GPU feedback, budgeted async upload и переход на детей только когда готова вся требуемая группа. Compression, GPU transcoding, partial group fixups, скелеты, voxels и curves — отдельные этапы. Это высокая сложность, но очень большой эффект для сцен с тяжёлой геометрией. + +**Дополнительный образец.** Persistent GPU workers обрабатывают общую очередь hierarchy nodes, а когда nodes ещё не готовы, берут cluster jobs: реальная ветка [NaniteHierarchyTraversal.ush:274](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteHierarchyTraversal.ush#L274), fallback work [326](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteHierarchyTraversal.ush#L326). Для первой реализации послойные indirect dispatch проще отладить; MPMC scheduler оправдан только после профиля occupancy/launch overhead. + +**Проверка.** Asset set заведомо больше page pool, flythrough с возвратом и внезапной сменой направления; искусственная задержка I/O. Требовать ограниченного VRAM, отсутствия дыр/двойной геометрии на переходах и контролируемого LOD error; измерять missed requests, page churn, upload budget, visible cluster count. Полноценная Nanite-копия — большой самостоятельный renderer/asset pipeline, не библиотека для подключения за неделю. + +### 4. Гибрид hardware/software raster для микротреугольников — исследовательский следующий шаг + +**Что интересно.** Один visibility target объединяет два raster пути: крупные/требующие clipping кластеры отправляются hardware rasterizer, мелкие — compute rasterizer. Это переносимая идея специализации под размер примитива, но наиболее аппаратно-зависимая из четырёх. + +**Доказательства.** `SmallEnoughToDraw` отдельно устанавливает `bUseHWRaster` по projected edge scale [NaniteClusterCulling.usf:297](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteClusterCulling.usf#L297); near clipping принудительно включает HW [860](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteClusterCulling.usf#L860). `EmitVisibleCluster` заполняет SW/HW списки с противоположных концов общего массива и отдельными atomic counters [688](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteClusterCulling.usf#L688). Compute entry вызывает `ClusterRasterize` [NaniteRasterizer.usf:2421](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteRasterizer.usf#L2421), triangle path готовит edge equations и вызывает adaptive raster [718](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteRasterizer.usf#L718). Внутри software raster есть ещё одна специализация: rectangle traversal для узких triangles, scanline для более широких или programmable shading [NaniteRasterizer.ush:292](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteRasterizer.ush#L292). Общая атомарная запись depth+ID описана выше. + +**Минимальный перенос.** После стабильного hardware visbuffer добавить opaque-only compute raster для кластера с маленьким screen bound. Всё остальное оставить HW. Подбирать порог на своей GPU по total raster time; не зашивать UE threshold как универсальную константу. Programmable raster/WPO/alpha cutouts вводить отдельно, когда оба пути совпадают по coverage. + +**Ловушки и проверка.** Нужны согласованные fill rules, subpixel precision, depth tie handling, synchronization, capability check для 64-bit image atomics. В текущем HW path есть явное предупреждение о проблемах depth precision у очень узких triangles [NaniteRasterizer.usf:3300](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteRasterizer.usf#L3300). Sweep треугольников от долей пикселя до сотен пикселей, front/back faces, shared edges и near-plane; сравнивать coverage с HW baseline, отдельно стрессировать overdraw и контенцию атомиков. Принимать только измеренный выигрыш в целевой сцене и на целевых GPU: наличие compute raster само по себе не доказывает ускорение. + +Рекомендуемый порядок: GPU Scene + indirect/two-pass visibility → простой visibility shading → resident cluster LOD → page streaming → гибридный raster и persistent traversal после профилирования. + +Дополнение по hardware prerequisites: mesh shaders не являются обязательным условием этой архитектуры и даже не единственным Nanite HW path. В реальном dispatch есть `DispatchIndirectMeshShader` для mesh-path и `DrawPrimitiveIndirect` в `else`: [NaniteCullRaster.cpp:3661](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Nanite/NaniteCullRaster.cpp#L3661). Поэтому `meshlet` здесь означает разбиение/единицу данных, а не обязательное использование mesh-shader API. В отличие от этого, приведённый UE visibility-write path явно ограждён `COMPILER_SUPPORTS_UINT64_IMAGE_ATOMICS` и имеет `#error` в неподдерживаемой non-depth-only ветке: [NaniteWritePixel.ush:27](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteWritePixel.ush#L27). Depth-only path использует обычный uint atomic. + +## Lumen и MegaLights: механизмы для собственного renderer + +Проверены тела C++/HLSL в `../../../UnrealEngine`, commit `16d75d84714512edfb744e1fd0a59e9c74d57873`. Это статическое исследование: движок и GPU-бенчмарки не запускались. Оценки полезности, минимальные варианты и тесты ниже — мои инженерные предложения; описания UE привязаны к реализации. + +### 1. Каскад трассировки: дешевое представление сначала, уплотнённый список незавершённых лучей затем + +**Что делает UE.** `TraceScreenProbes` сначала запускает экранную трассировку, после неё выбирает HWRT либо трассировку mesh SDF/heightfields, а финальным проходом обрабатывает оставшиеся лучи и применяет Radiance Cache/sky. Это именно условные альтернативы: HWRT и mesh SDF не обязательные последовательные ступени одной конфигурации. Сборка проходов: [LumenScreenProbeTracing.cpp:678](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Lumen/LumenScreenProbeTracing.cpp#L678), выбор HWRT: строка 795, software branch: 817, финальная compaction: 877, отключение global-SDF в HWRT permutation: 909. + +Экранный hit не принимается безусловно: HZB сообщает неопределённость, `bHit && !bUncertain` отбрасывает сомнительный результат, край экрана отсеивается стохастически, глубина прошлого кадра проверяется перед выборкой его цвета. Эти проверки находятся в [LumenScreenProbeTracing.usf:276](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenScreenProbeTracing.usf#L276), depth validation — 304–311, выборка radiance — 334–345. Это объясняет, почему просто добавить SSR/SSGI перед ray query недостаточно: нужна мера доверия и корректный переход между представлениями. + +`ScreenProbeCompactTracesCS` сохраняет лучи без hit, которые ещё не дошли до Radiance Cache, с дополнительными отсечениями по дистанции. `WavePrefixCountBits` считает локальные offsets; на группу делается одна глобальная atomic allocation, затем пишется плотный список. GPU сам формирует indirect dispatch arguments. Тела: [LumenScreenProbeTracing.usf:394](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenScreenProbeTracing.usf#L394), условие — 431, wave compaction — 455–481, indirect args — 508–523. Это переносимая оптимизация также для shadow rays, reflection rays и secondary bounces. + +**Минимум для своего движка.** G-buffer + HZB → экранный луч с hit/confidence/distance → append/scan оставшихся лучей → один существующий world-space backend → resolve. Не начинать с собственной SDF-системы, если уже есть BVH/ray queries. Первую версию сделать с простым append; wave-оптимизацию добавлять после измерений. Не терять mapping исходного pixel/ray ID при compaction, правильно сохранять достигнутую дистанцию и делать небольшой pullback для следующей ступени. + +**Условия и риски.** Нужны стабильная reprojection, motion vectors, предыдущее освещение, explicit GPU barriers и indirect dispatch. Повторное использование экранного света создаёт зависимость от видимости камерой и может образовать GI feedback; UE отдельно запрещает часть backface/foliage rays (`usf:99`). Качество geometry representations должно согласовываться, иначе границы экранной и мировой трассировки проявятся в движении. Compaction тоже стоит времени: при почти 100% misses она может проиграть прямой dispatch. + +**Проверка.** На одинаковых лучах сравнить world-only и hybrid: GPU ms по ступеням, долю rays, дошедших до world, количество indirect workgroups. Камера пересекает край экрана у тонкой перегородки; свет за камерой; движущийся объект; быстрый разворот. Проверять не только среднее изображение, но покадровую ошибку и вспышки на переходе backend. Успех — измеримое сокращение дорогих rays без систематического исчезновения света/occluders. + +### 2. Radiance Cache: кэшировать дальнее освещение по запросу и распределять обновления на GPU + +**Что делает UE.** Потребитель помечает восемь соседних probes, необходимых для интерполяции: [LumenRadianceCacheMarkCommon.ush:111](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenRadianceCacheMarkCommon.ush#L111). Предыдущие probes сопоставляются с новым clipmap, используются повторно либо освобождаются; новые выделяются из пула. Это demand-driven cache, а не обязательная трассировка всей регулярной 3D-сетки каждый кадр. Сопоставление: [LumenRadianceCacheUpdate.usf:77](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenRadianceCacheUpdate.usf#L77); allocation: 289–333. + +Самая интересная отдельная технология — **гистограмма приоритетов вместо сортировки всех probes**. Новые probes получают bucket 0; остальные — логарифмический bucket по времени между последним trace/use с поправкой на clipmap (`GetPriorityBucketIndex`, строка 221). Гистограмма суммирует trace-cost, а не только число probes (270–281). Один маленький проход выбирает порог по бюджету (399–425), последний bucket ограничивается atomic-счётчиком (455–463). Новые probes без данных имеют особый путь: они обновляются, а превышающие бюджет могут трассироваться с меньшим разрешением (466–477). Поэтому нельзя обещать математически жёсткий лимит общего GPU-времени; это бюджет вычислительной работы с fallback для заполнения пустого кэша. + +Cache используется после локальной видимости. `GetRadianceCacheCoverage` задаёт минимальную дистанцию до интерполяции как `probe TMin + cellSize * sqrt(3)`; контракт требует предварительно помечать позиции. [LumenRadianceCacheInterpolation.ush:176](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenRadianceCacheInterpolation.ush#L176). Screen probes сокращают дальнюю трассировку до этой границы и затем применяют кэш: [LumenScreenProbeTracing.usf:754](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenScreenProbeTracing.usf#L754), применение — 825–835. Переносимая идея — переиспользовать дальнее низкочастотное освещение, сохранив локальную occlusion отдельно. + +**Минимум для своего движка.** Одна сетка вокруг камеры, radiance+depth на probe, pool+indirection, last-used/last-updated, 8–16 priority buckets и фиксированный ray budget; добавить clipmaps после работающего proof-of-concept. Начать с diffuse GI и rough reflections. Новым probes дать начальное приближение и отдельный режим заполнения. Отдельно показывать age, residency, miss rate и trace cost; без этих debug views сложно отличать неправильную трассировку от устаревшего кэша. + +**Риски и проверка.** Разреженные probes внутри стен, резкие перемены освещения, teleport и camera cut, забитый pool, clipmap seams. Предлагаемые benchmark-сцены: две комнаты с тонкой стеной; открывающаяся дверь; включение мощного emissive; телепорт на новую площадку. Считать число новых/повторно использованных probes, память, worst-frame cost и число кадров до восстановления заданного уровня ошибки против offline reference. Не принимать красивый статичный кадр как достаточную проверку. + +### 3. Surface Cache: отделить стоимость материала и освещения поверхности от каждого ray hit + +**Что делает UE.** Card capture сохраняет diffuse color, card-space normal и emissive в atlas: [LumenCardBasePass.ush:134](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenCardBasePass.ush#L134), outputs 145–147. На hit выбираются подходящие cards по направлению normal и bounds, проецируется позиция, проверяется совпадение card-depth, затем читаются Direct/Indirect/FinalLighting atlases. Реальные тела: [LumenSurfaceCacheSampling.ush:235](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/SurfaceCache/LumenSurfaceCacheSampling.ush#L235), depth weights — 279–310, atlas reads — 312–314; selection — 464–525. Нормализованная radiance возвращается в `EvaluateRayHitFromCardSampleAccumulator` (578–592). + +Связь с HWRT явная: `CalculateSurfaceCacheLighting` по scene instance получает mesh-cards index и вызывает lookup, используя размер ray cone для радиуса фильтрации. Слишком близкие hits обнуляются для предотвращения self-intersection/GI feedback: [LumenHardwareRayTracingCommon.ush:647](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenHardwareRayTracingCommon.ush#L647). Таким образом точность ray intersection и стоимость hit lighting можно выбирать раздельно. Не следует утверждать, что весь Lumen всегда использует только это: есть отдельные hit-lighting пути. + +Кэш обслуживается постепенно. Раздельные бюджеты direct/indirect; приоритет страницы учитывает возраст, расстояние до камеры, близость frustum и high-res feedback: [LumenSceneLighting.usf:85](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenSceneLighting.usf#L85), приоритет — 105–177, гистограмма — 180–205, выбор budget cutoff — 210–233. Lookup также пишет feedback и last-used page metadata: `LumenSurfaceCacheSampling.ush:594–618`. + +**Минимум для своего движка.** Не воспроизводить сразу automatic card generation и virtual paging. Для статической сцены можно начать с ограниченного набора planar captures или существующей UV-развёртки lightmap: baked material attributes + динамический low-resolution lighting atlas + validity. Трассировать существующим BVH и возвращать atlas radiance. Это адаптация принципа, не описание UE. Сначала только diffuse secondary lighting; зеркальные отражения требуют другого качества. + +**Цена и тест.** Инфраструктура ощутимая: parameterization/capture, atlas allocation, hit→surface mapping, invalidation для transform/material/light, обновление освещения. Thin geometry и вогнутые объекты дают coverage holes, view-dependent material нельзя точно представить одним nondirectional texel. Добавить debug invalid texels; UE тоже явно раскрашивает отсутствие данных (`Sampling.ush:626–630`). Тестировать материал с дорогой процедурной текстурой при росте secondary-ray count; сравнивать ray-hit shading ms/atlas-update ms/память. Отдельно проверять щели, окрашенный GI после движения объекта, смену emissive и старое освещение. Это более крупный проект, чем compaction или priority buckets. + +### 4. MegaLights: фиксировать бюджет дорогой visibility, выбирать свет по вкладу и истории + +**Что делает UE.** Для каждого shading location рассматриваются lights его clustered cell; `GetLocalLightTargetPDF` оценивает освещение через shading routine без реальной shadow visibility, включает IES и формирует perceptual weight `log2(Lum + 1)`: [MegaLightsLightTargetPDF.ush:23](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/MegaLights/MegaLightsLightTargetPDF.ush#L23). `FLightSampler` хранит ограниченное число samples; `AddLightSample` потоково обновляет weighted selection и накопленную сумму: [MegaLightsSampling.ush:47](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/MegaLights/MegaLightsSampling.ush#L47), тело — 84–114. При финализации sample weight становится `WeightSum / selectedWeight`: [MegaLightsSampling.usf:544](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/MegaLights/MegaLightsSampling.usf#L544). + +История видимых lights уменьшает вероятность выбора скрытого источника, но не обязана обнулять её; при invalid history используется другой множитель. Учитывается изменение мощности, чтобы вновь ставший значимым источник не оставался подавленным: [MegaLightsSampling.usf:164](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/MegaLights/MegaLightsSampling.usf#L164), history multiplier — 203–210. Reprojected tile history и blue-noise offset — 325–387. Не называть эту конкретную цепочку автоматически «ReSTIR с temporal/spatial reservoir reuse»: здесь непосредственно подтверждены weighted sampling, visibility guiding и последующая фильтрация, не перенос reservoirs между pixels. + +Важное ограничение: ограничено число выбранных expensive samples, **не вся сложность независимо от количества lights**. Кандидаты всё ещё обходятся циклом (cell header — 317–319; loop — 409–440). Поэтому density lights влияет на candidate evaluation, а при большом числе существенных вкладов возрастает variance. + +Шум устраняется не просто TAA. Temporal denoiser использует shading confidence, neighborhood clamp отдельно для diffuse/specular и ограничивает число накопленных кадров; затем смешивает с `alpha = 1 / accumulatedFrames`: [MegaLightsDenoiserTemporal.usf:379](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/MegaLights/MegaLightsDenoiserTemporal.usf#L379), clamp — 390–419, accumulation — 422–446. Spatial filter учитывает depth, normals и luminance, причём specular normal tolerance зависит от ширины lobe: [MegaLightsDenoiserSpatial.usf:474](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/MegaLights/MegaLightsDenoiserSpatial.usf#L474). + +**Минимум для своего движка.** Clustered light lists, 1–4 stochastic shadow samples на pixel/half-res pixel, weighted streaming sampler, shadow ray backend, correct probability compensation, temporal accumulation с rejection. Visibility history guiding добавлять после unbiased/reference baseline; затем filter. Начать с point/spot и rough opaque, area lights добавить следующим этапом. Не применять denoiser к ошибочной вероятности: он может скрыть bias. + +**Проверка.** 10/100/1000 lights при фиксированном числе rays, но измерять отдельно candidate selection, visibility, shading, denoiser. Сопоставлять с exhaustive lighting/offline reference: яркий скрытый light, внезапное включение, движущаяся тень, glossy floor, history reset. Смотреть converged energy, variance, время восстановления после disocclusion и ghost trails. Реальный выигрыш — множество источников с предсказуемой стоимостью shadow queries; оплачивается шумом, памятью истории и сложностью устойчивого denoising. + +Приоритет для отдельного renderer: сначала compaction/staged tracing и visibility-budget sampling, затем GPU cache scheduling; Surface Cache — только если измерения покажут, что повторный hit shading действительно главный bottleneck. Никаких численных обещаний ускорения без реализации и benchmark. + +## Продвинутая графика: VSM, virtual texturing, TSR + +Исследован локальный `../../../UnrealEngine`, commit `16d75d84714512edfb744e1fd0a59e9c74d57873` (UE 5.8.2). Ниже факты из прочитанных тел C++/HLSL-функций отделены от предложений для своего движка. Это статический разбор: UE не собирался, производительность на GPU не измерялась. Порядок внедрения зависит от исходного рендера: при отсутствии хорошего temporal pipeline начать с TSR-подобной реконструкции; при уже стабильном TAA и неудовлетворительных тенях — с VSM. VT окупается при реальной нехватке памяти на текстуры или дорогих материалах ландшафта. + +### 1. Virtual Shadow Maps: кэшировать видимые страницы теней, отдельно статическую и динамическую часть + +**Что обнаружено.** Главное заимствование — превращение shadow rendering в обслуживание разреженного кэша, востребованного пикселями камеры. В UE размер страницы 128×128, а начальная таблица содержит 128×128 страниц: виртуальная сторона получается 16384 текселя. Это следует из вычисляемых констант, а не означает физическую 16K-текстуру для каждого источника света. [Константы VSM](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Shared/VirtualShadowMapDefinitions.h#L12). + +`InitPositionData` восстанавливает мировую позицию из глубины; `GeneratePageFlagsFromPixels` отмечает необходимые страницы directional lights через `MarkPageDirectional`. При этом marking предусматривает расширение на соседние страницы около границы, включая чередование диагонального направления. Это важно, потому что footprint фильтра тени может выйти за страницу, которую непосредственно запросил пиксель. [Восстановление позиции](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPageMarking.usf#L301), [расширение запросов](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPageMarking.usf#L480), [запрос directional page](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPageMarking.usf#L525). + +В `UpdatePhysicalPages` уже существующие страницы переиспользуются, получают возраст и переносятся в список запрошенных. Ключевая деталь: если страница сейчас не нужна, её dirty/invalidation flags сохраняются до будущего возвращения в кадр. Иначе ушедший за камеру объект оставит устаревшую тень после возвращения камеры. Отдельные флаги позволяют обновить только динамическую часть, сохранив статическую. [Управление возрастом и сохранение invalidation](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPhysicalPageManagement.usf#L280), [раздельная инвалидация](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPhysicalPageManagement.usf#L328). + +`AllocateNewPageMappings` берёт физическую страницу из available list, обязательно очищает прежнее отображение в page table, записывает новое и помечает обе части некэшированными. При переполнении пула запрос может остаться без физического backing: это реальное ограничение качества, которое должно быть видно в диагностике. `SelectPagesToInitializeCS` пропускает неизменённые страницы; `InitializePhysicalPagesIndirectCS` инициализирует обновляемую динамическую страницу сохранённой статикой. [Выделение](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPhysicalPageManagement.usf#L475), [пропуск cached pages](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPhysicalPageManagement.usf#L852), [копирование статической основы](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPhysicalPageManagement.usf#L993). + +**Как перенять.** Первый прототип: один directional light, фиксированный физический атлас, таблица virtual→physical, GPU bitset запросов, компактный список dirty pages, обычный depth raster для объектов, пересекающих страницы. Начать с одного уровня и твёрдых теней; затем добавить 3–5 clipmap уровней и привязку их координат к устойчивой сетке. UE также округляет центр каждого clipmap в пространстве света, что помогает сохранять кэш при движении камеры. [Snap clipmap origin](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VirtualShadowMaps/VirtualShadowMapClipmap.cpp#L356). + +Nanite для самого принципа не требуется: в этом checkout существует полноценный `RenderVirtualShadowMapsNonNanite`; но это не обещание дешёвого рендера больших классических мешей, пересекающих много страниц. [Путь обычной геометрии](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VirtualShadowMaps/VirtualShadowMapArray.cpp#L4389). Для своего варианта понадобятся стабильные instance IDs, world bounds, учёт изменения света/материалов/деформаций, compute atomics и корректные GPU barriers. Страницы надо инвалидировать по старому и новому положению переместившегося occluder — это предложение для архитектуры, а не утверждение о конкретной просмотренной функции UE. + +**Ловушки.** Смена направления солнца способна разрушить почти весь выигрыш кэша; анимированная листва и displacement требуют явной политики. Статический и динамический depth pools увеличивают расход памяти. Camera-visible marking не покрывает автоматически все прозрачные или объёмные receivers. Не начинать со SMRT: в UE `SMRTRayCast` действительно шагает по shadow depth samples и экстраполирует глубину/наклон, но это отдельная апроксимация мягких теней с численной терпимостью, а не трассировка полной геометрии. [SMRT](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapSMRTTemplate.ush#L26). + +**Проверка прототипа.** Сравнить кадр с режимом принудительного обновления всех страниц: статичная сцена после прогрева, локально движущийся объект, объект движется вне кадра и возвращается, camera teleport, вращение света, намеренно малый пул. Собирать requested/allocated/rendered/invalidated pages, промахи пула, долю повторно использованной статики, GPU ms marking/culling/raster/projection. Цель: после прогрева статичного вида обновления должны практически исчезнуть; выигрыш должен сохраняться в общих GPU ms, а не только в числе draw calls. **Приоритет: высокий для крупной статичной сцены; сложность высокая.** + +### 2. Virtual Texturing: demand feedback, гарантированный грубый fallback и ограниченное обновление + +**Что обнаружено.** VT содержит полезный законченный контур управления качеством: материал читает page table на нужном mip, формирует идентификатор страницы, разреженно пишет feedback; CPU обрабатывает готовый readback, объединяет запросы, обновляет LRU резидентных страниц и заказывает отсутствующие. `FinalizeVirtualTextureFeedback` пишет только выбранные пиксели и ограничивает длину буфера. `CanMap`/`Map` опрашивают fence и прекращают перебор на неготовом результате. Это позволяет строить поток без обязательного ожидания GPU в том же кадре. [GPU feedback](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualTextureCommon.ush#L90), [page-table lookup](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualTextureCommon.ush#L361), [опрос fence](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VT/VirtualTextureFeedback.cpp#L167). + +В `FeedbackAnalysisTask` соседние одинаковые запросы объединяются со счётчиком. `GatherRequestsTask` быстро распознаёт резидентную страницу и обновляет её использование, не создавая лишний load. `FTexturePagePool::Alloc` берёт LRU-страницу из heap, удаляет старые mappings и назначает её новому producer. [Объединение feedback](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VT/VirtualTextureSystem.cpp#L1455), [resident fast path](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VT/VirtualTextureSystem.cpp#L1606), [LRU allocation](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VT/TexturePagePool.cpp#L275). + +Наиболее ценная деталь — fallback является частью отображения страниц. `UnmapPage` ищет ближайшего резидентного предка и ставит в очередь update page table на более грубую страницу. Грубый корень ожидается закреплённым в памяти. Шейдер использует фактический mip из записи, вычисляет локальные UV, учитывает border и масштабирует градиенты. Поэтому eviction может снижать детализацию вместо появления пустой/чужой текстуры. [Ancestor fallback](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VT/TexturePageMap.cpp#L113), [UV и derivatives](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualTextureCommon.ush#L819). + +Планировщик различает streaming pages и runtime-generated pages: `SubmitThrottledRequests` расходует отдельные бюджеты, так как асинхронный I/O и генерация GPU-материала имеют разный профиль стоимости. Оставшийся бюджет может идти на continuous updates. `SubmitRequests` различает Pending/Available и ограничивает фактическое production. [Бюджеты SVT/RVT](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VT/VirtualTextureSystem.cpp#L2190), [готовность producer](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VT/VirtualTextureSystem.cpp#L2300). + +**Как перенять.** Практичный старт — один большой terrain albedo atlas: нарезанные offline mip tiles с border, небольшой physical atlas, page table, закреплённый самый грубый mip, feedback readback ring, CPU дедупликация, асинхронное чтение и upload budget. Добавить normal/roughness только после проверки согласованного residency нескольких слоёв. Система producer должна отвечать «pending/ready», а публикация нового mapping должна происходить после готовности данных. Для своего движка полезно ограничивать не только tiles/frame, но и bytes/frame и время генерации: одинаковое количество страниц не означает одинаковую стоимость. + +RVT — следующий самостоятельный эксперимент: bake нескольких дорогих слоёв ландшафта/декалей в востребованные страницы и затем дешёвая выборка. Этот механизм уменьшает повторную работу материалов, но требует региональной invalidation после изменений и контроля устаревших страниц. Он не нужен игре с небольшим обычным набором текстур: там цена dependent texture fetch, feedback и авторского tooling может превысить выигрыш памяти. + +**Проверка прототипа.** Teleport через terrain при пуле существенно меньше рабочего набора; медленное вращение, быстрый полёт, высокий anisotropy, переход mip и граница тайлов, намеренная задержка I/O. Условие корректности: ни одного sampling из перераспределённой чужой страницы, fallback всегда валиден, отсутствие seams. Измерять полезные загруженные bytes, residency hit rate, латентность запроса до показа, долю показа грубого mip, повторные загрузки/секунду, CPU feedback cost, GPU стоимость выборки. **Приоритет: условно высокий для больших материалов/мира; средний либо низкий без давления на VRAM.** + +### 3. TSR: хранить доверие к истории и отдельно распознавать движение, disocclusion и мерцание + +**Что обнаружено.** Самое переносимое здесь — структура информации, которую temporal reconstruction сохраняет между кадрами. В `DilateVelocityCS` UE читает 3×3 depth/velocity neighborhood, выбирает ближайшую глубину, вычисляет движение от этой поверхности и ошибки репроекции. Это уменьшает неправильное использование фонового motion vector на границе объекта. Одновременно существуют флаги pixel animation и temporal responsiveness. [Depth-aware velocity dilation](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/TemporalSuperResolution/TSRDilateVelocity.usf#L177). + +`RejectShadingCS` формирует отдельно разрешение ослабить clamp и множитель доверия; при disocclusion без resurrection оба обнуляются. `UpdateHistoryCS` clamp-ит цвет истории по текущему окружению, ограничивает её вес по скорости движения, затем смешивает с текущим сигналом через HDR-aware weights. Вместе с цветом записывается validity, специально квантованная под 8-битное хранение. То есть история — это цвет плюс информация, насколько на него можно опираться, а не постоянный коэффициент lerp. [Выход rejection](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/TemporalSuperResolution/TSRRejectShading.usf#L858), [clamp, движение, blend и validity](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/TemporalSuperResolution/TSRUpdateHistory.usf#L1249). + +Особенно интересен `ComputeMoireError`: он следит за сменой знака временного градиента яркости, накапливает вариацию и счётчик, сбрасывает их при disocclusion и подавляет послабление для движущихся/изменённых пикселей. Затем оценка мерцания расширяет допустимый интервал истории в `MeasureRejection`. Так алгоритм разрешает дополнительное усреднение там, где jitter заставляет статичную мелкую деталь чередоваться, но старается не размазать настоящую анимацию. [Детектор мерцания](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/TemporalSuperResolution/TSRShadingAnalysis.ush#L411), [расширение допустимого диапазона](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/TemporalSuperResolution/TSRShadingAnalysis.ush#L567). + +UE также допускает history размером от 100% до 200% выходной стороны и отдельный resolve. Это не бесплатная резкость: удвоение обеих сторон даёт в четыре раза больше текселей соответствующих history buffers. [Размер истории](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/PostProcess/TemporalSuperResolution.cpp#L2054), [resolve](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/PostProcess/TemporalSuperResolution.cpp#L3388). + +**Как перенять.** Сделать собственный минимальный temporal upscaler: jitter; цвет, depth, корректные motion vectors для камеры/объектов/skinning; 3×3 dilation; reprojection; depth disocclusion; цветовой neighborhood clamp; отдельный validity buffer; spatial fallback для раскрытых областей. Начать с history в выходном разрешении, стабильной экспозиции и opaque геометрии. Затем добавить коррекцию экспозиции, responsive mask для анимированных материалов/частиц и простую temporal luminance статистику. Память предыдущих деформированных вершин и дисциплина jitter conventions зачастую важнее очередного фильтра в конце пайплайна. + +Не переносить сразу весь набор TSR permutations, half-precision tensor abstractions, resurrection, lens distortion и анализ тонкой геометрии. Это связанные оптимизации промышленной реализации, которые делают маленький прототип существенно сложнее. При корректном базовом upscaler можно отдельно проверить историю повышенного разрешения и antialiasing для тонких линий. Детектор мерцания принципиально допускает компромисс стабильности против ghosting: требуется управляемое ограничение его действия, а не глобальное увеличение history weight. + +**Проверка прототипа.** Зафиксировать воспроизводимые sequence: решётка/провода в статике; медленный pan; тонкий движущийся объект перед контрастным фоном; раскрытие фона; emissive flicker; листва; particles; резкий exposure jump; camera cut; переключение render scale. Сравнивать с высокоразрешённой эталонной последовательностью и простым TAA baseline. Помимо GPU ms и bytes истории нужны temporal variance в статике, ошибка после disocclusion, длина ghost trail и сохранение контраста тонких деталей. Нельзя принимать результат только по красивому неподвижному кадру. **Приоритет: высокий и наиболее универсальный из трёх; минимальный вариант средней сложности, достижение качества UE — большой отдельный проект.** + +Общая архитектурная идея всех трёх подсистем: тратить вычисления на востребованную часть сигнала, явно хранить состояние и уверенность кэша, проектировать отказ/устаревание как нормальный режим и визуализировать причины повторной работы. Сначала вывести в debug view pages/invalidations, fallback mip и history validity — это часть самой технологии, а не косметика после реализации. + +## Дополнительные механизмы: RDG, Substrate, glints + +### Render Dependency Graph — инфраструктура сложного рендерера + +**В исходниках.** API требует описывать используемые ресурсы в параметрах прохода; граф выводит барьеры и время жизни: [RenderGraphBuilder.h:45](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/RenderCore/Public/RenderGraphBuilder.h#L45). Это подтверждается реализацией, а не только комментарием API: `RenderGraphBuilder.cpp:1310` обходит producers от нужных выходов и снимает флаг culling; `:1327` компилирует граф; `:1383` описывает и обрабатывает недостижимые проходы; `:1426` объединяет совместимые raster passes. В `:1562` строятся fork/join для async compute, причем при отключенном async transient aliasing времена жизни расширяются на область параллельного исполнения (`:1590`, `:1646`). `:3217` передает fence-ограничения transient allocator при создании и освобождении ресурсов; `:3783` — компиляция барьеров. + +**Идея для собственного движка.** Объявление `pass → reads/writes → output` позволяет безопасно добавлять HZB, denoise, shadow pages, history buffers. Начать с одной GPU queue, явных read/write и imported/exported ресурсов и отладочного просмотра графа. Удаление ненужных проходов и lifetime-based reuse ресурсов добавить после корректного базового пути; только потом aliasing физической памяти и async compute. + +**Ловушка.** Два ресурса, стоящие последовательно в CPU-списке, могут одновременно использоваться GPU на разных очередях. Декларировать ресурсы следует на уровне mip/subresource, либо консервативно считать весь ресурс одним состоянием. History resources живут между кадрами и должны быть external/persistent. Экспорт и побочные эффекты — roots графа. Нельзя обещать, что сам граф ускорит каждый кадр: он имеет CPU overhead. + +**Проверка прототипа.** После отключения эффекта все изолированные предшественники исчезают из GPU capture; attachment/compute/readback цепочка работает с validation; повторное использование памяти не меняет изображение; отдельно измерять compile CPU time, GPU pass times и peak transient bytes. При нескольких frames in flight завершение CPU кадра не означает возможность переиспользовать память. + +### Substrate — ограниченная сложность материала + специализированные тайлы + +**В исходниках.** [SubstrateTranslatorCommon.cpp:687](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Engine/Private/Materials/SubstrateTranslatorCommon.cpp#L687) получает бюджет bytes/closures per pixel. В `:695` при выходе за бюджет компилятор выбирает глубокий оператор для parameter blending; в `:705` после исчерпания этого упрощения отключает optional features; `:1319` проверяет сразу лимиты памяти и closures; `:1433` повторяет цикл до выполнения бюджета. Это **компиляционное** упрощение, не измеритель GPU времени и не автоматический runtime LOD по дистанции. + +[SubstrateMaterialClassification.usf:172](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Substrate/SubstrateMaterialClassification.usf#L172) читает компактный заголовок материала; `:178` различает simple/single/complex/special; `:358` пишет отдельные списки тайлов и indirect counters; `:443` конвертирует их в dispatch args. `SubstrateDeferredLighting.ush:112` перебирает closures и распаковывает BSDF. C++ wiring: [Substrate.cpp:1746](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Substrate/Substrate.cpp#L1746) выбирает permutation, `:1774` — tile size 8 или 16, `:1785` — indirect args. + +**Что перенять.** Для начала PBR + clear coat как два фиксированных класса, компактные feature flags, экранные тайлы simple/complex и специализированные lighting shaders. Следующий шаг — максимум 2–3 closures и явный бюджет material buffer. Самостоятельно спроектировать предупреждения authoring: что было упрощено и почему. Полный граф произвольных физических слоев — отдельная большая задача. + +**Ловушки.** Если complex-пиксели равномерно рассыпаны по экрану, сложными становятся почти все тайлы. Классификация стоит GPU времени и окупается не всегда. Несколько closures увеличивают расходы не только direct lighting, но и GI/reflections и историю. Влажность/лак можно показать обычным clear-coat BRDF, не строя весь Substrate. + +**Проверка.** Сравнить одинаковое изображение с универсальным shader и classified shaders на сценах: только simple, локальные complex, шахматное смешение; измерять classification + lighting целиком. Отдельно проверять material compiler, что лимит bytes/closures соблюдается и упрощение видно художнику. + +### Glints — относительно изолированный эффект для материалов + +**В исходниках.** [GlintThirdParty.ush:13](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Substrate/Glint/GlintThirdParty.ush#L13) называет исходную работу Chermain et al. 2021; `:425` — `f_P`, который использует view/light direction и UV derivatives; `:518` выбирает LOD по footprint; `:543` оценивает распределения двух уровней; `:553` смешивает их. В [SubstrateEvaluation.ush:308](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Substrate/SubstrateEvaluation.ush#L308) результат используется в `Substrate_D_Glint`, а `:320` обеспечивает переход к GGX. [GlintShadingLUTs.cpp:122](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Substrate/Glint/GlintShadingLUTs.cpp#L122) получает LUT texture, `:140` выбирает словарь, `:156` задает его параметры. + +**Что это дает.** Материал с различимыми микробликами — например, краска с частицами, блестки или снег — вместо только гладкого среднего блика. Ключевая переносимая идея: фильтровать распределение микрофасеток с учетом footprint и масштаба, а не просто добавлять случайные яркие точки. + +**Прототип.** Один opaque-материал, tangent basis, UV derivatives, lookup-словарь, один источник света. Обособить glint BRDF от общей системы слоев; затем добавить normal-map filtering, area lights и environment lighting. По охвату архитектуры это существенно меньше Nanite/Lumen, но математика фильтрации все равно нетривиальна. + +**Проверка.** Плавно двигать камеру и свет, менять roughness/плотность, уменьшать объект до субпиксельного размера; проверять мерцание, устойчивость энергии и переход к обычному specular. Сравнивать с supersampled reference, измерять добавочное GPU время. Стабильность не доказывается одним скриншотом. + +**Публичный первоисточник.** https://xavierchermain.github.io/glint_anti_aliasing/ — статья, видео и ссылка на оригинальный код. Не делать вывода о лицензии всей интеграции UE из лицензии исходной исследовательской реализации. + +## Публичные первоисточники для дальнейшего изучения + +Результаты выше основаны на локальной ревизии. Публичные материалы помогают восстановить замысел и найти самостоятельные алгоритмические описания: + +- [Nanite: A Deep Dive, SIGGRAPH 2021](https://advances.realtimerendering.com/s2021/Karis_Nanite_SIGGRAPH_Advances_2021_final.pdf) — оригинальный дизайн. Ограничения ранней версии из доклада не следует автоматически переносить на UE 5.8.2. +- [Lumen, SIGGRAPH 2022](https://advances.realtimerendering.com/s2022/SIGGRAPH2022-Advances-Lumen-Wright%20et%20al.pdf) — устройства представлений сцены и переиспользования освещения. +- [MegaLights, документация Epic](https://dev.epicgames.com/documentation/unreal-engine/megalights-in-unreal-engine) — авторы отдельно отмечают влияние пересекающихся lights на выбор кандидатов и компромиссы denoising; это согласуется с просмотренными циклами shader-кода. +- [MegaLights: Stochastic Direct Lighting, SIGGRAPH 2025](https://advances.realtimerendering.com/s2025/content/MegaLights_Stochastic_Direct_Lighting_2025.pdf) — материалы авторов об архитектуре прямого света. +- [Geometric Glint Anti-Aliasing, страница авторов](https://xavierchermain.github.io/glint_anti_aliasing/) — описание фильтрации микробликов, публикация, видео и оригинальный исследовательский код. + +Полный перенос исходников модулей здесь не предлагается: описаны механизмы и собственные минимальные реализации. Выбор технологий и их эффективность нужно подтвердить на будущих целевых сценах и GPU. diff --git a/docs/studies/08-godot-ux-source-study.md b/docs/studies/08-godot-ux-source-study.md new file mode 100644 index 0000000..051c642 --- /dev/null +++ b/docs/studies/08-godot-ux-source-study.md @@ -0,0 +1,117 @@ +# Godot: удобство разработки как устройство движка + +**Синхронизация Faset, 18.09.2026.** Принятые решения — [ARCHITECTURE.md](../ARCHITECTURE.md), этапы до/после MVP — [PLAN.md](../../PLAN.md). Ниже сохранено исследование чужих исходников; это не отчёт о реализованных возможностях Faset. Рекомендации, помеченные **superseded**, остаются только историей рассмотренных вариантов. + +Для Faset приняты authoring objects/components с JSON и постоянными IDs, EnTT runtime, явная C++ metadata schema (`TypeId`/`FieldId`) и собственный retained C++ UI. Godot inheritance/live editing исследованы как сравнение. MVP Faset использует nested composition и overrides без variants/Apply to template, отдельный Player со snapshot сцены и MCP только в редакторе: authoring/import/build/PlayStop/editor logs, без чтения или изменения runtime world. + +Исследован публичный [репозиторий Godot](https://github.com/godotengine/godot), скачанный командой `git clone --depth 1` в `../../../godot`. Зафиксирован commit `9c776068d6ed23acd0c78bfe534272d1d2a3a619`, дата commit `2026-09-17T12:55:51-05:00`; [version.py:3](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/version.py#L3) сообщает **4.8.0 dev**, а не стабильный релиз. Размер рабочего каталога с Git на момент исследования — около 429 МБ. Исходники не изменялись. Локального `AGENTS.md` и `graphify-out/graph.json` в клоне нет; применены указания `локальный AGENTS.md`. + +Изучены тела функций в `Node`, `Resource`, `SceneState/PackedScene`, `PropertyUtils`, `EditorInspector`, `EditorUndoRedoManager`, run/debugger, filesystem/importer и `EditorPlugin`. Официальная документация `stable` сверена 17 сентября 2026 года для общих пользовательских контрактов; детали реализации относятся именно к указанному dev commit. Сборка, запуск редактора и пользовательский плейтест не проводились. Выводы о полезности и предложенные критерии — инженерная оценка, а не измерение скорости работы пользователей. + +Самый ценный перенос из Godot — **связанный путь от данных до действия пользователя**: свойства объекта автоматически становятся редактируемыми; изменение проходит через undo, сериализацию, обновление интерфейса и при необходимости live editing. Панели удобны благодаря этому общему основанию. В Faset принят общий authoring contract, при этом runtime хранится в EnTT, UI и renderer реализуются отдельно; live editing Godot не становится runtime MCP API. + +## 1. Сцена как повторно используемая сборка, Resource как явная модель данных + +**Сценарий.** Дизайнер собирает дверь из визуальной части, коллизии и скрипта, сохраняет её отдельно и ставит пять экземпляров в уровень. Геометрия и общая конфигурация используются совместно; состояние открытия и специально локализованный материал принадлежат конкретному экземпляру. Такой же способ сборки должен работать для оружия, UI-панели и комнаты. + +**Что делает код.** `SceneState::instantiate` создаёт вложенный `PackedScene` тем же механизмом, что и корневую сцену: [packed_scene.cpp:305](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/scene/resources/packed_scene.cpp#L305), [342](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/scene/resources/packed_scene.cpp#L342). Сохранение определяется отдельной связью ownership: `_parse_node` пропускает узлы, не принадлежащие сохраняемой сцене и не являющиеся editable instance: [870](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/scene/resources/packed_scene.cpp#L870). Это отличается от самого положения в дереве. `Node::add_child` проверяет наличие родителя и циклы [node.cpp:1711](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/scene/main/node.cpp#L1711), а `set_owner` отдельно требует, чтобы owner был предком [2303](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/scene/main/node.cpp#L2303). + +Особенно полезна семантика локальных ресурсов. `SceneState::make_local_resource` возвращает обычный ресурс без копирования, а для `local_to_scene` ищет уже созданную копию в remap текущей сцены и только затем дублирует: [packed_scene.cpp:781](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/scene/resources/packed_scene.cpp#L781). Поэтому две ссылки внутри экземпляра могут указывать на одну локальную копию, тогда как другой экземпляр получает другую. Рекурсивное копирование учитывает `ALWAYS_DUPLICATE`, `NEVER_DUPLICATE` и remap cache: [resource.cpp:278](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/io/resource.cpp#L278). Это существенно точнее, чем кнопка «скопировать все поля». + +Официальные [Resources](https://docs.godotengine.org/en/stable/tutorials/scripting/resources.html) подтверждают общую модель: узлы используют данные ресурсов, ресурсы бывают внешними и встроенными, пользовательские ресурсы получают сериализацию и Inspector. [PackedScene](https://docs.godotengine.org/en/stable/classes/class_packedscene.html) явно связывает сохранение с `owner`. + +**Перенос.** Нужны authoring scene/prefab, typed asset references и три понятных режима данных: общий asset, локальное состояние экземпляра, явно созданная уникальная копия. В UI полезнее показывать «изменение затронет все экземпляры», имя источника и переход к нему, чем рассчитывать на знание ref-counting. Внутреннее дерево редактора можно компилировать в ECS; исходники Godot не обязывают переносить его runtime object model. + +**Чего избегать.** Не делать ownership невидимым условием сохранения для обычного автора: узел, видимый в редакторе, но не попадающий в файл, создаёт неприятный сюрприз. Не смешивать «встроен в файл» и «уникален для экземпляра» — это разные свойства. Избегать глубоко вложенных shared mutable ресурсов без индикации области воздействия. + +**Минимальный прототип и приёмка.** Одна дверь, два references на её материал внутри сцены, пять экземпляров, external `DoorConfig`. Изменение config обновляет все экземпляры; local material меняется только у одной двери, сохраняя sharing её внутренних ссылок; save/reload сохраняет эти отношения. Пользователь должен объяснить область воздействия изменения, глядя на интерфейс, без чтения документации. + +## 2. Наследование и overrides должны быть обозримыми и обратимыми + +**Сценарий.** Есть базовый противник и тяжёлый вариант. Вариант меняет здоровье и скорость, но наследует анимации. Автор повышает базовую скорость, хочет понять, какой вариант получил изменение, и отменить только локальное переопределение здоровья. + +**Что делает код.** При сериализации наследуемой сцены сохраняется ссылка на базовый `PackedScene`: [packed_scene.cpp:1428](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/scene/resources/packed_scene.cpp#L1428). `_parse_node` сравнивает значение со значением по умолчанию и не сохраняет совпадающее; pinned properties обходят это исключение: [1058](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/scene/resources/packed_scene.cpp#L1058). У «default» есть происхождение: `PropertyUtils::get_property_default_value` сначала ищет override в instantiation/inheritance stack, затем экспортированный default скрипта и, наконец, native class default: [property_utils.cpp:80](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/scene/property_utils.cpp#L80), [152](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/scene/property_utils.cpp#L152). + +Inspector использует тот же вычислитель для revert и определения, отличается ли текущее значение: [editor_inspector.cpp:906](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/inspector/editor_inspector.cpp#L906). Клик revert отправляет обычный `emit_changed`, а не тайно меняет память отдельным способом: [1271](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/inspector/editor_inspector.cpp#L1271). Даже свёрнутая секция показывает число изменённых свойств [2463](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/inspector/editor_inspector.cpp#L2463). Таким образом, provenance данных поддерживает конкретные affordances интерфейса. + +**Перенос.** Хранить базовую ссылку и sparse overrides; у каждого значения иметь понятное происхождение. Нужны «перейти к источнику», «вернуть наследуемое значение», список overrides и отдельное «зафиксировать это значение», включая совпадение с нынешним default. Для Faset MVP приняты обычные и вложенные экземпляры; прежнее предложение одного уровня prefab variation **superseded**. Variant inheritance и Apply overrides to template отложены. Patch использует instance chain/ObjectId/ComponentId/FieldId, а не имя или путь узла; точные правила — в [архитектурном исследовании](01-architecture.md). + +**Чего избегать.** Нельзя вычислять revert просто из zero/default C++ конструктора: пользователь ожидает значение базы. Нельзя считать равенство чисел доказательством отсутствия намеренного override. Переименование узла, изменение структуры базы, удаление свойства и изменение типа требуют явной политики миграции. Source показывает сложность этих случаев; он не доказывает, что произвольная цепочка inheritance безошибочна. + +Официальная [Scene organization](https://docs.godotengine.org/en/stable/tutorials/best_practices/scene_organization.html) также предупреждает о связности сцен через пути и рекомендует самостоятельные под-сцены и явно передаваемые зависимости. Это хороший предел для обещаний prefab workflow. + +**Прототип и приёмка Faset.** Шаблон → вложенный экземпляр → два размещения в сцене. Изменить default базы, добавить override, pin совпадающее значение, выполнить revert/undo, затем save/reload. Override layer должен сохранять только намеренные отличия; UI должен показывать, откуда пришло значение. При удалении target свойства нужен видимый orphaned override, а не молчаливое исчезновение пользовательского решения — это предлагаемое улучшение собственного движка. + +## 3. Inspector и Undo — единый редакторский транзакционный слой + +**Сценарий.** Автор тянет slider скорости, меняет несколько объектов сразу, переключается на другую сцену и нажимает Undo. Он ожидает возврата целого жеста в правильном документе и восстановления связанных свойств, а не сотни микрошагов или поломанного объекта. + +**Что делает код.** Inspector получает property list [editor_inspector.cpp:4480](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/inspector/editor_inspector.cpp#L4480), фильтрует editor flags [4635](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/inspector/editor_inspector.cpp#L4635), отдаёт тип, имя, hints и usage Inspector plugins; exclusive plugin останавливает выбор следующих обработчиков [5105](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/inspector/editor_inspector.cpp#L5105). Значит, тип данных и metadata действительно определяют редактор поля. + +`EditorInspector::_edit_set` создаёт action с `MERGE_ENDS`, записывает do/undo property, сохраняет связанные свойства и вызывает расширяемые undo hooks: [5703](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/inspector/editor_inspector.cpp#L5703), [5754](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/inspector/editor_inspector.cpp#L5754). В action входят refresh и notifications; затем `commit_action`: [5789](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/inspector/editor_inspector.cpp#L5789). Это помогает сохранить согласованность UI и состояния объекта. + +Менеджер выбирает историю сцены, built-in resource, global или remote: [editor_undo_redo_manager.cpp:63](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/editor_undo_redo_manager.cpp#L63). Custom context позволяет явно указать принадлежность [149](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/editor_undo_redo_manager.cpp#L149). Commit ведёт saved version и инвалидирует несовместимые redo branches [246](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/editor_undo_redo_manager.cpp#L246). Официальный [EditorUndoRedoManager](https://docs.godotengine.org/en/stable/classes/class_editorundoredomanager.html) подтверждает отдельные истории и предупреждает, что автоматическое определение context иногда ошибается. + +**Перенос.** Сначала сделать универсальную команду редактирования `{document, object, property, before, after}` с transaction boundary, merge key, dirty revision и event notifications. Drag gesture — одна команда; изменение взаимосвязанных параметров — одна транзакция. Serializer, Inspector, gizmos, bulk-edit и plugins используют одинаковые descriptor IDs. Пользовательская UI-специализация должна посылать изменения в этот слой. + +**Чего избегать.** Не добавлять undo как последнюю оболочку вокруг готовых инструментов: побочные изменения уже будут потеряны. Не делать document context догадкой без возможности задать его явно. Нельзя копировать всю глобальную историю Godot без анализа: в этом source global edits могут очищать redo других сцен; собственному UI нужно ясно объяснять такую модель. Не считать каждый setter чистым присваиванием. + +**Прототип и приёмка.** Две открытые сцены, общий ресурс и custom slider. 100 обновлений drag отменяются одним жестом; редактирование другой сцены не отменяет неожиданно первую; Undo восстанавливает и зависимое значение. Сохранение, Undo к saved revision, Redo и новая ветка должны правильно менять dirty indicator. Custom property widget получает это поведение без собственного Undo кода. + +## 4. Запуск отдельной сцены и два явно различимых пути live editing + +**Сценарий.** Разработчик проверяет экран инвентаря без прохождения главного меню, меняет расстояние взаимодействия у работающей двери и выясняет, почему runtime объект получил неожиданное значение. Важна ясность: он сейчас меняет исходную сцену или только живой экземпляр. + +**Что делает код.** Run bar выбирает отдельную current/custom scene либо project main; для несохранённой сцены запускает save flow: [editor_run_bar.cpp:312](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/run/editor_run_bar.cpp#L312). Затем autosave/build hook, старт debugger server и subprocess: [350](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/run/editor_run_bar.cpp#L350). `EditorRun::run` передаёт проект и `--remote-debug`: [editor_run.cpp:51](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/run/editor_run.cpp#L51). Это инструментальное выполнение сцены, не специальный режим в игровом bootstrap. + +Для editor-authored edits undo notify подключён к debugger: [editor_debugger_node.cpp:245](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/debugger/editor_debugger_node.cpp#L245). `_property_changed` отправляет node-path/property/value либо resource path: [script_editor_debugger.cpp:1508](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/debugger/script_editor_debugger.cpp#L1508). Runtime `LiveEditor::_node_set_func` ищет instances редактируемой сцены и применяет изменение; root transform чужого instance специально сохраняется: [scene_debugger.cpp:871](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/scene/debugger/scene_debugger.cpp#L871). + +Remote Inspector использует другой путь: `update_remote_object` посылает runtime ObjectID [script_editor_debugger.cpp:281](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/debugger/script_editor_debugger.cpp#L281), runtime находит объект и делает `set` [scene_debugger.cpp:760](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/scene/debugger/scene_debugger.cpp#L760). Из этих тел нельзя вывести автоматическое сохранение remote-правки в исходную сцену. [Debugger panel](https://docs.godotengine.org/en/stable/tutorials/scripting/debug/debugger_panel.html) служит официальной сверкой общего debug workflow; конкретное различие путей подтверждено локальным source. + +**Принятый перенос.** Separate Player process и запуск snapshot текущей сцены либо project entry point. Сохраняется authoring context; Stop → incremental C++ build → Play создаёт новый snapshot. **Superseded для MVP:** remote tree/property edits, пересылка authoring transactions в runtime и Apply runtime value в источник. MCP управляет запуском на стороне редактора, но не инспектирует и не меняет runtime world; в Player и экспорте MCP отсутствует. Маленький test context позволяет проверить сцену без прохождения всей игры. + +**Чего избегать.** Не обещать произвольный state-preserving hot reload на основании property live edit. В показанном коде ref-counted resources без пути не пересылаются этим способом; структура сцены и instance mapping тоже накладывают ограничения. Runtime deletion, повторно использованные IDs, invalid paths и разрыв соединения должны быть нормальными состояниями protocol. + +**Прототип и приёмка Faset.** Запустить дверную сцену отдельным Player, изменить исходный документ и Undo во время игры: текущий snapshot остаётся прежним. После Stop/Play запускается новая версия; runtime изменения не записаны в source. Проверить два экземпляра, сбой Player, повторный запуск и сохранение редакторского контекста. У MCP отсутствуют runtime query/mutation tools, у Player — MCP endpoint. Измерять время build/start отдельно, без обещания фиксированной latency. + +## 5. Неразрушающий импорт и устойчивые ссылки — часть UX, а не только build pipeline + +**Сценарий.** Художник меняет исходную текстуру или glTF, возвращается в редактор, получает обновление с прежними настройками. Другая машина воспроизводит импорт; перенос ассета в редакторе не требует ручного поиска всех ссылок. + +**Что делает код.** `_reimport_file` читает сохранённые `params`, importer и UID из sidecar `.import`: [editor_file_system.cpp:2826](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L2826). Добавляет недостающие defaults, вызывает importer с source и отдельным output base path: [2899](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L2899). Затем пишет importer version, UID, output paths и options [2930](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L2930). Порядок options сохраняется специально для уменьшения шума diff; source/destination checksums вынесены в отдельный `.md5`: [3018](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L3018). + +Generated base path находится в imported-files directory и определяется исходным путём [resource_importer.cpp:541](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/io/resource_importer.cpp#L541). Reimport проверяет source и destination checksum [editor_file_system.cpp:727](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L727). При редакторском move `FileSystemDock` вызывает `rename_dependencies` для owners и собирает открытые сцены для reload [filesystem_dock.cpp:1688](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/docks/filesystem_dock.cpp#L1688). Значит, UID не отменяет maintenance зависимостей и путей. + +Официальный [Import process](https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/import_process.html) подтверждает разделение source, настроек импорта и generated data. [Import configuration](https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/importing_3d_scenes/import_configuration.html) описывает настройку импортируемых 3D scenes и ограничение прямого редактирования производных данных. + +**Перенос.** Source + маленький versioned recipe + disposable derived cache. Stable asset ID, dependency index, понятный progress/status и транзакционный remap при move. User overrides хранить отдельно от регенерируемого результата. Для начала достаточно PNG и одного mesh format; remote DDC и сотни импортёров преждевременны. + +**Чего избегать.** Не записывать ручные правки в файл, который importer завтра перезапишет. Не делать GPU cache source of truth. Не обещать полную защиту всех строковых путей одним UID: scene/script references и пользовательские данные требуют правил. Проверка checksum не означает, что importer доказанно детерминирован. + +**Прототип и приёмка.** Изменить source, поменять import recipe, перенести asset через browser, удалить derived cache, открыть проект на чистой машине. Связи и recipe сохраняются, cache восстанавливается, Git diff отражает пользовательские решения. Ошибка importer оставляет диагностируемое состояние; для своего движка разумно дополнительно сохранять последнюю успешную версию результата до завершения нового импорта. + +## 6. Предметные инструменты и локальная диагностика вместо обязательной правки движка + +**Сценарий.** Команда делает редактор волн противников: специальный Inspector, dock со списком волн, gizmo радиуса появления и предупреждение у spawner без точки назначения. Всё должно участвовать в обычном Undo и исчезать корректно при выключении расширения. + +**Что делает код.** `EditorPlugin` предоставляет registration/removal для docks [editor_plugin.cpp:134](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/plugins/editor_plugin.cpp#L134), inspector plugins [513](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/plugins/editor_plugin.cpp#L513), importers с отложенным filesystem rescan [445](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/plugins/editor_plugin.cpp#L445), viewport input/overlay [327](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/plugins/editor_plugin.cpp#L327). Inspector поддерживает undo hooks [427](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/plugins/editor_plugin.cpp#L427). Официальные [Inspector plugins](https://docs.godotengine.org/en/stable/tutorials/plugins/editor/inspector_plugins.html) описывают протокол выбора property editor и отправку изменений через `emit_changed`. + +Диагностика тоже имеет контракт: Node вызывает пользовательский `_get_configuration_warnings`, а `update_configuration_warnings` отправляет сигнал только для редактируемой сцены [node.cpp:3564](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/scene/main/node.cpp#L3564). Scene tree собирает сообщения и добавляет warning button прямо на соответствующий item [scene_tree_editor.cpp:594](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/scene/scene_tree_editor.cpp#L594). Это образец объяснения ошибки в её контексте, без поиска в общем логе. + +**Перенос.** Дать небольшой стабильный editor API: selection, property descriptors, commands, preview drawing, asset picker, dock lifecycle и structured validation. Для первого custom tool лучше десять согласованных точек расширения, чем доступ ко всем внутренним singleton. Вычисляемые warning должны объяснять исправление: «укажите маршрут», а не «invalid state». Кнопка исправления — отдельная undoable command. + +**Чего избегать.** Не давать plugin обходить transaction layer ради удобства. Не исполнять автоматически тяжёлую генерацию на каждый paint/tick. User tool code внутри редактора требует аккуратных lifecycle и error boundaries. Официальное [Running code in the editor](https://docs.godotengine.org/en/latest/tutorials/plugins/running_code_in_the_editor.html) отдельно предупреждает: tool/EditorScript изменения сами по себе не гарантируют Undo и dirty marking. Это ограничение надо учитывать при заимствовании, а не считать `@tool` бесплатной безопасной магией. + +**Прототип и приёмка.** Расширение spawner работает без пересборки ядра, валидирует отсутствие маршрута, редактирует параметры custom widget и рисует radius. Enable/disable дважды не оставляет widgets/callbacks; исправление warning отменяется; исключение пользовательского инструмента не повреждает сцену; сцена сохраняется через общий слой, без своего serializer. + +## Порядок заимствования + +1. Typed properties/resources, document identity и полноценные editor transactions. +2. Scene/prefab composition, явные sharing/local semantics, provenance и revert. +3. Отдельный Player со snapshot сцены и надёжный Stop/build/Play; remote/live workflow выше оставлен сравнением Godot. +4. Source/recipe/cache import, stable asset references и reference-aware move. +5. Plugin API и локальная validation как проверка качества предыдущих контрактов. + +Для сопоставления с Unreal и третьим движком: Godot здесь особенно ценен связью authoring model с ежедневным циклом разработки. Его node hierarchy не следует навязывать высокопроизводительному runtime, а editor transactions и provenance полезны почти независимо от renderer. Главный критерий будущего прототипа — можно ли создать, настроить, проверить, переиспользовать и исправить небольшой игровой объект без специальных обходных путей и без потери авторских изменений. + +Обычный glTF/GLB import Faset работает без Blender add-on. Используется официальный Blender; optional Python add-on обеспечивает IDs и удобный экспорт, но не модифицирует движок Blender. Без устойчивых source IDs соответствие частей после rename не гарантируется. diff --git a/docs/studies/09-unity-ux-extensibility-study.md b/docs/studies/09-unity-ux-extensibility-study.md new file mode 100644 index 0000000..2918103 --- /dev/null +++ b/docs/studies/09-unity-ux-extensibility-study.md @@ -0,0 +1,115 @@ +# Unity: UX редактора и расширяемость для собственного движка + +**Синхронизация Faset, 18.09.2026.** Принятые решения — [ARCHITECTURE.md](../ARCHITECTURE.md), этапы до/после MVP — [PLAN.md](../../PLAN.md). Ниже сохранено исследование чужих исходников; это не отчёт о реализованных возможностях Faset. Рекомендации, помеченные **superseded**, остаются только историей рассмотренных вариантов. + +Faset выбрал C++/EnTT, собственный retained C++ UI с декларативными layout/styles и тёмной темой по умолчанию; ImGui остаётся debug tool. UI Toolkit, C# assemblies и Unity lifecycle здесь — изученные образцы, не выбранные зависимости. Явная schema с TypeId/FieldId обслуживает Inspector и JSON; gameplay статически включается в отдельный Player, editor plugins — DLL/SO под точный SDK. MCP ограничен editor authoring/import/build/PlayStop/logs, не читает/меняет runtime world и отсутствует в Player/экспорте. + +Дата проверки: 17 сентября 2026. Исследован официальный [UnityCsReference](https://github.com/Unity-Technologies/UnityCsReference), shallow clone в `../../../UnityCsReference`. Зафиксирован commit `6b50e5544f6efcca1f44dbace3d1778b465ac6d0` от 2026-09-03, версия в README — **Unity 6000.7.0a6**. Это alpha-снимок C# reference source; его API нельзя автоматически считать контрактом каждой выпущенной Unity 6. + +UnityCsReference содержит C# части движка и редактора, но не полную native-реализацию Unity. README определяет reference-only использование; это не свободная библиотека для включения в свой движок. Здесь рассматриваются архитектурные идеи и наблюдаемое поведение. В каждом разделе различаются реально прочитанные C# тела, объявления native bindings и предлагаемый собственный дизайн. Основание: [README.md:1](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/README.md#L1) и [официальная страница репозитория](https://github.com/Unity-Technologies/UnityCsReference). + +Дополнительно проверены актуальные страницы официальной документации. Их current-адреса при проверке показывали Unity 6.5/6.6; они обновляются независимо от alpha-снимка. Unity Editor не запускался, измерений времени и пользовательских экспериментов нет. Все приёмочные сценарии ниже — предложения для будущей реализации, а не пройденные тесты Unity. Локальных `AGENTS.md` и graphify-графа в клоне не найдено; прочитан `локальный AGENTS.md`. + +Главная находка: удобство Unity строится на общих контрактах редактирования. Inspector, gizmo, importer и расширение не должны каждый заново изобретать сохранение, Undo, выделение и обновление интерфейса. Для своего редактора это полезнее буквального повторения расположения его панелей. + +## 1. Единый путь изменения свойства: multi-edit, Undo и prefab overrides + +**Сценарий пользователя.** Выделить двадцать источников света, изменить дальность, отменить одним действием, затем у одного экземпляра prefab вернуть только дальность к исходному значению. Сильный UX здесь — сохранение смысла операции независимо от того, выполнили её стандартным полем, пользовательским Inspector или инструментом сцены. + +**Что подтверждено.** `SerializedObject` создаётся для одного либо нескольких targets; `FindProperty` возвращает property по пути и удерживает owning object живым. Но `ApplyModifiedProperties`, `Update`, `UpdateIfRequiredOrScript` — `extern`: внутреннюю сериализацию и diff этого репозитория мы не видим. [SerializedObject.bindings.cs:23](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/SerializedObject.bindings.cs#L23), поиск — 77, native boundary — 122–139. Аналогично `Undo.RecordObject` привязан к native `RecordUndoDiff`, а не содержит C# diff-алгоритм: [Undo.bindings.cs:184](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/Undo/Undo.bindings.cs#L184). + +Зато orchestration доступна. При изменении UI-поля binding записывает property, применяет изменения, обновляет revision и объединяет Undo-операции. [SerializedObjectBindingPropertyToBaseField.cs:35](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Modules/UIElementsEditor/Bindings/SerializedObjectBindingPropertyToBaseField.cs#L35), [SerializedObjectBindingToBaseField.cs:100](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Modules/UIElementsEditor/Bindings/SerializedObjectBindingToBaseField.cs#L100). При синхронизации смешанных значений используется присваивание без уведомления: иначе само отображение могло бы записать значение первого объекта всем остальным (`PropertyToBaseField.cs:28`). + +`RevertPropertyOverride` снимает флаг override и применяет serialized changes; ветки различают пользовательскую операцию с Undo и автоматическую без Undo. `ApplyPropertyOverride` предварительно проверяет несовпадения массивов, managed references и недопустимые scene references, затем вызывает native implementation. [PrefabUtility.cs:658](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/Prefabs/PrefabUtility.cs#L658), revert — [922](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/Prefabs/PrefabUtility.cs#L922). + +**Что перенять.** Ввести `PropertyHandle` со стабильными object/type/property IDs, состояниями value/mixed/unavailable и единый edit transaction. Его commit должен обслуживать Undo, dirty state, изменение prefab delta и уведомления UI. Перетаскивание slider — одна транзакция; отмена возвращает каждое исходное значение. Для первого прототипа достаточно scalar/vector/color/reference, нескольких targets и плоских overrides. В Faset MVP вложенные экземпляры адресуются постоянной instance chain/ObjectId/ComponentId/FieldId; массив заменяется целиком. Variant inheritance и Apply to template — после MVP. Reparent ограничен одним экземпляром; удаление унаследованного объекта — suppression с диагностикой оставшихся ссылок. + +**Ловушки.** Unity предупреждает: разные serialized streams нужно синхронизировать; запись через serialized property обходит обычный setter; чтение возвращает значение первого target, тогда как запись меняет все. Поэтому validation должно существовать на уровне данных. [SerializedObject API](https://docs.unity3d.com/ScriptReference/SerializedObject.html). При прямой правке prefab instance отдельная запись prefab modifications тоже существенна; официальный API рекомендует serialized workflow. [Prefab modifications](https://docs.unity3d.com/ScriptReference/PrefabUtility.RecordPrefabInstancePropertyModifications.html). + +**Приёмка.** Mixed selection без действий не меняет данные. Drag создаёт один Undo. Undo/Redo восстанавливает разные прежние значения двадцати объектов. Два открытых Inspector сходятся после commit. Revert одного поля не сбрасывает остальные overrides. Сохранить/переоткрыть сцену — результат тот же. Невалидный reference отклоняется с объяснением до частичного изменения документа. + +## 2. UI Toolkit: декларативный Inspector поверх общей модели данных + +**Сценарий пользователя.** Автор компонента добавляет один удобный редактор диапазона или списка точек; стандартные поля, сторонняя панель и инструмент сцены продолжают показывать согласованные данные. Новое поле компонента остаётся доступным даже до написания специализированного UI. + +**Что подтверждено.** `InspectorElement.FillDefaultInspector` обходит visible serialized properties и создаёт `PropertyField`; специализированный путь вызывает `Editor.CreateInspectorGUI()`. Это реальные тела: [InspectorElement.cs:628](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/UIElements/Inspector/InspectorElement.cs#L628), custom path — [703](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/UIElements/Inspector/InspectorElement.cs#L703). То есть базовый редактор выводится из schema, а специальный UI подключается расширением. + +`SerializedObjectBindingContext.BindTree` проходит дерево элементов, разрешает `bindingPath` относительно родителя и продолжает привязку детей. При превышении внутреннего временного порога остаток откладывается на следующий кадр. [SerializedObjectBindingContext.cs:144](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Modules/UIElementsEditor/Bindings/SerializedObjectBindingContext.cs#L144), порог/решение — 99–106. Обновление serialized object ограничивается одним проходом на кадр данного updater; revision/change tracker сообщает, когда обновлять связанное представление: [651](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Modules/UIElementsEditor/Bindings/SerializedObjectBindingContext.cs#L651), [683](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Modules/UIElementsEditor/Bindings/SerializedObjectBindingContext.cs#L683). Это не доказательство полного отсутствия polling: вызов `PollForChanges` явно присутствует. + +Официальный workflow объединяет `CustomEditor`, `CreateInspectorGUI`, visual tree/UXML, `PropertyField` и property drawers; для вложенного plain serializable type рекомендуется drawer. [Custom Inspector](https://docs.unity3d.com/Manual/UIE-HowTo-CreateCustomInspector.html). SerializedObject binding — отдельный редакторский механизм; его не следует смешивать с любым runtime binding UI Toolkit. [Binding manual](https://docs.unity3d.com/Manual/UIE-Binding.html). + +**Что перенять.** Стандартный Inspector из schema должен работать первым. Поверх него — registry визуальных редакторов типов и отдельных свойств, общий binding adapter и возможность заменить участок, сохранив default fields вокруг. В начальной версии достаточно дерева widgets и стилей; XML-подобный язык и визуальный UI Builder не обязательны. Версии данных, no-notify assignment и корректный unbind важнее собственного markup. Для больших списков понадобятся virtualization и отложенное создание дорогих controls; конкретный Unity-порог 50 ms не стоит принимать за целевой frame budget своего UI. + +**Ловушки.** Переименование property ломает строковые пути; исчезновение target и смена типа инвалидируют binding. Программное обновление UI может рекурсивно породить запись данных. Custom Inspector способен скрыть новые поля и потерять multi-edit, если обходит общий слой. Недостаточно красиво отрисовать поле — нужно сохранить keyboard focus, numeric dragging, mixed state и семантику Undo. + +**Приёмка.** Добавить новое сериализуемое поле без редакторского кода. Написать custom drawer один раз и увидеть его в массиве, Inspector и popup. Удалить выделенный объект при открытых панелях — без stale callbacks. Менять одно поле извне — обновляются нужные controls, курсор в другом поле не прыгает. На большом документе измерять задержку открытия и input latency, а не только число UI-элементов. + +## 3. Contextual tools и overlays: расширение как часть рабочего пространства + +**Сценарий пользователя.** Выбрать spline и получить инструмент перемещения узлов рядом со сценой; выбрать обычный mesh — вернуться к подходящим инструментам. Настройки кисти можно закрепить, свернуть, перенести и восстановить после перезапуска. + +**Что подтверждено.** `EditorTool.targets` использует связанные targets для component tool, иначе текущее `Selection.objects`. В классе существуют lifecycle hooks, `OnToolGUI`, `IsAvailable`, toolbar icon и grid snapping. Activation/deactivation защищены от повторного вызова: [EditorTool.cs:57](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/Tools/EditorTool.cs#L57), [92](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/Tools/EditorTool.cs#L92). `EditorToolManager` запрещает рекурсивную смену tool внутри перехода, вызывает deactivate предыдущего и activate нового, запоминает предыдущий builtin/custom tool, затем рассылает события. [EditorToolManager.cs:238](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/Tools/EditorToolManager.cs#L238). Различие global/component tools подтверждает [официальный EditorTool API](https://docs.unity3d.com/ScriptReference/EditorTools.EditorTool.html). + +Overlay отделяет содержимое инструмента от размещения. `ToolbarOverlay` строит toolbar из идентификаторов элементов; `OverlayCanvas.SaveData` сохраняет ID, container, положение, visibility и сериализованное содержимое. Восстановление ищет container, имеет fallback, сортирует элементы по сохранённому индексу и dock-ит их. [ToolbarOverlay.cs:18](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/Overlays/ToolbarOverlay.cs#L18), [OverlayCanvas.cs:104](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/Overlays/OverlayCanvas.cs#L104), [1408](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/Overlays/OverlayCanvas.cs#L1408), [1457](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/Overlays/OverlayCanvas.cs#L1457). Публичная регистрация panel/toolbar через атрибуты описана в [Create your own overlay](https://docs.unity3d.com/Manual/overlays-custom.html). + +**Что перенять.** Сделать descriptor инструмента: stable ID, название/icon/shortcut, target predicate, viewport handlers, optional settings view и lifecycle. Selection service принадлежит редактору, не конкретной панели. Tool manager управляет input capture и переходами; layout service управляет размещением. Сначала достаточно одной полосы contextual tools и одной перемещаемой панели. Обязательно сразу предусмотреть ручное скрытие, понятное disabled-state объяснение и возврат к предыдущему инструменту. + +**Ловушки.** Нельзя хранить вечные raw pointers на выделенные объекты. Несколько Scene Views требуют window context. Закрытие панели не равнозначно завершению drag; уничтожение selection должно безопасно завершать транзакцию. Потерянный plugin или изменившийся ID не должен ломать сохранённый layout. Пустые панели всех установленных расширений одновременно создают визуальный шум. + +**Приёмка.** Включить spline tool, начать drag, сменить selection и закрыть viewport: input capture освобождён, Undo корректен. Работать с двумя viewport без переноса настроек камеры между ними. Сохранить layout, временно отключить plugin, снова включить — редактор открывается и восстанавливает допустимое размещение. Shortcut конфликтует явно, а недоступный tool не молча проглатывает клавишу. + +## 4. Importer как воспроизводимая операция с зависимостями и диагностикой + +**Сценарий пользователя.** Изменить внешний файл модели или настройки её обработки, вернуться в редактор и получить обновлённый asset без ручного удаления кэша; ссылки на его материалы сохраняются. При ошибке пользователь видит причину и исходный файл. + +**Что подтверждено.** C# `ScriptedImporter.GenerateAssetData` вызывает `OnImportAsset`. Регистрация обнаруживает атрибуты, проверяет base class, нормализует extensions, отклоняет конфликтующие автоматически выбранные обработчики и передаёт version/priority/cache flag в asset pipeline. [ScriptedImporter.cs:25](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Modules/AssetPipelineEditor/Public/ScriptedImporter.cs#L25), регистрация — [61](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Modules/AssetPipelineEditor/Public/ScriptedImporter.cs#L61), conflict — 103–117, native registration — 133–135. + +`AssetImportContext` предоставляет outputs с identifiers, main object и отдельные зависимости на source/artifact. C# проверяет аргументы; хранение результатов и dependency graph скрыты за `extern`. [AssetImportContext.bindings.cs:51](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs#L51), source dependency — 69, artifact dependency — 156. `AssetDatabase.GetAssetDependencyHash` и `RegisterCustomDependency` также native declarations, не доказательство конкретного алгоритма content-addressed storage: [AssetDatabase.bindings.cs:670](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Modules/AssetDatabase/Editor/ScriptBindings/AssetDatabase.bindings.cs#L670), [1287](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Modules/AssetDatabase/Editor/ScriptBindings/AssetDatabase.bindings.cs#L1287). + +Документация требует детерминированности и стабильных output IDs, объясняет регистрацию зависимостей; без этого кэш может возвращать неверные результаты. [Scripted Importers](https://docs.unity3d.com/Manual/ScriptedImporters.html). Static dependencies включают importer version и target platform; dynamic dependencies выясняются при импорте. Refresh может перезапускаться из-за созданных файлов и новых import requests. [Asset Database refresh](https://docs.unity3d.com/Manual/AssetDatabaseRefreshing.html). + +**Что перенять.** Контракт `Import(inputs, settings, target, context) → artifacts + diagnostics + dependencies`, stable source GUID и local output IDs. Моя адаптация: cache key из source/dependency hashes, версии importer и settings/target; прозрачная команда «почему переимпортирован». Первую версию сделать локальной и однопроцессной, с atomic publication успешного результата. Distributed cache и parallel workers требуют отдельной проверки thread/process safety и не нужны для доказательства полезности UX. + +**Ловушки.** Чтение незаявленного файла, времени, random seed или machine path делает результат невоспроизводимым. Нестабильный ID выходного mesh ломает scene references после reimport. Изменение исходников самим importer может запустить цикл. Ошибка обработки не должна уничтожать последний рабочий asset; это предлагаемое правило собственного движка, не установленный здесь контракт Unity. + +**Приёмка.** Дважды импортировать одинаковые inputs на чистом кэше: одинаковые artifacts. Изменить config dependency — переработаны только зависимые assets. Переименовать source при сохранении GUID — ссылки выживают. Повысить importer version — cache invalidated. Убить worker посередине — нет полузаписанного результата. При сломанном файле видны path, importer, dependency и actionable error; повторное исправление восстанавливает asset автоматически. + +## 5. Модули и registries: расширение устанавливается без правок ядра + +**Сценарий пользователя.** Команда добавляет пакет «дороги»: runtime component, importer формата дорог, Inspector, Scene tool и тесты. Сборка игры не тащит редакторские окна; отсутствие optional render pipeline объясняется зависимостью пакета. + +**Что подтверждено.** `CustomScriptAssemblyData` задаёт references, platform filters, define constraints, version defines и no-engine references. `FromJson` запускает validation; несовместимые include/exclude отклоняются. `IsCompatibleWith` проверяет build/editor/test context, define constraints и platforms. Реальные C# тела: [CustomScriptAssembly.cs:73](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssembly.cs#L73), parser/validation — 101–132, compatibility — [541](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/Scripting/ScriptCompilation/CustomScriptAssembly.cs#L541). Это подтверждение модели модулей, а не доказательство мгновенной пересборки или безопасного hot-unload любого plugin. + +Editor extension discovery тоже открыто: `CustomEditorAttributes.Initialize` очищает registry, ищет типы с `CustomEditor`, проверяет наследование, валидирует настройки и добавляет editors в cache. [CustomEditorAttributes.cs:169](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/CustomEditorAttributes.cs#L169). Аналогичный подход уже виден у importer. В итоге пользовательская функция подключается в существующий интерфейс через contract и metadata, а не fork основного editor. + +Актуальный manual требует asmdef для кода UPM-пакета и разделяет Editor/Runtime/Tests; runtime не должен ссылаться на Editor. [Package assembly definitions](https://docs.unity3d.com/Manual/cus-asmdef.html). Организация assemblies служит границам зависимостей и итерации компиляции. [Assembly introduction](https://docs.unity3d.com/Manual/assembly-definitions-intro.html). + +**Что перенять.** Manifest с ID/version/dependencies/capabilities и отдельные runtime/editor modules. Extension registry должен предлагать typed entrypoints для Inspector, tool, importer, menu и diagnostics. На раннем этапе явная регистрация через API проще reflection scanning. Все регистрации принадлежат одному plugin scope; отключение снимает hooks и UI. Заранее определить совместимость schema/API versions и сообщения о conflicting IDs. + +**Ловушки.** Dependency cycle и Editor→Runtime допустимая зависимость не означают обратную допустимость. Optional integration не должна становиться безусловным dependency. Не обещать live unloading, пока код может удерживать delegates, task callbacks или GPU resources. Registry discovery может быть быстрым, но тяжёлый plugin initialization всё равно блокирует пользователя; его нужно измерять и откладывать. + +**Приёмка.** Установить локальный roads package без изменения editor source. Построить standalone runtime без Editor symbols. Конфликт двух importer/tool IDs даёт конкретное сообщение. Отсутствующая optional dependency скрывает только соответствующую интеграцию. Отключить пакет — не остаётся меню, listeners и скрытых объектов; связанные документы сохраняют диагностируемые unknown component data, если такая политика выбрана. + +## 6. Быстрый Play требует явных границ сессии и очистки состояния + +**Сценарий пользователя.** Многократно менять параметр, нажимать Play и останавливаться без долгого рестарта и без того, чтобы второй запуск отличался от первого из-за забытых static fields или двойных подписок. + +**Что подтверждено.** `EnterPlayModeOptions` отдельно содержит `DisableDomainReload` и `DisableSceneReload`; сами настройки — native bindings. [EditorSettings.bindings.cs:67](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/EditorSettings.bindings.cs#L67), [274](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/EditorSettings.bindings.cs#L274). Это два разных механизма: по enum нельзя восстановить полный native порядок сохранения сцен и перезагрузки runtime. + +В данном свежем снимке доступна более интересная C# часть: `PlayModeScope.Enter` исполняет lifecycle methods по порядку, а `Exit` — в обратном. [PlayModeScope.cs:20](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/External/ScriptingCore/Unity.Scripting/LifecycleManagement/PlayModeScope.cs#L20). `ScopeTransitionHelper` вызывает зарегистрированные callbacks, оборачивает их в profiling markers и обрабатывает исключения: [ScopeTransitionHelper.cs:65](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/External/ScriptingCore/Unity.Scripting/LifecycleManagement/ScopeTransitionHelper.cs#L65), обратный проход — 115–152. Native интеграция обозначена явными `RequiredByNativeCode` входами: [DomainReloadLifecycleController.cs:70](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Runtime/Scripting/LifecycleManagement/DomainReloadLifecycleController.cs#L70). + +Текущая документация описывает сохранение static state/events при выключенном domain reload, ручные `OnEnteringPlayMode`/`OnExitingPlayMode` и code-generated `AutoStaticsCleanup`/`NoAutoStaticsCleanup`. Она также различает field initializer и static constructor: очистка не означает повторный запуск всего конструктора типа. [Domain reload manual](https://docs.unity3d.com/Manual/domain-reloading.html). Это сведения конкретной текущей документации; переносить эти API и defaults на старые Unity нельзя. Декларации cleanup attributes доступны в [StaticsCleanupAttributes.cs:31](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/External/ScriptingCore/Unity.Scripting/LifecycleManagement/StaticsCleanupAttributes.cs#L31), но это само по себе не тело source generator. + +**Что перенять.** Разделить persistent editor state, project/document state и play-session state. Создавать simulation scope с явными start/stop hooks, владением subscriptions/tasks/resources и детерминированной очисткой. Для Faset принят отдельный Player со snapshot authoring-сцены; дальнейшие правки документа попадут в новый запуск. Прежние варианты in-process world/snapshot restore и live Apply **superseded** для MVP. После надёжного старта/остановки можно оптимизировать безопасные caches; runtime changes не становятся authoring Undo. + +**Ловушки.** Static event удерживает старый scene object; background task завершится уже в другой play session; отключение scene reload меняет ожидания component lifecycle. Пропуск полной перезагрузки даёт скорость только при полном контракте reset. Profile start/stop отдельно от compilation/import: иначе оптимизируется не главный источник ожидания. + +**Приёмка.** Сто циклов Play/Stop: одинаковое стартовое состояние, стабильное число подписок и отсутствие постоянного роста памяти. Остановить игру во время загрузки asset — поздний callback не меняет редакторскую сцену. Exception одного cleanup hook виден и не оставляет input/audio активными. Изменение runtime transform исчезает после Stop, а независимая authoring-транзакция остаётся в документе; обратный Apply из Player в MVP отсутствует. Сравнить cold start и fast start на одной сцене, без обещаний ускорения до измерения. + +## Предлагаемый порядок для собственного редактора + +Первым строить identity/schema, edit transactions, multi-selection и Undo: на них опираются почти все остальные средства. Вторым — default Inspector и один typed extension registry. Третьим — import context с зависимостями и сохранение документов. Затем contextual tools, пользовательский layout и модульные пакеты. Ускоренный Play вводить после определения состояния, которое обязано переживать или завершать сессию. + +Для вертикального прототипа достаточно одного собственного компонента «дорога»: импорт данных, поле ширины с multi-edit, точки с gizmo, одна overlay, сохранение prefab override и Play-проезд камеры. Такой сценарий проверит взаимную работу контрактов. Копия всего UI Unity, собственный marketplace и универсальный UI Builder на этой стадии не проверяют главные архитектурные решения. Итог этого исследования — требования и проверяемые механизмы; код нового движка здесь не создавался. + +Под требования Linux/Windows, 2D/3D и равного удобства ручной работы и MCP эти идеи адаптируются через общий document API. Действие из Inspector и MCP-команда должны обращаться к одной validation/transaction/schema модели, возвращать одинаковые изменения и понятный результат; UI остаётся представлением, а не единственным способом вызвать функцию. Runtime tick при этом не обязан проходить через редакторский Undo. Интеграция Blender должна пользоваться stable asset IDs, importer dependencies и общей диагностикой: повторный экспорт с устойчивыми source/output IDs обновляет прежний asset и сохраняет совместимые scene overrides; исчезнувшая цель требует явного разрешения конфликта. Обычный glTF/GLB import не требует add-on; optional Python add-on работает в официальном Blender и добавляет IDs/кнопку экспорта. Без IDs matching после rename не гарантируется. Это требования к нашему будущему движку, а не утверждение о наличии такого MCP/Blender контракта в изученном Unity-коде. В приёмке одного сценария дороги нужно сравнить ручное создание, MCP-создание и повторный импорт из Blender по семантическому результату, включая Undo и сохранение ссылок. diff --git a/docs/studies/10-blender-editor-patterns.md b/docs/studies/10-blender-editor-patterns.md new file mode 100644 index 0000000..e992ff1 --- /dev/null +++ b/docs/studies/10-blender-editor-patterns.md @@ -0,0 +1,75 @@ +# Blender: четыре архитектурных приёма для удобного редактора собственного движка + +**Синхронизация Faset, 18.09.2026.** Принятые решения — [ARCHITECTURE.md](../ARCHITECTURE.md), этапы до/после MVP — [PLAN.md](../../PLAN.md). Ниже сохранено исследование чужих исходников; это не отчёт о реализованных возможностях Faset. Рекомендации, помеченные **superseded**, остаются только историей рассмотренных вариантов. + +Для Faset приняты общие authoring-команды UI/MCP, explicit C++ metadata с TypeId/FieldId, JSON source со стабильными IDs и собственный retained C++ editor UI. MCP работает только с редактором, импортом, build, Play/Stop и editor logs; runtime world и Player через MCP недоступны. Blender не модифицируется: используется официальная программа, стандартный glTF/GLB importer Faset самодостаточен. Optional Python add-on может сохранять IDs и добавлять кнопку экспорта; он не является условием обычного импорта. [Границы roundtrip](17-asset-pipeline-and-blender-roundtrip.md). + +Blender здесь рассматривается как дополнительный источник идей для инструментов, а не как четвёртый игровой движок. Самые полезные находки связаны с тем, как одна реализация действия получает интерактивный инструмент, параметры, отмену, поиск и доступ из скрипта. Для собственного редактора это может оказаться важнее внешнего сходства окон и панелей. + +## Что именно исследовано + +17 сентября 2026 загружен официальный репозиторий [blender/blender](https://github.com/blender/blender) в `../../../blender-source`. Зафиксирован commit `28d47268bddcb9dc69143f0e2d9410969da16311`; файл версии объявляет **5.3.0 alpha**, то есть это снимок разработки, не стабильный релиз. [Версия в исходниках](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/blenkernel/BKE_blender_version.h#L23). + +Checkout намеренно неполный: shallow depth 1, partial clone `blob:none`, sparse directories `windowmanager`, `makesrna`, `editors/interface`, `editors/undo`, `asset_system`, `blenkernel`, `blenloader`, `scripts/modules/bpy`. Git status чистый. Учтены локальные инструкции `AGENTS.md`; в дереве данного commit нет `AGENTS.md` и graphify-графа. Ни сборка, ни запуск Blender не выполнялись. Ниже факты из конкретных тел функций отделены от предлагаемой адаптации; требования и ожидаемый эффект прототипов ещё не являются измеренными результатами. + +## 1. Параметризованная команда как основа любого инструмента + +**Сценарий.** Дизайнер размещает фонарь мышью, программист создаёт тот же фонарь скриптом, а технический художник добавляет пакетное размещение. Всем нужны одинаковые ограничения, отчёт об ошибке и логика создания объекта. Если эта логика живёт внутри обработчика кнопки, каждое новое представление инструмента дублирует её. + +**В исходниках.** `WM_operator_poll` проверяет доступность оператора в текущем контексте, включая составные операции и Python callback. `wm_operator_invoke` создаёт экземпляр с параметрами, выбирает интерактивный `invoke`, когда есть событие, или прямой `exec`, затем обрабатывает результат как Finished/RunningModal и другие состояния. Для интерактивного вызова могут восстанавливаться предыдущие параметры. [Проверка доступности](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/windowmanager/intern/wm_event_system.cc#L1110), [диспетчеризация](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/windowmanager/intern/wm_event_system.cc#L1640). + +Регистрация создаёт RNA-описание параметров оператора, присваивает идентификатор и UI-метаданные, помещает тип в общий реестр, уведомляет keymap о регистрации. В итоге расширение получает общую инфраструктуру, а не обязано самостоятельно встраивать каждую кнопку и сочетание клавиш. [Регистрация оператора](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/windowmanager/intern/wm_operator_type.cc#L108). Разделение `poll`, `invoke`, `execute` и `modal` подтверждается [официальным описанием операторов](https://developer.blender.org/docs/features/interface/operators/). + +**Минимум для своего движка.** Реестр `CommandType` со стабильным ID, схемой аргументов, `can_execute(context)`, `execute(targets,args)` и необязательным интерактивным сеансом. Сеанс получает события, показывает preview и завершается commit/cancel. Кнопка, hotkey, command palette, editor extension и MCP вызывают одинаковую authoring-команду. Игровой C++/будущий Lua работают через отдельный runtime API, без editor Undo. `invoke` собирает недостающие аргументы; изменение модели делает отдельный слой, доступный без UI. Для размещения фонаря это `asset_id`, transform, parent/entity IDs и параметры света; mouse picking нужен только интерактивному входу. + +Контекст своего редактора лучше описывать явно: документ, набор выбранных IDs, активный viewport, режим инструмента. На старте сеанса сохранить цель, а перед commit повторно проверить её существование и доступность. Не передавать всей бизнес-логике глобальную «текущую область интерфейса»: зависимость команды от случайного фокуса затрудняет тесты и автоматизацию. Modal-инструмент обязан откатывать preview при Escape и корректно завершаться при закрытии документа. + +**Проверка и приоритет.** Один сценарий создания/перемещения должен выдавать эквивалентную модель из меню, hotkey и скрипта. Недопустимый контекст возвращает причину, отменённый preview не оставляет изменений, удаление цели не вызывает use-after-free. Это **P0 для редакторного каркаса**, реализуемое раньше сложных dock/workspace систем. + +## 2. Отмена целого жеста и изменение параметров уже выполненного действия + +**Сценарий.** Пользователь долго крутит slider радиуса источника света, затем нажимает Undo один раз. Или создаёт кольцо из объектов и после завершения меняет количество экземпляров с 12 на 20. Это две связанные, но разные функции: удобные границы undo и повторное выполнение операции с новыми аргументами. + +**В исходниках.** `wm_operator_finished` централизованно выбирает обычный или grouped undo push. Счётчик `op_undo_depth` предотвращает отдельные шаги внутренних операторов, когда внешняя операция уже отвечает за undo. Поэтому составной инструмент не обязан засорять историю промежуточными действиями. [Граница завершения](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/windowmanager/intern/wm_event_system.cc#L1279), [глубина вложенного вызова](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/windowmanager/intern/wm_event_system.cc#L1392). + +`ED_undo_grouped_push` при совпадении имени группы очищает активный шаг перед новым push; название группы берётся из `undo_group` или имени операции. `ED_undo_push` применяет ограничения количества шагов и памяти. Это конкретная реализация слияния последовательных действий, а не универсальное доказательство, что любые одинаково названные операции безопасно объединять. [Grouped undo](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/editors/undo/ed_undo.cc#L357), [выбор группы](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/editors/undo/ed_undo.cc#L561), [бюджет истории](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/editors/undo/ed_undo.cc#L103). + +Самая интересная функция — `ED_undo_operator_repeat`. Она проверяет возможность повторения, восстанавливает подходящий регион контекста, откатывает прежний результат, проверяет параметры и снова запускает оператор. Если повторение не завершилось успешно, делает redo предыдущего результата. Это основа пользовательской возможности поправить параметры последнего действия, описанной в [руководстве Adjust Last Operation](https://docs.blender.org/manual/en/latest/interface/undo_redo.html). [Реализация повторения](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/editors/undo/ed_undo.cc#L879). + +**Минимум для своего движка.** Ввести `EditTransaction` с начальным и конечным состоянием затронутых сущностей. Один drag — одна транзакция, вложенные команды присоединяются к ней. Для slider хранить initial value и последний preview; для структурных изменений — список созданных/удалённых сущностей с полными данными восстановления. Слияние ограничить transaction token, document ID и набором target IDs, а не одним названием команды. + +После MVP можно добавить панель последней параметризованной операции; это исследовательский backlog, не условие первого экспорта. Хранить checkpoint до операции и её аргументы; каждое изменение параметров пересчитывать от checkpoint, не поверх предыдущего результата. Начать только с детерминированных операций, например массива объектов. Внешние записи файлов, импорты с побочными эффектами и команды, зависящие от новых случайных чисел, потребуют отдельного контракта. Не следует трактовать Blender operator как объект с обязательным `inverse()`: фактическое хранение undo отделено, тип выбирается по контексту и сериализует шаг через свои callbacks. [Выбор UndoType](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/blenkernel/intern/undo_system.cc#L82), [кодирование шага](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/blenkernel/intern/undo_system.cc#L618). + +**Проверка и приоритет.** Сто движений slider дают один шаг; cancel возвращает исходную модель; undo/redo восстанавливают удалённые связи; ошибка пересчёта оставляет предыдущий корректный результат; смена selection не меняет цель незавершённой операции. **P0 — транзакции, P1 — панель параметров последнего действия.** + +## 3. Описание свойства одновременно обслуживает Inspector и обновление модели + +**Сценарий.** Новый компонент получает поле «дальность света». Нужно число с единицами и диапазоном, tooltip, запрет редактирования read-only ресурса, вызов через скрипт и обновление освещения после изменения. Ручное описание этого поведения в каждом окне быстро расходится. + +**В исходниках.** RNA задаёт тип, семантический подтип, UI-название, диапазон, проверку редактирования и update callback. Например, `location` имеет translation subtype, привязку к данным объекта, функцию editable-array, UI range и transform notification. [Схема transform-свойств](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/makesrna/intern/rna_object.cc#L3177). `Layout::prop` читает метаданные, выбирает подпись/иконку и учитывает доступность поля. [Создание UI из свойства](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/editors/interface/interface_layout.cc#L1978), [read-only UI](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/editors/interface/interface_layout.cc#L2165). + +После редактирования `rna_property_update` вызывает callback, отправляет уведомление, публикует событие RNA в message bus и помечает данные для dependency graph согласно флагам свойства. При этом исходник явно учитывает опасность бесконечного redraw loop, когда значения операторских параметров меняются во время построения UI. Это полезное предупреждение непосредственно из реальной реализации. [Обновление свойства](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/makesrna/intern/rna_access.cc#L2536), [защита от цикла redraw](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/makesrna/intern/rna_access.cc#L2591). + +**Минимум для своего движка.** `PropertyDescriptor`: стабильный ID, value type, units, default, hard/soft ranges, enum labels, help, getter/setter, editability и категория invalidation. Один setter-путь проходит через validation и транзакцию, затем выдаёт typed change event. Inspector использует готовые widgets; автор компонента может переопределить только layout. Первый набор типов: bool, number, enum, vector, color и asset reference. Скриптовый API и panel plugins должны обращаться к той же схеме. + +Сначала менять модель, затем один раз отправлять уведомления после commit; дорогие пересчёты объединять по кадру или транзакции. Draw-функция читает данные и строит интерфейс, но не запускает безусловную мутацию. Для preview допустим отдельный лёгкий update, а mesh rebuild или shader compile можно отложить до подтверждения. + +Не путать reflection и формат проекта: RNA — высокоуровневое описание и доступ к свойствам, тогда как Blender DNA описывает низкоуровневые сохраняемые структуры. Это прямо разъясняется в [официальном FAQ DNA/RNA](https://developer.blender.org/docs/handbook/new_developers/faq/) и [документации RNA](https://developer.blender.org/docs/features/core/rna/). Для своего движка формат сцен, версии и миграции всё равно надо проектировать отдельно. + +**Проверка и приоритет.** Изменение через Inspector и script даёт одинаковую validation/invalidation; один commit вызывает один необходимый rebuild; locked asset объясняет запрет; новое поле появляется без написания нового widget; schema ID переживает переименование подписи. Это **P0**, но достаточно небольшой reflection-системы — весь makesrna/DNA pipeline не нужен. + +## 4. Поиск команд, который учит интерфейсу и объясняет недоступность + +**Сценарий.** Пользователь помнит действие, но не помнит меню. Плагин добавил новый инструмент, а отдельный индекс поиска никто не обновил. Или кнопка серая без объяснения, какой объект необходимо выбрать. + +**В исходниках.** `operator_search_update_fn` перебирает общий реестр операторов, фильтрует internal-операторы, сопоставляет слова, проверяет `poll`, добавляет клавиатурную подсказку. Выбранный результат вызывает тот же оператор. [Поиск по реестру](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/editors/interface/templates/interface_template_search_operator.cc#L35). + +Отдельный menu search строит UI меню, обходит получившиеся кнопки, сохраняет оператор вместе с аргументами и контекстом, рекурсивно собирает подменю и их путь. Это позволяет искать конкретный пункт, например действие с заранее выставленным enum, а не только абстрактную команду. [Захват параметров/контекста пункта](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/editors/interface/templates/interface_template_search_menu.cc#L155), [обход меню](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/editors/interface/templates/interface_template_search_menu.cc#L667). Tooltip disabled-кнопки заново получает причину из operator poll и показывает её пользователю. [Причина недоступности](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/editors/interface/regions/interface_region_tooltip.cc#L1269). + +**Минимум для своего движка.** Command palette из того же реестра: label, keywords, breadcrumb, shortcut и текущая доступность. Пункты меню хранить как данные «command + args + optional context», чтобы индексировать их без выполнения UI draw. На выборе обязательно повторять validation: между поиском и Enter сцена может измениться. Для неудовлетворённого precondition возвращать короткое объяснение вроде «Выберите хотя бы два объекта»; можно показывать такой результат disabled, вместо полного исчезновения команды. Последнее — предлагаемая UX-политика, а не поведение рассмотренного operator search Blender. + +**Проверка и приоритет.** Новая зарегистрированная команда появляется без ручной регистрации в поиске; shortcut меняется вместе с keymap; одинаковые имена различаются breadcrumb; действие использует параметры найденного пункта; переход в другой документ не применяет устаревший context. **P1, небольшой объём после готовности command registry.** + +Практический порядок: сначала команды, транзакции и небольшой слой метаданных; затем Inspector и command palette поверх них; затем интерактивные сеансы и изменение последней операции. Дополнение Blender ценно именно этой взаимосвязью: extensibility и удобство пользователя выходят из общей модели действий и данных, а не требуют четырёх несвязанных подсистем. + +В Faset декларативные layout/styles описывают собственные retained widgets; заимствование RNA/operator идей не означает перенос Blender UI-кода или собственного fork Blender. Lua поддерживается позднее обязательным модулем и необязателен для конкретной игры; Python add-on Blender — отдельная внешняя интеграция, а не выбор Python для gameplay Faset. diff --git a/docs/studies/11-ecs-and-ergonomics.md b/docs/studies/11-ecs-and-ergonomics.md new file mode 100644 index 0000000..e53c294 --- /dev/null +++ b/docs/studies/11-ecs-and-ergonomics.md @@ -0,0 +1,120 @@ +# ECS: стоит ли применять и как сделать удобно + +Исследование от 17.09.2026. Основание: тела UE 5.8.2 MassEntity в локальном commit `16d75d84714512edfb744e1fd0a59e9c74d57873`, официальные документы Flecs 4.1, Bevy ECS 0.19.1, Unity Entities 1.4 и репозиторий EnTT. Производительность вариантов не измерялась. Ниже есть подтверждённые механизмы и отдельно предлагаемые решения для нового движка. + +**Актуализация 18.09.2026:** runtime ECS и **EnTT приняты**, ядро/gameplay сначала на C++, Lua добавляется следующим языковым этапом. Текущие контракты — в [архитектуре](../ARCHITECTURE.md), реализация и проверки — в [PLAN.md](../../PLAN.md). Разбор Mass/Flecs/Bevy ниже сохраняет исследовательское обоснование; это не продолжающийся конкурс библиотек. Код Faset ещё не создан. + +## Принятое решение для нашего движка + +**Используем EnTT для runtime с собственным авторским слоем сцен, объектов и компонентов.** Не делать архетипы, chunks и command buffers обязательными понятиями для художника или автора простого скрипта. ECS нужен как способ организовать состояние и обработку, а удобство должно обеспечиваться Inspector, шаблонами, типизированными API, понятным расписанием и диагностикой. + +Библиотечные детали закрываются API Faset; собственный storage не входит в первый этап. EnTT выбран ради ограниченной интеграции storage/views с нашим API, а не доказанного преимущества скорости. Проверки прототипа проверяют реализацию выбранного решения. Безболезненная замена backend не обещается: semantics queries, ownership и relationships влияют на дизайн. + +## Что здесь означает ECS + +- **Entity** — идентификатор объекта с жизненным циклом. +- **Component** — типизированные данные, принадлежащие entity. +- **System** — обработчик набора данных, например движения всех объектов с Position и Velocity. +- **Query** — описание набора компонентов и фильтров, по которому система получает данные. + +Компонентная композиция сама по себе ещё не даёт cache-friendly ECS. Тысяча объектов, у каждого из которых массив виртуальных `Update()`-компонентов, отличается по исполнению от одного пакетного прохода по Position/Velocity. Но ECS тоже не равен обязательным archetypes: существуют sparse-set, table/archetype и гибридные способы хранения. Bevy документирует tradeoff: table storage ориентировано на итерацию, sparse sets — на добавление/удаление компонентов; конкретный результат зависит от нагрузки. [Bevy ECS: storage и системы](https://docs.rs/bevy_ecs/0.19.1/bevy_ecs/). + +## Где ECS полезен, а где его не стоит навязывать + +**Сильные кандидаты:** множество похожих движущихся объектов, снаряды, толпы, состояние AI-агентов, массовое обновление transforms, подготовка данных для renderer, фильтрация объектов по составу компонентов. Общий признак — повторяемая обработка похожих данных, которую можно выполнять пакетами. + +**Слабые кандидаты для обязательного ECS API:** layout редактора, диалог импорта, undo stack, compiler graph, сетевой клиент, asset database, GPU allocator. Они могут взаимодействовать с ECS, оставаясь обычными сервисами и структурами данных. Даже игровую логику единичной двери или меню необязательно писать как несколько глобальных систем. + +**ECS не гарантирует** ускорение маленькой сцены, deterministic physics, масштабирование по потокам, хорошую архитектуру или простые скрипты. Случайные lookup по связанным entity могут вернуть cache misses; избыточное дробление данных — множество queries; частые смены состава — миграции между хранилищами. Пользу нужно проверять на полном кадре, включая adapters, physics и render extraction. + +## Что реально видно в UE MassEntity + +### 1. Данные упаковываются в массивы компонентов внутри chunk + +`ConfigureFragments` считает размер entity, доступный объём chunk, вместимость и offsets выровненных массивов каждого fragment. Это конкретная реализация структуры данных с массивом на тип компонента, а не только декларация data-oriented design. [MassArchetypeData.cpp:320](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/MassEntity/Private/MassArchetypeData.cpp#L320). + +**Вывод для нас:** hot-компоненты делать компактными и группировать по реальным проходам. Не обязательно разносить каждое поле Transform по отдельному компоненту: сначала выяснить, какие системы читают и изменяют поля вместе. В authoring Inspector можно показывать один блок Transform, даже если runtime layout иной. + +### 2. Смена архетипа имеет реальную стоимость + +`MoveEntityToAnotherArchetype` выделяет место в новом архетипе, переносит fragments и освобождает старое. Перенос копирует значения общих fragments для переносимых entities, инициализирует добавленные fragments и уничтожает исчезнувшие. [Миграция](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/MassEntity/Private/MassArchetypeData.cpp#L775), [перенос данных](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/MassEntity/Private/MassArchetypeData.cpp#L1759). + +**Вывод для нас:** состояние, меняющееся каждый кадр, не стоит автоматически кодировать add/remove набора tags. Для частых переключений рассмотреть поле state или enable mask. Это уменьшает миграции, но добавляет стоимость фильтрации. Unity отдельно предоставляет enableable components именно для частых переключений без структурных изменений. [Unity Entities: enableable components](https://docs.unity3d.com/Packages/com.unity.entities@1.4/manual/components-enableable-intro.html). + +### 3. Запросы кешируют подходящие архетипы + +`CacheArchetypes` проверяет версию набора архетипов, дополняет список подходящих и сохраняет mapping компонентов. При изменении requirements либо world сбрасывает кэш. Выполнение затем идёт по найденным archetypes/chunks. [Кэш запроса](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/MassEntity/Private/MassEntityQuery.cpp#L138), [выполнение](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/MassEntity/Private/MassEntityQuery.cpp#L368). + +Flecs также различает долгоживущие cached queries и одноразовые uncached queries: выбирать следует по частоте повторного выполнения и стоимости поддержки кэша. [Flecs: Queries](https://www.flecs.dev/flecs/Queries.html). + +**Вывод для Faset:** регистрировать требования систем заранее; EnTT views создавать по месту выполнения, не предполагая наличия Mass-подобной компиляции запросов. До параллельного запуска подготовить нужные component storage. Сложный динамический поиск в редакторе не должен навязывать ту же цену каждому объекту gameplay. + +### 4. Структурные изменения откладываются и исполняются по явным правилам + +`FMassCommandBuffer::Flush` группирует операции, стабильно сортирует группы и выполняет batch-команды. Порядок определяется типом операции; это не просто FIFO. Даже совместимость observer callbacks со временем удаления данных требует отдельного кода. [MassCommandBuffer.cpp:94](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/MassEntity/Private/MassCommandBuffer.cpp#L94), [исполнение](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/MassEntity/Private/MassCommandBuffer.cpp#L217). + +**Принято для Faset:** runtime structural changes применять одним владельцем в начале следующего fixed tick после завершения прежних задач. Определить поведение `create → add → delete` в одном tick, повторного delete, конфликтных записей и событий удаления. Не копировать порядок Mass вслепую: это часть публичной семантики нашего runtime. В Unity структурные изменения также могут вызывать synchronization points; entity command buffers позволяют записать изменения для последующего воспроизведения. [Структурные изменения Unity](https://docs.unity3d.com/Packages/com.unity.entities@1.4/manual/concepts-structural-changes.html), [Entity command buffers](https://docs.unity3d.com/Packages/com.unity.entities@1.4/manual/systems-entity-command-buffers.html). + +### 5. Handle не является вечной ссылкой на объект + +Mass handle содержит index и serial; хранилище сравнивает serial с текущим при проверке, а при выдаче слота назначает новый. При этом `FMassEntityHandle::IsValid()` сам по себе только проверяет заполненность, не существование entity. [Handle](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Mass/MassCore/Public/Mass/EntityHandle.h#L12), [проверка и выдача](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/MassEntity/Private/MassEntityManagerStorage.cpp#L97). + +**Вывод для нас:** runtime handle должен проверяться world; сериализуемый SceneEntityId должен быть отдельным типом. В разных worlds одинаковый локальный index не должен случайно обозначать один объект. Долгоживущие ссылки не должны хранить указатель на компонент, который переедет при structural change. + +## Как сделать ECS удобным + +### Автор работает со сценой, runtime — с оптимизированным представлением + +В редакторе: `Enemy.scene`, иерархия, mesh, collider, health, script, overrides. При запуске compiler создаёт runtime entities и компоненты, сохраняя карту происхождения `SceneEntityId → RuntimeEntity[]`. Связь может быть один-ко-многим: один авторский персонаж содержит несколько runtime объектов. Обратное отображение тоже нужно для выбора объекта мышью и сообщений об ошибках. + +Unity baking — полезный прецедент разделения человекочитаемой authoring-модели и оптимизированных runtime-данных; документация прямо предупреждает, что преобразование не обратимо. Faset не переносит runtime-изменения в авторскую сцену автоматически; возможное будущее GUI-действие такого переноса потребует отдельного контракта и не предоставляется через MCP. [Unity: baking overview](https://docs.unity3d.com/Packages/com.unity.entities@1.4/manual/baking-overview.html). + +Принятый Inspector показывает authoring-состояние, источник значения и sparse overrides вложенных шаблонов. Runtime существует в отдельном Player. Идея вкладки «Игра» и выборочного применения значений из раннего исследования не входит в начальный контракт; MCP runtime inspection/mutation исключён. Редакторский объект не становится неявной двусторонне синхронизируемой копией компонента. + +### Два уровня программирования, одно состояние игры + +Для простых сценариев — знакомый фасад: получить entity по типизированной ссылке, прочитать компонент, подписаться на событие, создать экземпляр сцены. Например, сценарий двери реагирует на взаимодействие и меняет целевой угол. Для массовой логики — типизированная system/query с `Read` и `Write`, обрабатывающая весь набор. + +Фасад — представление над теми же данными и проверяемыми handles, а не отдельный объектный мир, который требуется синхронизировать. В hot loop система пишет компоненты напрямую в пределах выданного доступа. Создание и удаление entity идут через runtime command buffer. Editor tool, консоль автора и автоматизация используют authoring-транзакции с validation/undo для изменений авторских документов. MCP ограничен авторскими документами и сервисами редактора: он не читает и не меняет runtime world/session. Native debugger остаётся отдельным инструментом программирования. **Gameplay structural commands не объединяются с editor Undo.** + +### Явное расписание без обязательного знакомства с scheduler + +C++-поведения, а позднее Lua, используют `OnStart`, `FixedUpdate`, `Update`, `LateUpdate`, `OnDestroy`. `OnStart` выполняется после создания и до первого обновления; `OnDestroy` — при применении удаления до освобождения допустимых данных. Для систем доступны read/write declarations, dependencies и фазы до/после физики. Начальный scheduler последовательный; параллельное выполнение вводится после профилирования и проверки доступов. + +Fixed tick по умолчанию 60 Гц, настраивается проектом. В начале tick применяются structural commands предыдущего tick; затем gameplay до физики, команды Box2D/Box3D, завершение шага, перенос transforms/events и реакции после физики. После фиксированных ticks идут `Update`, подготовка интерполированных presentation transforms, `LateUpdate` (камера и зависимые визуальные объекты), затем финализация render snapshot. `Update/LateUpdate` вызываются один раз за игровой кадр. Catch-up ограничен; начальный лимит четыре ticks за проход, избыточные целые интервалы отбрасываются с диагностикой. Render interpolation между завершёнными ticks не пишет результат обратно в физику; spawn/teleport сбрасывают историю. + +EnTT registry не является целиком thread-safe. До распараллеливания создаются storage; чтение/запись компонентов и внешних ресурсов объявляется явно. `ENTT_USE_ATOMIC` не заменяет синхронизацию пользовательских данных. `organizer` может дать граф зависимостей, но scheduling остаётся обязанностью Faset. [EnTT: multithreading и organizer](https://github.com/skypjack/entt/wiki/Entity-Component-System). + +Нужен Inspector системы: фаза, reads/writes, dependencies, сколько entities совпало, почему query пуста, время, выделения, последняя ошибка. Сообщение «не найден Velocity» полезнее молчаливого отсутствия движения. Bevy показывает, что типизированные параметры функций могут задавать доступ к данным, а порядок задаётся явными зависимостями; это хорошая идея интерфейса, даже при выборе другого языка. [Bevy ECS: systems/schedules](https://docs.rs/bevy_ecs/0.19.1/bevy_ecs/). + +### Иерархия остаётся понятной + +Parent/child нужны для организации сцены, трансформаций и ownership. Это не значит, что все они обязаны иметь одну семантику удаления. Нужно отдельно определить transform parent, owner сцены и attachment. Массовое удаление parent, перепривязка child с сохранением world transform и unload сцены должны иметь предсказуемые правила. + +В runtime transform propagation можно делать специальным проходом в порядке зависимостей. Renderer и physics не обязаны обходить дерево ради каждого запроса. В EnTT иерархия оформляется собственными компонентами/индексом Faset; transform parent и ownership не обязаны совпадать. Контракт вложенных шаблонов хранится в authoring, а стоимость runtime traversal измеряется отдельно. + +### События и изменения должны быть видимы + +Разделить события домена (`Damage`, `DoorOpened`) и служебные lifecycle уведомления (`component added`). Доменные события лучше начать с явных очередей и фазы доставки. Цепь скрытых observers, меняющих друг друга рекурсивно, плохо объясняется и тестируется. Flecs прямо различает немедленное emit и enqueue в deferred mode. [Flecs: observers](https://www.flecs.dev/flecs/ObserversManual.html). + +Change detection полезна для transform uploads и перестроения инструментов, но «был mutable-доступ» не всегда равно «значение изменилось». Предлагаю version/dirty markers с документированной семантикой и debug-кнопку «кто изменил». Не журналировать все значения в shipping build по умолчанию; подробную историю включать для выбранного объекта/системы. + +## Что взять готовым и что написать самим + +**История выбора от 17.09:** сравнивались **Flecs** (queries, relationships, phases/modules), **EnTT** (registry, views/groups, настройка storage) и, для варианта Rust, **bevy_ecs**. **Решение 18.09: C++ и EnTT.** Сильная сторона Flecs — готовая согласованная модель отношений и исполнения; Faset выбрал более узкую роль ECS и собственные authoring/metadata/API. Это архитектурный выбор, не рейтинг скорости. [Flecs design guide](https://www.flecs.dev/flecs/DesignWithFlecs.html), [EnTT repository](https://github.com/skypjack/entt), [bevy_ecs](https://docs.rs/bevy_ecs/0.19.1/bevy_ecs/). + +**Самим стоит написать слой, который отличает наш движок:** schema metadata, scene compiler, stable IDs, runtime mapping, Inspector, удобный scripting façade, system debugger, authoring transactions и tools SDK. Не стоит первой задачей писать свой parallel archetype allocator, borrow checker и универсальный query language. + +Если цель отдельно учебная — простой sparse-set ECS хорош как эксперимент. Но решать, нужен ли он в продукте, следует после сравнения с готовой библиотекой, с учётом tooling и жизненного цикла native/managed данных. + +## Прототип, который проверит решение + +1. **Обычная игра.** Создать сцену с игроком, дверью, несколькими врагами и UI. Простой script должен читаться без ручных archetype/chunk API. +2. **Массовая обработка.** 1 тыс., 10 тыс. и 100 тыс. entities с одной реальной системой движения/выбора целей. Сравнить простой packed-array baseline, выбранную ECS и удобный façade. Это параметры будущего теста, а не заявленные границы производительности. +3. **Изменение состава.** Варианты со стабильными компонентами и частыми spawn/despawn/add/remove. Проверить pointer/handle lifetime, очереди, latency и worst frame. +4. **Редактор.** Multi-edit, undo одного drag, duplicate вложенного шаблона, sparse overrides и одинаковая authoring-правка через GUI/MCP; отсутствие MCP-доступа к runtime. Инкрементальная пересборка должна давать семантически тот же результат, что и полная, без требования одинакового порядка сущностей в памяти. +5. **Расширение.** Отдельный пакет добавляет компонент Health, систему Damage, Inspector decoration и prefab preset без изменения ядра. + +Измерять whole-frame p50/p95, allocations, bytes/component, размеры component pools и эффективность views, время structural playback, query count, render extraction и authoring→visible latency. Для UX — число действий, ошибок и обращений к документации при одинаковой задаче. Для параллельного выполнения — корректность порядка и отсутствие data races; сам факт запуска на нескольких потоках не критерий успеха. + +**Критерий готовности интеграции EnTT:** она даёт полезную производительность и композицию, сохраняя простой authoring workflow. Если пользователь движка должен изучить устройство allocator, чтобы добавить фонарь или дверь, интерфейс ещё не готов. diff --git a/docs/studies/12-mcp-and-blender-integration.md b/docs/studies/12-mcp-and-blender-integration.md new file mode 100644 index 0000000..6fdec56 --- /dev/null +++ b/docs/studies/12-mcp-and-blender-integration.md @@ -0,0 +1,99 @@ +# Один движок для ручной работы, MCP и Blender + +**Принято 18.09.2026: редактор, MCP и интеграции пользуются одним сервисом авторских данных.** Сам сервис умеет работать без окон; GUI добавляет выделение, gizmos и preview, MCP — типизированный доступ для агента, Blender — подготовку и обновление ассетов. Пользователь должен свободно переходить между этими способами, сохраняя историю, идентификаторы, проверки и результат. + +Это принятый проект архитектуры, не реализованная интеграция; код движка ещё не создан. Актуальные решения — в [архитектуре](../ARCHITECTURE.md), этапы и критерии готовности — в [PLAN.md](../../PLAN.md). **MCP существует только в Editor/headless editor services: authoring, import, build, Play/Stop и журналы редактора. Никаких runtime inspection/mutation, world/session tools, MCP в Player, экспортной игре или SchemaExporter.** Он дополняет [UX редактора](./02-editor-ux.md), [разбор Godot](./08-godot-ux-source-study.md), [паттерны Blender](./10-blender-editor-patterns.md) и [ECS](./11-ecs-and-ergonomics.md). Исходники сверены в прежних commits: Godot `9c776068d6ed23acd0c78bfe534272d1d2a3a619`, Blender `28d47268bddcb9dc69143f0e2d9410969da16311`. Документация проверена 17.09.2026; для MCP зафиксирована редакция **2026-07-28**, поддержка которой конкретным клиентом не предполагается автоматически. + +## 1. AuthoringService как самостоятельное ядро редактора + +Принятое разделение: UI / MCP adapter / importer вызывают AuthoringService; он владеет документами сцен, схемами компонентов, транзакциями, asset registry и job system. Runtime получает подготовленное представление через authoring→runtime bridge. Сервис может жить в процессе редактора или отдельном локальном процессе: важен единый API, а не обязательная микросервисная архитектура. + +Все вызовы содержат явные `project_id`, `document_id` и стабильные entity/asset IDs. Команда изменения не зависит от положения курсора, активного tab или текущего выделения. GUI разрешает selection в IDs перед вызовом; MCP сначала делает query и получает те же IDs. Имена остаются подписями; runtime handle с generation не используется как постоянный идентификатор документа. + +Практическое основание видно в Blender: одна операторская модель разделяет проверку контекста, интерактивный invoke и прямой exec, а завершение централизует undo. Для нашего headless API интерактивный сбор аргументов должен оставаться снаружи authoring-операции. [Диспетчер Blender](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/windowmanager/intern/wm_event_system.cc#L1640), [завершение операции](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/windowmanager/intern/wm_event_system.cc#L1279). + +## 2. Типизированный API, который агент способен обнаружить + +Начальный набор семейств: `project.describe`, `schema.describe`, `scene.query`, `scene.inspect`, `edit.apply_batch`, `history.inspect`, `assets.import`, `build.start`, `operations.get/cancel`, `editor.play/stop`, `editor.logs`. Это **проектные имена tools**, не встроенные методы MCP. Все `scene.*` относятся к authoring-документу. Схемы описывают тип, единицы, диапазон, default, read-only, inherited/local provenance и правила ссылок; их декларативный источник общий с Inspector. `runtime.inspect/step`, чтение игровых компонентов и изменение симуляции не предоставляются. Preview может читать только зафиксированную authoring revision через editor renderer. + +`scene.query` принимает фильтр по типам компонентов, tags, asset references и bounds; возвращает выбранные поля, stable IDs, cursor и `snapshot_revision`. Продолжение pagination относится к тому же snapshot либо явно сообщает его истечение. `scene.inspect` отдаёт значение, источник, override и диагностику. Агенту не приходится угадывать JSON по screenshot или получать всю сцену для изменения одного поля. + +У команд и результатов есть версии схем, примеры аргументов и ограничения. Результат содержит `status`, `revision_before/after`, `transaction_id`, mapping временных IDs в созданные IDs, changed IDs, warnings и ссылки на подробный diff. Ошибки — структурированные `RevisionConflict`, `MissingTarget`, `ValidationFailed`, `UnsupportedCapability`, с точным полем и текущей revision. MCP поддерживает `inputSchema`, `outputSchema`, `structuredContent`, а ошибки выполнения tools отличаются от ошибок JSON-RPC. Сам MCP не обеспечивает наши транзакции или idempotency: это ответственность AuthoringService. [MCP Tools 2026-07-28](https://modelcontextprotocol.io/specification/2026-07-28/server/tools). + +Для расширения движка важен контракт плагина: C++ регистрация компонента экспортируется отдельным SchemaExporter в декларативный manifest; Inspector и editor MCP читают одну схему. Custom Inspector, исполняемые authoring validation/migration и commands относятся к отдельному editor module/helper-контракту. Gameplay-код не загружается в Editor ради обнаружения полей и не становится MCP tool автоматически. Произвольный `execute_python` или shell не является обязательным универсальным путём редактирования сцены. + +## 3. Транзакции, одновременная работа человека и агента + +`edit.apply_batch` получает `base_revision`, `idempotency_key`, человекочитаемое название и список типизированных операций. Пример задачи: создать 20 фонарей, выставить transform, назначить общий prefab и индивидуальную мощность — один batch с временными ссылками между создаваемыми сущностями. + +Сервис сначала проверяет весь batch на изолированном состоянии, затем сравнивает revision и публикует результат целиком. Первая версия гарантирует атомарность **одного authoring document**; импорт ассета и правки нескольких сцен не объявляются общей атомарной транзакцией без отдельной реализации. Если человек успел изменить документ, сервер возвращает conflict и diff, не выполняя молчаливое last-write-wins. Для первого прототипа достаточно общей document revision; field-level conflict detection можно добавить позже. + +Idempotency scope включает проект и клиента; журнал хранит ключ, hash канонического payload и результат. Повтор того же запроса возвращает прежний результат; одинаковый ключ с иными аргументами — ошибка. Сохранение dedup record согласовано с commit, иначе сбой после создания объектов породит дубликаты при retry. Это ограниченная прикладная гарантия с документированным сроком хранения ключей, а не обещание «exactly once» любой внешней операции. + +**Ручной drag:** begin edit → много временных preview → один commit; Escape отменяет preview. **MCP:** один атомарный batch с готовыми значениями, без имитации mouse events. Оба пути создают одинаковый undo record и notifications. На время drag сервис может удерживать короткую lease на затронутые поля; конфликтующий batch получает `TargetBusy` либо revision conflict. Агент не должен незаметно двигать объект под рукой пользователя. + +Undo — отдельная authoring-функция с сохранёнными обратными данными и зависимостями. Для MCP `undo(transaction_id, expected_revision)` в v1 разрешён только для подходящей вершины истории; нельзя перескочить поверх поздней ручной правки и стереть её. Godot также явно связывает commit с историей документа, nested action и merge, а не просто выполняет обратный setter. [Godot commit_action](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/editor_undo_redo_manager.cpp#L246). + +ECS command buffer решает другую задачу: безопасно применить runtime structural changes в sync point. Он сам по себе не даёт persistent IDs, rollback, сохранение сцены или undo. В Player такие команды адресуют собственный runtime world и применяются в начале следующего fixed tick. Через MCP они недоступны; запуск/остановка Play не открывает канал чтения или записи игрового состояния. Автоматического переноса runtime-значений в authoring нет. + +## 4. Долгие операции, отмена и протокольный адаптер + +Импорт, baking, сборка и сложный preview возвращают engine `operation_id`. `operations.get` показывает phase, выполненные work units, доступность cancel, diagnostics, artifact URIs и итоговую revision. Journal позволяет найти завершившуюся операцию после разрыва соединения. `operations.cancel` запрашивает кооперативную остановку; до точки публикации staging удаляется, после commit операция сообщает completed и undoability. Отмена запроса не равна откату уже сохранённой сцены. + +MCP adapter подбирает механизм под версию и capabilities клиента. Для живого запроса возможно protocol progress; для долгоживущих задач базовый переносимый путь — наши tools `operations.get/cancel`. В 2026-07-28 существует отдельное расширение Tasks с `tasks/get`, `tasks/update`, `tasks/cancel`; его можно отобразить на тот же job system при поддержке обеими сторонами. Tasks не используют обычные progress notifications, поэтому не нужно обещать их всем клиентам. [Progress](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/progress), [Tasks extension](https://tasks.extensions.modelcontextprotocol.io/specification/2026-07-28/tasks). + +Transport cancellation тоже зависит от версии: в проверенной редакции HTTP SSE disconnect отменяет текущий request, stdio использует `notifications/cancelled`; durable task отменяется отдельно. Adapter должен сохранять семантику job, а не считать любую потерю HTTP-соединения подтверждённым rollback. [Cancellation 2026-07-28](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/cancellation). Явные handles и собственные revisions полезны и для старых MCP-клиентов; перенос новых правил handshake на них не требуется. + +Для локального MVP подходит stdio bridge к выбранному проекту. MCP OAuth-профиль описывает HTTP; для stdio спецификация предусматривает получение credentials из окружения. Если позднее нужен сетевой HTTP endpoint, включается соответствующая authorization с проверкой доступа к проекту и операциям. Достаточно явных прав read/edit/build и видимого происхождения изменений в истории; окна подтверждения каждой обратимой правки не являются целью дизайна. [MCP Authorization](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization). + +## 5. Наблюдаемость и возможности без GUI + +Headless editor умеет открыть проект, query/validate/edit/save authoring-сцены, импортировать ассеты, выполнять build и управлять процессом Play/Stop без чтения симуляции. Это не требует selection и оконного event loop. Возможности объявляются явно: `authoring`, `asset_import`, `build`, `editor_play_control`, `authoring_preview`; render capability зависит от доступного backend. Состояние job, compiler/import diagnostics и editor logs не подменяются runtime world query. + +Authoring screenshot — инструмент проверки редакторской правки, но не канал наблюдения за Player. Проектный `preview.render` принимает authoring scene revision, камеру, размер и overlays; результат связывает PNG с этими параметрами и render frame ID редакторского preview. При наличии renderer/GPU возможен offscreen preview без окна. На чистом CI без графического backend возвращается `UnsupportedCapability`, при этом редактирование данных остаётся доступным. Совпадение пикселей между любыми GPU не обещается. + +Diff, scene snapshots, dependency reports и логи доступны по engine resource URI; крупные артефакты выдаются ссылками, небольшое изображение можно вернуть image content. URI включает immutable revision или content hash, чтобы screenshot не выдавался за картинку более нового состояния. MCP действительно предусматривает ресурсы по URI, templates и binary contents; engine naming и retention policy задаём мы. [MCP Resources](https://modelcontextprotocol.io/specification/2026-07-28/server/resources). + +## 6. Blender v1: glTF плюс manifest, исходники и overrides отдельно + +Принят обычный Blender без обязательного форка или MCP-плагина. Базовый путь — обычный экспорт GLB и импорт в Faset; importer хранит asset manifest/mapping. Необязательный собственный add-on упрощает экспорт выбранной collection, назначение устойчивых subasset IDs и согласованную публикацию GLB+manifest. Автор продолжает работать в `.blend`; runtime и обычный импорт движка его не открывают. Без source IDs нельзя гарантировать сохранение соответствия после произвольного rename/reparent; неоднозначность показывается пользователю. + +`bpy.ops.export_scene.gltf` официально имеет выбор format/collection, `export_extras`, `export_yup`, настройки материалов, skin/morph/animations и sampling. API предупреждает, что `export_apply` для modifiers препятствует экспорту shape keys. Поэтому export recipe фиксирует Blender/exporter version и все значимые опции, вместо зависимости от последнего состояния UI. [Blender export Python API](https://docs.blender.org/api/main/bpy.ops.export_scene.html). + +Manifest хранится вместе с authoring-данными в Git: `asset_id`, `bundle_revision`, source reference/hash, exporter/recipe version, dependencies, coordinate/unit policy, mapping object/mesh/material/animation IDs, engine metadata. Данные сначала пишутся в staging generation; проверяются hashes и completeness; затем один commit marker публикует поколение. Importer не должен видеть glTF нового поколения с manifest старого. Derived mesh/texture data и platform cache живут отдельно и пересобираются. + +Это согласуется с наблюдаемым подходом Godot: reimport читает настройки и UID из sidecar, вызывает importer с отдельным output path, затем записывает importer version и UID. [Сохранённые параметры](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L2826), [вызов и результат импорта](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L2922). Предлагаемый staging/commit marker — наше усиление, не заявление об атомарности этих функций Godot. + +## 7. Устойчивая идентичность при повторном импорте + +При использовании необязательного add-on первая публикация назначает UUID asset collection, объектам, mesh datablocks, материалам и поддерживаемым animation clips. Без add-on importer выдаёт собственные asset/subasset IDs и сохраняет mapping, но не выдаёт индекс или имя glTF за устойчивую source identity. IDs сохраняются в Blender custom properties и manifest; glTF `extras` можно использовать как дополнительный канал. В Godot прочитанный importer действительно переносит node extras в metadata, но это не означает автоматическую устойчивую идентичность: её семантику должен реализовать наш importer. [Node extras](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/modules/gltf/gltf_document.cpp#L619). + +Имя и порядковый индекс glTF не являются ключом reimport. Duplicate создаёт новый object ID, сохраняя shared mesh ID, если геометрия разделяется. Helper обнаруживает случайно скопированные GUID и предлагает/выполняет однозначное исправление; linked assets получают namespace по source asset. Rename/reparent не меняют identity. Content hash служит для кэша, но не заменяет UUID: изменение вершины не должно превращать объект в новую сущность. + +Модель данных движка: immutable imported baseline + prefab/scene instances + локальные overrides по `(source_id, property_id)`. Reimport обновляет baseline, затем повторно накладывает overrides: placement экземпляра, выбранный engine material, gameplay components и collision settings сохраняются. Material slot требует собственного стабильного ключа, а не только позиции в массиве. Если исходный узел удалён, но имеет overrides/ссылки, возникает диагностируемый orphan/conflict; такие данные не следует молча выбрасывать. + +Границы владения обозначены в Inspector: геометрия и skeleton принадлежат Blender; gameplay и размещение instances — движку; engine-specific material replacement — локальному override. V1 roundtrip означает «открыть источник → изменить → экспортировать → безопасно обновить instances», а не безошибочную двустороннюю синхронизацию произвольных Blender-сцен. + +## 8. Контракт формата и следующий шаг live link + +Профиль импорта должен последовательно покрыть triangle meshes, UV/нормали/tangents и metal-rough PBR; skinning, clips и morph targets вводятся на соответствующих этапах [плана](../../PLAN.md). Поддержка пока не реализована и не проверена. Процедурные node graphs и Geometry Nodes не превращаются в engine shaders: нужен bake/evaluated mesh и отчёт о потерях. glTF описывает свою систему материалов, а Blender exporter распознаёт поддержанные узлы. [Blender: glTF materials](https://docs.blender.org/manual/en/4.0/addons/import_export/scene_gltf2.html). + +glTF использует правую систему координат, Y-up и метры; наши engine-конвенции фиксируются в recipe, с преобразованием ровно на одной границе. Проверять root transforms, nonuniform/negative scale, winding и tangent handedness. Имена glTF не гарантируют уникальность. [glTF 2.0: координаты и структуры](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html). + +Для animation заранее определить rest pose, sampling rate, root motion, clip ranges и ограничения joints/influences. Constraints/IK могут потребовать bake в поддержанные channels; не обещать перенос всей Blender-логики. Collision и gameplay не считать универсальной частью базового glTF: v1 описывает engine shapes в manifest либо строит их по явно маркированным meshes. Unsupported extensions выдаются списком до публикации; материалы не должны незаметно становиться «почти похожими». + +V2 live link использует типизированное локальное соединение helper↔AuthoringService: handshake версий, список capabilities, asset ID, base revision, generation/hash и операции `publish_asset`, `set_transform`, `focus_source`, `query_status`. Тяжёлая геометрия передаётся файлами/блоками; metadata — сообщениями. Revision/origin IDs подавляют echo loops; после reconnect сравниваются поколения. Непрерывный preview можно слать чаще, persistent commit — по подтверждению или завершению жеста. Обе стороны сохраняют собственный undo; согласованный commit получает correlation ID, но «одна общая undo-stack на два приложения» не обещается. + +## 9. Три сквозных сценария приёмки + +**Ручная сцена → MCP → ручной Undo.** Пользователь ставит один фонарь, меняет цвет и сохраняет prefab. Агент находит prefab и поверхность query-запросом, одним batch размещает ряд экземпляров, возвращает IDs/diff/preview. Пользователь видит изменения в Tree/Inspector и одним Undo отменяет весь ряд. Повтор с тем же key/payload в пределах срока хранения dedup-журнала не создаёт дубликаты; конкурентный drag даёт понятный conflict; ручной и MCP-путь одинаково валидируют мощность света. + +**Blender → экземпляры → reimport.** Художник публикует дверь; пользователь ставит три экземпляра, одному меняет материал, всем добавляет gameplay. В Blender дверь переименовывается, меняется mesh и animation. Повторный экспорт сохраняет IDs, placement и overrides; удаление узла с override создаёт conflict. Прерванный экспорт оставляет последнее корректное поколение. После очистки derived cache импорт повторяется с теми же semantic IDs и содержимым в пределах заданного recipe. + +**Headless editor MCP → build → возвращение в GUI.** Агент без окна редактора импортирует bundle, правит и валидирует authoring-сцену, выполняет build, получает compiler/import/editor diagnostics. Play/Stop управляет только жизнью процесса; при поддержке renderer доступен PNG authoring revision, не runtime capture. Cancel до commit не публикует частичный результат; после reconnect операция находится по ID. GUI открывает тот же документ с той же revision и историей. Проверка отклоняет любые запросы MCP на чтение или изменение runtime; Player и SchemaExporter не содержат MCP-зависимостей. + +## 10. Порядок разработки + +Порядок до MVP и после него зафиксирован в [PLAN.md](../../PLAN.md). Авторский vertical slice: сцена с компонентами, headless AuthoringService, собственный retained Inspector и MCP поверх экспортированной схемы, atomic batch/revision/idempotency, один undo. Затем glTF+manifest importer и повторный импорт с overrides. Затем async jobs, диагностика и offscreen preview. Только после проверенного roundtrip — Blender live link. + +Измерять время от намерения до видимого результата, число вызовов/действий, объём ответа query, latency commit/undo, ошибки конфликтов и сохранность overrides. Критерий успеха — одна и та же задача удобно выполняется руками, агентом и с внешним asset source, а переход между ними не требует чинить скрытое состояние. diff --git a/docs/studies/13-build-pipeline-and-stack.md b/docs/studies/13-build-pipeline-and-stack.md new file mode 100644 index 0000000..3e32259 --- /dev/null +++ b/docs/studies/13-build-pipeline-and-stack.md @@ -0,0 +1,94 @@ +# Сборка игры, доставка и выбор стека собственного движка + +**Принято 18.09.2026:** C++ core/gameplay, затем Lua отдельным модулем (необязателен для конкретной игры); EnTT; собственный retained C++ editor UI с декларативными layout/styles и тёмной темой, ImGui для debug; Vulkan 1.3 и собственный RenderGraph, Slang/совместимый HLSL; SDL3 за API Faset; CMake+Ninja, Clang Linux и clang-cl Windows. Gameplay — статическая библиотека в Player dev/release, Editor plugins — DLL/SO под точный SDK с restart. Сравнение альтернатив ниже — **история исследования**, не открытый выбор. Канон — [архитектура](../ARCHITECTURE.md), этапы — [PLAN.md](../../PLAN.md). MCP только в Editor/headless editor services, без runtime inspection/mutation и без MCP в Player/SchemaExporter. + +Исходные требования: Linux и Windows desktop, игры 2D и 3D, продвинутая графика, полноценное ручное редактирование и управление через MCP. Текущие рекомендации ниже описывают принятый проект; pipeline и движок ещё не реализованы. Исторические варианты помечены отдельно. Проверка источников: 17 сентября 2026. Локальный Godot: `9c776068d6ed23acd0c78bfe534272d1d2a3a619`, версия дерева 4.8-dev; официальные текущие страницы Unity показывают 6.6. Исследованы тела desktop exporter, сбор зависимостей, remap импортированных ресурсов, кэш преобразования, запись PCK и конфигурация editor/player. Unity здесь изучен по документации, без утверждения о просмотре закрытой реализации. + +## Что именно должна делать кнопка Build + +Нужны четыре отдельные операции с понятными входами и результатами: + +1. **Компиляция кода:** engine/player и статическая C++ gameplay-библиотека превращаются в машинный код; отдельный SchemaExporter после сборки выпускает декларативную схему регистраций для Editor. Будущий Lua-модуль добавляется только проектам с такой зависимостью; Lua может поставляться исходниками или согласованным с VM байткодом. Изменение PNG не должно запускать C++ compiler. +2. **Компиляция шейдеров:** исходники, include-файлы, defines и описания вариантов превращаются в промежуточный код и reflection metadata. Принятый Slang pipeline выдаёт SPIR-V для Vulkan 1.3 и согласованные сведения о shader resources; существующий HLSL поддерживается в совместимом подмножестве. Создание GPU pipeline остаётся отдельной операцией драйвера; наличие SPIR-V не означает отсутствие runtime compilation/stutter. Vulkan прямо описывает затраты создания pipeline и сохранение pipeline cache между запусками. [Khronos: Pipeline Cache](https://docs.vulkan.org/guide/latest/pipeline_cache.html). +3. **Подготовка ассетов, или cook:** текстуры, модели, сцены, анимация, звук, шрифты и настройки превращаются в runtime-форматы для выбранного профиля. Сюда относятся mipmaps, GPU texture compression, mesh clusters/LOD, collision data и бинарные сцены. +4. **Упаковка:** готовый player, код игры, cooked content, необходимые библиотеки и manifest собираются в самостоятельный каталог/архив. Иконки, подпись, symbols и installer — последующие явные шаги, а не скрытые побочные действия импорта. + +Такой разрез нужен пользователю: он видит, что изменилось, почему задача выполняется повторно и какой шаг сломался. MCP должен возвращать те же объяснения, что показывает окно сборки. + +## Что действительно делает Godot и чему учит Unity + +**Повторно используемый player вместо сборки движка при каждом экспорте.** `EditorExportPlatformPC::export_project()` вызывает подготовку template, модификацию и экспорт данных. `prepare_template()` выбирает debug/release template и копирует его в output; `export_project_data()` создаёт отдельный PCK либо встраивает его в executable, затем переносит native shared objects. Это непосредственно видно в [цепочке экспорта](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/export/editor_export_platform_pc.cpp#L141), [выборе template](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/export/editor_export_platform_pc.cpp#L161), [копировании](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/export/editor_export_platform_pc.cpp#L188) и [упаковке данных](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/export/editor_export_platform_pc.cpp#L208). Вывод для Faset: переиспользовать собранные библиотеки core и зависимости, но **не обещать готовый универсальный Player без компилятора для C++ gameplay**. В принятом варианте код игры статически линкуется в Player; новая игра или изменение её кода требуют компиляции/линковки на worker. Правка только ассетов может переиспользовать готовый Player той же игры. Шаблонный export Godot не переносится на этот контракт буквально. + +**Development player и editor — разные продукты.** В Godot `TOOLS_ENABLED` появляется только у editor; debug features включаются для editor и template_debug; `DEV_ENABLED` управляет дополнительным кодом разработчика самого движка. Символы и оптимизация настраиваются отдельно. [SConstruct](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/SConstruct#L541). Следовательно, игровой development build может быть оптимизированным и диагностируемым, при этом не содержать редактор. Для собственного движка нужны editor, development player и shipping player, плюс независимые параметры symbols/validation/profiling. + +**Экспорт — обход графа ресурсов.** Godot начинает с выбранных сцен/ресурсов, рекурсивно добавляет их зависимости, отдельно учитывает autoload и include/exclude filters. Это тела [_export_find_dependencies](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/export/editor_export_platform.cpp#L670) и [export_project_files](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/export/editor_export_platform.cpp#L1301). При обработке импортированных ресурсов экспортируются remapped runtime-файлы для нужных feature tags, а служебные секции `deps` и `params` удаляются. [Выбор вариантов и очистка](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/export/editor_export_platform.cpp#L1621). Перенимать стоит явные roots и runtime remap; динамическая загрузка по строке требует отдельной декларации, иначе статический граф может не увидеть ресурс. + +**Кэш и отмена существуют на уровне конкретной операции.** `_export_customize()` проверяет существование результата, время изменения, затем hash исходника и `.import`. Импорт отдельно хранит checksum исходных и подготовленных файлов. [_export_customize](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/export/editor_export_platform.cpp#L1013), [import checksums](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L3028). PCK writer опрашивает progress и возвращает `ERR_SKIP` при отмене. [Запись файла](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/export/editor_export_platform.cpp#L434). Это подтверждает инкрементальность отдельных стадий, но не доказывает полную воспроизводимость любого Godot export. Даже [сортировка каталога PCK](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/export/editor_export_platform.cpp#L2334) сама по себе не гарантирует побайтово одинаковый пакет. + +Unity представляет сборку как получение target-specific Player, управляемое Build Profiles или BuildPipeline API. Инкрементальный pipeline охватывает content, code, compression и signing; clean build остаётся отдельным режимом. AssetBundles имеют собственные механизмы кэширования. [Unity: Introduction to building](https://docs.unity3d.com/6000.6/Documentation/Manual/building-introduction.html). Практический урок: единый план сборки должен учитывать разные виды работ, а отдельный asset pack не следует путать с полной сборкой игры. Для API полезен подход `BuildPlayerOptions → BuildReport`: результат должен быть структурой со статусом и диагностикой. [BuildPipeline.BuildPlayer](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/BuildPipeline.BuildPlayer.html). + +## Принятая архитектура сборки + +**Общий build service под editor, CLI и MCP.** Все три клиента отправляют один типизированный `BuildRequest`: project revision, target ОС/архитектура, configuration, renderer feature profile, startup scene, content roots, output path. Сервис создаёт job ID и неизменяемый snapshot настроек. Редактирование проекта во время cook не должно тихо смешивать две ревизии. Для несохранённых сцен интерфейс явно предлагает сохранить snapshot либо показывает, что будет собрана последняя сохранённая версия. + +Основные состояния: planning → compiling/cooking → packaging → validating → succeeded/failed/cancelled. Они нужны для восстановления клиента после перезапуска editor и повторного запроса MCP. В пределах срока хранения журнала повтор с тем же project/client scope, request ID и hash параметров возвращает существующий job; тот же ID с иным payload отклоняется. Согласованная запись результата и публикации предотвращает повторный build после разрыва ответа; это контракт сервиса, который ещё надо реализовать и проверить. В отчёте сохраняются toolchain versions, hashes входов, выполненные/пропущенные шаги, длительности, предупреждения и путь готового артефакта. Это наше проектное решение, не описание поведения Godot. + +**Компиляторы и импортёры запускаются дочерними процессами.** Editor не должен зависать из-за shader compiler или падения конвертера модели. Для каждого процесса задаются argv без shell-конкатенации, рабочий каталог, ограниченный набор environment variables и отдельный staging output. stdout/stderr сохраняются полностью; распознанные ошибки дополнительно превращаются в diagnostics с файлом, строкой, asset ID и шагом. При отмене сигнал получает группа процессов; после периода завершения сервис останавливает оставшихся потомков. Отменённая работа не публикует cache entry и не заменяет последнюю успешную игру. + +**Публикация артефакта — последняя транзакция.** Сначала все файлы собираются в staging, затем проверяются manifest и обязательные зависимости. Только успешная проверка делает каталог новой опубликованной версией, по возможности атомарным rename на том же файловом разделе. Сбой, занятый Windows executable или отмена оставляют предыдущую сборку доступной. Сервис должен показать причину конфликта output, а не объявить успех по exit code только одного compiler. + +**Player и SchemaExporter не содержат MCP.** Runtime содержит scene/resource loading, simulation, renderer, audio/input и Lua лишь при зависимости проекта. Editor UI/plugins, asset importers, build service, SchemaExporter и MCP adapter идут в отдельные targets. Это правило действует для development и release, не только для shipping по умолчанию. Editor MCP запускает/останавливает Play и читает editor/build/import logs, но не получает world/session inspection/mutation через другой транспорт. Состав пакета и граф зависимостей проверяют эту границу. + +## Инкрементальный cook и проверяемая воспроизводимость + +Предлагаемый cache key: hash содержимого входов и транзитивных зависимостей + importer/version + нормализованный recipe + целевой формат/feature profile + версия runtime schema. Для shader key дополнительно нужны include graph, compiler version, entry point, defines и flags. Timestamp годится как быстрый локальный hint; окончательное решение release-сборки не должно полагаться только на него. + +Manifest связывает стабильный asset ID с исходной ревизией, cooked hash, runtime path, типом, размером, прямыми зависимостями и вариантом платформы. Build roots включают startup scenes, явно объявленные preload/autoload, shader/material variants и наборы для динамической загрузки. Проверка заранее ловит missing references, коллизии регистра имён между Linux/Windows и неподдерживаемый формат текстуры. Editor может объяснить «этот файл включён, потому что сцена A ссылается на материал B». + +Для детерминированного content pack нужно фиксировать порядок записей, сериализацию floating-point/строк, compression version/settings, seeds генераторов и нормализацию путей; исключить абсолютный путь рабочего каталога и время сборки из content hash. Изменение одной текстуры должно пересобирать её варианты и зависимые данные согласно графу, сохраняя остальные cache hits. Общий pack допускает отдельный этап переупаковки, даже если cook не выполнялся. + +Побайтовая воспроизводимость native executable — дополнительная цель: compiler/linker, debug paths, подписи и platform metadata могут вносить различия. Не обещать её автоматически из-за content-addressed cache. Сначала доказать идентичность unsigned cooked data и одинаковость manifest при двух clean сборках одного snapshot; затем вводить reproducible native build отдельным профилем. + +Для доставки на старте достаточно каталога и ZIP: Windows executable, требуемые DLL и данные; Linux executable, нужные `.so`, корректные права и данные. Отдельно сохраняются symbols. Нужно определить поддерживаемую Linux runtime baseline и проверять запуск на ней, а не лишь на машине разработчика. Native dependencies, выбранный CRT и GPU driver остаются частью проверки; C++ gameplay статически линкуется в Player, но это не устраняет все динамические системные зависимости. + +## История: четыре варианта стека, рассмотренные 17.09.2026 + +Следующие A–D сохраняют аргументы раннего сравнения. **18.09 выбран C++ с первым gameplay на C++, а Lua добавляется затем; остальные языковые варианты не являются текущими финалистами.** + +**A. C++ core, SDL3, Vulkan, Lua gameplay, CMake/Ninja.** Наиболее прямой кандидат для исследований GPU-driven rendering, visibility buffer и streaming: renderer и runtime находятся в одном native окружении, границы памяти и GPU synchronisation видны разработчику. SDL3 даёт окна/input и создание Vulkan surface; он не заменяет renderer. [SDL_Vulkan_CreateSurface](https://wiki.libsdl.org/SDL3/SDL_Vulkan_CreateSurface). Lua встраивается через официальный C API; игровые объекты следует представлять проверяемыми handles, а bulk-работу оставлять core. [Lua: C API](https://www.lua.org/manual/5.4/manual.html#4). Цена — собственные metadata/reflection, Inspector bindings, диагностика и управление временем жизни. Hot reload Lua требует миграции игрового состояния; hot reload произвольного C++ core не обещать. Первая версия может просто перезапускать player, сохраняя editor. + +**B. C++ core/renderer плюс C# gameplay и .NET host.** Подходит, если native graphics нужен вместе с C# API для авторов игры. Но это не «вариант A с бесплатной заменой Lua»: появляются runtime hosting, managed/native lifetime, генерация bindings, загрузка игровых assemblies и отдельная проверка упаковки. Microsoft описывает `nethost/hostfxr` для C++ host и отдельно оговаривает framework-dependent модель этих hosting API; self-contained managed apps рассматриваются как самостоятельные executables. [Microsoft: custom .NET host](https://learn.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting). Поэтому обещание автономной доставки такого native host нужно подтвердить выбранной схемой размещения runtime на чистых машинах. Это разумный запасной вариант, но для малого проекта его сложнее довести до цельного UX, чем A или C. + +**C. C#/.NET core и editor, native renderer/physics через C ABI.** Финалист, если скорость создания editor, типизированных command API, сериализации и игровой логики важнее прямого контроля всего native core. Начальный player — обычная self-contained .NET публикация под конкретный RID: она доставляет runtime вместе с игрой, не требуя установленного SDK/.NET у игрока. [dotnet publish](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-publish). Native renderer может быть Vulkan; выбор C# не предписывает графический API. Граница должна передавать массивы и команды пакетами, с явным ownership и стабильными handles, избегая тысяч мелких переходов на каждый объект/свойство. + +NativeAOT здесь опция последующей shipping-оптимизации, не исходная обязанность. Он запрещает динамическую загрузку managed assemblies и runtime code generation, требует trimming; reflection/генерируемая сериализация нуждаются в проверке совместимости. Это не запрет native P/Invoke. [NativeAOT limitations](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/), [native interop](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/interop). JIT editor и AOT player могут существовать отдельно, но игровые типы и serialization metadata должны быть доступны AOT заранее. Начальный JIT player заметно уменьшает число одновременно решаемых задач. + +**D. Rust core, wgpu, Lua scripting, Cargo.** Кандидат при сильном опыте команды в Rust: типы и ownership помогают оформлять runtime границы, Cargo организует зависимости и сборку. wgpu даёт native Vulkan/D3D12 backend и переносимый API. [wgpu](https://docs.rs/wgpu/latest/wgpu/). Однако editor reflection, undo transactions, scene inheritance и скриптовые bindings всё равно придётся проектировать. Для C++ middleware потребуется FFI; перестройка сложного render graph под правила владения тоже инженерная работа. Rust не делает native зависимости автоматически переносимыми: Cargo отдельно настраивает target linker. [Cargo target configuration](https://doc.rust-lang.org/cargo/reference/config.html#targettriplelinker). Без уже имеющегося опыта Rust это третий кандидат, а не способ бесплатно сократить трудоёмкость. + +C# core не требует IL2CPP. Unity IL2CPP — конкретный backend Unity: managed IL преобразуется в C++, затем native compiler собирает результат вместе с его runtime; он устанавливается как модуль Unity. Использовать его как общедоступный компонент нашего движка в план не закладываем. [Unity: IL2CPP](https://docs.unity3d.com/6000.6/Documentation/Manual/il2cpp-introduction.html). + +## Принят Vulkan; историческое сравнение с wgpu и граница платформенной сборки + +Vulkan рационален для desktop graphics research: можно напрямую проектировать resource states, descriptors, memory allocation, indirect workloads и нужные extensions. Цена — больше собственного backend-кода, validation, синхронизации и испытаний на разных GPU. wgpu сокращает объём низкоуровневой обвязки и даёт несколько backend, но ставит их возможности в рамки своего API и версии. Эти решения независимы от языка core. + +Неверно говорить, что современный wgpu вообще не имеет mesh shaders или ray tracing: текущая документация содержит `EXPERIMENTAL_MESH_SHADER`, `EXPERIMENTAL_RAY_QUERY` и другие экспериментальные features. Mesh shader support и пути компиляции различаются по backend; ray query указан как native Vulkan feature. [wgpu Features](https://docs.rs/wgpu/latest/wgpu/struct.Features.html). Для advanced renderer нужен короткий feature spike на конкретных GPU/драйверах: indirect count, descriptor indexing, нужные atomics, subgroup operations, timestamps и выбранный RT путь. Наличие флага в документации не равнозначно готовому переносимому backend. Для Faset выбран один backend — Vulkan 1.3; второй backend не входит в начальный план. Профили развития: Baseline — обычный raster для 2D/3D без обязательных RT/mesh shaders; GPU-driven — после проверки нужных indirect/descriptor/subgroup features; Advanced — после проверки конкретного RT/atomic/mesh пути. Профиль задаёт формат cooked assets и fallback; неподдерживаемая GPU получает объяснение до загрузки несовместимого pipeline. Конкретный минимум GPU/driver ещё надо выбрать и зафиксировать отдельным ADR. + +Для надёжной первой поставки приняты два native build worker: Windows и Linux. Кнопка в editor может отправить job соответствующему worker, сохраняя единый UX. CMake поддерживает cross compilation через явно заданные compiler/toolchain/sysroot; это конфигурация, а не автоматическое получение всех SDK. [CMake toolchains](https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html). NativeAOT официально не поддерживает cross-OS compilation, хотя допускает некоторые cross-architecture пары при наличии tools. [Microsoft: AOT cross compilation](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/cross-compile). Даже готовый player template для другой ОС лишь упрощает упаковку: тест запуска всё равно нужен на целевой системе. + +## Реализация выбранного стека и проверка + +Языковой конкурс завершён: C++ первым, Lua следующим модулем. Собственный retained UI использует общий AuthoringService; выбранные Vulkan/Slang/SDL3 и CMake/Ninja проверяются прототипом, а не считаются уже интегрированными. Подробный порядок — в [PLAN.md](../../PLAN.md). + +**Toolchain и linkage.** Linux использует Clang с зафиксированными стандартной библиотекой и runtime baseline; Windows — clang-cl с выбранными Windows SDK/UCRT/VCRuntime и linker. LLVM не содержит автоматически весь Windows SDK. Editor DLL/SO требуют совпадающего SDK/build fingerprint; gameplay статически линкуется в Player. После правки C++: stop → build → restart; hot reload не обещается. [Clang: Windows headers/libraries](https://clang.llvm.org/docs/UsersManual.html#windows-system-headers-and-library-lookup). + +**Schema export.** Отдельный служебный executable связывается с C++ registrations и выдаёт schema manifest для Inspector/MCP authoring API. Он не запускает игровой мир, не содержит MCP и не входит в экспорт. Custom Inspector принадлежит editor module. Подробная граница — в [16](16-native-gameplay-and-metadata.md). + +Проверки выбранной реализации: + +- Создать 2D сцену со спрайтом и 3D сцену с материалом, светом и физикой; одну authoring-правку выполнить руками, вторую через editor MCP, проверить общий Undo и сохранение JSON со стабильными IDs. +- Упаковать сцены в development/release Player с бинарными cooked assets для Windows/Linux; запустить без Editor, MCP, SDK и исходников. Проверить runtime libraries и отсутствие MCP targets/listeners, включая development Player. +- Изменить texture, shader include и gameplay C++ по очереди; измерить cook/compile/schema export/package и точную invalidation chain. Сравнить cold, warm и no-op build без заранее обещанных чисел. +- Отменить compiler/cook, вызвать ошибку shader/schema export, затем повторить сборку. Последняя успешная игра и схема остаются доступными; GUI/MCP получают одну editor-диагностику. +- Проверить несовместимый editor plugin fingerprint, циклическую module dependency и ошибочную зависимость Player от Editor/MCP — получить понятный отказ. + +Все перечисленные испытания ещё предстоят; построение документации не является испытанием движка. diff --git a/docs/studies/14-engine-blueprint.md b/docs/studies/14-engine-blueprint.md new file mode 100644 index 0000000..f518cf3 --- /dev/null +++ b/docs/studies/14-engine-blueprint.md @@ -0,0 +1,150 @@ +# Новый движок: выводы исследования UE, Godot и Unity + +**Название проекта — Faset Engine. Синхронизация решений: 18.09.2026.** Каноническая архитектура — [ARCHITECTURE.md](../ARCHITECTURE.md), последовательность до и после MVP — [PLAN.md](../../PLAN.md). Этот отчёт сохраняет исследовательские аргументы и обновлённые принятые решения. Прежние альтернативы отмечены **superseded**; факты о чужих движках не означают, что соответствующие возможности реализованы в Faset. + +Дата: 17 сентября 2026. Цель — новый движок для **Linux и Windows, десктопных 2D/3D-игр**, одинаково удобный при ручной работе и через **MCP**, с интеграцией как минимум с **Blender**. Это исследовательская основа реализации. Код нового движка ещё не создавался; ниже принятые архитектурные правила отделены от экспериментальных графических направлений и деталей будущих прототипов. + +## Уточнения после обсуждения исследования + +**Подтверждённый выбор пользователя: Box2D для 2D-физики, Box3D для 3D-физики.** Под Box3D имеется в виду [erincatto/box3d](https://github.com/erincatto/box3d), анонсированный Erin Catto 30 июня 2026. Это отдельная 3D-библиотека с C API и реализацией на C17; репозиторий указывает поддержку Windows и Linux. [Анонс](https://box2d.org/posts/2026/06/announcing-box3d/), [Box2D overview](https://box2d.org/documentation/). Версии и commits зависимостей пока не выбраны, интеграция и собственные физические тесты не выполнялись. + +Принятый принцип интеграции: отдельные PhysicsWorld2D/PhysicsWorld3D, компоненты RigidBody2D/3D и Collider2D/3D, единые правила регистрации, идентичности, Inspector и диагностики. Миры 2D и 3D не сталкиваются автоматически друг с другом. Физика получает команды перед фиксированным шагом; после завершения шага движок переносит результаты и события в runtime. Для динамического тела физика владеет рассчитанным transform; runtime teleport и управление кинематическим телом — отдельные операции. События связываются с entity через проверяемые handles. Названия API предварительны; адаптеры ещё не реализованы. + +**Решение от 18.09.2026: сначала ядро и игровая логика на C++, затем добавляем Lua отдельным модулем. Его будущая реализация принята, использование конкретной игрой необязательно.** Игра на C++ не должна зависеть от Lua; точный Lua API и срок этапа уточняются по [плану](../../PLAN.md). Python и C# сохраняются в сравнении как ранее рассмотренные альтернативы. Каноническое решение записано в [архитектуре](../ARCHITECTURE.md). + +Для C++ приняты отдельная сборочная цель игрового проекта и небольшой API движка. Gameplay статически включается в Player; редакторские plugins собираются как DLL/SO под точный SDK/toolchain. Первая версия: остановить Player → инкрементально собрать затронутые цели → запустить сцену в отдельном процессе, сохранив редакторскую сессию. Изменение заголовка может потребовать пересборки зависимых файлов; мгновенная пересборка не обещается. Inspector, сериализация и MCP используют метаданные компонентов. Горячая замена кода с сохранением состояния — отдельный этап: изменения layout объектов, callbacks и фоновые задачи требуют управления временем жизни. Например, UE Live Coding использует отдельную систему Object Reinstancing. [UE Live Coding](https://dev.epicgames.com/documentation/unreal-engine/using-live-coding-to-recompile-unreal-engine-applications-at-runtime). + +## Короткий ответ: что именно объединять + +**От Godot — композицию сцен и короткий путь от изменения до запуска. От Unity — согласованные контракты Inspector, инструментов и расширений. От Unreal — способы строить масштабируемый renderer. От Blender — параметризованные команды и интерактивные инструменты с хорошей отменой.** + +Объединять это нужно через общую модель данных и действий. Иначе получится renderer уровня исследовательского проекта, несколько разных способов редактировать объекты и MCP, который ненадёжно нажимает кнопки. Принят небольшой независимый **AuthoringService**, над которым работают UI, CLI, MCP и плагины; из сохраняемых документов он строит runtime, где ECS и renderer получают удобное для исполнения представление. + +Своя отличительная черта: **движок умеет объяснить результат и показать изменения до их применения**. Человек и MCP видят одну сцену, версии документов, источники значений, diagnostics и историю редакторских операций. Это принятый проектный ориентир, а не заявление, что другие движки совсем не имеют таких функций или что Faset уже реализован. + +## Карта материалов + +- [07 — UE: графика по C++ и shader source](./07-unreal-graphics-source-study.md): Nanite/GPU Scene, Lumen, MegaLights, VSM, VT, TSR, RDG, Substrate, glints; исходники, ограничения и отдельные прототипы. +- [08 — Godot: UX по исходникам](./08-godot-ux-source-study.md): scene/resource composition, overrides, Inspector/Undo, запуск сцены, live editing, импорт и editor plugins. +- [09 — Unity: UX и расширяемость](./09-unity-ux-extensibility-study.md): serialized edits, UI binding, tools/overlays, importers, assembly/package boundaries, Play lifecycle. +- [10 — Blender: устройство редактора](./10-blender-editor-patterns.md): operators, modal tools, undo, RNA metadata и поиск команд. +- [11 — ECS и удобство](./11-ecs-and-ergonomics.md): нужен ли ECS, storage tradeoffs, authoring/runtime mapping, scripting façade, диагностика и критерии выбора реализации. +- [12 — MCP и обмен с Blender](./12-mcp-and-blender-integration.md): единый command contract, transactions, асинхронные операции и устойчивый reimport моделей. +- [13 — Компиляция, экспорт и стек](./13-build-pipeline-and-stack.md): code/shader compilation, asset cooking, packaging, варианты технологий и проверка на двух ОС. + +## Что проверено и чего исследование не доказывает + +Unreal исследован в имеющемся локальном checkout **5.8.2**, commit `16d75d84714512edfb744e1fd0a59e9c74d57873`. Godot скачан полностью по исходному дереву с короткой Git-историей в `../../../godot`: **4.8.0 dev**, `9c776068d6ed23acd0c78bfe534272d1d2a3a619`. Официальный UnityCsReference скачан в `../../../UnityCsReference`: **6000.7.0a6**, `6b50e5544f6efcca1f44dbace3d1778b465ac6d0`. Blender загружен выборочно, shallow/sparse, в `../../../blender-source`: **5.3.0 alpha**, `28d47268bddcb9dc69143f0e2d9410969da16311`. + +Godot и Blender здесь — снимки разработки, Unity — alpha reference source. Это не рекомендация строить production на этих версиях. UnityCsReference открывает C#-слой и native bindings, но не полную native-реализацию. В отчётах явно разделены тело функции, декларация API и собственное предложение. + +Проверены выбранные реальные C++/C#/shader тела и официальные документы. Редакторы не собирались и пользовательское тестирование не проводилось. Нет измеренных сравнений скорости движков или утверждения об объективном мировом рейтинге. Рассматриваются три выбранных ориентира и полезные механизмы; эффективность собственного сочетания предстоит доказать прототипом. + +## Семь принятых архитектурных решений + +### 1. Сцена — документ, объект — стабильная идентичность + +Authoring scene содержит понятную человеку композицию объектов и компонентов. Scene/prefab — одна базовая модель повторного использования. Приняты stable object/resource IDs, explicit C++ schema с TypeId/FieldId, JSON authoring с предсказуемым diff, binary cooked данные, типизированные asset references и явно видимые overrides. + +Hierarchy, ownership и resource sharing — разные отношения. Пользователь должен видеть, меняет ли он общий материал, локальный экземпляр или временное состояние игры. Из Godot полезны remap локальных ресурсов и sparse overrides; из Unity — mixed selection и property-level revert. В MVP входят обычные и вложенные экземпляры без variant inheritance. Patch адресует instance chain/ObjectId/ComponentId/FieldId, проверяя TypeId; имя и transform path не заменяют ID. Reparent разрешён внутри экземпляра, перенос через nested boundary отклоняется; suppression и добавления сохраняются отдельным слоем. Удалённые targets и несовместимые поля требуют разрешения конфликта, без потери overrides. Revert входит в MVP; variants, Apply to template и поэлементное слияние массивов — после него. Предыдущее предложение раннего варианта **superseded**. [Полные правила](01-architecture.md). [Godot: сцены и overrides](./08-godot-ux-source-study.md), [Unity: изменение свойств](./09-unity-ux-extensibility-study.md). + +### 2. Ручной интерфейс и MCP — два клиента одного сервиса + +AuthoringService владеет открытыми документами, схемами свойств, validation, командами, транзакциями, undo и revisions. UI делает preview во время drag и один commit при завершении. MCP передаёт явные IDs и batch изменений с ожидаемой revision; один batch создаёт один понятный шаг истории. + +Через общий сервис доступны: открыть/сохранить авторскую сцену, найти её объекты, изменить компоненты документа, импортировать ресурс, вызвать Play/Stop, получить editor diagnostics, снять editor viewport capture и собрать game build. MCP ограничен редактором/authoring/import/build/PlayStop/editor logs: он не инспектирует и не меняет runtime world. В Player и экспортированных играх MCP отсутствует. Не каждую UI-возможность нужно буквально экспортировать как tool: MCP нужны операции над намерением и данными, например «создать 20 экземпляров», без сотни mouse events. + +В headless режиме работают документы, импорт, validation и build. Viewport capture требует render-capability; тест интерактивного окна требует соответствующей среды. Возможности следует обнаруживать явно, а не обещать, что отсутствие UI означает отсутствие GPU-зависимостей. [Подробный протокол](./12-mcp-and-blender-integration.md). + +### 3. ECS скрывает устройство хранения, сохраняя понятность игры + +Для runtime выбран EnTT; это не обязательная модель хранения каждой панели, ассета и пользовательского сценария. В редакторе остаются сцены и компоненты; compiler создаёт runtime representation и карту происхождения. Простая дверь использует удобный C++ gameplay API, позднее также Lua; массовое движение — типизированный batch query. Оба работают с одним runtime-состоянием. + +Editor transaction отвечает за undo и авторские данные. Runtime command buffer отвечает за отложенное создание, удаление и изменение состава entity в безопасной фазе. Hot systems пишут выданные компоненты без редакторского журнала на каждое присваивание. GPU Scene из UE — ещё одно специализированное представление данных; она не заменяет игровой ECS. [Решение по ECS](./11-ecs-and-ergonomics.md). + +### 4. Отдельный Player и snapshot редактируемой сцены + +Play запускает отдельный Player process со snapshot текущей сцены или project entry point и статически собранным gameplay. Это изолирует сбой игры и задаёт ясный start/stop lifecycle. Авторский документ можно редактировать независимо; новая версия применяется к следующему запуску. Первый C++ цикл — Stop → incremental build → Play с сохранением контекста редактора. + +**Superseded для MVP:** remote tree/property inspection как обязательный этап, пересылка всех authoring edits в игру и Apply выбранного runtime state в исходную сцену. MCP управляет Play/Stop через редактор, но не получает runtime query/mutation tools. Mapping authoring→runtime полезен внутренней диагностике и не означает такого доступа. Универсальная горячая замена C++ с сохранением состояния отложена. + +Godot subprocess/debugger flow и Unity lifecycle остаются исследовательскими сравнениями. [Godot Play](./08-godot-ux-source-study.md), [Unity lifecycle](./09-unity-ux-extensibility-study.md). + +### 5. Инструменты регистрируются как пакеты возможностей + +Плагин добавляет component schema, system, importer, Inspector drawer, viewport tool, menu command и diagnostics через typed registries. Runtime и Editor code — разные модули. В Player игровой код линкуется статически; C++ editor plugins — DLL/SO с точным соответствием версии SDK, compiler/ABI profile. Универсальная двоичная совместимость между версиями SDK не обещается. У каждой регистрации есть владелец plugin scope; отключение снимает callbacks и UI. Опциональная Blender-интеграция не должна становиться обязательной зависимостью игрового Player. В v1 editor DLL/SO загружаются при старте Editor; обновление, отключение и повторное включение требуют его перезапуска. Hot-unload editor plugins не поддерживается. Данные неизвестных компонентов сохраняются в документе с diagnostic, а после включения пакета восстанавливается их редактирование. Проверка disable → restart Editor → re-enable → restart Editor должна проходить без потери данных и поздних callbacks. + +Один параметризованный command обслуживает menu, hotkey, palette и MCP. Интерактивная часть получает mouse/keyboard events и делает preview; headless execute принимает готовые параметры. Это сочетает operator model Blender с расширениями Godot/Unity. Важны tool lifecycle, cancellation и причина disabled state, а не копирование всей системы окон. + +### 6. Blender — источник, импортируемая модель — производный артефакт + +Используется обычный официальный Blender без модификации исходников. Стандартный glTF/GLB импорт работает самостоятельно, без add-on. Необязательное Python-дополнение добавляет IDs частей, manifest и удобную кнопку экспорта согласованного профиля. Engine importer создаёт mesh/material/animation artifacts; `.blend` остаётся исходником геометрии/rig/animation, а игра использует cooked данные без установленного Blender. Требование обязательного add-on **superseded**. + +Повторный импорт должен сохранять ссылки на объект и настройки уровня, а конфликтующие изменения объяснять. Имя mesh или индекс узла не должны быть единственной идентичностью. Без устойчивых source IDs нельзя гарантировать matching внутренних частей после rename/reorder; неоднозначность требует явного remap. Gameplay, physics settings и overrides принадлежат Faset и не перезаписываются экспортом Blender. Поддержку материала и анимации следует зафиксировать как профиль: произвольный Blender node graph, modifiers и симуляции не превращаются автоматически в эквивалент нашего renderer. + +После MVP — live link с выбором, переходом к исходнику, запросом экспорта и статусом reimport. Геометрию лучше передавать asset-артефактом, а команды и manifest — по управляющему соединению. [Roundtrip и проверки](./12-mcp-and-blender-integration.md). + +### 7. Экспорт игры проектируется вместе с редактором + +Build — общий сервис, доступный кнопкой, CLI и MCP: проверить проект → определить зависимости → скомпилировать код/шейдеры → подготовить assets → собрать manifest → упаковать Player → проверить артефакт. Каждая стадия имеет входы, версии инструментов, кэшируемые результаты, structured log, progress и cancellation. + +Игровой build не зависит от editor modules, source checkout или Blender и не содержит MCP. MCP доступен только в редакторе, который может заказать сборку или запустить отдельный Player. Linux/Windows получают свои целевые runtime и native libraries. «Разработка на Linux» не равна «проверенная Windows-сборка»: реальная проверка должна происходить на обеих ОС. + +Исходные финалисты исследования — **C++ + SDL3 + Vulkan + Lua** и **C#/.NET + native renderer/physics** — сохраняются в [сравнении стеков](./13-build-pipeline-and-stack.md) как история рассмотренных вариантов. **18.09.2026 принят порядок: сначала C++ для ядра и gameplay, затем добавление Lua отдельным модулем; его использование конкретной игрой необязательно.** Box2D и Box3D также выбраны. Дополнительно приняты SDL3, Vulkan 1.3, собственный renderer/RenderGraph и Slang, собственный retained C++ editor UI с декларативными layout/styles и тёмной темой по умолчанию (ImGui — debug tools), CMake + Ninja, Clang на Linux и clang-cl на Windows. Прежний статус открытого выбора этих частей **superseded**. Проверка сквозным прототипом должна подтвердить реализацию выбранного стека, а не заново выбрать язык. + +## Какой advanced renderer имеет смысл строить + +Первое MVP ограничено базовым 2D-путём, PBR и обычными тенями для 3D; сложные технологии ниже — после MVP. Для выбранного Vulkan 1.3 renderer нужно зафиксировать конкретный capability profile: базовый renderer должен запускать простую 2D/3D игру без RT/mesh shaders; advanced paths включаются после проверки возможностей и имеют явный fallback или diagnostic. Поддержка Linux/Windows сама по себе не гарантирует одинаковых GPU features. + +**Основа MVP:** собственный RenderGraph, GPU timestamps/captures и управление ресурсами. **Расширение после MVP:** stable GPU instance IDs, indirect rendering, depth pyramid, motion vectors, two-pass HZB по идеям Nanite и контроль доверия к истории по идеям TSR. Это создаёт базу для дальнейших экспериментов без обязательного полного виртуализированного renderer. + +Далее выбрать ветку по демонстрационной игре: + +- **Много геометрии:** cluster LOD и согласованные переходы, затем residency/streaming, visibility buffer/material bins, позже hybrid software/hardware raster. +- **Свет и атмосфера сцены:** screen/world hybrid tracing, radiance probes/cache, budgeted updates и специализированный denoiser; затем выборочная оценка lights по идеям MegaLights. +- **Большие тени:** cached atlas, page table/requests, VSM для одного directional light, invalidation и только затем расширение. + +Для относительно локального визуального эксперимента glints интереснее попытки немедленно повторить Substrate целиком. Материальные тайлы simple/complex также можно применить независимо от произвольного графа физических слоёв. VT имеет смысл при доказанном давлении на texture memory или дорогом повторном shading ландшафта. + +Важная связь с удобством: каждый cache должен иметь debug view актуальности и причины обновления; каждый проход — измерение. Локальные графические инструменты должны помогать выяснить причину артефакта или задержки. MCP получает editor diagnostics и результаты authoring/import/build, но не читает runtime world или данные Player через диагностический обходной путь. Все алгоритмы, источники и проверочные сцены подробно разобраны в [UE-исследовании](./07-unreal-graphics-source-study.md). + +## Что может стать нашим собственным преимуществом + +1. **Inspector происхождения.** Рядом со значением видно: asset/template default, instance override, scene edit. Можно перейти к источнику и вернуть нижележащее значение. Animation/runtime writers относятся к отдельным будущим debug tools, без runtime MCP. +2. **Предварительный просмотр пакетной правки.** MCP или инструмент предлагает изменить сцену; editor показывает затронутые объекты и diff. Применение проверяет revision. Повтор в пределах срока хранения журнала с тем же idempotency key и payload возвращает сохранённый результат authoring-транзакции. +3. **Объяснение результата.** «Почему объект не виден?» проходит через disabled/layer/camera/culling/material/pass diagnostics. «Почему модель не обновилась?» показывает source hash, export/import status и зависимости. +4. **Одинаковые сценарии из UI и headless.** Игра, созданная через MCP, редактируется вручную без особого формата. Ручная сессия воспроизводится как высокоуровневые команды, где это допускает контракт, без записи движений мыши. +5. **Понятный reimport diff.** Новые, исчезнувшие и изменённые mesh/material/animation outputs видны до публикации, а overrides имеют известного владельца. + +Эти идеи не заменяют базовую usability. Сначала должны хорошо работать выбор объекта, поиск, ввод чисел, Undo, сохранение, запуск и сообщения об ошибках. + +## Вертикальные прототипы и критерии выбора + +### A. Редактор и MCP делают одну и ту же сцену + +Создать дверь, collider, light, script и два экземпляра; изменить свойство, multi-edit и override, отменить drag, сохранить/открыть. Повторить через MCP с явными IDs. Сравнить канонические документы. Структура и семантика одинаковы; UI показывает результат MCP сразу; конфликтующая revision не приводит к частично применённому batch. + +### B. Полный круг Blender + +Создать модель, импортировать, расставить несколько instances, изменить mesh и переэкспортировать. Ссылки и local overrides сохраняются. После удаления mesh output появляется конкретный конфликт. Сломанный экспорт не заменяет последнюю рабочую модель наполовину записанными файлами. + +### C. Игра и ECS + +Простая 2D-аркада и маленькая 3D-сцена используют одинаковые save/import/Play/MCP workflows. Отдельная массовая сцена проверяет ECS, structural changes и render extraction. Удобство простой игры оценивается отдельно от throughput массового теста. + +### D. Самостоятельный build + +Одинаковый проект собирается из UI, CLI и MCP. Артефакты проверяются в Linux и Windows без редактора и Blender. Изменение одного texture source не пересобирает весь код, изменение gameplay-кода не переимпортирует всю графику. Build failure возвращает привязанный к стадии diagnostic. + +### E. После MVP: одна передовая графическая технология + +Демонстрационная сцена с измеряемой целью: GPU culling на множестве объектов либо гибридное освещение. Есть baseline, фиксированный camera path, p50/p95 frame time, VRAM и сравнение качества в движении. Не засчитывать количество реализованных именованных технологий как успех. + +## Порядок реализации после утверждения + +1. Формализовать принятые identity/schema, documents/transactions, EnTT façade, Player snapshot, asset/reimport, SDK и build contracts в тестируемых интерфейсах. +2. Провести выбранный стек через вертикаль «окно → viewport → свойство → Undo → editor MCP → JSON → Player» на Linux и Windows. +3. Довести маленькие 2D- и 3D-демо до самостоятельного экспорта: базовые rendering/physics, glTF/GLB import, шаблоны/overrides, editor UX, build/cook. Baseline PBR/тени достаточны для первого MVP. +4. После MVP реализовать обязательный Lua-модуль, затем наращивать графику и инструменты по измерениям; расширять Blender roundtrip, scene variants и plugins согласно [PLAN.md](../../PLAN.md). + +Первоначальные материалы 01–06 сохраняют сравнительный контекст. Исторические привязки к другому проекту, Flecs, C# или ранее существовавшим demo не считаются ограничениями Faset; при разночтениях действуют [ARCHITECTURE.md](../ARCHITECTURE.md) и [PLAN.md](../../PLAN.md). Принятие решения не является отметкой о завершённой реализации, а исследовательская проверка исходников не заменяет будущих сборок и приёмочных тестов. diff --git a/docs/studies/15-renderer-implementation-notes.md b/docs/studies/15-renderer-implementation-notes.md new file mode 100644 index 0000000..e983b21 --- /dev/null +++ b/docs/studies/15-renderer-implementation-notes.md @@ -0,0 +1,132 @@ +# 15. Первый GPU-driven renderer Faset: данные, порядок проходов и проверка корректности + +Исследование от 17 сентября 2026; проектные решения обновлены 18 сентября 2026. Это продолжение [обзора UE](07-unreal-graphics-source-study.md): здесь разобран контракт одного будущего GPU-driven прототипа. **Принято, реализация запланирована:** собственные Vulkan 1.3 backend и Render Graph, Slang → SPIR-V, SDL3, Linux/Windows desktop 2D/3D, базовый путь без обязательного RT. Поддерживаемые модели GPU и проверенная матрица драйверов ещё не определены. Актуальные границы — в [архитектуре](../ARCHITECTURE.md) и [плане до/после MVP](../../PLAN.md). + +**Место в плане:** M2 (базовая графика MVP) использует direct renderer с CPU frustum culling, простым PBR/тенями и отдельным упорядоченным 2D-путём; обе демки должны собираться в самостоятельный Player. GPU culling, indirect draws, HZB и LOD из этого исследования начинаются **после MVP, в P2**, с сохранением direct reference. Сначала fixed indirect bins и frustum, затем two-pass HZB, затем обычный mesh LOD с hysteresis. Кластерный LOD и streaming — дальнейшее исследование. Минимальный Render Graph с одной очередью и корректными barriers/lifetime нужен уже MVP; pass culling, aliasing и async compute не обязательны. + +**Граница инструментов:** MCP работает только в редакторе, включая headless authoring/import/build, Play/Stop и логи редактора. Player — отдельный процесс с отдельным окном, без MCP; чтение/изменение runtime worlds и игровых сессий через MCP исключено. Диагностика renderer и ручной GPU capture не превращаются в канал MCP-доступа к Player. + +Источники привязаны к [source-manifest.json](source-manifest.json): UE 5.8.2, commit `16d75d84714512edfb744e1fd0a59e9c74d57873`; Godot 4.8-dev, commit `9c776068d6ed23acd0c78bfe534272d1d2a3a619`. Прочитаны выбранные тела C++ и shader-функций, затем официальные Vulkan/D3D12 документы. Репозитории не изменялись. Движки и GPU-прототип не запускались; ускорения, совместимость конкретных карт и качество culling измерениями не подтверждены. **«Факт»** ниже относится к просмотренному коду/документу; **«предложение»** — к нашему проекту; гипотезы отмечены отдельно. + +## 1. Полезный результат не требует полного GPU command processor + +**Факт UE.** В обычном `FInstanceCullingContext` шаблон indexed indirect command получает index count, first index, base vertex и нулевое число instances. В GPU-буфер загружается массив этих шаблонов, затем отдельно очищается instance count. [Создание шаблона](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/InstanceCulling/InstanceCullingContext.cpp#L278), [upload и clear](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/InstanceCulling/InstanceCullingContext.cpp#L817). Shader `InstanceCullBuildInstanceIdBufferCS` для видимого instance атомарно увеличивает второй элемент соответствующей команды и записывает ID в выделенный этому draw диапазон. [Atomic и запись](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/InstanceCulling/BuildInstanceDrawCommands.usf#L346), [clear kernel](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/InstanceCulling/BuildInstanceDrawCommands.usf#L372). + +При этом `SubmitDrawCommands()` всё ещё обходит draw batches на CPU, выбирает indirect buffer/offset и вызывает submission. Это GPU-driven выбор экземпляров, но не обещание полного исчезновения CPU draw work. [Цикл submission](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/InstanceCulling/InstanceCullingContext.cpp#L1764). + +**Предложение post-MVP реализации.** В первом GPU-driven режиме Faset CPU формирует небольшое число bins по совместимому mesh/material/pipeline; GPU решает, какие instances входят в каждый bin. На bin заранее резервируется диапазон `VisibleIds`, вместимость которого равна числу его кандидатов. Compute меняет только count и содержимое этого диапазона; CPU отправляет фиксированный набор indirect draws. Нулевой `instanceCount` означает отсутствие геометрии. Это устраняет зависимость видимости от CPU readback и не требует сначала строить scan/compact массива draw-команд. + +Можно начать с `drawCount=1` на batch и `firstInstance=0`, передавая base offset списка ID через push constant. Vulkan требует отдельную feature `multiDrawIndirect` для `drawCount>1`; ненулевой `firstInstance` также зависит от `drawIndirectFirstInstance`. [vkCmdDrawIndexedIndirect](https://docs.vulkan.org/refpages/latest/refpages/source/vkCmdDrawIndexedIndirect.html), [VkPhysicalDeviceFeatures](https://docs.vulkan.org/refpages/latest/refpages/source/VkPhysicalDeviceFeatures.html). Позже multi-draw и GPU count buffer сокращают CPU submission, если профиль покажет именно это ограничение. Нельзя назвать фиксированный CPU draw loop «нулевыми draw calls». + +## 2. Контракт данных до написания culling shader + +**Предложение Faset.** Render extraction выдаёт immutable snapshot для конкретного `renderFrameId`, независимо от частоты physics/gameplay updates. В snapshot нужны: + +- `InstanceTable`: стабильный ID с generation, current/previous transform, conservative local bounds, mesh/bin ID, flags и признак корректности предыдущего состояния. +- `MeshTable` и геометрия: index ranges, vertex offsets, layout, bounds. На первом этапе все ресурсы резидентны, без streaming и LOD-переходов. +- `ViewData`: current/previous world-to-clip, projection type, viewport rectangle, near-plane convention, размеры depth/HZB и validity epoch. +- `DrawTemplates`: совместимые pipeline/material bins, неизменяемые геометрические поля и base offsets. +- Рабочие буферы кадра: `MainIds/MainArgs`, `DeferredIds/DeferredCount`, `PostIds/PostArgs`, debug counters. +- Изображения: current depth, color/debug instance-ID target, current HZB и imported previous HZB. + +Идентичность GPU instance не должна зависеть от позиции entity в уплотняемом ECS storage. При удалении/повторном использовании slot меняется generation; вновь созданный, деформированный неизвестным способом или изменивший mesh instance получает `previousValid=false`. Для движения хранится предыдущий **отрисованный** transform, а не transform последнего physics substep. + +**Факт UE.** Обычный instance culling использует current frustum, но тест previous HZB строит из `PrevLocalToTranslatedWorld` и предыдущих view/projection, пропуская near-plane crossing. Он дополнительно округляет сравниваемую глубину вверх для защиты от self-occlusion из-за точности. [IsInstanceVisible](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/InstanceCulling/BuildInstanceDrawCommands.usf#L197). Это связывает геометрию прошлого кадра с его HZB; подстановка current transform в этот тест меняет смысл алгоритма. + +**Новое уточнение к 07.** Отдельный velocity render target и TAA не являются предпосылкой этого HZB-прототипа: просмотренный тест использует матрицы и bounds. Motion vectors понадобятся для последующих temporal effects. Сначала полезнее зафиксировать отсутствие jitter и проверить culling; затем отдельно добавить согласованное jittered/unjittered преобразование. + +## 3. Один кадр: конкретный порядок и смысл ресурсов + +Следующая последовательность — **предложение**, адаптирующее Nanite two-pass к обычным meshes и hardware raster. Начальный материал — opaque, без alpha test/WPO, один sample, одно view. 2D overlay и прозрачность рисуются отдельным упорядоченным путём после opaque-сцены. + +1. **Import/Upload.** Подключить previous HZB и связанный с ним `HistoryMetadata`. Загрузить изменившиеся instance records и текущие view constants. Сохранить ресурсы старых submitted frames до окончания их GPU-использования. +2. **Init.** Записать templates, очистить main/post instance counts, deferred count и debug counters. Пустая сцена тоже должна иметь определённые выходы. UE отдельно очищает ID buffer, если compute был пропущен из-за отсутствия instances. [Обработка нуля](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/InstanceCulling/InstanceCullingContext.cpp#L956). +3. **MainCull.** Сначала current frustum. Кандидат с недействительной историей сразу попадает в Main. Остальные проверяются previous bounds/view против previous HZB: предположительно закрытые попадают в Deferred, остальные — в Main. Frustum reject, Main и Deferred образуют непересекающееся разбиение входов. +4. **MainRaster.** Hardware indexed indirect рисует Main в очищенные current depth и color/ID attachments. Если объект с прошлого кадра переехал, здесь он уже находится в текущем положении. +5. **BuildCurrentHZB.** Построить furthest-depth pyramid из **текущего результата MainRaster**. Это подмножество текущих opaque occluders, а не копия предыдущей глубины. +6. **PostCull.** Только Deferred проверяется уже current bounds/view против этого current HZB. Прошедшие instances записываются в Post. Dispatch count сначала можно оставить фиксированным верхним пределом, с проверкой индекса относительно GPU deferred count; indirect dispatch — следующая оптимизация. +7. **PostRaster.** Нарисовать Post, сохраняя depth/color Main через load, с теми же depth convention и geometry shader-параметрами. Нельзя очистить depth между main и post. +8. **Export/Finish.** Сохранить HZB и его metadata для будущего кадра; отрисовать остальные разрешённые категории, UI, diagnostic overlays; отправить timestamps/counters в отложенный readback. + +**Факт UE.** Nanite добавляет main cull/raster, затем `BuildHZBFurthest`, подставляет полученную текстуру в culling parameters и добавляет post cull/raster. [Main](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Nanite/NaniteCullRaster.cpp#L7003), [построение HZB и Post](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Nanite/NaniteCullRaster.cpp#L7021). Main shader берёт предыдущие матрицы; post permutation проверяет current rectangle. [FBoxCull::HZB](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteCullingCommon.ush#L623). Очередь occluded instances хранит одновременно view и instance IDs. [WriteOccludedInstance](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteInstanceCulling.usf#L169). + +**Почему это безопаснее previous-HZB-only.** Main может избыточно нарисовать скрытый объект; depth test это разрешает. Ошибочное предположение «закрыт» не окончательно: объект получает current-frame post test. Main HZB содержит только реальные текущие occluders, поэтому неполнота этой пирамиды уменьшает отсечение, а не создаёт дополнительные заслоняющие поверхности. Это аргумент корректности при консервативных bounds/depth tests, не доказательство конкретной ещё не написанной реализации. + +**Что можно упростить.** Для первоначальной истории допустимо экспортировать HZB после Main, не перестраивая его после Post. Он будет неполным представлением предыдущего кадра; следующий post test сохраняет путь исправления. Полный final HZB потенциально улучшит следующий main culling и пригодится другим эффектам, но это отдельный проход и гипотеза о выгоде. Измерять оба варианта. Не приписывать этот выбор конкретному UE path: UE также сохраняет scene HZB отдельным этапом. [BuildHZB и extraction](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/DeferredShadingRenderer.cpp#L582). + +## 4. HZB: меньше оптимизаций, больше инвариантов + +**Факт UE.** `BuildHZB()` вычисляет power-of-two размеры, обычно начиная с половинного разрешения; выделяет отдельные closest/furthest textures, создаёт UAV каждого output mip и читает нужный parent mip. [Размеры и описание](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/SceneTextureReductions.cpp#L130), [mip UAV](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/SceneTextureReductions.cpp#L201), [parent SRV](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/SceneTextureReductions.cpp#L331). В обычной shader-ветке furthest HZB получает minimum depth по четырём входам; для reversed Z visible-test использует `Rect.Depth >= MinDepth`. [Reduction](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/HZB.usf#L247), [сравнение](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteHZBCull.ush#L195). + +**Предложение.** Начать с R32-float pyramid, manual min reduction и одной dispatch на mip. При reversed Z пустой depth равен far=0; включение пустого sample в minimum должно делать тест более разрешающим. Для обычного Z меняется и reduction, и сравнение — нельзя переключить только depth compare pipeline. В первой реализации использовать полное conservative покрытие projected bounds; ошибаться в сторону «видим» при near-plane crossing, неизвестной проекции или некорректной арифметике. + +Выбранный mip и количество sampled texels должны покрывать **весь** screen rectangle. Четыре произвольных угла крупного footprint не заменяют это условие. Нечётные размеры окна, viewport с ненулевым offset и padding до power-of-two входят в математический контракт. У UE есть отдельные viewport→HZB scale/bias и размеры view/texture. [InitHZBCommonParameter](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/HZB.cpp#L13). Применять эти коэффициенты от текущего окна к старой пирамиде нельзя. + +У Nanite оптимизированная footprint-формула прямо предполагает один центральный sample; авторы отмечают необходимость изменения для MSAA/conservative raster. [GetScreenRect](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteHZBCull.ush#L80). Более того, отдельная ветка смешивания depth с иным VisBuffer sampling stride помечена как неконсервативная. [HZB.usf, VIS_BUFFER_FORMAT 4](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/HZB.usf#L235). Это причина не переносить все permutations в стартовый shader и не объявлять любой UE helper универсальным доказательством conservative occlusion. + +FP16 pyramid, gather tricks, subgroup reductions и несколько mips за dispatch отложить. Их ввод должен сохранять conservative inequality, в том числе при равных глубинах; сравнение с R32 reference станет отдельным тестом. + +## 5. Минимальный render graph должен видеть реальные способы чтения + +**Факт UE.** Producer описывает indirect arguments как UAV, consumer — как `ERHIAccess::IndirectArgs`, а instance offset stream отдельно как vertex/index access. [Compute parameters](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/InstanceCulling/InstanceCullingContext.cpp#L582), [FInstanceCullingDrawParams](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Public/InstanceCulling/InstanceCullingContext.h#L33). RDG объединяет совместимые состояния по subresources, затем отдельно создаёт texture/buffer transitions. [CompilePassBarriers](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/RenderCore/Private/RenderGraphBuilder.cpp#L3783), [CollectPassBarriers](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/RenderCore/Private/RenderGraphBuilder.cpp#L3885). + +**Предложение.** Первому Faset graph достаточно фиксированного порядка на одной graphics-capable queue, declarations read/write с диапазоном mip/layer, проверки read-before-write, initial/final state imported ресурсов и генерации барьеров. Pass culling, transient aliasing и async compute не обязательны. Имена и полный журнал transitions обязательны для отладки. + +Для указанной схемы выделяются разные зависимости: + +- Upload/clear writes → compute reads/writes scene tables и counters. +- Compute writes `Args` → **draw-indirect** reads. Если compute записал `VisibleIds`, его потребитель — **vertex shader storage read**, а не indirect stage. Одна декларация «GPU buffer» этих двух назначений не выражает. +- Main depth attachment writes → compute sampled reads для HZB. Source stages — early/late fragment tests, не fragment shader. +- HZB mip write → sampled read этого mip следующей dispatch; затем HZB writes → PostCull reads. `GENERAL` layout не отменяет memory dependency. +- PostCull writes → indirect и vertex reads; depth после HZB sampling возвращается в attachment use, color/depth Main сохраняются для Post. + +Официальные [Vulkan synchronization examples](https://docs.vulkan.org/guide/latest/synchronization_examples.html) отдельно показывают compute→indirect и depth attachment→compute зависимости. Это подтверждает различие потребителей; полный набор access/layout нужно выводить из нашего фактического usage. Первый backend может применять консервативные барьеры, затем сужать их под validation и capture. Не копировать `SkipBarrier` из специализированных UE allocations без восстановления причин его безопасности. + +**Полезная проверка в Godot.** `draw_list_draw_indirect()` проверяет usage flag, pipeline и индексный буфер, передаёт команду драйверу и отдельно регистрирует `RESOURCE_USAGE_INDIRECT_BUFFER_READ` в draw graph. [Validation](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/servers/rendering/rendering_device.cpp#L6288), [команда и tracking](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/servers/rendering/rendering_device.cpp#L6370). Для нашего API полезно также проверять весь адресуемый диапазон commands с учётом count/stride, а diagnostics должны называть pass и ресурс. + +## 6. История — ресурс с владельцем и версией, а не texture pointer + +**Факт UE.** `QueueTextureExtraction()` очищает output pointer, помечает texture extracted, регистрирует extraction и делает её culling root; без явного разрешения отключает transient extraction. При compile extracted resource получает дополнительную reference, а результат возвращается в конце graph execution. [QueueTextureExtraction](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/RenderCore/Public/RenderGraphBuilder.inl#L447), [reference](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/RenderCore/Private/RenderGraphBuilder.cpp#L1373), [результат extraction](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/RenderCore/Private/RenderGraphBuilder.cpp#L2202). Следующий graph импортирует external texture и дедуплицирует её по underlying RHI texture. [RegisterExternalTexture](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/RenderCore/Private/RenderGraphBuilder.cpp#L1097). + +Так становится виден важный контракт: если HZB нужен только следующему кадру, он всё равно необходимый output текущего graph. Его producer нельзя удалить как «никто в этом кадре не читает». Graph completion на CPU, наличие pooled handle и фактическое завершение использования GPU — разные события. **Наше правило:** history slot и staging memory переиспользуются лишь после установленного порядка GPU-доступов; CPU descriptors/allocations освобождаются после соответствующего completion fence. На первом этапе никакого aliasing history с transient attachments. + +**Факт camera cut.** UE создаёт новый `FPreviousViewInfo` из текущих view rect/matrices. При first frame/time reset, camera cut, большом движении либо forced visibility reset текущий view получает этот новый набор и `bPrevTransformsReset`; обычный кадр получает прошлый набор из ViewState. [Создание](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/SceneVisibility.cpp#L5472), [условия](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/SceneVisibility.cpp#L5487), [выбор history](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/SceneVisibility.cpp#L5543). Это больше, чем `bIgnoreExistingQueries`, который обрабатывается отдельной веткой. [Occlusion query reset](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/SceneVisibility.cpp#L5572). Nanite отключает two-pass, если previous HZB отсутствует. [Проверка](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Nanite/NaniteCullRaster.cpp#L4088). + +**Предложение Faset.** `ViewHistory` хранит `{viewId, frameId, epoch, extent, viewport, projection/depth convention, matrices, HZB, completion token}`. Camera cut, новая игровая сессия, несовместимые projection/size или смена view сбрасывают validity. В invalid-frame весь current-frustum набор идёт в Main; после него создаётся новая история. Resize сначала делает новые attachments, прежние остаются жить до завершения старых submissions. Первый корректный кадр важнее попытки немедленно восстановить максимальную эффективность culling. + +Не делить history между editor viewport, Game view и thumbnail camera. При временном отсутствии рендера нельзя считать frame counter симуляции возрастом GPU history. ID reuse и импорт нового mesh инвалидируют историю соответствующего instance даже при прежней камере. + +Godot даёт небольшой полезный образец владения: `configure()` обновляет параметры и очищает старые render buffers; texture cache адресуется context/name, а `clear_context()` освобождает ресурсы выбранного эффекта. [Configure](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp#L155), [cache](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp#L328), [clear_context](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp#L496). Наш вариант должен дополнительно проверять descriptor/epoch при lookup: совпадение имени само по себе не подтверждает актуальность размеров. + +## 7. Возможности GPU и отложенные системы + +**Принятый API и будущая проверка профиля.** Минимальная версия API — Vulkan 1.3; собственный backend использует synchronization2/dynamic rendering. Для данного post-MVP прототипа проверять graphics+compute queue, storage buffers/32-bit atomics, indirect-buffer usage, vertex shader чтение instance data, sampled depth format и R32 storage/sampled image, limits buffers/dispatch и presentation на целевой ОС. API version — лишь часть проверки, а не обещание поддержки любой карты с Vulkan 1.3. Более старый backend/fallback одновременно не разрабатывается. + +**Shader toolchain — принято, ещё не интегрировано.** Один закреплённый Slang compiler в editor/cook выдаёт SPIR-V и метаданные Faset для bindings/layout; совместимый HLSL допускается в пределах проверенного подмножества. Player получает готовые `.spv` и metadata, без shader compiler. Создание GPU pipelines остаётся работой драйвера; готовый SPIR-V не устраняет эту стоимость. Согласованные matrix/buffer layouts, отражение параметров и совместимость интерфейса при замене shader проверяет наш движок. Hot reload shader — отдельная реализация Faset, не автоматическое свойство выбранного языка. См. [Slang: SPIR-V target](https://github.com/shader-slang/slang/blob/master/docs/user-guide/a2-01-spirv-target-specific.md) и [Khronos: pipeline cache](https://docs.vulkan.org/guide/latest/pipeline_cache.html). + +Mesh shaders, RT, 64-bit image atomics, sparse residency, bindless descriptor indexing и async compute не нужны предложенному fixed-material hardware prototype. Их следует запрашивать только для конкретного следующего механизма. Material bins сначала могут bind-иться CPU; GPU-generated commands не отменяют pipeline compatibility. + +D3D12 подтверждает переносимость самой идеи, но не бинарного backend-контракта: `ExecuteIndirect` читает аргументы согласно заранее созданной command signature, часть состояния наследуется от command list, а затронутые root/vertex/index bindings после выполнения имеют документированные reset rules. [Microsoft: Indirect Drawing](https://learn.microsoft.com/en-us/windows/win32/direct3d12/indirect-drawing). Поэтому не закладывать в общий API возможность «любые state changes из GPU» только потому, что один backend имеет соответствующую command signature. D3D12 здесь источник сравнения, не второй обещанный renderer. + +Полный visibility-buffer material resolve откладывается. Для проверки occlusion достаточно hardware depth плюс целочисленный instance-ID debug attachment и простое shading. Debug ID не равен Nanite visbuffer: нет восстановления triangle barycentrics, derivatives, material bins или общего SW/HW atomic winner. Также откладываются streaming, clusters/LOD, masked materials, skinning/WPO, MSAA, temporal upscaling и transparent compaction. Последнее особенно важно для 2D: unordered append не сохраняет авторский порядок слоёв. + +## 8. Проверочные сцены и критерий завершения + +Это план измерений, не результаты. Baseline — та же geometry/material/raster pipeline, но без HZB; фиксируются scene snapshot, camera path, seed, разрешение, GPU, driver и flags. Сравнивать финальную depth/instance-ID картинку после Post; у coplanar ties допустим другой победивший ID, но не дырка или неверная глубина. Culling debug показывает причины: outside frustum, history-invalid, main-visible, deferred, post-visible, post-occluded. + +1. **Пусто / один объект / capacity boundary.** Нулевые args, без чтения мусора; заполнение каждого bin до предела; сброс counts каждый кадр. При overflow запрещено лишь ограничить запись, оставив indirect count больше capacity. Initial design резервирует worst-case диапазоны, debug проверяет counts и canaries. +2. **Стена и дверь.** За стеной плотная сетка meshes; дверь резко открывается, удаляется и телепортируется. Открытые объекты должны появиться в том же итоговом кадре через Post. +3. **Камера.** Поворот, teleport/cut, переключение perspective/orthographic, near-plane crossing и камера внутри bounds. Проверять первый кадр без истории отдельно. +4. **Размеры и представления.** Нечётные width/height, смещённый viewport, resize/minimize/restore, два viewport разных размеров. Отсутствие чужой или устаревшей history — критерий корректности, не только производительности. +5. **Жизненный цикл.** Массовые spawn/despawn, reuse GPU slot, повторный импорт mesh, несколько frames in flight. Проверять generation, delayed deletion и предыдущие transforms. +6. **Контрольная открытая сцена.** Почти все instances видны. Здесь дополнительный HZB/Post overhead может сделать renderer медленнее baseline; этот результат должен оставаться видимым, а не исключаться из отчёта. + +Собирать CPU extraction/upload/submission, GPU времена каждого прохода, количество Main/Deferred/Post, counts по bins, peak memory и invalidation reasons. Readback выполнять асинхронно; profiler не должен каждый кадр ждать GPU и тем самым менять исследуемую нагрузку. Разработчик сравнивает capture bundle с настройками, изображениями, counters и diagnostics средствами отладки renderer; это не MCP endpoint к игровому процессу. MCP получает только предусмотренные операции и логи редактора, без runtime session/world inspection. Профиль без ошибок validation — необходимая, но не достаточная проверка консервативности HZB. + +Последовательность прототипа: **direct reference → GPU frustum и fixed indirect bins → current HZB визуализация → two-pass с history reset → adversarial scenes → профиль**. Только затем выбирать следующую затрату: multi-draw compaction, final HZB, FP16/mip batching или geometry LOD. Гипотеза ускорения на закрытой массовой сцене остаётся открытой до сравнения полного кадра. + +## Новые находки и фактический охват + +Главное дополнение к 07: фиксированные CPU draw templates совместимы с GPU instance visibility; velocity target не требуется для первого HZB; history extraction — output/lifetime контракт; camera reset заменяет весь previous-view набор; indirect и vertex reads требуют разных зависимостей; неполный Main HZB может быть начальным вариантом истории с измеряемой ценой в эффективности. + +Прочитаны тела UE `FInstanceCullingContext` создания buffers/submission, `InstanceCullBuildInstanceIdBufferCS`, `ClearIndirectArgInstanceCountCS`, Nanite `FBoxCull::HZB`, `WriteOccludedInstance` и main/HZB/post orchestration; `BuildHZB`, `HZBBuildCS` и HZB parameter helpers; previous-view setup/reset в SceneVisibility; RDG import/extraction, barrier compile/collection и resource reference handling. В Godot — indirect draw validation/tracking и named render-buffer creation/configuration/cleanup. В интернете — официальные Vulkan indirect/features/synchronization и Microsoft D3D12 indirect signatures. Формат/driver capability matrix для конкретных машин и runtime-поведение пока не проверялись. diff --git a/docs/studies/16-native-gameplay-and-metadata.md b/docs/studies/16-native-gameplay-and-metadata.md new file mode 100644 index 0000000..a2047a6 --- /dev/null +++ b/docs/studies/16-native-gameplay-and-metadata.md @@ -0,0 +1,118 @@ +# C++ gameplay и метаданные: от объявления поля до безопасного изменения игры + +Дата: 17 сентября 2026. Это углубление исследований [Godot UX](08-godot-ux-source-study.md), [Unity extensions](09-unity-ux-extensibility-study.md), [Blender RNA/operators](10-blender-editor-patterns.md) и [ECS](11-ecs-and-ergonomics.md). Здесь исследуется нижележащий контракт: как описание C++-типа становится доступным инструментам, как вызывается код, что переживает пересборку и кто отвечает за время жизни данных. + +**Принятые решения 18.09.2026:** C++ core/gameplay первым, затем Lua отдельным модулем (необязательным для конкретной игры); EnTT; собственная явная C++ metadata registration; статическая gameplay-библиотека в отдельном Player dev/release; schema export отдельным служебным процессом; custom inspectors в Editor DLL/SO под точный SDK с restart. **MCP только в Editor/headless editor services, без runtime inspection/mutation и без MCP в Player, игре или SchemaExporter.** Канон — [архитектура](../ARCHITECTURE.md), этапы — [PLAN.md](../../PLAN.md). Исследование ниже объясняет решения, но не является реализацией или отчётом о тестах движка. + +Исходники сверены с [manifest](source-manifest.json): UE **5.8.2**, `16d75d84714512edfb744e1fd0a59e9c74d57873`; Godot **4.8.0 dev**, `9c776068d6ed23acd0c78bfe534272d1d2a3a619`. Оба checkout остались чистыми. Изучены тела функций и генератора; движки и тестовый gameplay-модуль не собирались. Дополнительно прочитаны официальные документы UE 5.8, Lua 5.5 и текущий Godot docs source, commit `e1129cb5c3f27e0e3a12e816ae405ab5ce032e3f`. Последний нужен потому, что разделы GDExtension перемещены относительно прежних URL; это документация разработки, а не гарантия поведения любого стабильного Godot. + +Главный вывод: **небольшая явная схема экспортируемого API важнее универсальной reflection**. Нужны отдельно идентичность данных, способ доступа к памяти текущего бинарного модуля и правила исполнения операций. Inspector, MCP и будущий Lua используют выбранную часть этой схемы; обычный C++ сохраняет типизированные вызовы и не обязан превращать каждое присваивание в динамический вызов. + +## 1. UE: генерация дескриптора связывает C++ layout с выбранным набором возможностей + +**Путь исполнения.** UBT запускает UHT до обычного C++-компилятора: UHT разбирает размеченные заголовки и генерирует код UObject-системы, затем компилятор собирает результат. Это отдельная стадия сборки, а не обнаружение произвольных типов уже работающей программы. Поддерживаемый механизм стороннего расширения UHT — exporters; возможность добавить свой exporter не означает возможность без ограничений расширить весь язык описаний UE. [Официальный UHT](https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-header-tool-for-unreal-engine). + +У `UhtProperty.Validate` есть проверки допустимости описания: например, статические массивы контейнеров отклоняются. `ValidateMember` проверяет согласованность edit-флагов и требует Category для определённых экспортируемых полей engine-модулей. Это **валидация объявления**, а не проверка каждого будущего значения `Health`. [UhtProperty.cs:2367](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Programs/Shared/EpicGames.UHT/Types/UhtProperty.cs#L2367), [ValidateMember:2547](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Programs/Shared/EpicGames.UHT/Types/UhtProperty.cs#L2547). + +Далее `AppendParamsDefStart` выводит имя поля, property flags, указатели на доступные getter/setter wrappers, размер массива и `STRUCT_OFFSET`. В Params-пути `ConstructUClassHelper` строит зависимости, регистрирует класс, связывает функции, вызывает `ConstructFProperties`, а затем `StaticLink`. `ConstructFProperty` выбирает специализированный тип свойства и рекурсивно создаёт дочерние описания контейнеров. `NewFProperty` выбирает вариант с accessor-функциями, если они заданы; конструктор `FProperty` сохраняет offset текущего бинарного layout. [Генерация параметров](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Programs/Shared/EpicGames.UHT/Types/UhtProperty.cs#L1643), [конструирование класса](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectConstructInternal.h#L213), [создание свойств](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectGlobals.cpp#L6175), [сохранение offset](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/CoreUObject/Private/UObject/Property.cpp#L774). + +В этом checkout нельзя описывать Params как единственный актуальный механизм. `AppendPropertiesDecl/Defs` отдельно поддерживают **ConstInit** и **Params**, с условием `UE_WITH_CONSTINIT_UOBJECT`. ConstInit-генерация тоже содержит имя, флаги, связи и `STRUCT_OFFSET`, но создаёт непосредственно инициализируемые описания. Здесь проверены обе ветки генератора; активная конфигурация конкретной сборки не устанавливалась. [Выбор формата](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Programs/Shared/EpicGames.UHT/Exporters/CodeGen/UhtHeaderCodeGeneratorCppFile.cs#L1758), [ConstInit-параметры](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Programs/Shared/EpicGames.UHT/Types/UhtProperty.cs#L1887). + +Методам тоже требуется glue code. `AppendFunctionThunk` генерирует получение аргументов, завершение разбора параметров, вызов C++-реализации и запись результата. Дополнительная проверка `_Validate` здесь включается для `NetValidate`, а не для каждого экспортированного метода. Следовательно, наличие thunk само по себе не даёт доменную валидацию, транзакцию или право вызывать метод из редактора. [Генерация thunk](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Programs/Shared/EpicGames.UHT/Exporters/CodeGen/UhtHeaderCodeGeneratorCppFile.cs#L3483). + +**Что перенять.** Для Faset принята явная регистрация выбранных компонентов, полей и методов в C++ gameplay-модуле. Шаблонные helpers могут принимать типизированные member pointers/accessors и проверять поддерживаемые сигнатуры компилятором. Не начинать с собственного парсера всего C++; codegen поверх ограниченных аннотаций имеет смысл позже, когда повторяющиеся регистрации станут измеримой проблемой. Сгенерированные файлы должны зависеть от исходной схемы, версии генератора и конфигурации, попадать в build diagnostics и обновляться только при изменении содержимого. + +Отдельно хранить UI-подсказки и обязательные runtime-правила. UE прямо предупреждает, что metadata предназначена для editor; просмотренный codegen ограждает её `WITH_METADATA`, тогда как runtime property flags и типизированные описания остаются отдельными сущностями. Нельзя строить правило «отрицательная масса запрещена» только на строке для slider. [Metadata specifiers](https://dev.epicgames.com/documentation/en-us/unreal-engine/metadata-specifiers-in-unreal-engine), [условное добавление metadata](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectGlobals.cpp#L6189). + +## 2. UE: загрузка свойства — сопоставление схем, а не копирование старого layout + +**Путь исполнения.** `SerializeTaggedProperties` выбирает versioned либо unversioned-представление архива. Ниже исследован именно **versioned tagged path**, поэтому его особенности нельзя приписывать всем cooked-данным UE. [Развилка сериализации](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp#L1476). + +При загрузке `SerializeVersionedTaggedProperties` читает `FPropertyTag`, сначала сопоставляет его ожидаемому свойству, затем при несовпадении ищет по имени. При разрешённых условиях применяются property redirects. GUID-ветка также условная: код отдельно отмечает доступность property GUID для поддерживающих их классов, в частности Blueprint-generated classes. **Это не доказательство наличия постоянного GUID у каждого native UPROPERTY.** [Чтение и сопоставление](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp#L1620), [redirect имени](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp#L1685). + +После сопоставления проверяются editor-only policy, индекс массива и `ShouldSerializeValue`. Затем вызывается `ConvertFromType`: результат может означать выполненное преобразование, собственную десериализацию, обычный `SerializeItem` либо невозможность преобразования. Только у подходящего свойства вычисляется **нынешний** адрес через `ContainerPtrToValuePtr`, после чего читается значение. При несовпадении типа есть диагностический путь; обработка неизвестных свойств имеет дополнительные editor-only условия и не равна обещанию автоматически сохранить произвольные потерянные поля. [Проверки и преобразование](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp#L1793), [адрес текущего поля](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp#L1843), [unknown-property tracking](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp#L1867). + +**Что перенять.** В authoring-формате Faset определить `TypeId`, `FieldId` и версию схемы независимо от C++-имени, `typeid`, offset и порядка деклараций. Читаемая подпись и путь к исходнику — изменяемые атрибуты. Fingerprint текущего layout полезен для проверки совместимости бинарного модуля, но не должен становиться постоянной идентичностью сохраняемого поля. + +Переименование `open_speed` в `angular_speed` при неизменном смысле сохраняет `FieldId`; смена единиц с градусов на радианы требует явной миграции значения. Разделение одного поля на несколько требует миграции с доступом к документу. Новые поля получают defaults по правилам версии, удалённые — явную политику сохранения/удаления неизвестных данных. Один UUID этого не решает. Миграцию следует выполнять над копией authoring-документа, валидировать результат и публиковать целиком, с диагностикой и возможностью отката. + +Отсутствующий gameplay-пакет должен оставлять читаемые непрозрачные записи компонентов и их ссылки, а не превращать открытие и сохранение сцены в потерю данных. Запуск сцены может быть запрещён до восстановления обязательного типа. Это предлагаемая политика Faset, а не универсальное поведение изученных UE-архивов. Cooked-представление можно уплотнять после миграции и проверки; оно привязывается к конкретному schema/build fingerprint и при несовместимости пересобирается. + +## 3. Reflection и владение объектами — связанные, но разные механизмы + +**Путь исполнения.** UE `FObjectProperty::EmitReferenceInfo` добавляет в GC schema позиции объектных ссылок, учитывая offset и элементы массива. Базовая реализация `FProperty::EmitReferenceInfo` пустая: понимание того, что поле содержит объектную ссылку, обеспечивается специализированным descriptor. Это полезный пример того, что тип свойства участвует не только в Inspector. [GC reference schema](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/CoreUObject/Private/UObject/GarbageCollection.cpp#L6833). + +Но слабая ссылка решает другую задачу. `FWeakObjectPtr` при присваивании сохраняет index/serial; при разрешении проверяет соответствие текущему элементу object array, а `Internal_Get` дополнительно проверяет пригодность объекта. В checkout есть отдельная конфигурация remote-object handles; приведённое объяснение index/serial относится к обычному локальному пути. [Создание weak handle](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/CoreUObject/Private/UObject/WeakObjectPtr.cpp#L29), [сопоставление serial](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/CoreUObject/Public/UObject/WeakObjectPtr.h#L428), [разрешение указателя](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/CoreUObject/Public/UObject/WeakObjectPtr.h#L560). + +Godot даёт независимую проверку идеи: `ObjectDB::get_instance` извлекает slot и validator из ObjectID, возвращает null при несовпадении и освобождает spinlock перед возвратом указателя. Это проверка идентичности, а не автоматически удерживаемая блокировка на всё дальнейшее использование объекта. [ObjectDB lookup](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/object/object.h#L914). + +**Что перенять.** Не нужно копировать UObject GC, чтобы получить удобный C++ gameplay. Нужны явные категории: значение компонента, ссылка на entity, удерживаемый ресурс, наблюдаемая слабая ссылка и временный доступ к памяти. При принятом EnTT descriptor поля не вправе хранить постоянный pointer на компонент: structural change может переместить данные, что подробно разобрано в [исследовании ECS](11-ecs-and-ergonomics.md). + +Принятый runtime handle включает world/session и generation; постоянная ссылка authoring-документа использует другую идентичность. Runtime handles не открываются для inspection/mutation через MCP. Доступ к компоненту разрешается на время фазы/заимствования, а дальнейшее использование требует повторного resolve. Проверка handle не заменяет scheduler и владение при многопоточности. Подписки, отложенные callbacks и Lua userdata должны хранить проверяемую ссылку либо явно владеть ресурсом; завершение сессии инвалидирует её даже при повторном использовании локального index. + +## 4. Godot: поле регистрируется через методы, а ошибка вызова не заменяет проверку смысла + +**Путь исполнения.** У `Light3D::_bind_methods` зарегистрирован `set_param`; несколько полей света используют `ADD_PROPERTYI` с одним setter/getter и разными индексами параметра. Например, `light_energy` описан как float с range hint. Это конкретный способ не писать отдельный универсальный getter/setter для каждой записи схемы. [Регистрация метода](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/scene/3d/light_3d.cpp#L362), [описания полей](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/scene/3d/light_3d.cpp#L413). + +В данном dev-снимке `ClassDB::add_property` передаёт описание в **GDType**, поэтому сводить реализацию только к старым таблицам ClassDB было бы неточно. `GDType::add_property` разрешает регистрацию в mutable-фазе и на потоке владельца, отклоняет дубликаты, находит MethodBind getter/setter, проверяет число аргументов с учётом индекса и сохраняет property record. Отдельный ordered list нужен перечислению. [ClassDB bridge](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/object/class_db.cpp#L1339), [GDType registration](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/object/gdtype.cpp#L192). + +`Object::set` сначала предлагает изменение script-instance и extension callbacks, затем вызывает native setter. `set_native` ищет свойство по имени, формирует один аргумент либо пару «индекс + значение» и вызывает сохранённый MethodBind. `r_valid` определяется через `Callable::CallError`. У `Light3D::set_param` проверяется индекс, затем значение сохраняется, передаётся RenderingServer, при нужных параметрах обновляются gizmos/warnings. Здесь range hint не читается и не превращается автоматически в clamp входного числа. [Общий set](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/object/object.cpp#L198), [native dispatch](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/object/object.cpp#L349), [setter света](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/scene/3d/light_3d.cpp#L40). + +Ещё одна важная граница: `Object::validate_property(PropertyInfo&)` вызывает native/extension/script callbacks, которые могут изменить **тип, hint, имя и usage описания**. Эта функция не получает новое значение свойства. `ClassDB::get_property_list` использует её при перечислении для конкретного объекта. Название `_validate_property` поэтому нельзя понимать как единый валидатор входных данных. [Изменение PropertyInfo](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/object/object.cpp#L623), [валидация при перечислении](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/object/class_db.cpp#L1376). + +**Что перенять.** Разделить минимум три контракта: `describe(context)` для представления и доступности поля; чистый `validate(proposed_state)` для типа, диапазона и связанных инвариантов; `commit(changes)` для публикации и уведомлений. Если setter меняет второе поле, посылает renderer-команду или событие, простой вызов нескольких setters не даёт атомарный MCP batch и надёжный Undo. + +В Faset UI и MCP должны валидировать один proposed authoring-state до commit, а дорогую invalidation собирать после него. Gameplay hot loop при этом вправе работать напрямую с разрешёнными компонентами: не нужно прогонять каждое обновление Position через editor transaction. Доменные операции вроде «применить импульс» и «перестроить collider» отличаются от редактирования сохраняемого параметра; публичный API обязан выражать это явно. + +Официальный C++-пример Godot показывает ручную регистрацию methods, properties и signals и появление полей в Inspector после компиляции. Это подтверждает практичность явного экспортируемого подмножества C++, но не автоматическую поддержку любого класса или шаблона. [Текущий пример в Godot docs](https://github.com/godotengine/godot-docs/blob/e1129cb5c3f27e0e3a12e816ae405ab5ce032e3f/tutorials/scripting/cpp/gdextension_cpp_example.rst). + +## 5. GDExtension: ABI требует версий и правил восстановления, даже когда функции уже описаны + +**Путь исполнения.** `GDExtensionLibraryLoader::initialize` получает entry symbol из динамической библиотеки и вызывает функцию инициализации с `get_proc_address`, library pointer и структурой результата. Официальная документация разделяет C interface, экспортируемый API и `.gdextension`-описание загрузки. Это организованная граница между движком и библиотекой, не загрузка произвольного C++-объекта по его имени. [Инициализация библиотеки](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/extension/gdextension_library_loader.cpp#L221), [устройство GDExtension](https://github.com/godotengine/godot-docs/blob/e1129cb5c3f27e0e3a12e816ae405ab5ce032e3f/engine_details/engine_api/gdextension/what_is_gdextension.rst). + +`_register_extension_class_method` создаёт GDExtensionMethodBind и передаёт его в ClassDB. `update` копирует callback pointers, argument/return descriptions и defaults. `call` передаёт аргументы extension-функции и преобразует её error record обратно в `Callable::CallError`; `validated_call`/`ptrcall` образуют отдельные пути для уже подготовленных данных. Эти пути не означают одинаковую стоимость и одинаковый набор проверок. При получении engine method bind API учитывает hash сигнатуры и compatibility fallback; несовместимый метод не просто вызывается по совпавшему имени. [Регистрация](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/extension/gdextension.cpp#L574), [описание и dispatch](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/extension/gdextension.cpp#L100), [копирование method info](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/extension/gdextension.cpp#L194), [signature lookup](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/extension/gdextension_interface.cpp#L1633). + +Reload особенно показателен. `try_update` проверяет static/vararg, наличие и тип результата, число и типы аргументов. При несовместимости старая привязка помечается invalid и создаётся новая; вызов invalid bind в tools-сборке имеет отдельную ошибку. `prepare_reload` сохраняет выбранные `PROPERTY_USAGE_STORAGE` значения, пропуская часть defaults/nulls. `finish_reload` восстанавливает extension-части и затем вызывает `set` для сохранённых свойств. Смена parent type при регистрации отдельно отклоняется с требованием restart. Это **восстановление через описанные свойства и специальные lifecycle hooks**, не универсальное сохранение всей native памяти. [Совместимость метода](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/extension/gdextension.cpp#L161), [состояние до reload](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/extension/gdextension.cpp#L916), [восстановление](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/extension/gdextension.cpp#L999), [ограничение parent type](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/extension/gdextension.cpp#L473). + +Документация ориентирует расширения на совместимость с более поздними minor-версиями, указывает исключение Godot 4.0 и требует согласованной floating-point precision; для custom engine предлагается генерировать свой API description. Это поддерживаемая политика с условиями, а не обещание вечной совместимости любого бинарного файла. [Godot-cpp compatibility](https://github.com/godotengine/godot-docs/blob/e1129cb5c3f27e0e3a12e816ae405ab5ce032e3f/tutorials/scripting/cpp/about_godot_cpp.rst). + +**Принято для Faset.** Gameplay — отдельная **статическая библиотека, связанная с Player в development и release**. Linux использует Clang, Windows — clang-cl с согласованными SDK/CRT. Editor plugins поставляются DLL/SO под конкретный Editor SDK/build и загружаются при запуске; обновление требует restart Editor. Manifest задаёт module ID/version, зависимости, назначение runtime/editor и build fingerprint. Entry function согласует API version, ownership и ошибки; произвольные compiler/stdlib combinations не получают обещания стабильного ABI. EnTT registry не экспортируется как универсальный native plugin ABI. + +Принятый Play: **stop → build → restart**. Сохранение существующих vtables, component pointers, closures, задач и layout не обещается. Native editor plugin способен уронить Editor; отдельный Player изолирует игровой код, который действительно остаётся вне редактора. + +**Schema export после сборки — отдельная обязательная стадия.** Служебная target `SchemaExporter` связывается с теми же C++ registration units, выполняет регистрацию без мира, renderer и lifecycle callbacks и пишет декларативный manifest: TypeId/FieldId, версии, value types, явные defaults, constraints, units, hints и source locations. Editor запускает процесс, проверяет exit status/manifest/build fingerprint и лишь затем публикует новую схему Inspector. Сбой оставляет последнюю корректную схему и diagnostics. C++ регистрация всё же исполняется, поэтому отдельный процесс не называется sandbox; произвольные static initializers нельзя считать безопасными только из-за имени режима. **MCP в этом процессе отсутствует.** + +Custom Inspector реализуется отдельным Editor module над authoring command API. Декларативные constraints проверяет AuthoringService; исполняемые дополнительные валидаторы/миграции требуют явного editor/helper-контракта. Inspector не вызывает runtime accessor по адресу из manifest и не загружает gameplay DLL ради перечисления полей. Сборка отбрасывает зависимости runtime от editor/MCP; JSON схемы не содержит pointers или сериализованных C++ layouts. + +## Принятый контракт Faset: C++ первым, Lua следующим этапом + +**Компонент.** `TypeId`, version, module owner, зависимости, правила создания/удаления и список полей. Отдельно — runtime size/alignment, callbacks construction/destruction/move и registration generation. Выбран EnTT, но descriptor отделён от его runtime storage и не сериализует registry. Для полиморфных или нестандартных layout использовать типизированные accessors; не публиковать вычисленные offsets через MCP. + +**Поле.** `FieldId`, переносимый value type, default, единицы, hard constraints, UI hints, read/write policy и категории invalidation. AssetRef и SceneEntityRef — явные типы, не произвольные строки. Коллекциям нужны правила адресации элементов и конфликтов после вставки/удаления. В JSON сохраняются смысловые значения и ID; memory adapters принадлежат конкретной сборке. + +**Метод.** `MethodId`, именованные аргументы/результат, error schema, домен исполнения, требуемая фаза, side effects и lifetime результата. Обычный native метод не получает автоматически права editor-команды. `OpenDoor` — runtime-действие gameplay/Lua, `SetDoorDefaults` — authoring-транзакция, `InspectDoorAuthoring` — запрос к документу. MCP может вызывать только editor authoring-контракты; runtime method exposure не предоставляется. Общая metadata не объединяет права и домены исполнения. + +**Событие.** `EventId`, payload schema, фаза доставки и ownership подписки. Начать с очереди копируемых payload и scope, освобождаемого при удалении поведения/мира. Listener не удерживает случайный pointer на переехавший компонент; callback другого языка не вызывается из произвольного потока. Metadata описывает payload, но сама не выбирает порядок, рекурсию и гарантию доставки. + +**Ручное программирование.** Отдельный `Gameplay` модуль, понятный typed API и обычный C++ debugger; минимальная регистрация нужна только инструментально доступной части. Для двери автор пишет реакцию на взаимодействие и целевой угол, для тысячи объектов — batch system. Метаданные дают Inspector и `schema.describe` один источник имён, типов и документации, но MCP вызывает только authoring/build/import/PlayStop/editor-log операции из [контракта автоматизации](12-mcp-and-blender-integration.md), без произвольного native address и без runtime world inspection/mutation. + +**Lifecycle.** C++-поведения используют `OnStart`, `FixedUpdate`, `Update`, `LateUpdate`, `OnDestroy`; Lua повторит те же фазы. Первый scheduler последовательный. Spawn/despawn/add/remove записываются в очередь и применяются в начале следующего fixed tick после завершения прежних задач. `OnStart` идёт после создания, `OnDestroy` — до освобождения допустимых данных; подписки имеют ограниченный lifetime. Fixed tick 60 Гц по умолчанию, bounded catch-up и render interpolation описаны в [архитектуре](../ARCHITECTURE.md). + +**Lua после C++.** Реализация Lua-модуля принята как следующий этап после C++ gameplay; конкретный срок определяется отдельно. Необязательным остаётся использование Lua отдельной игрой. На первом этапе проектируем пригодную границу для его подключения. Модуль добавит VM, conversion wrappers, binding registry, сообщения об ошибках и scope подписок. Экспортируются явно разрешённые компоненты/методы; templates, arbitrary pointers и все C++ overloads не становятся скриптовым API автоматически. Генерация справки/IDE declarations полезна, но не заменяет runtime-проверки. + +Lua full userdata предоставляет память для wrapper, а `luaL_checkudata` проверяет его metatable type; это не проверка существования entity. Поэтому wrapper хранит handle и повторно разрешает его при обращении. `lua_pcall` возвращает ошибку защищённого вызова, а message handler позволяет собрать traceback. Lua использует `longjmp` либо C++ exceptions в зависимости от сборки: bindings должны явно учитывать эту границу и не рассчитывать, что RAII cleanup автоматически сработает через любой Lua error. [Lua 5.5: userdata](https://www.lua.org/manual/5.5/manual.html#lua_newuserdatauv), [проверка wrapper](https://www.lua.org/manual/5.5/manual.html#luaL_checkudata), [ошибки](https://www.lua.org/manual/5.5/manual.html#4.4), [protected call](https://www.lua.org/manual/5.5/manual.html#lua_pcall). + +Это адаптация для Faset, не доказательство безопасности будущего binding. Lua GC освобождает wrapper по его правилам, а игровая entity живёт по правилам мира; владение ресурсом следует указывать отдельно. Базовая C++-игра должна собираться без Lua headers/runtime и без динамической ветки на каждом компонентном доступе. + +## Первый прототип и критерии принятия + +Объём первого сквозного прототипа по [плану](../../PLAN.md): один gameplay-модуль **Door**, поля угла/скорости и asset reference, метод взаимодействия, событие открытия; одна 2D- и одна 3D-сцена используют тот же контракт описания, сохраняя разные spatial/physics-типы. Здесь не требуется реализовать полный animation graph, native hot reload или универсальный binding generator. + +1. **Регистрация и discovery.** Дубликат TypeId/FieldId, несовместимый accessor и неподдерживаемый тип дают ошибку со ссылкой на исходник. Inspector и MCP описывают одинаковые поля, units и read-only правила. Новый layout меняет build fingerprint, но не IDs сохранённых данных. +2. **Одинаковая правка.** Ручной multi-edit и MCP batch дают эквивалентный документ; недопустимая скорость и конфликт revision не применяют половину изменений. Побочная invalidation выполняется после commit, Undo восстанавливает связанные поля. Прямая runtime-правка не записывает authoring-сцену. +3. **Эволюция данных.** Переименовать C++-поле, изменить порядок членов, добавить поле с default, затем выполнить отдельную миграцию единиц. Сохранённые overrides остаются привязаны к правильным полям. Неудачная миграция и временное отсутствие пакета не уничтожают исходные записи. +4. **Время жизни.** Сохранить handle, удалить entity, переиспользовать slot, перезапустить Play. Старый handle и поздний callback отвергаются; ссылка на компонент не переживает structural change. Если позже подключён Lua, тот же сценарий выполняется с userdata и сборкой мусора. +5. **Сборка и границы.** Редактирование gameplay `.cpp` пересобирает реальные зависимости и статически связанный Player. SchemaExporter выдаёт manifest без OnStart/Update и без загрузки gameplay в Editor; сбой не заменяет рабочую схему. GUI/editor MCP получают compiler/schema diagnostics и build/schema IDs. Crash Player не закрывает Editor; изменение layout требует restart. Несовместимый editor plugin manifest отклоняется до загрузки. Player и exporter не содержат MCP, запросы runtime inspection/mutation через Editor отклоняются. +6. **Опциональность.** C++-вариант собирается и запускается без Lua. В отдельном будущем эксперименте Lua-реакция вызывает ту же доменную операцию и видит те же компоненты; ошибки аргументов, traceback и cleanup подписок проверяются явно. Это проверка запланированного Lua-модуля и его необязательности для проектов только на C++. + +Измерять edit→diagnostic, edit→Play, стоимость schema export и загрузки сцены, количество allocations на вызов, время массового typed-path относительно динамического вызова и число действий/ошибок при создании двери. Не подменять удобство краткостью регистрации: если автор не понимает, почему setter изменил другое поле или почему ссылка устарела, reflection ещё не решила задачу API. diff --git a/docs/studies/17-asset-pipeline-and-blender-roundtrip.md b/docs/studies/17-asset-pipeline-and-blender-roundtrip.md new file mode 100644 index 0000000..2e31f4d --- /dev/null +++ b/docs/studies/17-asset-pipeline-and-blender-roundtrip.md @@ -0,0 +1,142 @@ +# Импорт ассетов и надёжный обмен с Blender + +Исследование от 17.09.2026; синхронизация решений 18.09.2026. Для Faset Engine принято **импортировать опубликованное поколение ассета, сохранять идентичность его частей независимо от имён и хранить пользовательские изменения вне результата импортёра**. Основная новая задача — определить, что именно осталось прежним после экспорта, какие зависимости изменились и когда новый результат можно сделать активным. + +Документ углубляет [контракт MCP/Blender](12-mcp-and-blender-integration.md), [Godot UX](08-godot-ux-source-study.md) и [редактор Blender](10-blender-editor-patterns.md). Основные контракты приняты в [архитектуре](../ARCHITECTURE.md), этапы до/после MVP — в [PLAN.md](../../PLAN.md). Точные имена API, поля примерного manifest и dependency pins уточняются реализацией. Box2D/Box3D выбраны; исследовательские SHA ниже не фиксируют версии зависимостей Faset. Ни импортёры, ни движки здесь не запускались; тесты ниже — критерии будущей реализации. + +**Два равноправных входа.** Стандартный `.gltf`/`.glb` импортируется без Blender и без специального add-on. Faset создаёт собственные asset metadata/recipe и импортирует проверенный snapshot файлов. Используется обычный официальный Blender без модификации исходников; optional Python add-on сохраняет IDs частей и публикует GLB + manifest удобной командой. Расширенный bundle ниже описывает именно этот дополнительный надёжный roundtrip, не обязательный формат любого входного ресурса. + +Без устойчивых source IDs нельзя гарантировать matching после rename/reorder/split. Sidecar сохраняет уже назначенные engine IDs, но не доказывает, что новый glTF node — прежний объект. Неоднозначность требует diagnostic/remap; эвристика имени не становится гарантией. Геометрия/rig/animation принадлежат Blender; gameplay, physics settings и instance overrides — Faset. MCP запускает импорт и читает его editor diagnostics, но не редактирует/инспектирует runtime world и отсутствует в Player. + +## 1. Godot: что запускает повторный импорт + +**Подтверждено исходниками.** Проверка имеет два уровня. `_is_test_for_reimport_needed` сначала сопоставляет времена изменения исходника и `.import`; при соответствующей настройке проверяет отсутствие outputs. Следующий `_test_for_reimport` проверяет checksum sidecar, сохранённые importer/UID/outputs, актуальность project-dependent settings и контрольные суммы исходного и производных файлов. Рост `get_format_version()` относительно сохранённой версии также требует импорта. Это не универсальный content-addressed build graph: внешний быстрый фильтр доверяет совпадению timestamps. [Быстрый фильтр](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L562), [версия и настройки](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L680), [checksums](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L727). + +Путь результата вычисляется из имени и hash **пути** исходника, а не только его содержимого. Поэтому move способен изменить адрес кэша при сохранении идентичности ресурса. `_reimport_file` получает прежний UID и параметры из sidecar, добавляет defaults, вызывает importer и записывает outputs, UID, format version и параметры. Контрольные суммы хранятся отдельно от пользовательских настроек. [Адрес кэша](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/io/resource_importer.cpp#L541), [чтение прежних параметров](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L2826), [вызов importer](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L2922), [раздельное сохранение](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L3018). + +Обработка ошибки тоже часть архитектуры: sidecar может получить `valid=false`; последующая проверка не запускает бесконечный автоматический retry для уже неудачного импорта. Из этого **не следует** сохранение последнего рабочего результата или атомарность нескольких outputs: показанная функция вызывает importer, а затем отдельно пишет `.import` и `.md5`. [Неудачный результат](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L2954), [подавление цикла ошибок](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L630). + +**Принятый принцип Faset; детали реализации.** Watcher только сообщает «возможно изменилось». Для воспроизводимого импорта нужен digest реальных входов и зафиксированный recipe. Первая версия может инвалидировать целый bundle; позднее разделить mesh, texture, animation и collider jobs. Важнее получить объяснение `why_reimport`: source bytes / recipe / importer build / dependency artifact / target profile / missing output. Ошибка становится состоянием с диагностикой и последним успешным поколением, а не поводом перезапускаться при каждом обновлении дерева файлов. + +## 2. UID файла не решает идентичность внутренних объектов + +**Подтверждено Godot.** `ResourceUID::create_id_for_path` первоначально использует seed из имени проекта, пути в нижнем регистре и MD5 файла. Устойчивость при следующих импортах обеспечивается сохранённым UID и registry, а не повторным вычислением этой формулы. При обнаружении двух существующих файлов с одинаковым UID сканер назначает новому файлу другой ID; если старого пути больше нет, может обновить сопоставление прежнего ID. [Создание UID](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/core/io/resource_uid.cpp#L127), [дубликат и перемещение](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/file_system/editor_file_system.cpp#L912). + +Для внутренних элементов есть отдельные ключи. Настройки узла находят по `import_id`, с fallback `PATH:` + путь от корня. Mesh и material используют `import_id`, а при его отсутствии имя. Для внешне сохраняемого subresource `convert_path_to_uid` предпочитает существующий UID; иначе может получить его из source UID и логического ключа. Это полезное пространство имён, но переименование самого ключа не становится автоматически безопасным. [Узлы](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/import/3d/resource_importer_scene.cpp#L1138), [mesh](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/import/3d/resource_importer_scene.cpp#L2811), [material](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/import/3d/resource_importer_scene.cpp#L1626), [производный UID](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/import/3d/resource_importer_scene.cpp#L3199). + +**Принятый принцип Faset; детали реализации.** Развести четыре понятия: + +- `asset_id` — постоянная идентичность публикуемого ассета, например двери целиком. +- `source_id` — идентичность исходного Object/Mesh/Material/Action, назначенная в Blender. +- `output_id` — идентичность части, на которую ссылается движок: mesh, material slot, clip, collider или узел импортируемой сцены. +- `content_hash` и `generation` — конкретное содержимое и согласованная версия результатов. + +Ссылка сцены — `(asset_id, output_id, expected_kind)`. Имя, glTF index, путь файла и runtime handle в неё не входят. Один source может давать несколько outputs; несколько Actions могут образовать один clip. Для простых соответствий output ID допустимо выводить из `asset_id + source_id + постоянная semantic role`; изменение содержимого или версии compiler не должно само менять ID. Split/merge и смена типа output требуют явного migration/remap, а не новой случайной нумерации. + +## 3. Unity: зависимости должны различать исходник и результат + +**Подтверждено C# и документацией.** Native-код вызывает `ScriptedImporter.GenerateAssetData`, который передаёт контекст в `OnImportAsset`; регистрация importer передаёт version, extension, очередь и настройку caching. `AssetImportContext` создаётся native-стороной. `AddObjectToAsset(identifier, object)` добавляет часть результата; официальный контракт требует воспроизводить один и тот же identifier при повторном импорте, уникальный внутри asset. [Вход импортёра](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Modules/AssetPipelineEditor/Public/ScriptedImporter.cs#L25), [регистрация](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Modules/AssetPipelineEditor/Public/ScriptedImporter.cs#L121), [native boundary](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs#L35), [контракт identifier, Unity 6.0](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/AssetImporters.AssetImportContext.AddObjectToAsset.html). + +API отдельно выражает `DependsOnSourceAsset`, `DependsOnArtifact` и `DependsOnCustomDependency`. C# проверяет аргументы и вызывает native bindings; отсюда виден контракт, но не устройство хранилища, scheduler или crash-safe commit. External remap — ещё один механизм: `SourceAssetIdentifier` содержит type/name, `GetExternalObjectMap` собирает пары из native-массивов. Это не тот же идентификатор, что stable local ID результата. [Разделение зависимостей](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs#L66), [artifact dependency](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs#L156), [custom dependency](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs#L232), [remap key](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/AssetPipeline/AssetImporter.bindings.cs#L25), [external map](https://github.com/Unity-Technologies/UnityCsReference/blob/6b50e5544f6efcca1f44dbace3d1778b465ac6d0/Editor/Mono/AssetPipeline/AssetImporter.bindings.cs#L150). + +**Принятый принцип Faset; детали реализации.** Import context предоставляет `read_source`, `read_artifact`, `read_setting` и записывает зависимости автоматически. Смена текстуры должна инвалидировать читающий её material stage; смена только placement экземпляра не должна перекомпилировать texture. В ключ входят importer build digest, canonical options, target capabilities и фактически прочитанные dependency digests. Blender/exporter version относится к export recipe; изменение `.blend`, давшее идентичный опубликованный glTF и metadata, само по себе не обязано пересобирать runtime mesh. Случайные зависимости от рабочего каталога, времени или последнего UI preset исключаются контрактом importer. + +## 4. Что реально делает Blender exporter + +Godot показывает готовую границу процессов: background Blender открывает `.blend`, вызывает `bpy.ops.export_scene.gltf` с явными options; затем Godot импортирует полученный glTF. Это подтверждает полезность такого обмена, но не делает Blender обязательным runtime dependency. [Фоновый экспорт](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/modules/gltf/editor/editor_import_blend_runner.cpp#L85), [запуск процесса](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/modules/gltf/editor/editor_import_blend_runner.cpp#L160), [настройки и последующий импорт](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/modules/gltf/editor/editor_scene_importer_blend.cpp#L277). + +В Blender `save` переключает object mode при необходимости, меняет frame для экспорта, вызывает gather и write, затем возвращает frame. Это код с контекстом и побочными изменениями UI-состояния, поэтому helper должен явно выбрать scene/collection и восстанавливать собственный временный контекст при ошибках. `generate_extras` фильтрует custom properties и преобразует значения; node exporter подключает это только при включённом extras. Собственная строка UUID проходит здесь как данные, но стандарт не назначает ей семантику. [save](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/scripts/addons_core/io_scene_gltf2/blender/exp/export.py#L21), [extras](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/scripts/addons_core/io_scene_gltf2/blender/com/extras.py#L23), [node extras](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/scripts/addons_core/io_scene_gltf2/blender/exp/nodes.py#L265). + +Для устойчивых IDs есть важные ограничения. Обычное чтение `.blend` сбрасывает `session_uid`, поэтому он не подходит для межсессионных asset references. Custom ID properties сохраняются в `.blend`, но копирование datablock копирует и properties: дублирование объекта может дублировать наш UUID. [Сброс session UID](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/blenloader/intern/readfile.cc#L2275), [копирование properties](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/blenkernel/intern/lib_id.cc#L1732), [сериализация](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/source/blender/blenkernel/intern/lib_id.cc#L2895). + +**Необязательный Python helper/add-on в официальном Blender:** хранить namespaced UUID properties, проверять uniqueness в публикации и сохранять IDs в authoring-файл. Два Objects могут законно ссылаться на один Mesh ID; два разных Mesh datablocks с одним ID — ошибка. Если helper наблюдал операцию duplicate, новый Object получает новый ID. Если обнаружены уже сохранённые дубликаты и непонятно, кто оригинал, показать `DuplicateSourceId` и явную команду fork identity; не выбирать по порядку обхода. Linked library data требуют отдельного namespace исходной библиотеки или подготовленных IDs в библиотеке; в v1 не обещать автоматическую устойчивость произвольного linked/generated контента. + +## 5. Минимальный bundle и публикация поколения + +**Эскиз проектного формата для расширенного обмена, не существующий стандарт и не финальная wire schema.** Пример `bundle.json` optional add-on содержит: + +```json +{ + "schema_version": 1, + "asset_id": "", + "generation": "", + "source": {"document_id": "", "path_hint": "door.blend"}, + "exporter": {"blender_build": "", "helper_version": 1}, + "recipe": {"profile": "faset-gltf-v1", "digest": ""}, + "files": [{"path": "payload/.glb", "sha256": ""}], + "outputs": [{ + "output_id": "", "kind": "mesh", "source_ids": [""], + "role": "render_mesh", "name": "DoorLeaf", + "locator": {"file": 0, "json_pointer": "/meshes/2"} + }], + "dependencies": [], + "profile": {"coordinates": "gltf-rh-y-up", "linear_unit": "meter"} +} +``` + +`generation` считают без собственного поля; canonicalization и hash algorithm входят в спецификацию schema. Locator действителен только внутри данного поколения и получается **после** окончательного формирования glTF. Helper сопоставляет extras с outputs, проверяет единственность и полноту; имени недостаточно. Полные export options сохраняются в versioned recipe, engine import/cook options — отдельно. Generated collider, LOD или mesh variant могут появляться только в derived manifest импортёра; export manifest не обязан заранее знать все платформенные outputs. + +Принятый принцип публикации; последовательность для расширенного bundle: + +1. Helper готовит временный каталог, экспортирует payload и формирует manifest. Проверяет glTF, доступность всех URI, IDs, соответствие профилю и hashes. `.glb` сам по себе не гарантирует отсутствие внешних файлов — это разрешено форматом. [glTF, GLB structure](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#glb-file-format-specification). +2. Неизменяемые payload files получают окончательные имена; manifest публикуется последним через замену одного файла. Требования к atomic replace/durability проверяются отдельно на целевых filesystem Linux/Windows. Watcher реагирует на commit manifest, а не на каждый временный файл. +3. Import job фиксирует snapshot manifest, recipe и dependencies; пишет derived outputs в отдельное поколение. Перед commit повторно проверяет digests/revision: более поздний экспорт не должен быть затёрт завершившимся старым job. +4. Registry атомарно переключает активный manifest одного ассета после validation. Читатель получает весь прежний или новый набор outputs. Сбой и cancel до commit оставляют предыдущий набор; незавершённый staging удаляется при восстановлении. + +Нужно различать **новый source уже опубликован** и **новый imported asset принят проектом**. Ошибочный импорт показывает pending source generation и прежнюю active generation. Это честнее, чем выдавать старую картинку за успешный reimport. Git хранит authoring IDs, source bundle и recipes; build cache и незавершённый staging восстанавливаются. Уборка старых payload/derived generations учитывает действующие manifests, jobs и открытые snapshots; бесконечное накопление не является частью дизайна. + +## 6. Overrides и разбор rename/delete + +Godot уже различает внешний авторский материал и импортируемое содержимое: material settings могут подставить ресурс по UID с fallback path. При сохранении animation опция `keep_custom_tracks` копирует только неимпортированные tracks из прежнего ресурса; это конкретная политика сохранения, не общий трёхсторонний merge. [External material](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/import/3d/resource_importer_scene.cpp#L1687), [custom tracks](https://github.com/godotengine/godot/blob/9c776068d6ed23acd0c78bfe534272d1d2a3a619/editor/import/3d/resource_importer_scene.cpp#L1971). + +**Принятый принцип Faset; детали реализации.** Хранить imported baseline, override patches и provenance отдельно. Ключ patch включает цепочку вложенных InstanceId, ObjectId/ComponentId/FieldId либо устойчивый resource output/slot ID с проверкой TypeId. Instance chain не совпадает с transform path, имя не входит в адрес. Material override относится к semantic slot, не к номеру primitive. При новом baseline применять только совместимые patches; удалённая цель или изменившийся тип поля создают conflict с предыдущим значением и контекстом. Унаследованное значение обновляется автоматически; явный override сохраняется, даже если прежнее значение случайно совпадало с baseline. + +Проверочный walkthrough: дверь размещена тремя экземплярами; второму назначен другой материал, третьему добавлены gameplay и локальное смещение ручки. + +**Rename при наличии устойчивых source IDs.** Blender `DoorLeaf` становится `Panel`, mesh меняется, порядок glTF arrays перестраивается. Source/output IDs прежние: importer обновляет label и locator, placement и overrides остаются. Перенос файла ассета аналогично меняет registry path, не идентичность. + +**Delete.** Ручка исчезает из нового экспорта. Reverse-reference index находит локальный patch третьего экземпляра и возможные прямые ссылки других сцен. Candidate generation готова, но её активация получает `NeedsResolution`. Можно явно сопоставить прежний output совместимому новому, убрать зависимость или отделить старую ручку в авторский asset. Нельзя приклеить patch к «похожему» узлу по имени. Для v1 достаточно держать прежнее поколение активным до разрешения обязательных ссылок; исправления сцен проходят обычные document transactions. Это не обещание одной атомарной транзакции поверх всех файлов проекта. + +**Split/merge.** Одну ручку заменили двумя частями или несколько materials объединили. Сохранение старого ID допускается только при определённой семантической преемственности; прочие outputs получают новые IDs. Миграция указывает mappings, а не только список renamed names. Идентичность collider и visual mesh также независима: смена triangulation не обязана обнулять gameplay-ссылку на коллайдер. + +### Связь с принятыми scene overrides + +Повторно используемая сцена и её вложенные экземпляры сохраняют отдельные override layers. В v1 допустимы field overrides, новые объекты/компоненты, suppression унаследованного объекта с поддеревом и reparent внутри одного экземпляра. Нельзя переносить объект через границу nested instance; массив меняется целиком. Revert удаляет override. Variant inheritance, Apply to template и сложное слияние массивов отложены. Удаление output импортёром и пользовательский suppression различаются по provenance, но обе операции обязаны выявлять оставшиеся обязательные ссылки. [Правила identity и структурных изменений](01-architecture.md). + +## 7. Профиль материалов, координат и анимации + +Материальный exporter собирает конкретные поля PBR, textures и extensions. Это преобразование поддерживаемого представления; не перенос произвольного Blender shader graph в движок. Для первого профиля предлагаются metallic/roughness, base color, normal, occlusion, emissive, alpha mode и double-sided; необязательные расширения имеют явную политику fallback, неизвестные required extensions блокируют импорт. Procedural appearance заранее bake в поддерживаемые textures. [Формирование материала](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/scripts/addons_core/io_scene_gltf2/blender/exp/material/materials.py#L347), [официальный material workflow, Manual 4.0](https://docs.blender.org/manual/en/4.0/addons/import_export/scene_gltf2.html#materials). + +Color space — часть texture usage/recipe: base-color RGB декодируется из sRGB, данные roughness/metallic/normal обрабатываются как данные. Один image source может иметь разные usage-specific outputs. Профиль проверяет tangent basis, UV set и packing; материал физики с friction/restitution хранится отдельно от материала поверхности renderer. [glTF material semantics](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#materials). + +glTF задаёт правую систему, Y-up и метры. В Blender exporter location swizzle при `gltf_yup` — `(x,z,-y)`, rotation и scale преобразуются отдельно. Faset должен нормализовать весь профиль один раз: geometry, node transforms, inverse bind matrices, animation и colliders. `scene.unit_settings` в UI не заменяет тест фактического размера экспорта. Negative determinant требует согласованного winding/tangent handling; shear и non-uniform scale на иерархии требуют явного bake/reject правила. [Swizzle](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/scripts/addons_core/io_scene_gltf2/blender/exp/nodes.py#L597), [glTF coordinates](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#coordinate-system-and-units). + +Анимация не имеет обязательного соответствия «Action = clip»: `gather_actions_animations` собирает результаты по объектам, а `ACTIVE_ACTIONS` может объединить их. Значит, clip identity описывает набор source Actions/slots и recipe range, а не animation array index. V1 фиксирует rest pose, frame range/rate, clip grouping, root-motion policy и допустимые influences. Constraints/IK bake в поддерживаемое движение; неподдерживаемые channels диагностируются. [Сборка и объединение](https://github.com/blender/blender/blob/28d47268bddcb9dc69143f0e2d9410969da16311/scripts/addons_core/io_scene_gltf2/blender/exp/animation/action.py#L227). Bone/slot rename и удаление joints проверяются отдельно от переименования clip. + +## 8. Collider cooking для выбранных Box2D и Box3D + +**Box2D.** `b2ComputeHull` ограничивает число входных точек, сваривает близкие и удаляет коллинеарные; неудача возвращает пустой hull. В проверенном header предел — 8 vertices. Поэтому нельзя отправить произвольный контур спрайта непосредственно как один polygon. Наш v1: ручные circle/capsule/box, валидируемые convex polygons; сложный contour — отдельное упрощение и convex decomposition с ограничением числа частей. Для окружения возможны chains с правильными соседями/winding, а не набор несвязанных сегментов. [Hull implementation](https://github.com/erincatto/box2d/blob/77619f4f7baebe5117a2e3ddc3ac8c404e82d243/src/hull.c#L87), [предел vertices](https://github.com/erincatto/box2d/blob/77619f4f7baebe5117a2e3ddc3ac8c404e82d243/include/box2d/collision.h#L25), [официальное описание chains](https://box2d.org/documentation/md_collision.html). + +**Box3D.** `b3CreateMesh` проверяет входной layout, опционально выполняет welding, отбрасывает degenerate triangles, строит BVH, сортирует triangles вместе с material indices и при настройке вычисляет adjacency edges. Обычный triangle index из cooked query поэтому нельзя считать индексом исходного Blender polygon. В source `b3CreateMeshShape` проверяет `B3_MESH_VERSION`. [Cooker](https://github.com/erincatto/box3d/blob/f555ee42084e0b43cbffa863f40bff8117c08896/src/mesh.c#L1586), [порядок triangles и edges](https://github.com/erincatto/box3d/blob/f555ee42084e0b43cbffa863f40bff8117c08896/src/mesh.c#L1799), [version check](https://github.com/erincatto/box3d/blob/f555ee42084e0b43cbffa863f40bff8117c08896/src/shape.c#L409). + +Документация относит triangle meshes к static geometry. Для Faset v1 предлагаются dynamic bodies из convex shapes и static mesh/terrain отдельно. Указатель geometry тоже имеет жизненный цикл: рассмотренная ветка shape хранит mesh data pointer, тогда как hull проходит через world database; освобождение старого поколения после reimport должно ждать удаления его physics shapes. [Назначение mesh](https://github.com/erincatto/box3d/blob/f555ee42084e0b43cbffa863f40bff8117c08896/docs/collision.md#triangle-meshes), [владение geometry](https://github.com/erincatto/box3d/blob/f555ee42084e0b43cbffa863f40bff8117c08896/src/shape.c#L133). + +Collider recipe включает source output, 2D projection/3D local frame, единицы, scale policy, shape mode, welding/decomposition параметры, physics build и schema. Physics switch не должен тайно менять массу/pivot: authoring показывает cooked bounds, volume/area, части и diagnostics. Изменение цвета не запускает geometry cook; изменение baked scale запускает. + +Безопасный минимальный формат — собственные versioned primitives/vertices/indices и параметры; создание backend BVH при загрузке учитывается отдельно во времени startup. Перенос BVH полностью в offline cook требует проверенного сериализуемого backend-формата и совместимости версий. Нельзя объявить произвольный memory dump `b3MeshData` вечным переносимым asset format только потому, что в структуре есть version. Если позднее появится runtime asset reload, смена collider выполняется в physics safe point, а старые данные удерживаются до завершения использования. В MVP Player использует snapshot запуска; reimport обновляет authoring/imported generation для следующего запуска и не подразумевает live runtime MCP. + +## 9. Приёмка и новые выводы + +Первый комплект fixtures: дверь с shared mesh/material, три экземпляра, material-slot override, skeleton с двумя clips, метрический калибровочный объект, negative scale, convex collider и static mesh со швом. Проверки: + +- Со source IDs: rename Object/Mesh/Material, move source и перестановка glTF arrays сохраняют semantic IDs и overrides. Без IDs: обычный glTF/GLB импорт работает, а неоднозначное сопоставление диагностируется без обещания rename-safe matching. +- Duplicate Object сохраняет shared Mesh, duplicate datablock выявляет повторный source ID; повторный экспорт и очистка cache не создают новые identities. +- Delete/split/type change показывают точные затронутые references; unresolved generation не заменяет рабочую. +- Изменения recipe, importer build, зависимой texture и collider scale дают правильные причины invalidation; placement экземпляра не делает лишний cook. +- Обрыв после payload, после derived output и перед registry commit не даёт смешанного поколения. Старый job не побеждает более новый export. +- На Linux/Windows совпадают IDs, dependency graph и semantic output; byte-identical артефакты проверяются только для явно детерминированных stages. +- Проверяются масштаб, pivots, skin bind pose, clip ranges, normal maps, winding и скольжение по collider seams. Cache hit не принимается за доказательство корректности. + +Новые относительно предыдущего обзора выводы: UID файла недостаточен для rename частей; UUID property наследуется при duplicate; Actions и outputs могут иметь соответствие многие-ко-многим; зависимости source/artifact/settings нужно различать; публикация source и активация imported generation — две разные точки; physics geometry имеет отдельную идентичность, версию и срок жизни. + +**Охват источников.** Локальные тела Godot `9c776068d6ed23acd0c78bfe534272d1d2a3a619`, Blender `28d47268bddcb9dc69143f0e2d9410969da16311`, UnityCsReference `6b50e5544f6efcca1f44dbace3d1778b465ac6d0` соответствуют [манифесту](source-manifest.json). Недостающие пять Blender exporter files прочитаны из official raw source того же commit без расширения sparse checkout. Дополнительно прочитаны отдельные Box2D files commit `77619f4f7baebe5117a2e3ddc3ac8c404e82d243` и Box3D files/docs commit `f555ee42084e0b43cbffa863f40bff8117c08896`; это исследовательские pins, не выбор версий Faset. Их permalink lines сверены по скачанным файлам. Веб-сверка: спецификация glTF 2.0, Unity 6.0 API, Box2D collision documentation; Blender Manual 4.0 использован только для общего material workflow, текущие механизмы сверены по source 5.3 alpha. Native asset database Unity, весь exporter, автоматическая convex decomposition и переносимость cooked binaries не исследованы полностью. Производительность и roundtrip пока не измерялись. diff --git a/docs/studies/18-build-cook-and-delivery.md b/docs/studies/18-build-cook-and-delivery.md new file mode 100644 index 0000000..49949f6 --- /dev/null +++ b/docs/studies/18-build-cook-and-delivery.md @@ -0,0 +1,136 @@ +# 18. Сборка, cook и доставка: как сделать результат объяснимым + +Дата исследования: 17.09.2026; актуализация решений: **18.09.2026**. Дополнение к [исследованию стеков](13-build-pipeline-and-stack.md). **Приняты** CMake+Ninja, Clang Linux/clang-cl Windows, C++ core/gameplay первым и Lua следующим модулем. Gameplay статически линкуется в отдельный Player dev/release; schema export выполняется служебным процессом; Editor plugins — DLL/SO под точный SDK/restart. MCP существует только в Editor/headless editor services, без runtime inspection/mutation и без MCP в Player/SchemaExporter. Канон — [архитектура](../ARCHITECTURE.md), этапы — [PLAN.md](../../PLAN.md). Ниже описан выбранный проект, не реализованный build service. + +Исходники: Unreal Engine 5.8.2, commit `16d75d84714512edfb744e1fd0a59e9c74d57873`; [общий манифест](source-manifest.json). Ни UE, ни будущий Faset в этом исследовании не собирались. Ниже — чтение тел функций и официальной документации, затем собственный проект контрактов и проверок. + +## 1. Первый полезный перенос: два разных вопроса об актуальности + +В Unreal есть отдельная проверка **плана сборки** и проверка **действий внутри плана**. Это существенно для UX: изменение одного `.cpp` не должно выглядеть так же, как смена SDK или добавление нового исходного файла. + +В `TargetMakefile` сравниваются дополнительные аргументы, использованные конфигурационные значения и внешние метаданные платформы. `IsValidForSourceFiles` ищет новые, удалённые и заменённые исходники, изменение набора inline-generated C++, внешних и внутренних зависимостей, платформенного SDK. Причины возвращаются вызывающему коду текстом. Это позволяет решить, можно ли повторно использовать сохранённое описание сборки. [Проверки конфигурации](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Programs/UnrealBuildTool/System/TargetMakefile.cs#L810), [проверки исходников](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Programs/UnrealBuildTool/System/TargetMakefile.cs#L902). + +После получения графа `GatherPrerequisiteActions` начинает с требуемых выходных файлов и рекурсивно собирает производящие их действия. `GatherAllOutdatedActions` сначала параллельно проверяет действия по отдельности, затем распространяет устаревание через зависимости. Это **не** доказательство, что любая часть UBT параллельна, и не готовый рецепт собственного планировщика. Полезен сам порядок: выбрать необходимую часть графа, проверить локальные причины, учесть зависимые действия. [Обход prerequisites](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Programs/UnrealBuildTool/Actions/ActionGraph.cs#L647), [два этапа актуальности](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Programs/UnrealBuildTool/Actions/ActionGraph.cs#L921). + +Faset должен показывать цепочку причин; для принятой явной C++ регистрации пример выглядит так: + +```text +PlayerMovement.hpp изменён + → компиляция зависящих gameplay/registration файлов + → обновление статической gameplay-библиотеки и линковка Player + → линковка/запуск SchemaExporter при изменении его зависимостей + → проверка и публикация декларативного schema manifest +``` + +Это пример будущего интерфейса. Регистрация пишется явно на C++, собственный parser/codegen всего C++ на первом этапе не нужен. Если schema export выдаёт прежнее содержимое, зависимые от самой схемы стадии не должны получать ложную invalidation; изменившийся gameplay binary всё равно требует нового Player. «План обновлён», «команда выполнена» и «выходной файл изменился» — три разных события. CLI, окно сборки и MCP должны получать их из одного журнала. + +## 2. Почему mtime и один хеш исходника недостаточны + +`IsIndividualActionOutdated` учитывает путь команды, аргументы и версию команды; отсутствие результата; для compile-действий — также нулевой размер `.obj/.o`; прямые prerequisites и dependency-list. Отсутствующий dependency-list делает действие устаревшим. Список позволяет проверить зависимости, которые не были обычными prerequisites. Проверки времени в этом коде имеют собственные допуски и правила обработки import libraries: переносить их механически не следует. [Тело проверки](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Programs/UnrealBuildTool/Actions/ActionGraph.cs#L692). + +Отдельная интересная деталь: `ActionHistory.ComputeHash` переводит строку в верхний регистр перед хешированием, а `UpdateProducingCommandLine` хранит историю по выходному файлу. Это характеристика данного механизма UE, не доказательство ошибки его использования. Для собственного кэша Faset нельзя бездумно повторять нормализацию: регистр Linux-пути, имени macro и значения аргумента может менять смысл. [История команды](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Programs/UnrealBuildTool/Actions/ActionHistory.cs#L109). + +Ключ подготовленного артефакта должен учитывать: + +- идентификатор и версию преобразователя, его binary/toolchain identity; +- байты входов и транзитивных зависимостей; +- упорядоченные аргументы, значимые настройки и явно разрешённые переменные окружения; +- целевую ОС, архитектуру, конфигурацию и графический профиль; +- версию формата результата и схему метаданных, если она влияет на выход. + +Для первого локального прототипа достаточно манифеста зависимостей и кэша файлов. Удалённый кэш, распределённое исполнение и собственная замена Ninja не являются необходимым началом. Быстрая проверка по размеру/mtime допустима как оптимизация обнаружения изменений, но не как единственное основание корректности content cache. + +Изменение SDK, compiler flags или импорта должно давать объяснимую причину промаха. Ключ без полного набора входов может очень быстро выдавать неправильную игру. Ключ со слишком широкими зависимостями будет правильным, но лишит проект быстрой итерации. Поэтому correctness и гранулярность проверяются отдельно. + +## 3. Cook-зависимость не равна runtime-зависимости + +В `FSaveCookedPackageContext::FinishPlatform` после сохранения обрабатываются пакеты, незапланированно загруженные во время save/cache/generator-работы. Затем вычисляются общие и платформенные runtime-зависимости, cook-зависимости и build-зависимости. После этого заполняется `CommitPackageInfo` и вызывается writer. У commit есть статус; наличие вызова само по себе не означает успешную публикацию всей игры. [Последовательность FinishPlatform](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Editor/UnrealEd/Private/Cooker/CookSavePackage.cpp#L442). + +`CalculatePlatformRuntimeDependencies` добавляет общие зависимости, imports и soft references из результата save, затем обнаруженные зависимости. `CalculateCookDependencies` отдельно собирает зависимости результата подготовки; когда обычного результата save нет, выполняет harvesting через cook events и архив. `RecordPlatformBuildDependencies` извлекает транзитивные build dependencies из cook attachments. [Runtime-зависимости](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Editor/UnrealEd/Private/Cooker/CookSavePackage.cpp#L776), [сбор cook-зависимостей](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Editor/UnrealEd/Private/Cooker/CookSavePackage.cpp#L851), [build-зависимости](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Editor/UnrealEd/Private/Cooker/CookSavePackage.cpp#L945). + +Практический пример для Faset: Blender-файл, профиль экспорта и версия mesh-cooker нужны для получения mesh, но не обязаны входить в поставку игры. Материал, skeleton и runtime-текстуры могут понадобиться Player. Debug symbols нужны разработчику при разборе сбоя, но могут поставляться отдельно. Один список «всех файлов проекта» не выражает эти отношения. + +Предлагаемый importer/cooker возвращает три набора: использованные входы, полученные артефакты и runtime-ссылки. Если во время преобразования он прочитал дополнительный файл, зависимость надо зарегистрировать до признания результата пригодным для кэша. Доступ к файлам через предоставленный контекст импорта позволит собирать эти сведения; произвольные внешние процессы потребуют явного dependency manifest. Сам по себе этот API не обнаруживает чтения, которые его обходят. + +Сборка пакета начинает с выбранных сцен и явно заданных runtime-корней. Ссылки, формируемые строками во время игры, требуют явного правила включения или каталога; статический обход не может угадать произвольную строку. В редакторе полезны два объяснения: «почему файл попал в игру» и «почему его изменение требует пересборки». Их графы связаны, но не совпадают. + +## 4. Проверять нужно и попадания в кэш + +Самая полезная находка в cooker — `FIncrementalValidatePackageWriter`. В режимах проверки он может принудительно пересохранить пакет, который инкрементальная логика сочла неизменённым. Первая фаза выявляет расхождение с ранее сохранённым результатом; повторная помогает отделить недетерминированный save от неверного решения пропустить работу. [Выбор пакетов](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Editor/UnrealEd/Private/Cooker/IncrementalValidatePackageWriter.cpp#L636), [сравнение и дополнительные проходы](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Editor/UnrealEd/Private/Cooker/IncrementalValidatePackageWriter.cpp#L953). + +`LogIncrementalDifferences` прямо разделяет случаи: повторные результаты различаются — проблема детерминизма; повторные результаты совпадают, но отличаются от принятого ранее — false positive решения об отсутствии изменений. Это хороший способ диагностировать кэш, хотя конкретная причина неверного пропуска ещё требует расследования. [Классификация](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Editor/UnrealEd/Private/Cooker/IncrementalValidatePackageWriter.cpp#L1143). + +Для Faset предлагается отдельный проверочный режим `verify-cache`. Он не должен делать каждую обычную сборку вдвое дороже. На фиксированном наборе fixtures либо на выборке CI: + +1. Сохранить старый артефакт A и решение кэша «пригоден». +2. Для зафиксированного snapshot входов принудительно получить B, затем C. +3. Если B и C различаются, сначала исследовать нестабильность преобразования или среды. +4. Если B = C, но B ≠ A, исследовать недостающую зависимость, неверный ключ или порчу A. +5. Если все равны, сохранить свидетельство пройденной проверки для этого набора входов. + +Нельзя объявлять глобальную детерминированность после одной пары запусков. Сравнение должно учитывать формат: для бинарного runtime-артефакта желательны воспроизводимые байты; timestamps и диагностические поля лучше вынести в отдельный manifest. Допустимая нормализация сравнения обязана быть перечислена, иначе она может скрыть ошибку. Межплатформенное совпадение байтов не требуется там, где выход намеренно платформенный. + +## 5. Что стоит поручить CMake, Ninja и инструментам C++ + +Faset владеет моделью проекта, схемами компонентов, импортом, job status и упаковкой; CMake/Ninja отвечают за native compilation graph. Это уменьшает количество одновременно создаваемых механизмов. + +**CMake File API** даёт редактору структурированные сведения о сконфигурированном build tree. Клиент размещает запрос в своём каталоге query; после configure/generate читает индекс ответа и указанные им файлы. Версии протокола и объектов нужно согласовывать. Это источник сведений о targets и конфигурации, а не поток прогресса выполняемой компиляции. Файлы ответа принадлежат CMake; клиент не должен удалять их. [CMake File API](https://cmake.org/cmake/help/latest/manual/cmake-file-api.7.html). + +**Presets** позволяют отделить общие настройки от локального окружения. `CMakePresets.json` предназначен в том числе для хранения в репозитории, `CMakeUserPresets.json` — для персональных настроек без включения в Git. Для Faset это подходит как нижний слой общих профилей и локальных путей SDK; пользовательский экран может называться просто «Сборка Linux / Windows». Минимальную версию CMake и формат presets ещё нужно выбрать. [CMake Presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html). + +**Schema export и компиляция Slang/совместимого HLSL в SPIR-V** имеют явные outputs, byproducts и входные зависимости. `add_custom_command` описывает такие результаты и поддерживает depfile при совместимом генераторе. Для Ninja byproducts позволяют восстановить отсутствующий побочный файл. В принятом первом варианте C++ metadata регистрируется вручную, а exporter выдаёт JSON manifest. Если позднее появится generator `.hpp/.cpp`, исчезновение одного output не должно маскироваться существованием другого. [CMake add_custom_command](https://cmake.org/cmake/help/latest/command/add_custom_command.html). + +**Ninja depfile и restat** решают разные задачи. Depfile сообщает обнаруженные файловые зависимости. `restat` повторно проверяет время изменения outputs после команды и позволяет убрать зависимые действия из очереди, когда выход не изменился. Следовательно, генератору полезно записывать файл лишь при изменении содержимого; одного имени режима «incremental» недостаточно. Зависимость от генерации заголовка должна существовать уже для первой чистой сборки, когда depfile ещё отсутствует. [Ninja manual](https://ninja-build.org/manual.html), [официальный исходник документа](https://github.com/ninja-build/ninja/blob/master/doc/manual.asciidoc). + +**compile_commands.json** передаёт C++-инструментам контекст компиляции файла. Формат содержит рабочий каталог, файл и аргументы либо команду; документация предпочитает массив `arguments`, чтобы не вносить ошибки shell escaping. Для Faset это связующее звено с анализом кода и редактором C++, но не полное описание упаковки и ресурсов игры. [Clang compilation database](https://clang.llvm.org/docs/JSONCompilationDatabase.html). + +Собственный launcher также должен запускать executable с массивом аргументов и явным рабочим каталогом. Отображаемая командная строка — диагностическое представление; её не следует повторно парсить как единственный источник данных. Структурированные ошибки сохраняют файл, позицию, этап и исходный текст сообщения, чтобы человек и MCP видели один и тот же результат. + +**SchemaExporter и граница редактора.** После компиляции отдельная служебная target выполняет C++ registration entrypoints и выдаёт TypeId/FieldId/defaults/value types/constraints/UI hints с build fingerprint. Она не запускает игровой мир, lifecycle или renderer, не содержит MCP и не попадает в игровой пакет. Inspector читает проверенный декларативный manifest; custom inspectors принадлежат отдельному Editor module. Сбой exporter оставляет прежнюю схему. Исполнение C++ в другом процессе изолирует crash, но не является sandbox. + +**Зависимости модулей.** Gameplay статически связан с Player в development/release; исправления C++ требуют stop → build → restart. Editor DLL/SO имеют manifest с зависимостями и точным SDK/build fingerprint, загружаются при старте и обновляются через restart Editor. Toolchain fingerprint фиксирует target/architecture, compiler, stdlib/CRT и настройки; Windows SDK/UCRT/VCRuntime являются отдельными компонентами, а не частью обещанной полностью независимой поставки LLVM. Стабильного ABI любых компиляторов и C++ hot reload нет. [Clang: Windows headers/libraries](https://clang.llvm.org/docs/UsersManual.html#windows-system-headers-and-library-lookup). + +## 6. Результат сборки — описанный набор файлов + +UE `TargetReceipt` содержит target/platform/configuration, launch executable, build products и runtime dependencies. Writer упорядочивает списки; пути внутри engine/project могут заменяться переменными, но вне этих каталогов `InsertPathVariables` возвращает абсолютный путь. Это не готовый переносимый формат Faset. `ModuleManifest` отдельно хранит build ID и соответствие имён модулей файлам. Это не универсальная гарантия ABI-совместимости: manifest обозначает состав и идентичность, а проверка совместимости требует отдельного контракта. [TargetReceipt.Write](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Programs/UnrealBuildTool/System/TargetReceipt.cs#L783), [правила путей](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Programs/UnrealBuildTool/System/TargetReceipt.cs#L443), [ModuleManifest](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Programs/UnrealBuildTool/System/ModuleManifest.cs#L20). + +Результат Faset — проектируемый `ArtifactManifest`: target, build ID, snapshot ID, toolchain identity, entry point, список файлов с хешами и назначением, графический профиль, версии runtime-форматов, ссылки на symbols и отчёт проверки. В нём нет зависимости от абсолютного каталога разработчика. Для сторонних runtime-библиотек нужен явный список поставки; загрузка библиотек по вычисляемому имени требует отдельного правила включения. + +`BuildCookRun` в UE последовательно вызывает Build, Cook, CopyBuildToStagingDirectory, Package, Archive, Deploy и Run. Разделение шагов полезно, но код orchestration сам по себе не доказывает атомарность публикации. [DoBuildCookRun](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Programs/AutomationTool/Scripts/BuildCookRun.Automation.cs#L251). + +Для первого Faset принят следующий протокол: + +1. Зафиксировать входной snapshot/revision, материализовать его в изолированном рабочем каталоге worker и запретить изменение входов на время job. Compiler/cooker читают этот snapshot, а не живые файлы редактора. Build tree имеет единственного writer; разные worker не используют один CMake binary directory одновременно. Каталог можно повторно использовать между совместимыми job после контролируемого обновления snapshot. Одного revision ID или проверки timestamps после сборки для изоляции недостаточно. +2. Компилировать C++, выполнять schema export, компилировать шейдеры и готовить binary cooked assets, сохраняя прогресс и использованные зависимости. +3. Собрать candidate в новом каталоге build ID, проверить обязательные файлы, хеши и согласованность manifest. +4. Запустить минимальную проверку Player на целевой ОС. Результат сборки и результат запуска — разные статусы. +5. После успеха опубликовать ссылку на готовый build ID. Старые результаты сохраняются до явной очистки. + +Этот подход позволяет не перезаписывать бинарники запущенного Player. Обновление маленького указателя на актуальную сборку через временный файл и rename/replace требует платформенной реализации и fault-тестов. Атомарная видимость не тождественна сохранности при потере питания. Для первого прототипа можно возвращать путь неизменяемого каталога без понятия глобального `current`, ещё больше сокращая протокол. + +## 7. Одинаковая сборка руками, из CLI и через MCP + +Предлагаемые сущности: `BuildRequest`, `BuildJob`, `BuildEvent`, `ArtifactManifest`. Запрос содержит проект, target/configuration, snapshot, выбранные сцены/профиль и ключ повторного запроса. Job получает устойчивый ID; события имеют порядковый номер. Конкретные имена инструментов и wire format ещё не утверждены. + +MCP-адаптер Editor/headless editor service запускает job, читает статус/editor-диагностику с позиции журнала, запрашивает отмену и получает артефакты. Play/Stop управляет процессом; runtime world/session inspection/mutation не предоставляется, MCP в Player/SchemaExporter отсутствует. Длительная работа не должна зависеть от жизни одного HTTP-запроса или открытого окна редактора. В пределах документированного scope и срока хранения журнала повторный вызов с тем же ключом и тем же содержимым возвращает существующую работу; с другим содержимым — конфликт. Это проект прикладного API, не утверждение о встроенных гарантиях протокола MCP. + +Отмена означает переход через `cancelling`: worker прекращает новые действия, завершает или останавливает дочерние процессы, закрывает файлы и только затем подтверждает конечное состояние. Candidate отменённой сборки не становится последней успешной игрой. После перезапуска сервиса незавершённые job требуют reconciliation по журналу и состоянию workers; наличие каталога не означает успех. + +В GUI полезны этап, текущая задача, elapsed time, причины пересборки и ссылки на ошибки. Процент допустим при известном знаменателе; динамически обнаруживаемые зависимости не оправдывают выдуманную точность. Для первой версии хватит локального worker текущей ОС. Вторую ОС следует проверять собственным worker/CI; переключатель target не является доказательством готовой кросс-компиляции. + +## 8. Минимальный набор испытаний принятой реализации + +- **Чистая и повторная сборка.** Удалить только производные outputs; проверить порядок генерации. Повторить без правок и увидеть, какие стадии действительно не выполнялись. +- **Изменения по одному.** `.cpp`, общий header, схема компонента, shader include, исходная текстура, параметры importer, версия cooker, compiler flags. Проверить правильный результат и объяснение цепочки. +- **Отсутствующий output.** Удалить schema manifest, dependency-list, runtime-библиотеку или cooked mesh; получить восстановление либо явную ошибку, а не ложный успех. +- **Кэш.** Испытать verify-cache на специально пропущенной зависимости и намеренно нестабильном преобразовании. Классификации должны различаться. +- **Сбой публикации.** Отмена, завершение процесса cooker, ошибка компилятора, нехватка места, прерывание записи manifest. Последняя успешная игра остаётся доступной. +- **Повтор и конкуренция.** Повторить запрос MCP, запустить два разных snapshot, изменить проект во время cook. Не смешать outputs и не выполнить одну логическую job повторно без причины. +- **Доставка.** Запустить development и release Player на чистых Linux/Windows окружениях без Editor, исходников, Blender, MCP и SDK. Проверить пути с пробелами/Unicode, case-sensitive файловую систему, runtime-библиотеки и отсутствие editor/MCP targets/listeners в обоих Player. +- **Схема и плагины.** Проверить сбой SchemaExporter без потери прежней схемы, отсутствие lifecycle вызовов при export, несовместимый plugin fingerprint и запрещённую зависимость runtime→editor. Проверить, что GUI/editor MCP видят одну authoring-схему и не умеют читать игровой мир. + +Измерять следует cold/warm/no-op wall time, время стадий, причины cache hit/miss, объём пересозданных данных и корректность результата. Численные бюджеты появятся после измерения прототипа на зафиксированном проекте и оборудовании. + +## Куда перейти дальше + +[Нативные компоненты и метаданные](16-native-gameplay-and-metadata.md) определяют входы явной регистрации/schema export и совместимость модулей. [Импорт и обмен с Blender](17-asset-pipeline-and-blender-roundtrip.md) определяют происхождение артефактов, их идентификаторы и поведение reimport. [Графический pipeline](15-renderer-implementation-notes.md) определяет требования runtime к данным и GPU-профилю. Канонические решения остаются в [архитектуре Faset](../ARCHITECTURE.md), этапы до MVP и после — в [PLAN.md](../../PLAN.md). Все испытания движка ещё предстоят. diff --git a/docs/studies/README.md b/docs/studies/README.md new file mode 100644 index 0000000..e21b423 --- /dev/null +++ b/docs/studies/README.md @@ -0,0 +1,50 @@ +# Исследования для Faset Engine + +Обновлено 18.09.2026. Исходники и официальная документация исследовались прежде всего 17.09.2026; затем результаты согласованы с принятой архитектурой. + +**Актуальные решения — в [ARCHITECTURE.md](../ARCHITECTURE.md), порядок реализации — в [PLAN.md](../../PLAN.md).** Движок ещё не реализован. Исследования дают обоснования и проверочные сценарии, а не доказанные показатели будущего Faset. + +## Принято по результатам обсуждения + +Linux/Windows, десктопные 2D/3D, C++ сначала и Lua следующим языковым этапом, EnTT, Box2D/Box3D, SDL3, собственные Vulkan 1.3 renderer/Render Graph, Slang, CMake/Ninja/Clang. Собственный retained C++ UI редактора использует декларативную компоновку и стили с основной тёмной темой; ImGui остаётся диагностическим. + +Авторинг использует JSON, постоянные ID, вложенные сцены и sparse overrides. Игра работает в отдельном Player со статически связанным gameplay; C++-цикл — stop/build/restart. Метаданные экспортируются отдельным процессом. Официальный Blender остаётся внешним инструментом, с glTF/GLB и необязательным дополнением для ID/экспорта. + +**MCP работает только с редактором**, его авторскими документами, импортом, сборкой, Play/Stop и диагностикой. API управления runtime world и MCP в игре не планируются. + +## Обзор и сравнительная база + +- [01 — Архитектура](01-architecture.md): данные, владение, сцены, компоненты, фазы и принятые правила шаблонов. +- [02 — UX редактора](02-editor-ux.md): повседневный workflow, Inspector, Undo, поиск и диагностика. +- [03 — Сравнение подходов](03-comparison.md): Godot, Unity, Unreal, дополнительные примеры O3DE/Bevy. +- [04 — Рекомендации](04-engine-recommendations.md): сводка принятых решений и отличия от ранних вариантов. +- [05 — Навигация по дорожной карте](05-roadmap.md): обзор, полный план находится в корне. +- [06 — Источники](06-sources.md): библиография и официальные документы выбранного стека. + +## Разборы исходников + +- [07 — Unreal Engine: передовая графика](07-unreal-graphics-source-study.md). +- [08 — Godot: сцены, ресурсы, Inspector, Undo и Play](08-godot-ux-source-study.md). +- [09 — Unity: UX и расширяемость](09-unity-ux-extensibility-study.md). +- [10 — Blender: команды и инструменты](10-blender-editor-patterns.md). +- [11 — ECS и удобство API](11-ecs-and-ergonomics.md). +- [12 — MCP редактора и интеграция с Blender](12-mcp-and-blender-integration.md). +- [13 — Компиляция, cook, экспорт и сравнение стеков](13-build-pipeline-and-stack.md). +- [14 — Общая архитектура и проверочные сценарии](14-engine-blueprint.md). + +## Углублённые технические материалы + +- [15 — GPU-driven renderer](15-renderer-implementation-notes.md): visibility, HZB, синхронизация и управление историей; этап после MVP. +- [16 — C++ gameplay и метаданные](16-native-gameplay-and-metadata.md): схемы, ABI, сериализация и будущий Lua. +- [17 — Asset pipeline и Blender](17-asset-pipeline-and-blender-roundtrip.md): IDs, зависимости, reimport, overrides и физическая геометрия. +- [18 — Сборка и доставка](18-build-cook-and-delivery.md): инкрементальность, cache, состав Player и единый BuildService. + +## Происхождение и воспроизводимость + +[Манифест источников](source-manifest.json) фиксирует изученные версии и commits. Основные source-ссылки ведут на upstream-файлы конкретного commit с указанием строки; доступ к Unreal требует соответствующих прав Epic. Исходники сторонних движков не включены в этот репозиторий. + +Исследование статическое и выборочное: читались отдельные реализации и официальные документы. Движки не собирались, пользовательские UX-тесты и GPU-бенчмарки не выполнялись. UnityCsReference содержит C# reference source и bindings, а не весь native движок. Blender изучался через sparse checkout и выбранные удалённые файлы. + +[Карта](map/README.md) запускается на обычном clone Faset. Соседние checkout из manifest необязательны и нужны только для дополнительного локального просмотра исходников. Пути manifest считаются от корня проекта, а относительные Markdown-ссылки — от документа. + +Первоначальные сравнения Python/C#/Flecs, готовых UI toolkits и других вариантов сохранены там, где объясняют решение, и отмечены как история. Они не являются нерешённым выбором стека. Графика — одно из основных направлений развития Faset; передовые техники вводятся после проверенного сквозного MVP. diff --git a/docs/studies/map/README.md b/docs/studies/map/README.md new file mode 100644 index 0000000..6d309bb --- /dev/null +++ b/docs/studies/map/README.md @@ -0,0 +1,38 @@ +# Карта исследования Faset Engine + +Локальное React-приложение с интерактивной картой, просмотром Markdown и переходами к строкам исходников. `research.tsx` хранится здесь же; сборка не зависит от Codex, абсолютного пути к рабочему столу или файлов в домашнем каталоге. Карта показывает принятые решения и будущие критерии этапов; движок ещё не реализован. + +Главные документы: [о проекте](../../../README.md), [план до/после MVP](../../../PLAN.md), [архитектура](../../ARCHITECTURE.md), [исследования](../README.md). Актуальный охват: собственный Vulkan 1.3/Render Graph, Slang, SDL3, C++/EnTT, retained editor UI; две демки в MVP, продвинутая графика и Lua после него. MCP запланирован строго в Editor, без доступа к runtime worlds/сессиям и без MCP в Player. + +В этой папке, с установленными Node.js 20+ и npm: + +```sh +npm ci +npm run build +npm start +``` + +Открыть . Сервер слушает только `127.0.0.1`; `PORT` позволяет выбрать другой порт. После правки исходников повторить сборку; после правки сервера перезапустить его. + +Отчёты читаются из `docs/`, также доступны корневые `README.md` и `PLAN.md`. Карта запускается после обычного clone Faset без чужих исходников. Для опционального локального просмотра source-ссылок можно разместить checkout рядом с проектом: + +```text +workspace/ + Faset_Engine/ + README.md + PLAN.md + docs/ + ARCHITECTURE.md + studies/ + map/ + UnrealEngine/ + godot/ + UnityCsReference/ + blender-source/ +``` + +Без внешнего checkout локальная source-ссылка показывает пояснение. Карточки UE также ведут на upstream конкретного research commit; доступ к Unreal Engine регулируется Epic. Номера строк проверялись на snapshots из [source-manifest.json](../source-manifest.json); иной локальный commit может иметь другие строки. Исходники движков не копируются в сборку карты. + +API принимает пути относительно корня проекта, проверяет реальные пути и разрешает только текстовые файлы из перечисленных областей размером до 8 MiB. Скрытые каталоги и `node_modules` исключены. Сервер предназначен для локального чтения исследования, без изменения файлов. Его следует запускать локально; это не публичный file server и не MCP будущего редактора. + +Проверка разрешения ссылок и границ файлового API: `npm test`. diff --git a/docs/studies/map/build.mjs b/docs/studies/map/build.mjs new file mode 100644 index 0000000..fd60ddd --- /dev/null +++ b/docs/studies/map/build.mjs @@ -0,0 +1,7 @@ +import { build } from 'esbuild'; +import { copyFile, mkdir } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +const here = fileURLToPath(new URL('.', import.meta.url)); +await mkdir(new URL('./dist/', import.meta.url), {recursive:true}); +await build({absWorkingDir:here,entryPoints:['main.tsx'],bundle:true,outfile:'dist/app.js',jsx:'automatic',minify:true,alias:{'cursor/canvas':'./canvas-browser.tsx'},define:{'process.env.NODE_ENV':'"production"'}}); +await copyFile(new URL('./index.html', import.meta.url), new URL('./dist/index.html', import.meta.url)); diff --git a/docs/studies/map/canvas-browser.tsx b/docs/studies/map/canvas-browser.tsx new file mode 100644 index 0000000..75e2ab0 --- /dev/null +++ b/docs/studies/map/canvas-browser.tsx @@ -0,0 +1,33 @@ +import React, { useState } from 'react'; +import { fileUrl } from './file-links.mjs'; +export { fileUrl } from './file-links.mjs'; + +const theme = { + text: { primary: 'var(--text)', secondary: 'var(--muted)', tertiary: 'var(--subtle)' }, + bg: { elevated: 'var(--surface)' }, fill: { secondary: 'var(--selected)' }, + stroke: { primary: 'var(--line)', secondary: 'var(--line)' }, + accent: { primary: 'var(--accent)' }, +}; +export const useHostTheme = () => theme; +export function useCanvasState(key: string, initial: string) { + const [value, setValue] = useState(() => { try { return localStorage.getItem(key) ?? initial; } catch { return initial; } }); + return [value, (next: string) => { setValue(next); try { localStorage.setItem(key, next); } catch {} }] as const; +} +export const useCanvasAction = () => (action: any) => { + if (action.type === 'openFile') window.location.assign(fileUrl(action.path, action.selection?.startLineNumber)); +}; +export function Stack({ gap = 12, style, ...props }: any) { return
; } +export function Row({ gap = 12, justify, align = 'center', wrap, style, ...props }: any) { return
; } +export function Grid({ columns, gap = 12, style, ...props }: any) { return
; } +export const H1 = (props: any) =>

; +export const H2 = (props: any) =>

; +export const H3 = (props: any) =>

; +export const Text = ({ size, tone, weight, style, ...props }: any) =>

; +export const Pill = ({ active, onClick, ...props }: any) => onClick ? + + Архитектура принята; реализация движка запланирована. Эта карта — работающий просмотрщик исследования, не редактор Faset. GPU-бенчмарки и сборки движка ещё не выполнены. MCP предусмотрен строго в Editor; Player не содержит MCP и не предоставляет ему runtime worlds или игровые сессии. + {["Решение", "Графика", "ECS и данные", "MCP и Blender", "Стек и экспорт", "Прототипы", "Источники"].map(name => setTab(name)}>{name})} + + + {tab === "Решение" && +

Принятые решения

+ {decisions.map(([name, description]) =>

{name}

{description}
)}
+

Зачем изучались другие движки

+ + Запланированный пользовательский контракт

Понятно, откуда пришло значение и что изменится

Shared/local asset, наследование template, sparse override, preview и Undo видны в редакторе. Диагностика импорта и сборки объясняет входы, ошибки и результат. Вложенность сцен не требует знать внутреннее устройство ECS.
+ + } + + {tab === "Графика" && +

В MVP — direct renderer; сложные механизмы после него

+ Vulkan 1.3 + собственный Render Graph, CPU frustum, sprites/layers/HUD для 2D и static meshes/simple PBR/обычные тени для 3D. Далее: GPU frustum/indirect → HZB → обычный LOD → расширение света/теней → temporal → GI. Карточки ниже сохраняют результаты изучения UE, а не список уже реализованных функций. + {graphics.map(g => setGraphicsId(g.id)}>{g.name})} +

{selected.name}

{selected.stage}
+ {selected.benefit} + +

Подтверждено кодом UE

{selected.fact}Upstream · строка {selected.line}{selected.source} · Для upstream UE требуется доступ Epic.
+

Планируемый минимальный эксперимент

{selected.start}

Ограничение

{selected.risk}
+
+

Будущий критерий проверки

{selected.check} + + UE 5.8.2 · commit 16d75d847145 · статический разбор C++ / HLSL. Полное воспроизведение Nanite/Lumen не обещается; минимальные GPU и driver matrix ещё требуют проверки. +
} + + {tab === "ECS и данные" && +

EnTT в runtime; сцены и метаданные в authoring

+ + +

Authoring — принято

JSON, stable SceneEntityId и asset IDs, nested scene templates, sparse overrides, revision, Undo и save. Generated imports отделены от пользовательских настроек. Схема компонентов экспортируется helper process.
+

Runtime — запланировано

EnTT, generational handles, компактные компоненты, queries и structural commands. Scene compiler выдаёт бинарные cooked данные; authoring ID не равен временной позиции entity в ECS. UI/Undo/GPU allocator не обязаны храниться в ECS.
+
+
+ Lua обязательно появится после MVP как отдельный модуль с ограниченным API. Использование каждой игрой необязательно; произвольный C++ не получает bindings автоматически. + + } + + {tab === "MCP и Blender" && +

MCP — только редактор и его authoring-сервисы

+ Разрешённый планируемый охват: документы и транзакции, validation, import/build jobs, Play/Stop и логи Editor; headless — для тех же доступных без UI операций. Play запускает отдельный Player, Stop завершает его. Ни runtime world inspection/mutation, ни API игровых сессий, ни MCP server в Player не предусмотрены. + {["Руками", "Через MCP"].map(v => setClient(v)}>{v})} + +

{client === "Руками" ? "Переместить пять источников в документе" : "Сместить пять lights authoring batch-операцией"}

{client === "Руками" ? "Выбрать объекты → начать drag → видеть preview → отпустить мышь. Один commit хранит исходные и конечные transforms." : "Прочитать IDs/revision → отправить batch → получить новую revision и affected IDs. Конфликт не должен оставлять частичные правки; повтор проверяется по idempotency key и payload в пределах срока журнала."}
+

Общий запланированный результат

Один валидный authoring-документ, одно Undo и обновлённые panels. MCP не получает доступа к запущенным runtime entities; изменения входят в следующий подготовленный запуск игры.
+
+

Официальный Blender; addon необязателен

+
+ Live link — возможное дальнейшее развитие после надёжного файлового roundtrip; plain GLB не даёт автоматической гарантии matching любых изменённых subassets. + + } + + {tab === "Стек и экспорт" && +

Принятый стек; интеграция запланирована

+
+

Четыре разные стадии сборки

+
+ UI, CLI и MCP редактора запускают один BuildRequest. Компиляторы работают в дочерних процессах; staging публикуется после проверки. Планируемый контракт отмены сохраняет последнюю успешную сборку; cache корректность и повторяемость требуют тестов, а не только hash-ключей. + Windows и Linux сборки проверяются в своих окружениях. Player не включает Editor/MCP, shader compiler или editor plugins. Драйвер всё равно создаёт GPU pipelines; shader hot reload требует собственной проверки bindings/layout и безопасной замены ресурсов. + Версии toolchain/dependencies и проверенная GPU/driver matrix ещё не закреплены. C#/.NET и Rust + wgpu остаются историей сравнения вариантов, не параллельными реализациями Faset. + + } + + {tab === "Прототипы" && +

M0–M9 / P1–P6 · все этапы запланированы

+ Этапы синхронизированы с PLAN.md: M0–M9 составляют MVP; P1–P6 идут после него. P1–P3 могут развиваться параллельно, P4 требует зрелого renderer. Обе демки и доставка на двух ОС обязательны для MVP. + {steps.map((s, i) =>
{s.task}Будущий критерий приёмки{s.pass}
)} +

Общие измерения

Время от правки до результата, cold/warm Play, build/import latency, ошибки UX; CPU/GPU времена, память и качество последовательностей кадров. Каждый эффект сравнивается с воспроизводимым baseline. Даты и ускорения пока не обещаются. + +
} + + {tab === "Источники" && +

Исследовательские snapshots и границы доступа

+
+ Карта и отчёты открываются без локальных копий движков. Optional source viewer использует sibling checkout; исходники чужих движков не входят в Faset. Dev/alpha snapshots — материалы исследования, не выбранные production-зависимости. + + {reports.map(r =>
)}
+ Прочитаны выбранные тела функций и официальные документы. Движки не собирались; UX-сравнение и GPU-бенчмарки не выполнены. Принятые решения не означают готовую реализацию. + } + ; +} diff --git a/docs/studies/map/server.mjs b/docs/studies/map/server.mjs new file mode 100644 index 0000000..2ca2f8c --- /dev/null +++ b/docs/studies/map/server.mjs @@ -0,0 +1,29 @@ +import http from 'node:http'; +import { readFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readDocument } from './file-access.mjs'; +const here = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = path.resolve(here, '../../..'); +const port = Number(process.env.PORT || 4178); +const server = http.createServer(async (req,res) => { + res.setHeader('X-Content-Type-Options','nosniff'); + res.setHeader('Cache-Control','no-store'); + res.setHeader('Content-Security-Policy',"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self'; connect-src 'self'; frame-ancestors 'self'"); + if (!['localhost:'+port,'127.0.0.1:'+port].includes(req.headers.host)) {res.writeHead(403);res.end();return;} + if (req.method !== 'GET' && req.method !== 'HEAD') {res.writeHead(405);res.end();return;} + try { + const url = new URL(req.url,'http://localhost:'+port); + let data, mime; + if (url.pathname === '/api/file') { + data = JSON.stringify(await readDocument(projectRoot, url.searchParams.get('path')));mime='application/json; charset=utf-8'; + } else { + const files = {'/':['index.html','text/html; charset=utf-8'],'/app.js':['app.js','text/javascript; charset=utf-8'],'/app.css':['app.css','text/css; charset=utf-8']}; + const entry = files[url.pathname]; + if (!entry) {res.writeHead(404);res.end();return;} + data=await readFile(path.join(here,'dist',entry[0]));mime=entry[1]; + } + res.writeHead(200,{'Content-Type':mime});res.end(req.method === 'HEAD' ? undefined : data); + } catch (error) {res.writeHead(error.status === 403 ? 403 : 404);res.end(error.status === 403 ? 'File not allowed' : 'File not found');} +}); +server.listen(port,'127.0.0.1',()=>console.log('Research map: http://localhost:'+port)); diff --git a/docs/studies/map/style.css b/docs/studies/map/style.css new file mode 100644 index 0000000..085ea93 --- /dev/null +++ b/docs/studies/map/style.css @@ -0,0 +1,41 @@ +:root { color-scheme: dark; --bg:#15181c; --surface:#1c2025; --text:#e2e5e8; --muted:#b2bac3; --subtle:#929da9; --line:#363f49; --accent:#b3d5c6; --selected:#273c35; font-family:Inter,system-ui,sans-serif; font-size:14px; line-height:1.65; background:var(--bg); color:var(--text); } +* { box-sizing:border-box; } +body { margin:0; } +h1,h2,h3,p { margin:0; } +h1 { font-size:24px; line-height:1.3; max-width:800px; font-weight:650; } +h2 { font-size:20px; line-height:1.4; font-weight:600; } +h3 { font-size:15px; line-height:1.4; font-weight:600; } +button { font:inherit; cursor:pointer; } +button:focus-visible,a:focus-visible,summary:focus-visible { outline:2px solid var(--accent); outline-offset:3px; } +.button,.pill { border:1px solid var(--line); color:var(--text); background:var(--surface); line-height:1.5; border-radius:5px; padding:7px 11px; display:inline-block; text-align:left; } +.button:hover,.pill:hover { border-color:var(--muted); } +.pill { font-size:12px; padding:5px 10px; } +.pill.active { color:var(--accent); background:var(--selected); border-color:var(--accent); } +.pill.label { color:var(--muted); } +hr { margin:0; border:0; border-top:1px solid var(--line); width:100%; } +.table-scroll { overflow:auto; } +table { border-collapse:collapse; width:100%; font-size:13px; } +th,td { text-align:left; padding:12px 14px; border-bottom:1px solid var(--line); vertical-align:top; } +th { color:var(--muted); font-size:12px; font-weight:600; background:var(--surface); } +td:first-child { min-width:145px; font-weight:500; } +.card { border:1px solid var(--line); border-radius:5px; background:var(--surface); } +.card-header { padding:12px 16px; color:var(--muted); font-size:12px; border-bottom:1px solid var(--line); } +.card-body { padding:16px; } +details { border-bottom:1px solid var(--line); padding:10px 0; } +summary { cursor:pointer; font-weight:600; padding:5px 0; } +.details-content { padding:12px 0; } +a { color:var(--accent); text-underline-offset:3px; overflow-wrap:anywhere; } +.document { max-width:1040px; margin:auto; padding:24px; } +.document.source { max-width:none; } +.document-header { display:flex; gap:24px; flex-wrap:wrap; padding-bottom:18px; margin-bottom:24px; border-bottom:1px solid var(--line); } +.document-header span { color:var(--subtle); overflow-wrap:anywhere; font-size:12px; } +.markdown h1,.markdown h2,.markdown h3 { margin:28px 0 12px; } +.markdown p,.markdown ul,.markdown ol,.markdown pre,.markdown table { margin:12px 0; } +.markdown pre { padding:16px; background:var(--surface); overflow:auto; } +.markdown blockquote { border-left:2px solid var(--line); margin-left:0; padding-left:18px; color:var(--muted); } +code { font-family:'DejaVu Sans Mono',monospace; font-size:12px; } +.source-code { overflow:auto; line-height:1.6; } +.source-code code > div { min-width:max-content; display:flex; } +.line-number { width:64px; flex-shrink:0; text-align:right; padding-right:18px; color:var(--subtle); text-decoration:none; user-select:none; } +.highlight-line { background:var(--selected); } +@media(max-width:600px) { h1 {font-size:21px;} td,th {padding:10px;} .document {padding:16px;} } diff --git a/docs/studies/source-manifest.json b/docs/studies/source-manifest.json new file mode 100644 index 0000000..11309d2 --- /dev/null +++ b/docs/studies/source-manifest.json @@ -0,0 +1,136 @@ +{ + "date": "2026-09-17", + "purpose": "Исследование для нового движка, не реализация", + "targets": [ + "Linux", + "Windows" + ], + "game_types": [ + "2D", + "3D" + ], + "requirements": [ + "manual editor", + "editor-only MCP; no runtime-world API or Player MCP", + "unmodified Blender integration with optional add-on", + "standalone game compilation/cooking/packaging" + ], + "method": "Selected implementation bodies plus primary official documentation; no engine builds, user study or GPU benchmark.", + "repositories": [ + { + "name": "Unreal Engine", + "path": "../UnrealEngine", + "version": "5.8.2", + "commit": "16d75d84714512edfb744e1fd0a59e9c74d57873", + "acquisition": "existing local checkout", + "source_worktree_clean": true, + "repository": "https://github.com/EpicGames/UnrealEngine", + "path_usage": "Optional sibling checkout for local source viewing; not included in Faset repository", + "access": "Epic account/repository authorization required" + }, + { + "name": "Godot", + "path": "../godot", + "version": "4.8.0 dev", + "commit": "9c776068d6ed23acd0c78bfe534272d1d2a3a619", + "acquisition": "shallow depth 1, complete source checkout", + "source_worktree_clean": true, + "repository": "https://github.com/godotengine/godot", + "path_usage": "Optional sibling checkout for local source viewing; not included in Faset repository", + "access": "Public upstream; original project license applies" + }, + { + "name": "UnityCsReference", + "path": "../UnityCsReference", + "version": "6000.7.0a6", + "commit": "6b50e5544f6efcca1f44dbace3d1778b465ac6d0", + "acquisition": "shallow depth 1, C# reference source only", + "source_worktree_clean": true, + "repository": "https://github.com/Unity-Technologies/UnityCsReference", + "path_usage": "Optional sibling checkout for local source viewing; not included in Faset repository", + "access": "Public upstream; original project license applies" + }, + { + "name": "Blender", + "path": "../blender-source", + "version": "5.3.0 alpha", + "commit": "28d47268bddcb9dc69143f0e2d9410969da16311", + "acquisition": "shallow depth 1, partial clone, sparse source checkout", + "source_worktree_clean": true, + "sparse_directories": [ + "scripts/modules/bpy", + "source/blender/asset_system", + "source/blender/blenkernel", + "source/blender/blenloader", + "source/blender/editors/interface", + "source/blender/editors/undo", + "source/blender/makesrna", + "source/blender/windowmanager" + ], + "repository": "https://github.com/blender/blender", + "path_usage": "Optional sibling checkout for local source viewing; not included in Faset repository", + "access": "Public upstream; original project license applies" + } + ], + "supplemental_source_snapshots": [ + { + "name": "Blender glTF exporter", + "repository": "https://github.com/blender/blender", + "commit": "28d47268bddcb9dc69143f0e2d9410969da16311", + "acquisition": "Selected files read remotely at the same commit as the sparse checkout; not added to the local checkout", + "files": [ + "scripts/addons_core/io_scene_gltf2/blender/com/extras.py", + "scripts/addons_core/io_scene_gltf2/blender/exp/nodes.py", + "scripts/addons_core/io_scene_gltf2/blender/exp/export.py", + "scripts/addons_core/io_scene_gltf2/blender/exp/animation/action.py", + "scripts/addons_core/io_scene_gltf2/blender/exp/material/materials.py" + ], + "study": "docs/studies/17-asset-pipeline-and-blender-roundtrip.md" + }, + { + "name": "Box2D", + "repository": "https://github.com/erincatto/box2d", + "commit": "77619f4f7baebe5117a2e3ddc3ac8c404e82d243", + "acquisition": "Selected remote source files; research pin, not a selected Faset dependency version", + "files": [ + "src/hull.c", + "include/box2d/collision.h" + ], + "study": "docs/studies/17-asset-pipeline-and-blender-roundtrip.md" + }, + { + "name": "Box3D", + "repository": "https://github.com/erincatto/box3d", + "commit": "f555ee42084e0b43cbffa863f40bff8117c08896", + "acquisition": "Selected remote source files and collision documentation; research pin, not a selected Faset dependency version", + "files": [ + "src/shape.c", + "src/mesh.c", + "include/box3d/collision.h", + "docs/collision.md" + ], + "study": "docs/studies/17-asset-pipeline-and-blender-roundtrip.md" + }, + { + "name": "Godot documentation", + "repository": "https://github.com/godotengine/godot-docs", + "commit": "e1129cb5c3f27e0e3a12e816ae405ab5ce032e3f", + "acquisition": "Selected current development documentation pages read remotely", + "files": [ + "tutorials/scripting/cpp/gdextension_cpp_example.rst", + "engine_details/engine_api/gdextension/what_is_gdextension.rst", + "tutorials/scripting/cpp/about_godot_cpp.rst" + ], + "study": "docs/studies/16-native-gameplay-and-metadata.md" + } + ], + "supplemental_note": "Studies 15–18 deepen the earlier static research. Remote links in these reports pin selected source files where possible; official evolving documentation is identified by URL and research date. No engine builds, importer runs, UX user tests or GPU benchmarks were performed.", + "path_base": "project_root", + "path_note": "Repository paths are relative to the Faset_Engine project root; external source checkouts are not part of this repository.", + "architecture_updated_at": "2026-09-18", + "architecture": "docs/ARCHITECTURE.md", + "implementation_plan": "PLAN.md", + "implementation_status": "Architecture selected; engine not implemented. The documentation map is an existing separate web tool.", + "source_worktree_note": "source_worktree_clean records the research-time observation, not a guarantee about a reader's checkout.", + "public_link_note": "Source citations use upstream blob URLs pinned to research commits; Unreal links require appropriate Epic access. No external engine code is bundled." +}