7 Commits
Author SHA1 Message Date
Emil dd02f9cae8 Complete MVP acceptance, publish platform evidence and finalize the manual
Native and manual checks / native (ubuntu-24.04) (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
Native and manual checks / manual (push) Canceled after 0s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
2026-09-18 06:23:32 +03:00
Emil 4cb82556de Preserve source locations across cached imports and record MVP acceptance 2026-09-18 05:55:29 +03:00
Emil 0f34b03631 Checkpoint 5: complete asset freshness, schema migrations and editor diagnostics 2026-09-18 05:43:02 +03:00
Emil e0b965166e Compare canonical Windows launcher paths in UI acceptance 2026-09-18 05:33:21 +03:00
Emil b2fcd7a956 Separate historical engine research from current implementation evidence 2026-09-18 05:26:39 +03:00
Emil 7481a2e029 Validate gameplay metadata before publishing build generations 2026-09-18 05:21:31 +03:00
Emil afd773fffb Register pinned Vulkan software driver on elevated Windows CI 2026-09-18 05:19:59 +03:00
109 changed files with 6771 additions and 195 deletions
+9 -2
View File
@@ -50,13 +50,20 @@ jobs:
.cache/windows-graphics/sdk
.cache/windows-graphics/driver
- name: Probe Vulkan loader and SwiftShader before compiling the engine
# Hosted Windows jobs are elevated, so Khronos correctly ignores driver
# environment overrides. Register the software ICD only on this disposable VM.
# https://github.com/KhronosGroup/Vulkan-Loader/blob/main/docs/LoaderDriverInterface.md#driver-discovery-on-windows
env:
VK_LOADER_DEBUG: all
run: python tools/ci/probe_windows_vulkan.py
run: |
$driverKey = 'HKLM:\SOFTWARE\Khronos\Vulkan\Drivers'
New-Item -Path $driverKey -Force | Out-Null
New-ItemProperty -Path $driverKey -Name $env:VK_DRIVER_FILES -PropertyType DWord -Value 0 -Force | Out-Null
python tools/ci/probe_windows_vulkan.py
- name: Fetch checksum-verified Slang compiler
run: python tools/fetch_slang.py
- name: Configure full editor and Player
run: cmake --preset windows-debug -DFASET_BUILD_RENDERER=ON -DFASET_BUILD_EDITOR=ON
run: cmake --preset windows-debug -DFASET_BUILD_RENDERER=ON -DFASET_BUILD_EDITOR=ON -DFASET_DEBUG_IMGUI=ON
- name: Build full editor and tests
run: cmake --build --preset windows-debug --parallel 2
- name: CPU contracts and software GPU pixel tests
+2 -1
View File
@@ -56,7 +56,7 @@ endif()
if(TARGET faset_authoring AND EXISTS "${PROJECT_SOURCE_DIR}/cmake/EditorCommands.cmake")
include(cmake/EditorCommands.cmake)
endif()
if(TARGET faset_assets AND EXISTS "${PROJECT_SOURCE_DIR}/cmake/BuildService.cmake")
if(TARGET faset_assets AND TARGET faset_authoring AND EXISTS "${PROJECT_SOURCE_DIR}/cmake/BuildService.cmake")
include(cmake/BuildService.cmake)
endif()
if(TARGET faset_editor_commands AND TARGET faset_build_service)
@@ -107,5 +107,6 @@ if(BUILD_TESTING)
endif()
include(cmake/Tutorials.cmake)
include(cmake/Diagnostics.cmake)
include(cmake/WindowsPlatform.cmake)
+71 -69
View File
@@ -1,8 +1,8 @@
# План разработки Faset Engine
Версия 1.1 · 18 сентября 2026 года.
Версия 1.2 · 18 сентября 2026 года.
**Статус:** идёт реализация MVP. Готовность этапов определяется всеми их критериями, включая проверку обеих ОС; отдельные работающие подсистемы ещё не закрывают этап целиком. Текущие результаты и ограничения записаны в [журнале реализации](docs/IMPLEMENTATION.md). Этот документ определяет порядок работ; контракты подсистем находятся в [ARCHITECTURE.md](docs/ARCHITECTURE.md).
**Статус:** C++ MVP реализован и принят; первый tag — **v0.1.0-mvp**. Исходники движка проверены на `4cb82556de31268d2bde73948dd1ff1b6c02f162`; финальная публикация добавляет документацию и свидетельства, сохраняя код движка. [Досье M0–M9](docs/validation/mvp-acceptance.md) связывает каждый этап с проверками и точными revisions. Linux проверен на RTX 2080 Ti, Windows — в native CI через SwiftShader; это не сертификация всех GPU/драйверов. Системный IME и физические переходы между мониторами не проверены, native Wayland restore имеет явный skip; XWayland и Windows lifecycle прошли. Текст/DPI проверены на уровне widgets и SDL. Эти границы покрытия сохраняются открыто и не выдаются за пройденные сценарии. Контракты находятся в [ARCHITECTURE.md](docs/ARCHITECTURE.md), история — в [журнале реализации](docs/IMPLEMENTATION.md).
## 1. Результат MVP
@@ -42,12 +42,12 @@ Lua, C++ hot reload, Blender live link, встроенное изображен
**Результат:** минимальный 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, зафиксировать их лицензии и владельцев интеграции.
- [x] Создать targets Core, Runtime, Editor, Player, SchemaExporter, инструментов и примеров; запретить зависимости Runtime/Player на Editor/MCP.
- [x] Настроить CMake presets, Ninja, Clang/clang-cl, development/release и базовые проверки CI обеих ОС.
- [x] Зафиксировать стандарт C++, версии компиляторов, Windows SDK/runtime, библиотек и Slang; определить проверяемую матрицу ОС, архитектур и GPU.
- [x] Закрепить EnTT, SDL3, Slang, Box2D, Box3D и отладочный ImGui с воспроизводимым получением, notices и возможностью локальной сборки. Research commits не считать автоматически dependency versions.
- [x] Определить ошибки, журналирование, проверки инвариантов, владение ресурсами и формат диагностик.
- [x] Выбрать необходимые библиотеки шрифтов/текста, изображений и glTF, зафиксировать их лицензии и владельцев интеграции.
**Готово:** чистая сборка проходит на обеих ОС, пример запускается, версии видны в отчёте. Офлайн-сборка проверяется с заранее подготовленными инструментами и зависимостями; обязательных облачных сервисов нет.
@@ -55,12 +55,12 @@ Lua, C++ hot reload, Blender live link, встроенное изображен
**Зависит от:** 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, несовместимый тип и отсутствующая схема.
- [x] Реализовать постоянные ID ресурсов, объектов, компонентов, типов и полей; временные runtime handles с world/session и generation хранить отдельно.
- [x] Создать явную типизированную C++-регистрацию схем, декларативные constraints и подсказки Inspector; отделить описание данных от исполняемых callbacks.
- [x] Ввести версионированные JSON-форматы проекта, сцены, материала и import settings; стабильную запись и сохранение неизвестных данных отсутствующего модуля.
- [x] Создать AuthoringService: query, команды, транзакции, validation, revisions, Undo/Redo, dirty state, атомарное сохранение и recovery.
- [x] Поддержать объекты, компоненты, изменение полей и иерархии, batch-команды и понятные ошибки.
- [x] Добавить миграции с fixtures: переименование при прежнем FieldId, новый default, несовместимый тип и отсутствующая схема.
**Готово:** round-trip сохраняет смысл и ID; неверная транзакция не применяется частично; Undo/Redo восстанавливает ссылки; устаревшая revision даёт конфликт. Документы не содержат EnTT handles или адресов памяти.
@@ -68,13 +68,13 @@ Lua, C++ hot reload, Blender live link, встроенное изображен
**Зависит от:** M0; загрузка сцены подключается к M1.
- [ ] Подключить SDL3 через Faset Platform: окно, ввод, текст, DPI и Vulkan surface.
- [ ] Реализовать Vulkan 1.3 backend с прямыми вызовами Vulkan API, проверкой features/limits/formats, ресурсами, синхронизацией и освобождением после завершения GPU; изолировать Vulkan handles и команды от gameplay и редакторских API.
- [ ] Создать 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 требует проверки совместимости.
- [x] Подключить SDL3 через Faset Platform: окно, ввод, текст, DPI и Vulkan surface.
- [x] Реализовать Vulkan 1.3 backend с прямыми вызовами Vulkan API, проверкой features/limits/formats, ресурсами, синхронизацией и освобождением после завершения GPU; изолировать Vulkan handles и команды от gameplay и редакторских API.
- [x] Создать Render Graph на одной graphics queue с явными чтениями/записями и barriers; подключить validation и метки проходов.
- [x] Компилировать Slang в SPIR-V с закреплёнными layout/binding conventions; выгружать reflection в собственный формат. Проверить совместимый HLSL-пример.
- [x] Получить спрайты, прозрачность и слои для 2D; static meshes, текстуры, базовый PBR, свет и обычную shadow map для 3D.
- [x] Использовать direct draws и CPU frustum culling как эталон; ввести CPU/GPU timings и счётчики ресурсов.
- [x] Обновлять шейдер с безопасной заменой pipeline: ошибка сохраняет рабочий вариант, изменение layout требует проверки совместимости.
**Готово:** корректны resize/minimize, пересоздание attachments и повторный запуск; validation не сообщает ошибок в проверяемых сценариях. В игру идут SPIR-V и метаданные без обязательного Slang compiler. Создание pipelines драйвером остаётся отдельным этапом.
@@ -82,14 +82,14 @@ Lua, C++ hot reload, Blender live link, встроенное изображен
**Зависит от:** 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, логи и обработку завершения.
- [x] Преобразовывать authoring-сцену в EnTT-мир с картой происхождения и удобным typed API объектов/компонентов.
- [x] Создать gameplay static library и SchemaExporter с теми же регистрациями: экспорт схем без запуска gameplay lifecycle или графического окна. Editor читает проверяемый декларативный результат.
- [x] Реализовать OnStart, Update, FixedUpdate, LateUpdate, OnDestroy и события физики; регистрировать только нужные обработчики.
- [x] Зафиксировать tick (default 60 Гц): structural commands → ввод → FixedUpdate → физика → readback/events → реакции. Spawn/despawn и изменение состава компонентов вступают в силу со следующего tick.
- [x] Вызывать Update один раз за игровой кадр, LateUpdate после подготовки отображаемых положений. Интерполяция не пишет обратно в симуляцию.
- [x] Подключить независимые адаптеры Box2D/Box3D: тела и коллайдеры для демо, collision layers, события и debug drawing. Динамическим телом управляет физика; телепортация явная.
- [x] Ограничить catch-up (начальный лимит четыре ticks за проход); лишнее время локального Player отбрасывать с диагностикой. После pause/reset не догонять паузу; teleport/spawn сбрасывает историю интерполяции.
- [x] Запускать Player отдельным процессом/окном из текущего снимка сцены, включая несохранённые правки; реализовать Play/Stop, pause/single-step, логи и обработку завершения.
**Готово:** C++-поведение появляется в Inspector через schema export, двигает объект и реагирует на физику. Stop сохраняет авторскую сцену. Ошибка сборки оставляет старую схему/сборку с явным статусом устаревания; старый результат не выдаётся за новый. Player не содержит MCP и не требует Editor.
@@ -97,13 +97,13 @@ Lua, C++ hot reload, Blender live link, встроенное изображен
**Зависит от:** M1, M2.
- [ ] Построить retained tree, layout, clipping/scroll, события, focus, keyboard navigation, drag/drop и lifecycle widgets.
- [ ] Подключить шрифты/shaping по выбору M0; проверить кириллицу, выделение/редактирование текста, clipboard, IME и разный DPI.
- [ ] Разделить C++-поведение, декларативную компоновку и стили с тёмной темой; поддержать программное создание Inspector.
- [ ] Реализовать кнопки, текстовые/числовые поля, списки/дерево, выбор ресурса, вкладки, панели и разделители.
- [ ] Оформить встроенные подписи, команды, подсказки и сообщения на английском; проверить, что Unicode-текст проекта отображается и редактируется независимо от языка интерфейса.
- [ ] Добавить минимальный docking в одном окне и сохранение раскладки; дополнительные системные окна редактора отложить.
- [ ] Перезагружать layout/styles с проверкой и сохранением рабочего состояния; ImGui использовать для диагностики.
- [x] Построить retained tree, layout, clipping/scroll, события, focus, keyboard navigation, drag/drop и lifecycle widgets.
- [x] Подключить шрифты/shaping по выбору M0; проверить кириллицу, выделение/редактирование текста, clipboard, IME и разный DPI.
- [x] Разделить C++-поведение, декларативную компоновку и стили с тёмной темой; поддержать программное создание Inspector.
- [x] Реализовать кнопки, текстовые/числовые поля, списки/дерево, выбор ресурса, вкладки, панели и разделители.
- [x] Оформить встроенные подписи, команды, подсказки и сообщения на английском; проверить, что Unicode-текст проекта отображается и редактируется независимо от языка интерфейса.
- [x] Добавить минимальный docking в одном окне и сохранение раскладки; дополнительные системные окна редактора отложить.
- [x] Перезагружать layout/styles с проверкой и сохранением рабочего состояния; ImGui использовать для диагностики.
**Готово:** интерфейс доступен клавиатурой, текст и DPI работают, стили меняются без C++ rebuild. Правка widget вызывает AuthoringService; один drag создаёт один Undo.
@@ -111,14 +111,14 @@ Lua, C++ hot reload, Blender live link, встроенное изображен
**Зависит от:** 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.
- [x] Реализовать AssetId, import settings, dependency graph, artifacts и manifest готового поколения.
- [x] Включить в ключ кэша входы, настройки, версии importer/toolchain и целевой профиль; показывать ошибки/устаревшие результаты.
- [x] Импортировать профиль glTF/GLB и изображения для демо; описать PBR-подмножество, единицы, оси и collider policy.
- [x] Поддержать обычный GLB без Blender add-on; явно ограничить matching после rename/restructure без устойчивых IDs.
- [x] Создать минимальное необязательное дополнение обычного Blender: export button, сохраняемые IDs и manifest; записывать версию exporter и профиль.
- [x] Обновлять только импортированную основу, сохраняя gameplay/physics settings и overrides Faset. Исчезновение цели создаёт конфликт.
- [x] Публиковать GLB/manifest/зависимости как согласованное поколение; ошибка/отмена оставляет последний рабочий artifact.
- [x] Предоставить одинаковый импорт через UI/CLI/MCP редактора; длительная операция имеет ID, progress и cancellation.
**Готово:** изменение mesh в Blender обновляет несколько экземпляров без потери компонентов/overrides; rename со стабильным ID сохраняет связь, удаление создаёт понятный конфликт. Игра использует cooked assets без Blender.
@@ -126,13 +126,13 @@ Lua, C++ hot reload, Blender live link, встроенное изображен
**Зависит от:** 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; восстановить проект с очищенным кэшем.
- [x] Собрать Scene Tree, Inspector, Asset Browser, 2D/3D viewport, Console, Project Settings и команды Save/Play/Build.
- [x] Реализовать selection, focus, gizmos, создание объектов/компонентов, поиск и основные shortcuts.
- [x] Ввести шаблон как scene asset: InstanceId, исходные ObjectId/ComponentId, sparse overrides и вложенные экземпляры без циклов.
- [x] Адресовать overrides по цепочке инстанцирования и IDs, независимо от имён/parenting; при duplicate remap внутренних ссылок, внешние сохранить.
- [x] Поддержать локальные добавления, suppression, ограниченный reparent внутри экземпляра с явным local/world transform; массивы переопределять целиком.
- [x] Показывать происхождение значения, Revert и открытие источника. Сохранять конфликтующие данные исчезнувших targets/types; Apply to template и inherited variants отложить.
- [x] Обеспечить одинаковые операции и Undo/Redo для 2D/3D; восстановить проект с очищенным кэшем.
**Готово:** пользователь создаёт сцену без ручного JSON, размещает два экземпляра, меняет один, обновляет источник, отменяет правки и переоткрывает проект без потери идентичности.
@@ -140,12 +140,12 @@ Lua, C++ hot reload, Blender live link, встроенное изображен
**Зависит от:** 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.
- [x] Подключить MCP к AuthoringService: документы/query/schema, create/delete/set, batches, Undo/Redo, import/build/export, Play/Stop и диагностика редактора.
- [x] Ввести capabilities и структурированные ошибки. Headless authoring/build не требует GUI; screenshot editor viewport требует render backend и GPU.
- [x] Проверять revisions и конфликты ручных/MCP-изменений; не повторять слепую запись поверх нового состояния. Повтор запроса не дублирует завершённую транзакцию.
- [x] Предоставить jobs ID/progress/result/cancellation; отделить отмену задачи от Undo документа.
- [x] Загружать editor DLL/SO при старте: manifest, exact SDK/build compatibility, зависимости и владельцы регистраций. Обновление — через перезапуск.
- [x] Проверить extension-пакет с runtime-компонентом и editor-командой/панелью; правки документов проходят через command API.
**Готово:** ручные и MCP-операции дают эквивалентные канонические документы и историю. Нет MCP runtime-entity read/write; MCP transport отсутствует в Player и SchemaExporter. Отключённый пакет не уничтожает данные неизвестного компонента; экспорт сообщает о нерешённой зависимости.
@@ -153,12 +153,12 @@ Lua, C++ hot reload, Blender live link, встроенное изображен
**Зависит от:** 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, отчёт и инструкции разработчику/игроку.
- [x] Создать BuildService: validate → C++/schema build → resource/Slang cook → package → проверить результат.
- [x] Разделить development/release manifests; исключить Editor, MCP, Blender, schema helpers и shader compiler из обязательного runtime игры.
- [x] Подготовить две маленькие 2D/3D-игры с исходниками, понятным управлением и сценариями проверки.
- [x] Проверить stop/build/restart, save/reopen, nested scenes, Blender reimport и shader compile failure.
- [x] Собрать обе игры на каждой целевой ОС и запустить в чистой среде с поддерживаемым GPU-драйвером и документированными runtime-зависимостями.
- [x] Сформировать notices, manifest ресурсов/toolchain, отчёт и инструкции разработчику/игроку.
**Готово:** все четыре сочетания «2D/3D × Linux/Windows» запускаются без исходного дерева Faset и редактора. Недостающий asset или библиотека обнаруживаются до публикации пакета.
@@ -166,17 +166,19 @@ Lua, C++ hot reload, Blender live link, встроенное изображен
**Зависит от:** 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.
- [x] Пройти новую установку и создание проекта по документации на обеих ОС.
- [x] Повторить authoring-сценарий руками и через MCP; проверить Undo/Redo и конфликт revision при одновременной правке.
- [x] Проверить recovery после сбоя сохранения, импорта, сборки и завершения Player с ошибкой.
- [x] Записать startup, edit/build/run, import, CPU/GPU frame time и memory с оборудованием/сценами; по результатам установить бюджеты следующего этапа.
- [x] Устранить блокирующие дефекты UX, текста, DPI, форматов и exports; записать ограничения.
- [x] Обновить docs, приложить проверенные результаты и только затем поставить первый MVP tag.
## 4. Развитие после MVP
### P1. Lua и скорость итераций
Начальные бюджеты отслеживания для конкретных демо и Linux reference host: frame p95 ≤ 4 мс, GPU/readback p95 ≤ 1 мс, simulation/snapshot p95 ≤ 0,5 мс, явные Vulkan allocations ≤ 20 MiB, startup от `main()` ≤ 500 мс. [Методика и исходные измерения](docs/validation/linux-release-2026-09-18/README.md) ограничивают область этих чисел; для других сцен/ОС нужны отдельные baselines. Это бюджеты P1, а не обещание такой производительности любой игры.
Добавить Lua runtime/editor пакет поверх публичного API, проверяемых handles и схем. Предусмотреть диагностику, отладку, Inspector и явный lifecycle перезагрузки. Сохранение состояния при reload проектируется отдельно. Проверка: C++ и Lua используют одни данные/фазы; C++-only export не включает Lua.
Улучшать schema/build cache, сообщения компилятора, шаблоны проектов, переход к коду, autosave и измеренное время «изменение → результат». Dynamic gameplay loading рассматривать при подтверждённой проблеме линковки.
+9 -4
View File
@@ -2,7 +2,7 @@
Faset is an independent engine project for desktop **2D and 3D games on Linux and Windows**. Its priorities are a custom editor that is comfortable to use by hand and through MCP, integration with Blender, and a path toward advanced graphics.
**Current status: MVP acceptance is in progress.** The native Editor, shared GUI/MCP authoring, C++ gameplay builds, Vulkan Player, standalone export and two playable sample games are integrated. Linux GPU workflows are tested; complete Windows graphics/export acceptance and final validation remain in progress. See the [implementation checkpoints](docs/IMPLEMENTATION.md) for observed results; a technology appearing in the plan does not mean it is complete or benchmarked.
**Current status: the C++ MVP is implemented and accepted for the recorded Linux and Windows test profiles.** It includes the native Editor, shared GUI/MCP authoring, gameplay builds, Vulkan Player, Blender import and standalone export. Both playable games passed Release export and relocated execution on both operating systems. Windows graphics acceptance used software Vulkan; physical Windows GPUs, system IME and mixed-monitor transitions need additional coverage. See the [acceptance dossier](docs/validation/mvp-acceptance.md) for exact revisions, checks and limits, and the [implementation checkpoints](docs/IMPLEMENTATION.md) for the development record.
## Start here
@@ -18,7 +18,8 @@ After following the manual's build setup, run `build/linux-debug/faset_editor`
(or `build/windows-debug/faset_editor.exe`) to open the project launcher. The
[`collect-2d`](examples/projects/collect-2d) and
[`collect-3d`](examples/projects/collect-3d) projects include playable C++ examples;
the 3D example includes an original Blender asset and import instructions.
the 3D example includes an original Blender asset and import instructions. Import its
`Assets/exit-arch/manifest.json` once before Play, including after clearing its cache.
## Language
@@ -38,7 +39,7 @@ This README is in English. The current planning documents, studies, and research
- **Editor-only MCP:** authoring, assets, import, builds, export, Play/Stop, and editor diagnostics. MCP is absent from the Player and exported games.
- Standard, **unmodified Blender**, glTF/GLB import, and an optional add-on for convenient export and stable IDs.
MVP is complete when two small games, one 2D and one 3D, can be created, saved, played, and exported for both operating systems. GPU-driven rendering, HZB, advanced shadows, temporal reconstruction, and dynamic global illumination follow the baseline.
The MVP provides two small games, one 2D and one 3D, with scene editing, C++ behavior, physics, Play and standalone export. Lua, GPU-driven rendering, HZB, advanced shadows, temporal reconstruction and dynamic global illumination follow this baseline.
## Run the research map
@@ -56,6 +57,10 @@ Open [localhost:4178](http://localhost:4178). The map is a documentation viewer,
## Repository contents
Documentation, studies, the source manifest, and the map's code are tracked in Git. Third-party engine source trees, installed dependencies, build outputs, and caches are excluded. Source links are pinned to the commits examined during research; Unreal Engine links may require access through Epic.
Faset's C++ source, tests, sample games, Blender add-on, Manual, architecture, studies,
selected validation evidence, and research map are tracked in Git. Third-party engine
source trees, installed dependencies, build outputs, and caches are excluded. Research
source links are pinned to the commits examined; Unreal Engine links may require
access through Epic.
See the [publication notes](docs/PUBLICATION.md) for publication scope and licensing status. A license for Faset's own content has not yet been selected. Third-party projects retain their own license terms.
+26 -3
View File
@@ -3,6 +3,9 @@
#include <faset/core/io.hpp>
#include <faset/editor/editor_ui.hpp>
#include <faset/editor/mcp.hpp>
#ifdef FASET_HAS_DEBUG_OVERLAY
#include <faset/editor/debug_overlay.hpp>
#endif
#include <thread>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
@@ -54,6 +57,22 @@ int run_editor_ui(Session& session, bool enable_mcp, std::uint64_t max_frames,
const auto font = session.config().engine_root / "assets/fonts/NotoSans.ttf";
const auto theme = session.config().engine_root / "assets/ui/dark.json";
EditorUI ui(session, renderer, font, theme);
#ifdef FASET_HAS_DEBUG_OVERLAY
DebugOverlay diagnostics;
auto previous_frame = std::chrono::steady_clock::now();
#endif
const auto draw_frame = [&] {
#ifdef FASET_HAS_DEBUG_OVERLAY
const auto now = std::chrono::steady_clock::now();
const auto delta = std::chrono::duration<float>(now - previous_frame).count();
previous_frame = now;
auto snapshot = ui.snapshot();
diagnostics.append(snapshot, renderer, delta);
renderer.render(snapshot);
#else
renderer.render(ui.snapshot());
#endif
};
ui.set_project_switch_enabled(!enable_mcp);
McpServer server(session.commands());
StdioTransport transport;
@@ -66,7 +85,7 @@ int run_editor_ui(Session& session, bool enable_mcp, std::uint64_t max_frames,
[&](const Json& arguments) {
session.poll();
ui.frame({});
renderer.render(ui.snapshot());
draw_frame();
auto region =
arguments.value("viewport_only", true)
? ui.snapshot().scene_rect
@@ -101,8 +120,12 @@ int run_editor_ui(Session& session, bool enable_mcp, std::uint64_t max_frames,
}
}
session.poll();
ui.frame(renderer.poll_events());
renderer.render(ui.snapshot());
auto events = renderer.poll_events();
#ifdef FASET_HAS_DEBUG_OVERLAY
events = diagnostics.process_events(events);
#endif
ui.frame(events);
draw_frame();
if (ui.project_switch_requested())
return 3; // Application-level request: destroy this Session before opening another.
++frame;
+8 -1
View File
@@ -1,10 +1,17 @@
add_library(faset_build_service STATIC ${PROJECT_SOURCE_DIR}/src/editor/build_service.cpp)
target_include_directories(faset_build_service PUBLIC ${PROJECT_SOURCE_DIR}/include)
target_compile_features(faset_build_service PUBLIC cxx_std_20)
target_link_libraries(faset_build_service PUBLIC faset_core PRIVATE faset_asset_data Threads::Threads)
target_link_libraries(faset_build_service PUBLIC faset_core PRIVATE faset_assets faset_authoring Threads::Threads)
if(BUILD_TESTING)
add_executable(faset_build_service_tests ${PROJECT_SOURCE_DIR}/tests/build_service_tests.cpp)
target_link_libraries(faset_build_service_tests PRIVATE faset_build_service faset_assets)
target_compile_definitions(faset_build_service_tests PRIVATE FASET_ENGINE_SOURCE="${PROJECT_SOURCE_DIR}")
add_test(NAME process_and_cook COMMAND faset_build_service_tests)
add_executable(faset_build_schema_tool ${PROJECT_SOURCE_DIR}/tests/build_schema_tool.cpp)
target_link_libraries(faset_build_schema_tool PRIVATE faset_core)
add_executable(faset_build_schema_tests ${PROJECT_SOURCE_DIR}/tests/build_schema_tests.cpp)
target_link_libraries(faset_build_schema_tests PRIVATE faset_build_service faset_authoring faset_editor_commands)
add_dependencies(faset_build_schema_tests faset_build_schema_tool)
add_test(NAME build_schema_publication COMMAND faset_build_schema_tests $<TARGET_FILE:faset_build_schema_tool> ${PROJECT_SOURCE_DIR})
set_tests_properties(build_schema_publication PROPERTIES TIMEOUT 60)
endif()
+15
View File
@@ -0,0 +1,15 @@
if(FASET_DEBUG_IMGUI AND TARGET faset_imgui AND TARGET faset_render)
add_library(faset_debug_overlay STATIC ${PROJECT_SOURCE_DIR}/src/editor/debug_overlay.cpp)
target_include_directories(faset_debug_overlay PUBLIC ${PROJECT_SOURCE_DIR}/include)
target_link_libraries(faset_debug_overlay PUBLIC faset_render PRIVATE faset_imgui)
if(TARGET faset_editor AND TARGET faset_editor_ui)
target_link_libraries(faset_editor PRIVATE faset_debug_overlay)
target_compile_definitions(faset_editor PRIVATE FASET_HAS_DEBUG_OVERLAY=1)
endif()
if(BUILD_TESTING)
add_executable(faset_debug_overlay_tests ${PROJECT_SOURCE_DIR}/tests/editor_debug_overlay.cpp)
target_link_libraries(faset_debug_overlay_tests PRIVATE faset_debug_overlay)
add_test(NAME editor_debug_overlay COMMAND faset_debug_overlay_tests)
set_tests_properties(editor_debug_overlay PROPERTIES LABELS "gpu")
endif()
endif()
+2
View File
@@ -47,6 +47,8 @@ Entry function плагина согласует версию интерфейс
`TypeId/FieldId` описывают смысл сохраняемых данных и не зависят от имени C++, offset, порядка членов или `typeid`. Layout fingerprint нужен для совместимости конкретной сборки. Переименование сохраняет ID; изменение смысла/единиц требует миграции. Отсутствующий плагин не уничтожает неизвестные записи. Lua позже использует явно экспортированный runtime API и проверяемые handles; наличие metadata не делает каждый native-метод доступным MCP или Lua.
MVP доставляет декларативные шаги миграции через тот же schema manifest: `default`, `scale` и `require_manual`. Вся схема и правила проверяются до публикации Player/schema generation. Старые версии открываются как opaque data; миграция выполняется явно командой `component.migrate` с revision, одним Undo и записью recovery. Inherited component изменяется в исходной сцене; sparse overrides других экземпляров требуют явного обзора при изменении смысла или единиц. Произвольный C++ migration callback в Editor не загружается. Контракт и проверяемый пример — в [Manual](manual/scripting/api.md#editor-data-migrations).
## 5. Авторские данные, сцены и редактор — принято
Пользователь работает со сценами, объектами, компонентами и ресурсами. Сцены допускают **вложенные экземпляры шаблонов**. JSON хранит ссылку на исходный шаблон и **sparse overrides** — только локальные отличия, с устойчивыми адресами полей/объектов. Подписи и пути не являются идентичностью. Для добавления, удаления и изменения вложенного содержимого нужны явные операции; потерянная цель override становится диагностируемым конфликтом, а не молчаливым удалением данных.
+127
View File
@@ -3,6 +3,11 @@
This log records working implementation and observed validation. It does not replace
the acceptance criteria in `PLAN.md`. Incomplete platform or workflow checks remain open.
**Current result:** the C++ MVP is accepted in the recorded Linux and Windows profiles.
The [final dossier](validation/mvp-acceptance.md) maps M0–M9 to evidence and keeps
unverified compatibility scenarios explicit. Earlier pending/failure statements below
describe their respective checkpoints, not the final status.
## Checkpoint 1 — native foundation and independent subsystems
Implemented:
@@ -206,3 +211,125 @@ The final build-service review also reproduced a publication defect: an invalid
custom-field default can pass the build stage's shallow schema check before the
Editor rejects it. Full schema validation before publishing `last_build.json` is the
next bounded correction; checkpoint 4 does not claim this gate is already complete.
## Acceptance corrections after checkpoint 4
Gameplay schema publication now uses the same complete metadata validation as the
Editor, before either the Player generation or `last_build.json` is published.
A native fixture drives the real BuildService with valid custom schema v2 and twelve
invalid manifests, checking that the previous binary, schema, manifest and pointer
remain unchanged. The focused authoring/process/schema/Session/MCP suite passed 5/5.
An authoring-disabled Player configuration still builds without editor services.
Windows native checks passed for checkpoint 4 (`45bc352`) and `afd773f`, including
the previous long-path regression. The full graphics runner also passed its Vulkan
1.3 device probe after registering the pinned software ICD on the disposable elevated
runner. Full Windows graphics and export acceptance is still in progress; a successful
probe alone does not close that gate.
The final audit identified additional bounded work: source/dependency freshness in
the Assets panel, scrolling keyboard focus into view, delivery of explicit gameplay
schema migrations, GPU pass labels, and wiring optional ImGui diagnostics. These
remain under implementation and verification; the research map now links current
implementation evidence instead of claiming the engine has not been started.
## Checkpoint 5 — close the final authoring and diagnostics gaps
- Asset freshness compares source, bundle payload, external buffer/image, recipe,
importer and profile contents without publishing a generation. GUI and MCP expose
the same state/reasons; selecting a stale imported row prepares Reimport. Cook and
export reject stale referenced sources while keeping the previous successful result.
- Gameplay schemas carry validated declarative migration steps. Inspector and MCP
apply `component.migrate` as an explicit revision-checked Undo transaction. Opening
old data remains possible without rules. Tests cover local components, instance-local
additions, opening inherited sources, missing/manual rules and overflow rollback.
The Manual explains the sparse-override limitation when field units change.
- Keyboard focus scrolls long and nested Inspector/Assets lists into view at 1×/2×,
preserves unfinished text and keeps invalid numeric edits visible. Template preview
and conflicts are also invalidated when schema metadata changes without a scene edit.
- Vulkan passes emit optional debug-utils labels. An optional `FASET_DEBUG_IMGUI=ON`
Editor module shows real renderer diagnostics via F12; the Player remains independent.
GPU tests cover textured/clipped ImGui geometry and event ownership for gestures
crossing the panel in either direction. Offscreen clipboard operations are local
to that renderer and never touch the desktop clipboard.
- The Windows launcher fixture compares canonical filesystem identities, including
hosted-runner short TEMP aliases, and now distinguishes selection/path failures.
Integrated Linux with optional diagnostics enabled: **34 passed, 1 skipped, 0 failed**
of 35 tests. The skip remains native Wayland programmatic restore; XWayland passed.
ASan/UBSan passed **18/18**, including process cleanup, metadata publication and
migration transactions. Strict MkDocs and local Markdown file-link checks passed.
Windows run `35299805623` at `e0b9651` passed all **34** tests in its CPU/GPU/UI suite,
including the launcher fix, and proceeded to real native Release exports. That run
predates checkpoint 5's new authoring/diagnostic changes, whose Windows checks remain
separate. The next full Windows build enables the optional diagnostic module too.
No MVP tag is claimed at this checkpoint.
## Checkpoint 5 acceptance and source relocation correction
The clean `0f34b03` Linux checkout built offline with 20/20 CPU tests, then created
and rendered a fresh project using the Manual's command. Both checked-in games were
exported in Release, moved outside the SDK into Unicode paths and run for 120 frames
with source projects hidden; Khronos validation was active with no errors. All seven
recovery scenarios and the real Blender/live-Editor round-trip passed. Exact inputs,
hashes and limits are in [the Linux acceptance record](validation/checkpoint5-linux-2026-09-18/README.md).
A subsequent source-relocation regression was reproduced and corrected: a cache-hit
rename/move updated the logical source pointer while retaining the historical payload
path, so the new freshness check incorrectly kept the asset stale. Publication now
stores both paths atomically. Earlier pointers remain readable by resolving the
payload relative to the moved logical source; immutable content generations and cache
keys are unchanged. Regression cases cover PNG, Blender bundles and external glTF
buffers/images, with both new and legacy pointer records. Full Linux integration
remains 34 passed / 1 explicit Wayland skip / 0 failed; targeted ASan/UBSan asset/cook
checks passed 2/2 after this correction.
Windows run `35299805623` at `e0b9651` completed successfully, including both Release
integration exports, incremental C++ rebuilding, and the two checked-in games with
Unicode relocation. It used SwiftShader without the Khronos validation layer and is
functional software-Vulkan evidence, not a physical-GPU benchmark. Checkpoint 5's
newer Windows graphics/export run and the relocation correction have their own
revision-specific gates.
## MVP acceptance — `v0.1.0-mvp`
Final engine source: `4cb82556de31268d2bde73948dd1ff1b6c02f162`. The publication
commit adds documentation, acceptance records and research-viewer wording; it does
not change the accepted engine, gameplay, shader or build-system sources.
- [Final Linux evidence](validation/final-linux-2026-09-18/README.md): 34 passed,
one explicit native Wayland restore skip, zero failed. Both exact-source Release
games passed manifest validation and 120 frames after Unicode relocation with
their source projects hidden. An additional private namespace hid the entire SDK,
build directories, Editor and cached tools; both games still passed another 120
frames with matching captures, active Khronos validation and zero errors.
- [Final Windows evidence](validation/windows-software-vulkan-2026-09-18/README.md):
[run 35301244334](https://github.com/emil28092005/Faset_Engine/actions/runs/35301244334)
passed the fresh full Editor build with ImGui diagnostics, **35/35 tests**, real
BuildService Release exports/incremental Debug rebuild and both checked-in games
after Unicode relocation. Each rendered 120 frames. SwiftShader supplied software
Vulkan; the Khronos layer was unavailable. This is functional Windows execution,
not physical Windows GPU or hardware-performance evidence.
- [Native/manual CI](https://github.com/emil28092005/Faset_Engine/actions/runs/35301244366)
passed for the same final source. The earlier clean offline build and first project
launch, seven recovery scenarios, actual Blender/live-Editor round-trip and full
18-test sanitizer run retain their `0f34b03` provenance. The two affected asset/cook
sanitizer checks passed again after the relocation correction.
- PLAN now closes M0–M9 against the dossier. The English Manual documents the working
C++ tutorials, metadata/migrations, authoring, assets, MCP, extensions and export.
A final instruction check corrected the 3D sample's required first bundle import.
The recorded reference-scene thresholds are initial P1 tracking budgets.
Final publication checks passed: strict MkDocs build, both research-map tests and its
production build, 318 local Markdown links, retained Windows evidence hashes, all
validation JSON records and `git diff --check`. The engine-source diff from the
accepted commit is empty; no untested engine changes were bundled into publication.
Known coverage limits remain: real OS IME composition, movement between physical
monitors with different scales, native Wayland programmatic restore and additional
GPU/driver families. Widget composition/DPI, SDL text-input boundaries, XWayland and
Windows window lifecycle have their own passing evidence. The narrow static-mesh,
root-level box-physics, one-window/C++ MVP profile remains explicit. Lua and advanced
graphics are later work. UI references guide appearance; they do not define behavior.
+3 -3
View File
@@ -4,9 +4,9 @@
## Что публикуется
Собственные архитектурные описания и планы Faset, исследования со ссылками на первичные источники, манифест исследованных snapshots и исходники локальной карты документации. Утверждённый план не выдаётся за готовую реализацию движка.
Собственный C++-код Faset, тесты, примеры игр, Blender add-on, пользовательский Manual, архитектура и план, исследования со ссылками на первичные источники, манифест исследованных snapshots и исходники локальной карты документации. Выбранные воспроизводимые отчёты и измерения публикуются в docs/validation с указанием исходного состояния и ограничений. Готовность MVP определяется критериями PLAN, а не наличием отдельных подсистем.
Сторонние source checkouts, локальные индексы graphify, node_modules, dist, логи, credentials и пользовательские рабочие каталоги не входят в публикацию. Браузерный source viewer предназначен для локального использования и не является сервисом публичной раздачи стороннего кода.
Сторонние source checkouts, локальные индексы graphify, node_modules, dist, временные логи, credentials и пользовательские рабочие каталоги не входят в публикацию. Браузерный source viewer предназначен для локального использования и не является сервисом публичной раздачи стороннего кода.
Исходники Unreal требуют соответствующего доступа Epic; публикация ссылки на commit не предоставляет этот доступ. У Godot, UnityCsReference, Blender и остальных источников остаются их собственные условия. Подробности — в [DEPENDENCIES.md](DEPENDENCIES.md).
@@ -16,6 +16,6 @@
## Проверки публикации
Проверяются состав Git, отсутствие локальных секретов и чужих source trees, внутренние ссылки, source pins, запуск/сборка карты и границы её файлового API. Проверки карты и документов не являются тестами будущего C++-движка, Blender integration или графической производительности.
Проверяются состав Git, отсутствие локальных секретов и чужих source trees, внутренние ссылки, source pins, запуск/сборка карты и границы её файлового API. Проверки карты и документов не заменяют тесты C++-движка, Blender integration или графической производительности. Native CI, GPU-проверки, автономные exports и приёмочные сценарии описаны отдельно в [журнале реализации](IMPLEMENTATION.md) и [результатах проверок](validation/README.md).
Локальные source paths в манифесте являются необязательными относительными подсказками. Публичные Markdown-ссылки на исходники закреплены на upstream commits и не зависят от имени пользователя или расположения Desktop.
+1 -1
View File
@@ -1,6 +1,6 @@
# Документация Faset Engine
Актуализировано 18.09.2026 по принятым решениям. Реализация MVP ведётся параллельно с проверками и пользовательским руководством.
Актуализировано 18.09.2026. C++ MVP реализован и принят в зафиксированных профилях Linux и Windows; результаты и пределы проверок — в [досье приёмки](validation/mvp-acceptance.md). Manual описывает текущие функции, PLAN отделяет их от следующих этапов.
## Канонические документы
+11 -7
View File
@@ -13,9 +13,10 @@ compiler, platform, CRT, configuration, dependencies and SDK source identity.
- **Linux CI:** Ubuntu 24.04, x86-64, Clang **18.1.3**. The headless CPU suite and
manual run on this profile. It is not evidence for desktop rendering on that runner.
- **Windows CI:** Windows Server 2025 runner, x86-64, clang-cl **20.1.8**,
MSVC toolset **14.51.36231**, Windows SDK **10.0.26100.0**. Headless tests have
passed. Full Editor, software Vulkan and Release-package acceptance are tracked
separately in the implementation log until that job completes.
MSVC toolset **14.51.36231**, Windows SDK **10.0.26100.0**. Native headless tests,
the full Editor with optional diagnostics, all 35 CPU/software-GPU tests and both
relocated Release games passed. See the
[Windows record](validation/windows-software-vulkan-2026-09-18/README.md).
These are recorded validation profiles, not a claim that every intermediate Clang
release or every supported Windows desktop has been tested. The presets deliberately
@@ -78,8 +79,11 @@ An offline build is recorded only after a clean build directory is configured an
built with network access disabled. A warm incremental build or archive checksum
verification alone is not that acceptance check.
Commit `d834cfad67cd81d8c4998b90c16791361ca8c0f8` passed this check on Linux: full
native build and 19 CPU tests in a new user/network namespace with no external
connectivity, using a `git archive` checkout and only prefetched inputs. See
[`validation/offline-linux-2026-09-18.json`](validation/offline-linux-2026-09-18.json).
Commit `0f34b036313c011861dbfd5828ed45c4f7940b05` passed this check on Linux: full
native build and 20 CPU tests in a new user/network namespace with no external
connectivity, using a `git archive` checkout and only prefetched inputs. A fresh
project was then created and rendered from that SDK. See the
[checkpoint 5 record](validation/checkpoint5-linux-2026-09-18/README.md).
The [earlier offline run](validation/offline-linux-2026-09-18.json) retains its own
`d834cfa` revision and 19-test result.
Reproduce using `python3 tools/verify_offline_build.py --output .cache/offline-check`.
+12
View File
@@ -26,6 +26,18 @@ With MCP, call `faset_import`, then query `faset_job` using the returned job ID.
`faset_job_cancel` requests cancellation. Cancelling an import does not Undo an
authoring edit. See [MCP and CLI](mcp.md) for transport setup and errors.
The Assets panel marks an imported asset **Stale** when its source, external glTF
buffer/image, Blender bundle, saved import settings, importer, or target profile
differs from the active generation. Select the imported row, then **Import / Reimport**;
the Console reports the changed input. **Refresh** checks immediately, and the panel
also refreshes periodically. `faset_assets` exposes the same `freshness.state` and
structured `freshness.reasons` to MCP clients. Checks compare contents, including
files whose size and timestamp stayed unchanged. The last good cooked asset remains
visible until reimport succeeds; checking freshness never publishes a generation.
Cook and export refuse referenced stale or unavailable sources and explain which
input needs reimport. A standalone exported game uses only its packaged cooked
generation and never needs the original source files.
## Import an image
PNG and JPEG become image assets. Drag an imported image into a scene to create a
+19
View File
@@ -0,0 +1,19 @@
# Developer diagnostics
The optional Dear ImGui overlay shows renderer counters inside the Editor. The Editor's normal interface remains the retained Faset UI. Enable the diagnostic build explicitly:
```sh
cmake --preset linux-debug -DFASET_DEBUG_IMGUI=ON
cmake --build --preset linux-debug --parallel 4
build/linux-debug/faset_editor --project examples/projects/collect-3d --gui
```
On Windows, use `windows-debug` for both presets and `build/windows-debug/faset_editor.exe`. Press **F12** to show or hide the panel. Drag its title bar to move it; **Freeze counters** holds a completed-frame sample for inspection. Closing the panel does not stop rendering. Pointer gestures inside the overlay are kept out of the authoring UI.
The panel reports the previous completed frame: renderer wall time, GPU timestamp time where available, synchronous readback time, draw calls, packed vertices, culled meshes, textures, explicit Vulkan allocation sizes, actual validation availability/errors, and GPU pass-label count. Renderer wall time includes waiting for GPU work; it is not thread CPU usage. Memory excludes driver-internal allocations. The overlay itself adds drawing work, so hide it for a baseline performance measurement.
The Vulkan backend emits `VK_EXT_debug_utils` labels for `ShadowMap`, `ForwardAndUI`, `Readback`, and, when presenting, `Presentation`. A graphics capture tool that supports this extension can identify those command-buffer regions. Labels remain available without the Khronos validation layer when the extension is exposed; unsupported systems continue rendering and report labels unavailable. A submitted-label count confirms calls were emitted, not that an external capture tool was tested.
This module is disabled by default and is linked only to the graphical Editor and its dedicated test when enabled. Player and exported games do not link ImGui. No overlay control changes authoring documents, gameplay state or export settings.
Run `ctest --test-dir build/linux-debug -R '^editor_debug_overlay$' --output-on-failure` in an enabled build to check actual ImGui geometry/font rendering, F12 toggling, pointer isolation and restoration of the underlying frame. The regular renderer pixel test also verifies GPU labels and clipped UI triangles.
+11 -3
View File
@@ -103,9 +103,17 @@ check **Jobs** and **Console**. A successful build and schema export refresh the
Inspector. A failed build retains the previous metadata and reports the failure.
A missing schema or unsupported component version appears as read-only raw fields
with **Copy raw fields**. The Editor preserves that data. Restore the matching module
or provide a migration and rebuild before expecting normal field editing or Play.
See [Build, Play, and export](export.md) for the C++ iteration loop.
with **Copy raw fields**. Restore a missing module to make its schema available.
For an older component, declare [data migration rules](../scripting/api.md#editor-data-migrations),
choose **Build C++**, then **Migrate to v…** in the Inspector. This is one undoable
authoring edit; save explicitly afterward. Inherited components show **Open source
to migrate** instead. Top-level local additions migrate in their owning instance.
Missing rules or conversion errors preserve the data and appear in Console.
Opening or recovering a scene never migrates it automatically. Future versions
stay opaque and cannot be downgraded. Review instance overrides separately when
changing the source field's units or meaning. See [Build, Play, and export](export.md)
for the C++ iteration loop.
## Project settings
+21 -8
View File
@@ -1,12 +1,13 @@
# Build from source
!!! note "Implementation checkpoint"
Linux Editor, Player and export integration are tested. Final Windows graphics/export
acceptance is tracked separately in the implementation report.
!!! note "Verified build profiles"
The Editor, Player and both sample exports passed the recorded Linux and Windows
profiles. Linux rendering used an RTX 2080 Ti; Windows CI used SwiftShader. This
does not certify every graphics driver or display configuration.
## Linux prerequisites
The selected toolchain is C++20, CMake 3.25 or later, Ninja, and Clang.
The selected toolchain is C++20, CMake 3.25 or later, Ninja, Clang, and Python 3.12+.
Graphical builds need Vulkan 1.3 headers/loader and a compatible driver.
SDL3, FreeType and HarfBuzz are built from pinned source archives.
@@ -64,8 +65,10 @@ disconnecting. Prefetching source archives alone is not a complete offline SDK.
## Windows prerequisites
Use an x64 Visual Studio Developer shell with the Windows SDK, MSVC runtime libraries,
LLVM `clang-cl`, Ninja, CMake, and the Vulkan SDK available. Then use the
`windows-debug` or `windows-release` presets.
LLVM `clang-cl`, Ninja, CMake 3.25+, Python 3.12+, and the Vulkan SDK available.
The commands below use the Python `py` launcher; substitute `python` if your
installation exposes that command instead. Use the `windows-debug` or
`windows-release` presets.
Enable **Win32 long paths** on the Windows development machine before starting the
build shell. Faset's executable manifest declares long-path support, and its direct
@@ -89,8 +92,18 @@ cmake --build --preset windows-debug --parallel
ctest --preset windows-debug
```
Windows acceptance is tracked separately from Linux; a successful Linux build does
not verify a Windows build.
Windows acceptance uses a fresh native CI checkout, full Editor build, launcher
Create/Open tests, native window/MCP tests and standalone Release exports. Its
software Vulkan driver is a CI fixture; install your normal hardware Vulkan driver
on a development desktop. See the
[acceptance dossier](https://github.com/emil28092005/Faset_Engine/blob/main/docs/validation/mvp-acceptance.md)
for observed results.
Native Wayland programmatic restore was skipped when the tested compositor declined
the operation; XWayland passed. If this affects your desktop, run the Editor with
`SDL_VIDEODRIVER=x11`. Real system IME composition and movement between physical
monitors with different scale factors remain compatibility checks, beyond the
passing deterministic text/DPI tests.
The repository's `docs/TOOLCHAINS.md` records the exact compiler, SDK and GPU profiles
used in observed validation, separately from the minimum tool requirements above.
+7 -5
View File
@@ -4,11 +4,13 @@ Faset is a C++ engine for desktop 2D and 3D games on Linux and Windows.
This manual focuses on writing gameplay: small working examples, the functions they use,
and how those functions interact with scenes, physics, and the editor.
!!! warning "Development status"
MVP implementation is in progress. A planned feature is not a working feature.
Individual guides state their prerequisites and validation status. The current
Editor, gameplay tutorials, two playable sample games and Linux export can be built
and tested. Final Windows graphics/export acceptance is still in progress.
!!! note "MVP scope"
The C++ MVP includes the Editor, compiled gameplay tutorials, two playable games,
Blender import and standalone Linux/Windows export. Acceptance used a physical
Linux GPU and software Vulkan on Windows. See the
[acceptance dossier](https://github.com/emil28092005/Faset_Engine/blob/main/docs/validation/mvp-acceptance.md)
for exact source revisions and coverage limits. Lua and advanced graphics remain
later milestones.
Start with [how C++ gameplay works](scripting/index.md), then read
[frame and physics updates](scripting/lifecycle.md). See
+76
View File
@@ -76,6 +76,82 @@ automatic migrations or authoring-style custom-field constraint validation.
`snapshot()` returns a value snapshot for rendering; `snapshotJson()` provides its JSON representation. `diagnostics()` returns a read-only vector of runtime messages. These are native C++ APIs for the Player and tests, **not MCP endpoints**.
## Editor data migrations
Gameplay schema versions describe saved component data. When a field changes units
or meaning, increase the type's `version` and include declarative `migrations` in
that type returned by `gameplay::schema()`. For example, this type declaration
converts version 1 speed values from centimetres per second to metres per second:
```json
{
"id": "game.mover",
"version": 2,
"fields": {
"speed": {"type": "number", "default": 2.5, "min": 0, "max": 10},
"enabled": {"type": "boolean", "default": true}
},
"migrations": [
{
"from_version": 1,
"fields": {
"speed": {"scale": 0.01},
"enabled": {"default": true}
}
}
]
}
```
Build C++ to export and validate the declaration. The Editor reads these rules
from the same schema manifest as field metadata; it does not load the gameplay
library or execute a migration callback. Invalid metadata or migration rules fail
the build before replacing the last published binary/schema generation.
Each step upgrades `from_version` to the next integer version. A component at
version 1 needs both steps 1 and 2 to reach version 3. Empty `fields` explicitly
allows a version step with no value conversion. Supported field operations are:
- `default`: insert a value only when the field is absent.
- `scale`: multiply an existing numeric field by a finite number.
- `require_manual`: when `true`, stop if the field is present, so incompatible data
requires an explicit manual conversion.
Rules preserve component/entity IDs and fields they do not mention. The final
values must satisfy the current field schema. Unsupported operations, repeated
steps, invalid version ranges, invalid rules and non-finite scale values are
rejected when the schema is loaded. Arithmetic overflow during conversion also
fails without applying the transaction.
Opening or recovering a scene preserves older component versions as opaque data;
it never migrates them automatically. Missing rules therefore do not prevent
opening the scene. Choose **Migrate to v…** in the Inspector after rebuilding the
schema, or use the same editor's `faset_scene_edit` operation with the current
document revision:
```json
{
"document": "document-id",
"revision": 3,
"operations": [
{"op": "component.migrate", "entity": "entity-id", "component": "component-id"}
]
}
```
The whole batch is one Undo step and is written to the recovery journal. Save
explicitly to update the scene file. Missing steps, a manual-conversion requirement
or a validation error leave the document and revision unchanged. Future versions
cannot be downgraded. The same operation accepts an `instance` ID when `entity`
and `component` identify a top-level instance-local addition using its original
stored IDs, rather than resolved preview IDs.
Inherited components belong to their source document: open that source to migrate
them. Sparse field overrides in other instances are not automatically converted;
review and explicitly update overrides when changing a field's units or meaning.
The Player performs no migration and still requires the scene version to match its
linked gameplay schema before Play or exported-game validation.
## Inspect a running Player locally
The Player has local development controls in addition to gameplay input: **P** toggles
+2 -2
View File
@@ -51,8 +51,8 @@ Schema declarations are tested for stable map-key/FieldId matching and defaults.
## Play complete small projects
`examples/projects/collect-2d` and `examples/projects/collect-3d` contain complete projects with a manifest, scene, and a separate `Scripts` module. Open either folder with the Editor's `--project` option, then use Play. Each project's README also provides a direct Player build command.
`examples/projects/collect-2d` and `examples/projects/collect-3d` contain complete projects with a manifest, scene, and a separate `Scripts` module. Open either folder with the Editor's `--project` option. The 2D project is ready to Play. For the 3D project, first import `Assets/exit-arch/manifest.json` from the Assets panel, then Play. Repeat this import after clearing its cache or cloning the project. Each project's README also provides the exact import and direct Player build commands.
Move the blue block with A/D in 2D or WASD in 3D, jump with Space, collect three gold cubes, and reach the green exit after its red gate opens. E resets the round. One pickup is on a raised platform. A visible gold marker and the Player log confirm completion; these initial examples use geometric progress displays instead of a text HUD.
The `playable_2d` and `playable_3d` CTests drive the actual modules through input, including the jump, objective, reset, and fresh session. The 3D module also explicitly registers a separately packaged `example.beacon` component from its local `Scripts/Extensions/Beacon.hpp`. Both projects use built-in geometry and need no imported assets to start.
The `playable_2d` and `playable_3d` CTests drive the actual modules through input, including the jump, objective, reset, and fresh session. The 3D module also explicitly registers a separately packaged `example.beacon` component from its local `Scripts/Extensions/Beacon.hpp`. Gameplay geometry uses built-in primitives; the 3D scene additionally references the imported Blender arch. Its GLB bundle is included, so Blender is only needed to edit or recreate that source asset.
+1 -1
View File
@@ -44,7 +44,7 @@ Read the callback from top to bottom:
The `[](...) { ... }` expression is a C++ lambda: a function stored in `Behavior::update`. Empty brackets mean it captures no local variables. `registerBehavior` takes ownership of the callback object. Register before calling `load`; registration while entities exist or a callback is running is rejected.
The `schema()` function describes editable configuration. It does not create a runtime object. `tutorial.move_x` is the stable `TypeId`; `speed` is a stable `FieldId` within that type. Keep these IDs when changing a display label. Changing a field's meaning or units needs an explicit data migration, not just a new label.
The `schema()` function describes editable configuration. It does not create a runtime object. `tutorial.move_x` is the stable `TypeId`; `speed` is a stable `FieldId` within that type. Keep these IDs when changing a display label. Changing a field's meaning or units needs an [explicit data migration](api.md#editor-data-migrations), not just a new label.
## Attach the behavior
@@ -20,7 +20,7 @@
6. **Более богатые материалы.** Из Substrate взять ограниченные классы сложности и обработку по тайлам; из glints — фильтруемое распределение микробликов. Небольшой фиксированный clear-coat/двухслойный набор часто разумнее произвольного графа BSDF.
7. **Большие наборы текстур.** Virtual Texturing полезна, когда именно residency и объём текстур стали проблемой. Стриминг целых mip-уровней проще; переход на страницы имеет смысл по измеренному рабочему набору.
## Принятый план Faset — реализация ещё не начата
## Принятый план Faset — продвинутая графика после MVP
Решения обновлены 18 сентября 2026. Подробные критерии продукта — в [PLAN.md](../../PLAN.md), технические границы — в [архитектуре](../ARCHITECTURE.md). Результаты чтения UE ниже сохраняются; они не означают, что эти системы уже есть в Faset, входят в MVP или обязательно будут воспроизведены целиком.
@@ -2,7 +2,7 @@
**Принято 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**, поддержка которой конкретным клиентом не предполагается автоматически.
Это исследовательский проект контрактов от 17–18.09.2026. Реализованный MVP-профиль, точные имена tools и проверки описаны в [Manual MCP](../manual/editor/mcp.md), [Manual ресурсов](../manual/editor/assets.md) и [журнале реализации](../IMPLEMENTATION.md). Более широкие API-примеры ниже не являются обещанием текущей реализации. Актуальные решения — в [архитектуре](../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 как самостоятельное ядро редактора
@@ -76,7 +76,7 @@ Manifest хранится вместе с authoring-данными в Git: `asse
## 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).
Профиль импорта должен последовательно покрыть triangle meshes, UV/нормали/tangents и metal-rough PBR; skinning, clips и morph targets вводятся на соответствующих этапах [плана](../../PLAN.md). Текущее проверенное статическое подмножество перечислено в [Manual ресурсов](../manual/editor/assets.md); skinning, clips и morph targets остаются за пределами MVP. Процедурные 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).
+1 -1
View File
@@ -2,7 +2,7 @@
**Принято 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 здесь изучен по документации, без утверждения о просмотре закрытой реализации.
Исходные требования: Linux и Windows desktop, игры 2D и 3D, продвинутая графика, полноценное ручное редактирование и управление через MCP. Рекомендации ниже фиксируют проект на этапе исследования; текущая реализация pipeline и её проверки описаны в [Manual экспорта](../manual/editor/export.md) и [журнале реализации](../IMPLEMENTATION.md). Исторические варианты помечены отдельно. Проверка источников: 17 сентября 2026. Локальный Godot: `9c776068d6ed23acd0c78bfe534272d1d2a3a619`, версия дерева 4.8-dev; официальные текущие страницы Unity показывают 6.6. Исследованы тела desktop exporter, сбор зависимостей, remap импортированных ресурсов, кэш преобразования, запись PCK и конфигурация editor/player. Unity здесь изучен по документации, без утверждения о просмотре закрытой реализации.
## Что именно должна делать кнопка Build
+3 -3
View File
@@ -2,13 +2,13 @@
**Название проекта — Faset Engine. Синхронизация решений: 18.09.2026.** Каноническая архитектура — [ARCHITECTURE.md](../ARCHITECTURE.md), последовательность до и после MVP — [PLAN.md](../../PLAN.md). Этот отчёт сохраняет исследовательские аргументы и обновлённые принятые решения. Прежние альтернативы отмечены **superseded**; факты о чужих движках не означают, что соответствующие возможности реализованы в Faset.
Дата: 17 сентября 2026. Цель — новый движок для **Linux и Windows, десктопных 2D/3D-игр**, одинаково удобный при ручной работе и через **MCP**, с интеграцией как минимум с **Blender**. Это исследовательская основа реализации. Код нового движка ещё не создавался; ниже принятые архитектурные правила отделены от экспериментальных графических направлений и деталей будущих прототипов.
Дата: 17 сентября 2026. Цель — новый движок для **Linux и Windows, десктопных 2D/3D-игр**, одинаково удобный при ручной работе и через **MCP**, с интеграцией как минимум с **Blender**. Это исследовательская основа реализации. На момент первоначального исследования код нового движка ещё не создавался; текущая реализация описана в [журнале](../IMPLEMENTATION.md), ниже принятые архитектурные правила отделены от экспериментальных графических направлений и деталей будущих прототипов.
## Уточнения после обсуждения исследования
**Подтверждённый выбор пользователя: 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 зависимостей пока не выбраны, интеграция и собственные физические тесты не выполнялись.
**Подтверждённый выбор пользователя: 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/). На момент исследования версии ещё не выбирались и собственные физические тесты не выполнялись. Теперь версии закреплены в [dependency lock](../../dependencies.lock.json), а результаты интеграции записаны в [журнале реализации](../IMPLEMENTATION.md).
Принятый принцип интеграции: отдельные PhysicsWorld2D/PhysicsWorld3D, компоненты RigidBody2D/3D и Collider2D/3D, единые правила регистрации, идентичности, Inspector и диагностики. Миры 2D и 3D не сталкиваются автоматически друг с другом. Физика получает команды перед фиксированным шагом; после завершения шага движок переносит результаты и события в runtime. Для динамического тела физика владеет рассчитанным transform; runtime teleport и управление кинематическим телом — отдельные операции. События связываются с entity через проверяемые handles. Названия API предварительны; адаптеры ещё не реализованы.
Принятый принцип интеграции: отдельные PhysicsWorld2D/PhysicsWorld3D, компоненты RigidBody2D/3D и Collider2D/3D, единые правила регистрации, идентичности, Inspector и диагностики. Миры 2D и 3D не сталкиваются автоматически друг с другом. Физика получает команды перед фиксированным шагом; после завершения шага движок переносит результаты и события в runtime. Для динамического тела физика владеет рассчитанным transform; runtime teleport и управление кинематическим телом — отдельные операции. События связываются с entity через проверяемые handles. Названия API в этом исследовательском описании предварительны; фактический API адаптеров и gameplay описан в [Manual](../manual/scripting/api.md).
**Решение от 18.09.2026: сначала ядро и игровая логика на C++, затем добавляем Lua отдельным модулем. Его будущая реализация принята, использование конкретной игрой необязательно.** Игра на C++ не должна зависеть от Lua; точный Lua API и срок этапа уточняются по [плану](../../PLAN.md). Python и C# сохраняются в сравнении как ранее рассмотренные альтернативы. Каноническое решение записано в [архитектуре](../ARCHITECTURE.md).
@@ -129,4 +129,4 @@ D3D12 подтверждает переносимость самой идеи,
Главное дополнение к 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-поведение пока не проверялись.
Прочитаны тела 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-поведение. Позднейшие проверки базового Faset renderer публикуются отдельно в [docs/validation](../validation/README.md); они не подтверждают GPU-driven техники этого раздела.
@@ -139,4 +139,4 @@ Collider recipe включает source output, 2D projection/3D local frame, е
Новые относительно предыдущего обзора выводы: 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 пока не измерялись.
**Охват источников.** Локальные тела 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 не измерялись. Позднейшие проверки собственного MVP-профиля Faset с Blender опубликованы в [результатах проверок](../validation/README.md).
+1 -1
View File
@@ -1,6 +1,6 @@
# 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.
Дата исследования: 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). Ниже сохранён проект BuildService на этапе исследования. Реализованный MVP-профиль и проверки — в [Manual экспорта](../manual/editor/export.md) и [журнале реализации](../IMPLEMENTATION.md).
Исходники: Unreal Engine 5.8.2, commit `16d75d84714512edfb744e1fd0a59e9c74d57873`; [общий манифест](source-manifest.json). Ни UE, ни будущий Faset в этом исследовании не собирались. Ниже — чтение тел функций и официальной документации, затем собственный проект контрактов и проверок.
+1 -1
View File
@@ -2,7 +2,7 @@
Обновлено 18.09.2026. Исходники и официальная документация исследовались прежде всего 17.09.2026; затем результаты согласованы с принятой архитектурой.
**Актуальные решения — в [ARCHITECTURE.md](../ARCHITECTURE.md), порядок реализации — в [PLAN.md](../../PLAN.md).** Движок ещё не реализован. Исследования дают обоснования и проверочные сценарии, а не доказанные показатели будущего Faset.
**Актуальные решения — в [ARCHITECTURE.md](../ARCHITECTURE.md), порядок реализации — в [PLAN.md](../../PLAN.md).** Реализация MVP и проверки идут отдельно: [журнал реализации](../IMPLEMENTATION.md), [результаты проверок](../validation/README.md), [пользовательский Manual](../manual/index.md). Исследования дают обоснования и проверочные сценарии; их статический анализ не является измерением Faset.
## Принято по результатам обсуждения
+3 -3
View File
@@ -1,8 +1,8 @@
# Карта исследования Faset Engine
Локальное React-приложение с интерактивной картой, просмотром Markdown и переходами к строкам исходников. `research.tsx` хранится здесь же; сборка не зависит от Codex, абсолютного пути к рабочему столу или файлов в домашнем каталоге. Карта показывает принятые решения и будущие критерии этапов; движок ещё не реализован.
Локальное React-приложение с интерактивной картой, просмотром Markdown и переходами к строкам исходников. `research.tsx` хранится здесь же; сборка не зависит от Codex, абсолютного пути к рабочему столу или файлов в домашнем каталоге. Карта показывает принятые решения и критерии этапов. Текущее состояние C++-реализации и проверок записано в [журнале реализации](../../IMPLEMENTATION.md).
Главные документы: [о проекте](../../../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.
Главные документы: [о проекте](../../../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:
@@ -33,6 +33,6 @@ workspace/
Без внешнего 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 принимает пути относительно корня проекта, проверяет реальные пути и разрешает только текстовые файлы из перечисленных областей размером до 8 MiB. Скрытые каталоги и `node_modules` исключены. Сервер предназначен для локального чтения исследования, без изменения файлов. Его следует запускать локально; это не публичный file server и не MCP редактора.
Проверка разрешения ссылок и границ файлового API: `npm test`.
+4 -4
View File
@@ -71,7 +71,7 @@ function Architecture() {
[560, 225, 245, "Player · отдельный процесс", "EnTT · C++ · physics · без MCP"],
[560, 330, 245, "Vulkan 1.3 renderer", "MVP: direct draws + CPU frustum"],
] as const;
return <div style={{ overflowX: "auto" }}><svg viewBox="0 0 825 425" role="img" aria-label="Принятая архитектура Faset; реализация запланирована" style={{ width: "100%", minWidth: 650 }}>
return <div style={{ overflowX: "auto" }}><svg viewBox="0 0 825 425" role="img" aria-label="Принятая архитектура и границы подсистем Faset" style={{ width: "100%", minWidth: 650 }}>
<defs><marker id="engine-arrow" markerWidth="7" markerHeight="7" refX="6" refY="3.5" orient="auto"><path d="M0 0 L7 3.5 L0 7" fill={t.text.tertiary}/></marker></defs>
<g fill="none" stroke={t.stroke.primary} strokeWidth="1.5" markerEnd="url(#engine-arrow)">
<path d="M140 80 V118 M410 80 V118 M685 80 V118 M560 155 H537 M140 185 V223 M265 258 H288 M535 258 H558 M685 185 V223 M685 290 V328"/>
@@ -101,7 +101,7 @@ export default function EngineResearch() {
<Stack gap={5}><Text size="small" tone="tertiary">FASET ENGINE · РЕШЕНИЯ 18.09.2026</Text><H1>Архитектура, MVP и развитие графики</H1><Text tone="secondary">Linux + Windows · 2D / 3D · C++ Editor · Blender</Text></Stack>
<Row gap={8} wrap><Button onClick={() => open("README.md")}>О проекте</Button><Button onClick={() => open("PLAN.md")}>План до / после MVP</Button><Button onClick={() => open("docs/ARCHITECTURE.md")}>Архитектура</Button></Row>
</Row>
<Card><CardBody><Text><strong>Архитектура принята; реализация движка запланирована.</strong> Эта карта — работающий просмотрщик исследования, не редактор Faset. GPU-бенчмарки и сборки движка ещё не выполнены. MCP предусмотрен строго в Editor; Player не содержит MCP и не предоставляет ему runtime worlds или игровые сессии.</Text></CardBody></Card>
<Card><CardBody><Text><strong>C++ MVP принят в зафиксированных профилях Linux и Windows.</strong> Эта карта — работающий просмотрщик исследования, не редактор Faset. Результаты и ограничения опубликованы в docs/validation/mvp-acceptance.md: Windows проверен через software Vulkan, физические Windows GPU и системный IME требуют отдельного покрытия. Исследовательские графические направления не считаются готовыми функциями. MCP предусмотрен строго в Editor; Player не содержит MCP и не предоставляет ему runtime worlds или игровые сессии.</Text></CardBody></Card>
<Row gap={7} wrap>{["Решение", "Графика", "ECS и данные", "MCP и Blender", "Стек и экспорт", "Прототипы", "Источники"].map(name => <span key={name}><Pill active={tab === name} onClick={() => setTab(name)}>{name}</Pill></span>)}</Row>
<Divider/>
@@ -192,7 +192,7 @@ export default function EngineResearch() {
]}/>
<Text>UI, CLI и MCP редактора запускают один BuildRequest. Компиляторы работают в дочерних процессах; staging публикуется после проверки. Планируемый контракт отмены сохраняет последнюю успешную сборку; cache корректность и повторяемость требуют тестов, а не только hash-ключей.</Text>
<Text>Windows и Linux сборки проверяются в своих окружениях. Player не включает Editor/MCP, shader compiler или editor plugins. Драйвер всё равно создаёт GPU pipelines; shader hot reload требует собственной проверки bindings/layout и безопасной замены ресурсов.</Text>
<Text size="small" tone="secondary">Версии toolchain/dependencies и проверенная GPU/driver matrix ещё не закреплены. C#/.NET и Rust + wgpu остаются историей сравнения вариантов, не параллельными реализациями Faset.</Text>
<Text size="small" tone="secondary">Версии закреплены в dependencies.lock.json и документации toolchain; фактически проверенные GPU/driver окружения указаны в docs/validation. C#/.NET и Rust + wgpu остаются историей сравнения вариантов, не параллельными реализациями Faset.</Text>
<Row gap={8} wrap><Button onClick={() => open(root + reports[7].path)}>Исследование сборки и вариантов стека</Button><Button onClick={() => open(root + reports[11].path)}>Build service, cache и delivery</Button></Row>
</Stack>}
@@ -215,7 +215,7 @@ export default function EngineResearch() {
<Text tone="secondary">Карта и отчёты открываются без локальных копий движков. Optional source viewer использует sibling checkout; исходники чужих движков не входят в Faset. Dev/alpha snapshots — материалы исследования, не выбранные production-зависимости.</Text>
<Row gap={8} wrap><Button onClick={() => open("README.md")}>README</Button><Button onClick={() => open("PLAN.md")}>PLAN</Button><Button onClick={() => open("docs/ARCHITECTURE.md")}>Архитектура</Button><Button onClick={() => open(root + "source-manifest.json")}>Полные source SHA и охват</Button></Row>
<Stack gap={7}>{reports.map(r => <div key={r.path}><Row><Button onClick={() => open(root + r.path)}>{r.name}</Button></Row></div>)}</Stack>
<Text size="small" tone="tertiary">Прочитаны выбранные тела функций и официальные документы. Движки не собирались; UX-сравнение и GPU-бенчмарки не выполнены. Принятые решения не означают готовую реализацию.</Text>
<Text size="small" tone="tertiary">В исследованиях прочитаны выбранные тела функций и официальные документы. Сравнительные UX-тесты и GPU-бенчмарки изучаемых движков не проводились. Проверки реализованных функций Faset описаны отдельно в досье приёмки.</Text>
</Stack>}
</Stack>;
}
+5 -2
View File
@@ -2,13 +2,16 @@
These files preserve bounded checks and their inputs. Each record states its source revision or working-tree limitation; a passing record does not certify later commits or every supported platform.
- [MVP acceptance dossier](mvp-acceptance.md): criterion-by-criterion closure, tested revisions and remaining compatibility coverage.
- [Windows software Vulkan](windows-software-vulkan-2026-09-18/README.md): fresh native build, 35 tests, launcher/window/MCP workflows and both relocated Release games on SwiftShader.
- [Checkpoint 5 Linux acceptance](checkpoint5-linux-2026-09-18/README.md): clean offline source build, first Editor launch, exact-candidate standalone games and live Blender checks.
- [Final Linux source checks](final-linux-2026-09-18/README.md): `4cb8255` integrated test results and both Release games after the asset-relocation correction, including package manifests and standalone captures.
- [Linux offline build](offline-linux-2026-09-18.json): a fresh build from committed `d834cfad67cd81d8c4998b90c16791361ca8c0f8`, prefetched dependency inputs, and a network namespace without external connectivity. CPU tests ran; graphics execution is separate.
- [Linux Release export and rendering](linux-release-2026-09-18/README.md): two relocated playable games, Unicode paths, imported Blender geometry, raw frame profiles and exact package hashes. This measurement used an evolving working tree around `5ac4db4`, not a clean checkout of the current commit.
- [Live Blender reimport](blender-live-linux-2026-09-18.json): actual Blender 4.5.3 exports and two live Editor instances, preserving authoring/gameplay/physics while refreshing geometry.
- [Editor recovery](editor-recovery-linux-2026-09-18.json): killed Editor, explicit journal recovery, failed save/import/build and failed Player startup with last-good preservation.
- [Native window boundaries](native-window-linux-2026-09-18.json): XWayland lifecycle, isolated Unicode clipboard, SDL text-input rectangles and the explicit native Wayland restore limitation.
- [Linux native window and input boundaries](native-window-linux-2026-09-18.json): XWayland resize/minimize/restore and SDL text-input-area checks; isolated Xvfb Unicode clipboard roundtrip with restoration. Native Wayland restore remains unconfirmed; real OS IME composition was not tested.
The Linux records do not establish Windows correctness, physical Windows GPU performance, native Wayland restore support, or editor responsiveness under large workloads. Windows CI artifacts record their own commit and software Vulkan results. A skipped compositor operation is not a completed acceptance check.
Performance budgets below are proposals for P1 regression tracking, not completed release requirements. Repeat measurements on a controlled reference host and add representative content before treating them as engine-wide targets.
The thresholds in the [Release baseline](linux-release-2026-09-18/README.md) are the initial P1 tracking budgets for its exact scenes and reference host. They are not engine-wide guarantees or enforced MVP performance gates. Repeat measurements on a controlled reference host and add representative content before expanding their scope.
@@ -0,0 +1,38 @@
# Checkpoint 5 Linux acceptance
These functional checks used the engine source at
`0f34b036313c011861dbfd5828ed45c4f7940b05`. They supplement the earlier Release
performance baseline; export runs overlapped compilation and are not benchmarks.
- [Clean offline build](offline.json): committed source extracted with `git archive`,
empty build directory, external networking disabled, prefetched dependencies and
toolchain. Full default native build and 20 CPU tests passed in 205.77 seconds.
- [First Editor launch](first-run.json): that freshly built default Editor created
a new 3D project following the Manual's command, copied the correct gameplay
templates and rendered three native X11/Xvfb frames before closing successfully.
- [Both standalone games](playable-exports.json): the real Editor produced Release
packages, verified package hashes, moved them outside the SDK into Unicode paths,
hid source projects, then validated and rendered 120 frames per game without
scene/asset overrides. RTX 2080 Ti, Khronos validation active, zero validation
errors. The packages contained 17 files for 2D and 22 for 3D.
- [Blender round-trip](blender.json): unmodified Blender 4.5.3 and the actual live
Editor probe updated two instances while preserving IDs, transforms, tint, physics,
opaque gameplay and document revision. Removed outputs kept the last good visible
generation. The verification harness was updated to distinguish source freshness
from active-generation preservation; its separate hash is included in the report.
- [Editor recovery](recovery.json): seven failure/recovery scenarios passed against
the same source revision and recorded Editor hash: interrupted process, explicit
journal recovery, failed save/import/build/Player startup, and subsequent recovery.
The integrated build with optional diagnostics also passed 34 tests with one
explicit native Wayland restore skip; the sanitizer configuration passed 18/18.
These results describe Linux. Windows has separate CI and export evidence.
Native OS IME composition and moving between physical monitors were not exercised;
widget composition, DPI and native SDL input-boundary tests have narrower scope.
The following image is an actual Editor capture from the Blender probe, after
rename/geometry reimport. It is not the generated UI reference. The test scene has
two placed instances and no selected object.
![Native Faset Editor after Blender reimport](editor-blender.png)
@@ -0,0 +1,70 @@
{
"source_commit": "0f34b036313c011861dbfd5828ed45c4f7940b05",
"recorded_at_utc": "2026-09-18T02:45:37.886125+00:00",
"platform": "Linux-7.0.0-31-generic-x86_64-with-glibc2.43",
"working_tree_diff": [
" M tools/verify_blender_roundtrip.py"
],
"candidate_binaries_note": "Existing integrated Debug binaries from checkpoint5 build; SHA256 captured after this run. Engine source unchanged; verification harness has explicit freshness assertions patched after candidate commit.",
"command": "python3 tools/verify_blender_roundtrip.py --blender .cache/blender/blender --editor build/linux-debug/faset_editor --ui-probe build/linux-debug/faset_blender_editor_probe --output .cache/blender-final-candidate/verified",
"hashes": {
"build/linux-debug/faset_editor": "04baf2ec09f3f097657553d5b862ba5c90749d2cdbf4a239c6b92a45dee56ee9",
"build/linux-debug/faset_blender_editor_probe": "141433f89ae20202a16597525bfdcd59586bf17ab72ada29104ca324857b5842",
".cache/blender/blender": "5e15447670804c855fef6033ac9b8fd82696b762095becf0df79b1c2f8034397",
"tools/verify_blender_roundtrip.py": "ce3bb9d02500a6fe8becef23a7c966449a13d7d9d00d92f2177c0c431ac7f930",
"tests/blender/generate_fixture.py": "6d7951c0d35b8e4e71b81644af31d66af4e395511d929452a178f81ba30e487b",
"tests/editor_blender_reimport_probe.cpp": "0474276d04e8c18c1d2d8d55ff2cff8cfabba815bb3428af51394e22649e3923",
"tools/blender_addon/__init__.py": "e1a44aed99e09a6ad208966eb2e3808110d87311f5721774575a14471ed852e2",
"tools/blender_addon/bundle.py": "f0fd9707b853662be00d08440ab9cc65928cec5a8e053561834bd64fd43d1488",
"dependencies.lock.json": "0583b24061dfc6ba3db76be89ceeaf408cac18d237773833204074af5d8a81d5",
".cache/blender-final-candidate/verified/fixture/initial/manifest.json": "830818ee07a003aad63ed1345b0179852b9de1ab9a322142bd23e50fb501ba7d",
".cache/blender-final-candidate/verified/fixture/removed/manifest.json": "45e07ec2c4ae98c6a9b74a1b60089a2da1ff2ade1b9822d048b33de640d80665",
".cache/blender-final-candidate/verified/fixture/renamed/manifest.json": "dfbe56099ec026eaedd4a25218f3e3270ac075a933d77d37f8b784963792250c",
".cache/blender-final-candidate/verified/fixture/initial/payload/81fdfbd230989c9f4985963cddf63edcfb89c13a0ef4a9265c853006830be488.glb": "81fdfbd230989c9f4985963cddf63edcfb89c13a0ef4a9265c853006830be488",
".cache/blender-final-candidate/verified/fixture/removed/payload/3a9c9671bc1804454227d14f38705b9deacfd95492bf0aea1d2754ba198f128b.glb": "3a9c9671bc1804454227d14f38705b9deacfd95492bf0aea1d2754ba198f128b",
".cache/blender-final-candidate/verified/fixture/renamed/payload/568624163d90cf20f71629ca1cb11f128898260bc521ae66bfc1a3ae6bf4b3e6.glb": "568624163d90cf20f71629ca1cb11f128898260bc521ae66bfc1a3ae6bf4b3e6",
".cache/blender-final-candidate/verified/live-editor/initial.ppm": "363273f070f212580088d52b273baf94cebd62410bb9cf775dee14792f18bd99",
".cache/blender-final-candidate/verified/live-editor/renamed.ppm": "973a99aa43d4e32345bba023ec34dd443db9dc747590c128f996f5f258fd9038",
".cache/blender-final-candidate/verified/live-editor/initial.png": "57313599964308512499eb94d35d8711ff230ec6e8f4542802a147741efc77d4",
".cache/blender-final-candidate/verified/live-editor/renamed.png": "7df8f30b892f58af00ebfe48db3ddfb58150c1faffa19fa2fc792e3e65e8cfe1"
},
"results": {
"blender": "Blender 4.5.3 LTS",
"asset_id": "54c039e4-c19f-4365-a9f8-a38416ec6e3f",
"initial_generation": "499f254ce440ecb86118f2f6803f21013e763d35cfdba1c4356be7a457eec718",
"renamed_generation": "797e63652213752dc7c92029c4e9743517c5f40547b97eac748e60b17f142926",
"stable_output_ids": true,
"removed_output_conflict": true,
"failed_import_keeps_generation": true,
"authoring_preserved": true,
"changed_source_marked_stale": true,
"invalid_bundle_marked_stale": true,
"live_editor": {
"conflict_keeps_visible_generation": true,
"instances": 2,
"live_geometry_updated": true,
"opaque_gameplay_preserved": true,
"physics_fields_preserved": true,
"placement_and_tint_preserved": true,
"status": "passed",
"validation_enabled": true,
"validation_errors": 0
}
},
"limits": [
"Linux offscreen Vulkan Editor snapshot with validation; not a physical desktop input test.",
"No native OS IME or physical mixed-monitor DPI exercised.",
"Static geometry/material fixture; no skeleton/animation or arbitrary Blender shader fidelity claim.",
"Windows export and native platform acceptance are separate."
],
"original_harness_failure": "Whole faset_assets response equality was invalidated by new freshness current->stale; immutable active manifests remain equal. Revised harness separately verifies stale bundle.changed/bundle.unavailable and last-good generation.",
"hardware_context": {
"host_gpu": "NVIDIA GeForce RTX 2080 Ti",
"nvidia_driver": "595.84"
},
"screenshot": {
"path": ".cache/blender-final-candidate/verified/live-editor/renamed.png",
"stage": "After successful stable-ID rename and geometry reimport, before removed-output conflict.",
"conversion": "PNG encoded from renderer-produced renamed.ppm without resizing, cropping, recoloring or content changes."
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

@@ -0,0 +1,20 @@
{
"source_commit": "0f34b036313c011861dbfd5828ed45c4f7940b05",
"recorded_at_utc": "2026-09-18T02:50:10.534440+00:00",
"platform": "Linux-7.0.0-31-generic-x86_64-with-glibc2.43",
"source": "Fresh offline git archive checkout, default options, no previous build directory",
"command": "xvfb-run -a <clean-sdk>/build/linux-debug/faset_editor --project <new-directory>/MyGame --new MyGame --dimension 3 --frames 3 --capture <evidence>/editor.ppm",
"editor_sha256": "71182902617d617749d383ff87d29929013751b9597dcdbcb5f25ce323ebb64b",
"checks": {
"exit_code": 0,
"project_name": "MyGame",
"dimension": 3,
"gameplay_files_match_templates": true,
"rendered_capture": [
1440,
900
],
"capture_sha256": "e15c1e50106a4a188c817e9acb6382f13f094f163768a937ae39ddee3637971b"
},
"scope": "Manual build prerequisites already installed; engine built from clean source. Three native X11/Xvfb Editor frames; GPU execution does not prove physical OS IME or mixed-monitor behavior."
}
@@ -0,0 +1,12 @@
{
"format": "faset.offline-build",
"version": 1,
"revision": "0f34b036313c011861dbfd5828ed45c4f7940b05",
"recorded_at_utc": "2026-09-18T02:47:01.536454+00:00",
"seconds": 205.76940188600202,
"returncode": 0,
"network": "fresh user/network namespace; external connection must fail",
"source": "git archive of committed HEAD; no existing build directory",
"inputs": "prefetched archives + Slang; system compiler, SDK and development libraries",
"tests": "full native build, CPU CTests; GPU/window execution verified separately"
}
@@ -0,0 +1,452 @@
{
"format": "faset.playable-export-verification",
"version": 1,
"started_utc": "2026-09-18T02:43:37.045153+00:00",
"platform": "linux",
"engine": "/home/emil/Desktop/Faset_Engine",
"editor": "/home/emil/Desktop/Faset_Engine/build/linux-debug/faset_editor",
"standalone_root": "/tmp/faset-playable-exports-25rumkwv",
"frames_per_game": 120,
"status": "passed",
"projects": [
{
"name": "collect-2d",
"dimension": 2,
"source_inputs": [
{
"path": ".gitignore",
"sha256": "53aa4d7124d4c8d93b8d7c4cb470b6c82b844e019a660d6243ffeadc50602de1"
},
{
"path": "README.md",
"sha256": "d6c35206b10ee28efd5cc02da77a3ce9b6e1f8ce085793150bbf2f23bd925d65"
},
{
"path": "Scenes/main.scene.json",
"sha256": "be48a6d21385e717b6c8ebf9a3b4322eebbeff6bb067428e089ab5214be79854"
},
{
"path": "Scripts/Gameplay.cpp",
"sha256": "6b581b24da613e1915819de0a0f0ea820dd3333efa62c4b479a9ea7ffdbc6bcc"
},
{
"path": "Scripts/Gameplay.hpp",
"sha256": "4121587182ed4f74901b1c0757c53daadbf309af1d6bb5dd87fef142130ccbbe"
},
{
"path": "project.faset.json",
"sha256": "36e2acd32210f5489372a2331346a3294ead2395f0713e3e5fd2da4e83263689"
}
],
"generation": "2bf52972-98ee-4379-8b7c-eb6f0dfca6a5",
"configuration": "Release",
"standalone_directory": "/tmp/faset-playable-exports-25rumkwv/Faset Café 世界/collect-2d",
"executable": "faset_player",
"package_file_count": 17,
"asset_generations": {},
"device": "NVIDIA GeForce RTX 2080 Ti",
"validation_enabled": true,
"validation_errors": 0,
"completed_frames": 120,
"summary_ms": {
"gpu": {
"max": 0.592896,
"min": 0.558112,
"p50": 0.55904,
"p95": 0.577952,
"samples": 120
},
"render_call": {
"max": 4.604147,
"min": 1.474391,
"p50": 1.713513,
"p95": 2.008409,
"samples": 120
},
"renderer_cpu": {
"max": 4.60056,
"min": 1.472197,
"p50": 1.710667,
"p95": 2.00375,
"samples": 120
},
"renderer_readback_cpu": {
"max": 2.634671,
"min": 0.395376,
"p50": 0.449368,
"p95": 0.596425,
"samples": 120
},
"simulation": {
"max": 0.394364,
"min": 0.133442,
"p50": 0.146897,
"p95": 0.212962,
"samples": 120
},
"snapshot": {
"max": 0.208042,
"min": 0.125827,
"p50": 0.134384,
"p95": 0.182785,
"samples": 120
},
"wall": {
"max": 5.039538,
"min": 1.763687,
"p50": 2.01393,
"p95": 2.375862,
"samples": 120
}
},
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 7,
"sha256": "f9d74067d87c1ebf30c6db372a26d93b7285791c8cb0307a24af5adb23bf201a"
},
"source_project_paths_unavailable": true,
"status": "passed"
},
{
"name": "collect-3d",
"dimension": 3,
"source_inputs": [
{
"path": ".gitignore",
"sha256": "53aa4d7124d4c8d93b8d7c4cb470b6c82b844e019a660d6243ffeadc50602de1"
},
{
"path": "Assets/exit-arch/create.py",
"sha256": "9c7291124fb1498a3f992ef74afc39cd98f9cc3e2ff4687c633bece85d580905"
},
{
"path": "Assets/exit-arch/manifest.json",
"sha256": "7a13347adc89761fb02ebeab3c58ba87f319cd62f53ec87a3bb61bcd066ffa55"
},
{
"path": "Assets/exit-arch/manifest.json.faset-import.json",
"sha256": "ef4eef28a18a0cc40a40df75db34c97faa5d783b891265f862633f3aec514afc"
},
{
"path": "Assets/exit-arch/payload/ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3.glb",
"sha256": "ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3"
},
{
"path": "Assets/exit-arch/source.blend",
"sha256": "ff2676bec97ab778e531e87a185ad716049ea3b05b4a4f0027a6345737735035"
},
{
"path": "README.md",
"sha256": "2df5ca4df47ab5d3d05d41508201296d4ad5ec876245f19f061767ade6a30e8f"
},
{
"path": "Scenes/main.scene.json",
"sha256": "61b65baf79e52f07ac386a7f98acf985a1b09e88770cdde37ef461dc94b9dcd7"
},
{
"path": "Scripts/Extensions/Beacon.hpp",
"sha256": "c60d56161eead16f541835053ad32ac3333383d83015709c060b7d45e0af5a97"
},
{
"path": "Scripts/Gameplay.cpp",
"sha256": "5d419786a6861d7f14ed4eca54634694d8cd43be5dbbd60a2c6790db665c3a6a"
},
{
"path": "Scripts/Gameplay.hpp",
"sha256": "4121587182ed4f74901b1c0757c53daadbf309af1d6bb5dd87fef142130ccbbe"
},
{
"path": "project.faset.json",
"sha256": "43d273b1054ec9069369073f54cc1fc7efb3d6b699dcd342c552a2dc2ef852ef"
}
],
"import": {
"asset_id": "5832763b-3ed0-44d6-9088-0b524f196a91",
"cache_hit": false,
"diagnostics": [],
"generation": "4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2",
"manifest": {
"asset_id": "5832763b-3ed0-44d6-9088-0b524f196a91",
"files": [
{
"path": "meshes/mesh-fc069a07a87e0f831fd92efdbc4efa1f-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "meshes/mesh-0961e2a3f92977836a3aa80cab09277e-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "meshes/mesh-93b5b3e52cb6bb120fefb80e2d8e222b-0.fmesh",
"sha256": "3875c16ea628d4126373ebfe07816621fbad3961596a2fcd112f1eec0e378071",
"size": 928
}
],
"generation": "4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2",
"importer": "faset-gltf-1/cgltf-1.15",
"input_key": {
"bundle_sha256": "7a13347adc89761fb02ebeab3c58ba87f319cd62f53ec87a3bb61bcd066ffa55",
"dependencies": {},
"importer": "faset-gltf-1/cgltf-1.15",
"settings": {},
"source": "ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3",
"target_profile": "desktop-static-pbr-v1",
"toolchain": {
"cgltf": "360db1a95480fe102ae9c69b27c5d101167ff5ba",
"stb": "2c980bb59875b0d32144a71867fbdebb2f77cd20"
}
},
"kind": "scene",
"materials": [
{
"alpha_cutoff": 0.5,
"alpha_mode": "OPAQUE",
"base_color": [
0.2199999988079071,
0.28999999165534973,
0.3400000035762787,
1.0
],
"base_color_texture": -1,
"double_sided": true,
"emissive": [
0.0,
0.0,
0.0
],
"emissive_texture": -1,
"format": "faset.material",
"id": "material-bb63b094febbf90bb7049c9bcf7587bc",
"metallic": 0.07999999821186066,
"metallic_roughness_texture": -1,
"name": "Weathered stone",
"normal_texture": -1,
"occlusion_texture": -1,
"roughness": 0.800000011920929,
"unlit": false,
"version": 1
}
],
"meshes": [
{
"id": "mesh-fc069a07a87e0f831fd92efdbc4efa1f",
"name": "Cube.001",
"primitives": [
{
"material": 0,
"path": "meshes/mesh-fc069a07a87e0f831fd92efdbc4efa1f-0.fmesh"
}
]
},
{
"id": "mesh-0961e2a3f92977836a3aa80cab09277e",
"name": "Cube.002",
"primitives": [
{
"material": 0,
"path": "meshes/mesh-0961e2a3f92977836a3aa80cab09277e-0.fmesh"
}
]
},
{
"id": "mesh-93b5b3e52cb6bb120fefb80e2d8e222b",
"name": "Cube.003",
"primitives": [
{
"material": 0,
"path": "meshes/mesh-93b5b3e52cb6bb120fefb80e2d8e222b-0.fmesh"
}
]
}
],
"nodes": [
{
"id": "node-cec6e8cea80cf5a98ba3263a0156bee4",
"local_transform": [
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
1.4500000476837158,
1.2999999523162842,
1.0
],
"mesh": 0,
"name": "Left post",
"parent_id": "",
"stable_source_id": true
},
{
"id": "node-5f8a90a6c4d27020d20bd4fa81d1d958",
"local_transform": [
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
1.4500000476837158,
-1.2999999523162842,
1.0
],
"mesh": 1,
"name": "Right post",
"parent_id": "",
"stable_source_id": true
},
{
"id": "node-482463e486a8d1fd741340c653cd128f",
"local_transform": [
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
3.0,
0.0,
1.0
],
"mesh": 2,
"name": "Lintel",
"parent_id": "",
"stable_source_id": true
}
],
"outputs": [
"node-cec6e8cea80cf5a98ba3263a0156bee4",
"node-5f8a90a6c4d27020d20bd4fa81d1d958",
"node-482463e486a8d1fd741340c653cd128f",
"mesh-fc069a07a87e0f831fd92efdbc4efa1f",
"mesh-0961e2a3f92977836a3aa80cab09277e",
"mesh-93b5b3e52cb6bb120fefb80e2d8e222b",
"material-bb63b094febbf90bb7049c9bcf7587bc"
],
"payload_source": "/home/emil/Desktop/Faset_Engine/.cache/playable-final-candidate/Faset Café 世界/collect-3d/Assets/exit-arch/payload/ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3.glb",
"schema_version": 1,
"settings": {},
"source": "/home/emil/Desktop/Faset_Engine/.cache/playable-final-candidate/Faset Café 世界/collect-3d/Assets/exit-arch/manifest.json",
"source_sha256": "ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3",
"textures": []
},
"previous_generation": "",
"removed_output_ids": []
},
"generation": "b0a40126-7d6c-414f-843a-23a677ad6f8f",
"configuration": "Release",
"standalone_directory": "/tmp/faset-playable-exports-25rumkwv/Faset Café 世界/collect-3d",
"executable": "faset_player",
"package_file_count": 22,
"asset_generations": {
"5832763b-3ed0-44d6-9088-0b524f196a91": "4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2"
},
"device": "NVIDIA GeForce RTX 2080 Ti",
"validation_enabled": true,
"validation_errors": 0,
"completed_frames": 120,
"summary_ms": {
"gpu": {
"max": 0.615584,
"min": 0.578944,
"p50": 0.58096,
"p95": 0.597312,
"samples": 120
},
"render_call": {
"max": 4.88184,
"min": 1.554402,
"p50": 1.842957,
"p95": 2.153072,
"samples": 120
},
"renderer_cpu": {
"max": 4.876761,
"min": 1.552489,
"p50": 1.838648,
"p95": 2.149254,
"samples": 120
},
"renderer_readback_cpu": {
"max": 2.787459,
"min": 0.398262,
"p50": 0.483592,
"p95": 0.577219,
"samples": 120
},
"simulation": {
"max": 0.38139,
"min": 0.16457,
"p50": 0.192834,
"p95": 0.278605,
"samples": 120
},
"snapshot": {
"max": 0.816561,
"min": 0.155042,
"p50": 0.172175,
"p95": 0.243289,
"samples": 120
},
"wall": {
"max": 6.073458,
"min": 1.902519,
"p50": 2.248081,
"p95": 2.61298,
"samples": 120
}
},
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 42,
"sha256": "914060401de93d08b33bf09a1e210859e491a0060c331e5e2044f517356f4980"
},
"source_project_paths_unavailable": true,
"status": "passed"
}
],
"finished_utc": "2026-09-18T02:47:27.268249+00:00",
"engine_commit": "0f34b036313c011861dbfd5828ed45c4f7940b05",
"editor_sha256": "04baf2ec09f3f097657553d5b862ba5c90749d2cdbf4a239c6b92a45dee56ee9",
"working_tree_clean_at_start": true,
"engine_commit_at_completion": "0f34b036313c011861dbfd5828ed45c4f7940b05",
"timings_are_benchmark_evidence": false,
"timing_note": "Functional export/relocation/validation/profile completeness acceptance; timings are not benchmark evidence because a separate build runs concurrently.",
"visual_inspection": {
"status": "passed",
"notes": [
"2D capture shows player, platforms, three collectibles, barrier and exit marker.",
"3D capture shows player, room, collectibles, platform, gate and imported exit structure without missing geometry."
],
"png_previews": [
"evidence/collect-2d.png",
"evidence/collect-3d.png"
]
}
}
@@ -0,0 +1,36 @@
{
"format": "faset.editor-recovery-verification",
"version": 1,
"status": "passed",
"checks": [
"acknowledged unsaved edit survives killed Editor and explicit recovery",
"failed save preserves live authoring and last saved file",
"failed Player process is reported without changing authoring data",
"Undo repairs the scene, then Play and Stop leave authoring unchanged",
"failed C++ build keeps prior schema and reports stale status",
"fixed C++ rebuild restores current schema status",
"failed import preserves authoring"
],
"recorded_at_utc": "2026-09-18T02:50:13.733355+00:00",
"platform": "Linux-7.0.0-31-generic-x86_64-with-glibc2.43",
"editor_sha256": "04baf2ec09f3f097657553d5b862ba5c90749d2cdbf4a239c6b92a45dee56ee9",
"logs": [
"build completed",
"Gameplay schema loaded",
"Play started in a separate Player process",
"Player failed: unsupported builtin component version: faset.transform\n",
"Player exited with code 1",
"build completed",
"Gameplay schema loaded",
"Play started in a separate Player process",
"New round: collect the three gold cubes, then reach the green exit. E resets.\n{\"device\":\"NVIDIA GeForce RTX 2080 Ti\",\"dimension\":2,\"frames\":411,\"ticks\":104,\"validation_errors\":0}\n",
"Play stopped; authoring scene unchanged",
"build failed: Process exited with code 1; see job log; previous gameplay metadata remains available and is marked stale",
"build completed",
"Gameplay schema loaded",
"Asset import failed: Cannot read: /home/emil/Desktop/Faset_Engine/.cache/editor-recovery-final-candidate/project/Assets/missing.glb\n"
],
"engine_commit": "0f34b036313c011861dbfd5828ed45c4f7940b05",
"engine_commit_at_completion": "0f34b036313c011861dbfd5828ed45c4f7940b05",
"source_note": "Completed before pending moved-input freshness changes to asset_data.cpp/asset_pipeline.cpp; final-candidate committed engine source."
}
@@ -0,0 +1,61 @@
# Final Linux source checks — 2026-09-18
Engine source: **`4cb82556de31268d2bde73948dd1ff1b6c02f162`**. Both Release games
were exported from a clean working tree at this commit, which remained unchanged
through completion. Editor SHA-256:
`05e2b40c6c05282af71a03990c98c89c673c48a3cf1da0e17a59f911a947049a`.
The [export report](playable-exports.json) records both checked-in projects, their
source hashes, package generations and relocation. The 2D package contains 17 files;
the 3D package contains 22, including the imported Blender arch. Each package passed
CPU validation and 120 offscreen frames after being moved into a `Faset Café 世界`
directory outside the SDK, with the disposable source-project paths hidden. No scene
or asset-directory override was supplied. Package hashes are preserved in the
[2D manifest](collect-2d-manifest.json) and [3D manifest](collect-3d-manifest.json).
Both ran on the physical NVIDIA RTX 2080 Ti with the Khronos validation layer active
and zero reported errors. The [2D run](collect-2d-run.json) and
[3D run](collect-3d-run.json) retain the observed device and counters. Capture hashes
match the preceding checkpoint 5 candidate exactly. These runs are functional
acceptance evidence; the timings retained in the export report are not a controlled
performance benchmark. The separately scoped
[240-frame baseline](../linux-release-2026-09-18/README.md) remains historical evidence.
A further [SDK-unavailable check](sdk-unavailable.json) ran these same packages in a
private user/mount namespace with an empty read-only filesystem covering the entire
Faset checkout, including Editor, builds, cached tools and project copies. Each used
an unrelated empty working directory and fresh home/config/cache directories. Both
passed validation and another 120 frames, with active Vulkan validation, zero errors
and identical capture hashes. File/process tracing found no SDK path accesses or
helper executables; dynamic dependencies resolved to system libraries, with no ELF
RPATH/RUNPATH. The outer SDK was never renamed or hidden and remained unchanged.
The report preserves namespace/mount details and hashes of the retained local traces
and harness. This establishes SDK independence on this Linux host, not universal
distribution or driver compatibility; Windows has separate package checks.
[Test results](tests.json) record 34 passed, one explicit native Wayland restore skip
and zero failed in the 35-test integrated suite with optional diagnostics enabled.
The compositor declined programmatic restore; the skip does not establish that
operation's correctness. XWayland has its own passing
[window record](../native-window-linux-2026-09-18.json). Full ASan/UBSan passed 18/18
on `0f34b03`; the two affected asset/cook tests passed again after the source-location
correction at `4cb8255`.
The preceding [checkpoint 5 record](../checkpoint5-linux-2026-09-18/README.md)
retains the clean offline build, first project launch, all seven recovery scenarios
and live Blender reimport with their exact source/harness provenance. These checks
were not silently relabeled as runs of a later commit. Physical OS IME composition,
mixed-monitor transitions and other GPU/driver families need separate coverage.
Reproduce the export check from the repository root:
```sh
python3 tools/verify_playable_exports.py --editor build/linux-debug/faset_editor \
--output .cache/playable-verification
```
Actual standalone captures:
![2D standalone game](collect-2d.png)
![3D standalone game with the imported Blender arch](collect-3d.png)
@@ -0,0 +1,143 @@
{
"asset_generations": {},
"build_fingerprint": "0adaba82211a3869537b75939521e2877834b23fb06c1d8d0fdd37446c2704ee",
"configuration": "Release",
"executable": "faset_player",
"files": [
{
"path": "scene.fscene",
"sha256": "0d7b221abc1cae5e9f1d04357c684ac6993f5878d634b4b950eec66dd8e55b2a",
"size": 6214
},
{
"path": "faset_player",
"sha256": "f7902f414ab64573a697a8f36b59e203370779990f63c63fbd90c88a7eb1397b",
"size": 5829824
},
{
"path": "shaders/vertexMain.spv",
"sha256": "1ad2631c35d654f48166321ae43d4165043e1b919b14f1dc61caf97b1ada0898",
"size": 1224
},
{
"path": "shaders/fragmentMain.spv",
"sha256": "f5c4f289917ecab5d00d543c053aac1d10e6cca311fd172daf06d315160a3505",
"size": 7436
},
{
"path": "shaders/shadowMain.spv",
"sha256": "7643b4d688492b5ee923b9606f0b0e70343ca05fa67673838206119a6f86d8d8",
"size": 784
},
{
"path": "shaders/vertexMain.reflection.json",
"sha256": "c584f3b957ebf194a292451362a98590f85ff3cfbfd8f917b1c1265039381777",
"size": 2639
},
{
"path": "shaders/fragmentMain.reflection.json",
"sha256": "72ebf8d32452830650416dc4717d89a4324e97ece7028dfb97c66803c8dce71f",
"size": 2649
},
{
"path": "shaders/shadowMain.reflection.json",
"sha256": "e3fd0889cd2f569d410f5d7558a4091d9f57f247a1dcf1788462f67525340819",
"size": 2645
},
{
"path": "Notices/sdl3/LICENSE.txt",
"sha256": "97f35b302b361680ec1e891e95d2d52097bb95abff361434916d99dc1305f127",
"size": 884
},
{
"path": "Notices/entt/LICENSE",
"sha256": "0785027ce472d7c61f05fba664a1d3ba6639c1593ced351ad9e4bed868765d99",
"size": 1097
},
{
"path": "Notices/box2d/LICENSE",
"sha256": "68a3e676d7e94093b102d5cba0d4e04af812040d6f230c3db67a6664574e43d2",
"size": 1067
},
{
"path": "Notices/box3d/LICENSE",
"sha256": "da5e31a26bf3cfd5ba5c96d6823e480128c81e76c107ef9d3ee5d94789184b90",
"size": 1067
},
{
"path": "Notices/json/LICENSE.MIT",
"sha256": "46a65cffd1ea955132d95a8dd921640714a8d6b537d2e4e482d31145ae95b603",
"size": 1076
},
{
"path": "Notices/stb/LICENSE",
"sha256": "bebfe904b14301657e4e5d655c811d51fd31b97c455b9cc2d8600d6bac6cff63",
"size": 2510
},
{
"path": "Notices/dependencies.json",
"sha256": "a54639ad2d9ad13c2e5f1ec90d6775f5dfc493656789b78c7e44debe2a378fdc",
"size": 2206
},
{
"path": "Notices/Faset-NOTICE.txt",
"sha256": "30c2e7e80f5153b31ab57675e07246aef9165965d64e36022b426a26449ece3f",
"size": 204
},
{
"path": "README.txt",
"sha256": "4dc2f0271c69fe39017ff96aa2dfa00b625bec1401d924dc396193ce71c09f3e",
"size": 225
}
],
"format": "faset.export",
"generation": "a0bc193b-3d79-4372-98c2-8875621d4ab3",
"platform": "linux",
"prerequisites": [
"Linux x86_64",
"Vulkan 1.3 driver",
"Compatible glibc and libstdc++ runtime"
],
"renderer_profile": {
"api": "Vulkan 1.3",
"materials": [
"base-color factor and texture",
"metallic and roughness factors"
],
"required_features": [
"dynamicRendering",
"synchronization2"
],
"shadow_map": {
"resolution": 1024,
"world_extent": 40
},
"texture_sampling": "linear clamp, one mip level",
"unsupported_material_features": [
"normal maps",
"metallic-roughness maps",
"emissive and occlusion maps",
"alpha mode selection",
"unlit mode",
"per-material face culling"
]
},
"scene_hash": "fdfa1c49cf78a1cf52cd41b201c35bb0b5f852c90b7626b2a7f17da3e2e3e24c",
"simulation": {
"fixed_delta": 0.016666666666666666,
"gravity": [
0,
-9.81,
0
],
"max_catch_up_ticks": 4,
"physics_substeps": 4
},
"units": {
"angle": "radian",
"coordinates": "right-handed Y-up",
"distance": "metre"
},
"validation_log": "{\"dimension\":2,\"validated\":true}\n",
"version": 1
}
@@ -0,0 +1,18 @@
{
"arguments": [
"/tmp/faset-playable-exports-rnbvn4tk/Faset Café 世界/collect-2d/faset_player",
"--headless",
"--frames",
"120",
"--capture",
"/tmp/faset-playable-exports-rnbvn4tk/Faset Café 世界/collect-2d/verification.ppm",
"--profile",
"/tmp/faset-playable-exports-rnbvn4tk/Faset Café 世界/collect-2d/profile.json"
],
"cwd": "/tmp/faset-playable-exports-rnbvn4tk/empty-working-directory",
"seconds": 0.5882121719987481,
"exit_code": 0,
"timed_out": false,
"stdout": "New round: collect the three gold cubes, then reach the green exit. E resets.\n{\"device\":\"NVIDIA GeForce RTX 2080 Ti\",\"dimension\":2,\"frames\":120,\"ticks\":120,\"validation_errors\":0}\n",
"stderr": ""
}
@@ -0,0 +1,12 @@
{
"arguments": [
"/tmp/faset-playable-exports-rnbvn4tk/Faset Café 世界/collect-2d/faset_player",
"--validate"
],
"cwd": "/tmp/faset-playable-exports-rnbvn4tk/empty-working-directory",
"seconds": 0.004741746000945568,
"exit_code": 0,
"timed_out": false,
"stdout": "{\"dimension\":2,\"validated\":true}\n",
"stderr": ""
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -0,0 +1,170 @@
{
"asset_generations": {
"5832763b-3ed0-44d6-9088-0b524f196a91": "4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2"
},
"build_fingerprint": "36eac0c6901b352478ec474b922d0543fb0a4ccbf33bd5fa49cca887cf8e7a74",
"configuration": "Release",
"executable": "faset_player",
"files": [
{
"path": "scene.fscene",
"sha256": "21c364140669ec82c6886102667b255df5ccdf0ec77e0ca64060f84dd7b86632",
"size": 8639
},
{
"path": "faset_player",
"sha256": "5537942ff7cd999de044168420b730c772a6f21c3cf81ece389e9495c9eae974",
"size": 5834840
},
{
"path": "shaders/vertexMain.spv",
"sha256": "1ad2631c35d654f48166321ae43d4165043e1b919b14f1dc61caf97b1ada0898",
"size": 1224
},
{
"path": "shaders/fragmentMain.spv",
"sha256": "f5c4f289917ecab5d00d543c053aac1d10e6cca311fd172daf06d315160a3505",
"size": 7436
},
{
"path": "shaders/shadowMain.spv",
"sha256": "7643b4d688492b5ee923b9606f0b0e70343ca05fa67673838206119a6f86d8d8",
"size": 784
},
{
"path": "shaders/vertexMain.reflection.json",
"sha256": "c584f3b957ebf194a292451362a98590f85ff3cfbfd8f917b1c1265039381777",
"size": 2639
},
{
"path": "shaders/fragmentMain.reflection.json",
"sha256": "72ebf8d32452830650416dc4717d89a4324e97ece7028dfb97c66803c8dce71f",
"size": 2649
},
{
"path": "shaders/shadowMain.reflection.json",
"sha256": "e3fd0889cd2f569d410f5d7558a4091d9f57f247a1dcf1788462f67525340819",
"size": 2645
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2/meshes/mesh-fc069a07a87e0f831fd92efdbc4efa1f-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2/meshes/mesh-0961e2a3f92977836a3aa80cab09277e-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2/meshes/mesh-93b5b3e52cb6bb120fefb80e2d8e222b-0.fmesh",
"sha256": "3875c16ea628d4126373ebfe07816621fbad3961596a2fcd112f1eec0e378071",
"size": 928
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2/manifest.json",
"sha256": "ee3f68794c6dc44ab492b1ba4640aa74e4de79a5ec10aed8dcdd45a03afc508e",
"size": 4532
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/current.json",
"sha256": "b0732959808ce61ef5a3223fba2d9ad8c17a3ea7873d4a81c00aadee6c51032e",
"size": 134
},
{
"path": "Notices/sdl3/LICENSE.txt",
"sha256": "97f35b302b361680ec1e891e95d2d52097bb95abff361434916d99dc1305f127",
"size": 884
},
{
"path": "Notices/entt/LICENSE",
"sha256": "0785027ce472d7c61f05fba664a1d3ba6639c1593ced351ad9e4bed868765d99",
"size": 1097
},
{
"path": "Notices/box2d/LICENSE",
"sha256": "68a3e676d7e94093b102d5cba0d4e04af812040d6f230c3db67a6664574e43d2",
"size": 1067
},
{
"path": "Notices/box3d/LICENSE",
"sha256": "da5e31a26bf3cfd5ba5c96d6823e480128c81e76c107ef9d3ee5d94789184b90",
"size": 1067
},
{
"path": "Notices/json/LICENSE.MIT",
"sha256": "46a65cffd1ea955132d95a8dd921640714a8d6b537d2e4e482d31145ae95b603",
"size": 1076
},
{
"path": "Notices/stb/LICENSE",
"sha256": "bebfe904b14301657e4e5d655c811d51fd31b97c455b9cc2d8600d6bac6cff63",
"size": 2510
},
{
"path": "Notices/dependencies.json",
"sha256": "a54639ad2d9ad13c2e5f1ec90d6775f5dfc493656789b78c7e44debe2a378fdc",
"size": 2206
},
{
"path": "Notices/Faset-NOTICE.txt",
"sha256": "30c2e7e80f5153b31ab57675e07246aef9165965d64e36022b426a26449ece3f",
"size": 204
},
{
"path": "README.txt",
"sha256": "4dc2f0271c69fe39017ff96aa2dfa00b625bec1401d924dc396193ce71c09f3e",
"size": 225
}
],
"format": "faset.export",
"generation": "66aa0bad-fb8a-424a-a82a-a41e58b4a13d",
"platform": "linux",
"prerequisites": [
"Linux x86_64",
"Vulkan 1.3 driver",
"Compatible glibc and libstdc++ runtime"
],
"renderer_profile": {
"api": "Vulkan 1.3",
"materials": [
"base-color factor and texture",
"metallic and roughness factors"
],
"required_features": [
"dynamicRendering",
"synchronization2"
],
"shadow_map": {
"resolution": 1024,
"world_extent": 40
},
"texture_sampling": "linear clamp, one mip level",
"unsupported_material_features": [
"normal maps",
"metallic-roughness maps",
"emissive and occlusion maps",
"alpha mode selection",
"unlit mode",
"per-material face culling"
]
},
"scene_hash": "ee12794145c14ba5b599c5d9ae21648cccdfa91c47ee7742549f60d5fe7bc3e0",
"simulation": {
"fixed_delta": 0.016666666666666666,
"gravity": [
0,
-9.81,
0
],
"max_catch_up_ticks": 4,
"physics_substeps": 4
},
"units": {
"angle": "radian",
"coordinates": "right-handed Y-up",
"distance": "metre"
},
"validation_log": "warning: material material-bb63b094febbf90bb7049c9bcf7587bc has features beyond the initial base-color/PBR renderer\n{\"dimension\":3,\"validated\":true}\n",
"version": 1
}
@@ -0,0 +1,18 @@
{
"arguments": [
"/tmp/faset-playable-exports-rnbvn4tk/Faset Café 世界/collect-3d/faset_player",
"--headless",
"--frames",
"120",
"--capture",
"/tmp/faset-playable-exports-rnbvn4tk/Faset Café 世界/collect-3d/verification.ppm",
"--profile",
"/tmp/faset-playable-exports-rnbvn4tk/Faset Café 世界/collect-3d/profile.json"
],
"cwd": "/tmp/faset-playable-exports-rnbvn4tk/empty-working-directory",
"seconds": 0.5726850880018901,
"exit_code": 0,
"timed_out": false,
"stdout": "New round: collect the three gold cubes, then reach the green exit. E resets.\n{\"device\":\"NVIDIA GeForce RTX 2080 Ti\",\"dimension\":3,\"frames\":120,\"ticks\":120,\"validation_errors\":0}\n",
"stderr": "warning: material material-bb63b094febbf90bb7049c9bcf7587bc has features beyond the initial base-color/PBR renderer\n"
}
@@ -0,0 +1,12 @@
{
"arguments": [
"/tmp/faset-playable-exports-rnbvn4tk/Faset Café 世界/collect-3d/faset_player",
"--validate"
],
"cwd": "/tmp/faset-playable-exports-rnbvn4tk/empty-working-directory",
"seconds": 0.005225719000009121,
"exit_code": 0,
"timed_out": false,
"stdout": "{\"dimension\":3,\"validated\":true}\n",
"stderr": "warning: material material-bb63b094febbf90bb7049c9bcf7587bc has features beyond the initial base-color/PBR renderer\n"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,455 @@
{
"format": "faset.playable-export-verification",
"version": 1,
"started_utc": "2026-09-18T02:56:47.916086+00:00",
"platform": "linux",
"engine": "/home/emil/Desktop/Faset_Engine",
"editor": "/home/emil/Desktop/Faset_Engine/build/linux-debug/faset_editor",
"standalone_root": "/tmp/faset-playable-exports-rnbvn4tk",
"frames_per_game": 120,
"status": "passed",
"projects": [
{
"name": "collect-2d",
"dimension": 2,
"source_inputs": [
{
"path": ".gitignore",
"sha256": "53aa4d7124d4c8d93b8d7c4cb470b6c82b844e019a660d6243ffeadc50602de1"
},
{
"path": "README.md",
"sha256": "d6c35206b10ee28efd5cc02da77a3ce9b6e1f8ce085793150bbf2f23bd925d65"
},
{
"path": "Scenes/main.scene.json",
"sha256": "be48a6d21385e717b6c8ebf9a3b4322eebbeff6bb067428e089ab5214be79854"
},
{
"path": "Scripts/Gameplay.cpp",
"sha256": "6b581b24da613e1915819de0a0f0ea820dd3333efa62c4b479a9ea7ffdbc6bcc"
},
{
"path": "Scripts/Gameplay.hpp",
"sha256": "4121587182ed4f74901b1c0757c53daadbf309af1d6bb5dd87fef142130ccbbe"
},
{
"path": "project.faset.json",
"sha256": "36e2acd32210f5489372a2331346a3294ead2395f0713e3e5fd2da4e83263689"
}
],
"generation": "a0bc193b-3d79-4372-98c2-8875621d4ab3",
"configuration": "Release",
"standalone_directory": "/tmp/faset-playable-exports-rnbvn4tk/Faset Café 世界/collect-2d",
"executable": "faset_player",
"package_file_count": 17,
"asset_generations": {},
"device": "NVIDIA GeForce RTX 2080 Ti",
"validation_enabled": true,
"validation_errors": 0,
"completed_frames": 120,
"summary_ms": {
"gpu": {
"max": 0.580928,
"min": 0.558752,
"p50": 0.561056,
"p95": 0.577536,
"samples": 120
},
"render_call": {
"max": 4.353283,
"min": 1.355868,
"p50": 1.575242,
"p95": 1.779117,
"samples": 120
},
"renderer_cpu": {
"max": 4.349596,
"min": 1.354355,
"p50": 1.572978,
"p95": 1.777063,
"samples": 120
},
"renderer_readback_cpu": {
"max": 2.542697,
"min": 0.357785,
"p50": 0.393262,
"p95": 0.477721,
"samples": 120
},
"simulation": {
"max": 0.256133,
"min": 0.12707,
"p50": 0.136477,
"p95": 0.187975,
"samples": 120
},
"snapshot": {
"max": 0.263748,
"min": 0.120477,
"p50": 0.12702,
"p95": 0.16401,
"samples": 120
},
"wall": {
"max": 4.772504,
"min": 1.625006,
"p50": 1.861853,
"p95": 2.054506,
"samples": 120
}
},
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 7,
"sha256": "f9d74067d87c1ebf30c6db372a26d93b7285791c8cb0307a24af5adb23bf201a"
},
"source_project_paths_unavailable": true,
"status": "passed"
},
{
"name": "collect-3d",
"dimension": 3,
"source_inputs": [
{
"path": ".gitignore",
"sha256": "53aa4d7124d4c8d93b8d7c4cb470b6c82b844e019a660d6243ffeadc50602de1"
},
{
"path": "Assets/exit-arch/create.py",
"sha256": "9c7291124fb1498a3f992ef74afc39cd98f9cc3e2ff4687c633bece85d580905"
},
{
"path": "Assets/exit-arch/manifest.json",
"sha256": "7a13347adc89761fb02ebeab3c58ba87f319cd62f53ec87a3bb61bcd066ffa55"
},
{
"path": "Assets/exit-arch/manifest.json.faset-import.json",
"sha256": "ef4eef28a18a0cc40a40df75db34c97faa5d783b891265f862633f3aec514afc"
},
{
"path": "Assets/exit-arch/payload/ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3.glb",
"sha256": "ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3"
},
{
"path": "Assets/exit-arch/source.blend",
"sha256": "ff2676bec97ab778e531e87a185ad716049ea3b05b4a4f0027a6345737735035"
},
{
"path": "README.md",
"sha256": "2df5ca4df47ab5d3d05d41508201296d4ad5ec876245f19f061767ade6a30e8f"
},
{
"path": "Scenes/main.scene.json",
"sha256": "61b65baf79e52f07ac386a7f98acf985a1b09e88770cdde37ef461dc94b9dcd7"
},
{
"path": "Scripts/Extensions/Beacon.hpp",
"sha256": "c60d56161eead16f541835053ad32ac3333383d83015709c060b7d45e0af5a97"
},
{
"path": "Scripts/Gameplay.cpp",
"sha256": "5d419786a6861d7f14ed4eca54634694d8cd43be5dbbd60a2c6790db665c3a6a"
},
{
"path": "Scripts/Gameplay.hpp",
"sha256": "4121587182ed4f74901b1c0757c53daadbf309af1d6bb5dd87fef142130ccbbe"
},
{
"path": "project.faset.json",
"sha256": "43d273b1054ec9069369073f54cc1fc7efb3d6b699dcd342c552a2dc2ef852ef"
}
],
"import": {
"asset_id": "5832763b-3ed0-44d6-9088-0b524f196a91",
"cache_hit": false,
"diagnostics": [],
"generation": "4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2",
"manifest": {
"asset_id": "5832763b-3ed0-44d6-9088-0b524f196a91",
"files": [
{
"path": "meshes/mesh-fc069a07a87e0f831fd92efdbc4efa1f-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "meshes/mesh-0961e2a3f92977836a3aa80cab09277e-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "meshes/mesh-93b5b3e52cb6bb120fefb80e2d8e222b-0.fmesh",
"sha256": "3875c16ea628d4126373ebfe07816621fbad3961596a2fcd112f1eec0e378071",
"size": 928
}
],
"generation": "4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2",
"importer": "faset-gltf-1/cgltf-1.15",
"input_key": {
"bundle_sha256": "7a13347adc89761fb02ebeab3c58ba87f319cd62f53ec87a3bb61bcd066ffa55",
"dependencies": {},
"importer": "faset-gltf-1/cgltf-1.15",
"settings": {},
"source": "ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3",
"target_profile": "desktop-static-pbr-v1",
"toolchain": {
"cgltf": "360db1a95480fe102ae9c69b27c5d101167ff5ba",
"stb": "2c980bb59875b0d32144a71867fbdebb2f77cd20"
}
},
"kind": "scene",
"materials": [
{
"alpha_cutoff": 0.5,
"alpha_mode": "OPAQUE",
"base_color": [
0.2199999988079071,
0.28999999165534973,
0.3400000035762787,
1.0
],
"base_color_texture": -1,
"double_sided": true,
"emissive": [
0.0,
0.0,
0.0
],
"emissive_texture": -1,
"format": "faset.material",
"id": "material-bb63b094febbf90bb7049c9bcf7587bc",
"metallic": 0.07999999821186066,
"metallic_roughness_texture": -1,
"name": "Weathered stone",
"normal_texture": -1,
"occlusion_texture": -1,
"roughness": 0.800000011920929,
"unlit": false,
"version": 1
}
],
"meshes": [
{
"id": "mesh-fc069a07a87e0f831fd92efdbc4efa1f",
"name": "Cube.001",
"primitives": [
{
"material": 0,
"path": "meshes/mesh-fc069a07a87e0f831fd92efdbc4efa1f-0.fmesh"
}
]
},
{
"id": "mesh-0961e2a3f92977836a3aa80cab09277e",
"name": "Cube.002",
"primitives": [
{
"material": 0,
"path": "meshes/mesh-0961e2a3f92977836a3aa80cab09277e-0.fmesh"
}
]
},
{
"id": "mesh-93b5b3e52cb6bb120fefb80e2d8e222b",
"name": "Cube.003",
"primitives": [
{
"material": 0,
"path": "meshes/mesh-93b5b3e52cb6bb120fefb80e2d8e222b-0.fmesh"
}
]
}
],
"nodes": [
{
"id": "node-cec6e8cea80cf5a98ba3263a0156bee4",
"local_transform": [
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
1.4500000476837158,
1.2999999523162842,
1.0
],
"mesh": 0,
"name": "Left post",
"parent_id": "",
"stable_source_id": true
},
{
"id": "node-5f8a90a6c4d27020d20bd4fa81d1d958",
"local_transform": [
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
1.4500000476837158,
-1.2999999523162842,
1.0
],
"mesh": 1,
"name": "Right post",
"parent_id": "",
"stable_source_id": true
},
{
"id": "node-482463e486a8d1fd741340c653cd128f",
"local_transform": [
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
3.0,
0.0,
1.0
],
"mesh": 2,
"name": "Lintel",
"parent_id": "",
"stable_source_id": true
}
],
"outputs": [
"node-cec6e8cea80cf5a98ba3263a0156bee4",
"node-5f8a90a6c4d27020d20bd4fa81d1d958",
"node-482463e486a8d1fd741340c653cd128f",
"mesh-fc069a07a87e0f831fd92efdbc4efa1f",
"mesh-0961e2a3f92977836a3aa80cab09277e",
"mesh-93b5b3e52cb6bb120fefb80e2d8e222b",
"material-bb63b094febbf90bb7049c9bcf7587bc"
],
"payload_source": "/home/emil/Desktop/Faset_Engine/.cache/playable-release-final/Faset Café 世界/collect-3d/Assets/exit-arch/payload/ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3.glb",
"schema_version": 1,
"settings": {},
"source": "/home/emil/Desktop/Faset_Engine/.cache/playable-release-final/Faset Café 世界/collect-3d/Assets/exit-arch/manifest.json",
"source_sha256": "ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3",
"textures": []
},
"previous_generation": "",
"removed_output_ids": []
},
"generation": "66aa0bad-fb8a-424a-a82a-a41e58b4a13d",
"configuration": "Release",
"standalone_directory": "/tmp/faset-playable-exports-rnbvn4tk/Faset Café 世界/collect-3d",
"executable": "faset_player",
"package_file_count": 22,
"asset_generations": {
"5832763b-3ed0-44d6-9088-0b524f196a91": "4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2"
},
"device": "NVIDIA GeForce RTX 2080 Ti",
"validation_enabled": true,
"validation_errors": 0,
"completed_frames": 120,
"summary_ms": {
"gpu": {
"max": 0.609888,
"min": 0.5784,
"p50": 0.580288,
"p95": 0.600416,
"samples": 120
},
"render_call": {
"max": 4.356229,
"min": 1.486144,
"p50": 1.670422,
"p95": 1.897079,
"samples": 120
},
"renderer_cpu": {
"max": 4.352772,
"min": 1.484541,
"p50": 1.668107,
"p95": 1.894835,
"samples": 120
},
"renderer_readback_cpu": {
"max": 2.37976,
"min": 0.36052,
"p50": 0.407479,
"p95": 0.460479,
"samples": 120
},
"simulation": {
"max": 0.282002,
"min": 0.164811,
"p50": 0.176523,
"p95": 0.235354,
"samples": 120
},
"snapshot": {
"max": 0.7686,
"min": 0.154722,
"p50": 0.166093,
"p95": 0.187914,
"samples": 120
},
"wall": {
"max": 5.493053,
"min": 1.819833,
"p50": 2.0303,
"p95": 2.317552,
"samples": 120
}
},
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 42,
"sha256": "914060401de93d08b33bf09a1e210859e491a0060c331e5e2044f517356f4980"
},
"source_project_paths_unavailable": true,
"status": "passed"
}
],
"finished_utc": "2026-09-18T03:00:21.028754+00:00",
"engine_commit": "4cb82556de31268d2bde73948dd1ff1b6c02f162",
"editor_sha256": "05e2b40c6c05282af71a03990c98c89c673c48a3cf1da0e17a59f911a947049a",
"engine_commit_at_completion": "4cb82556de31268d2bde73948dd1ff1b6c02f162",
"working_tree_changes_at_start": [],
"timings_are_benchmark_evidence": false,
"timing_note": "Final release source functional export/relocation/validation/profile completeness acceptance. Timings are retained as functional evidence, not a controlled performance benchmark.",
"capture_comparison_to_candidate": {
"collect-2d": {
"prior_engine_commit": "0f34b036313c011861dbfd5828ed45c4f7940b05",
"exact_capture_sha256_match": true
},
"collect-3d": {
"prior_engine_commit": "0f34b036313c011861dbfd5828ed45c4f7940b05",
"exact_capture_sha256_match": true
}
},
"png_previews": [
"evidence/collect-2d.png",
"evidence/collect-3d.png"
]
}
@@ -0,0 +1,115 @@
{
"format": "faset.sdk-unavailable-verification",
"version": 1,
"status": "passed",
"started_utc": "2026-09-18T03:10:58.685939+00:00",
"engine_commit": "4cb82556de31268d2bde73948dd1ff1b6c02f162",
"editor_sha256": "05e2b40c6c05282af71a03990c98c89c673c48a3cf1da0e17a59f911a947049a",
"sdk_paths_unavailable": {
"/home/emil/Desktop/Faset_Engine/CMakeLists.txt": true,
"/home/emil/Desktop/Faset_Engine/build/linux-debug/faset_editor": true,
"/home/emil/Desktop/Faset_Engine/build/linux-debug/shaders": true,
"/home/emil/Desktop/Faset_Engine/.cache/deps-src": true,
"/home/emil/Desktop/Faset_Engine/.cache/playable-release-final": true
},
"sdk_empty": true,
"mount_namespace": "mnt:[4026533743]",
"outer_mount_namespace": "mnt:[4026531832]",
"sdk_mount": [
"1374 111 0:82 / /home/emil/Desktop/Faset_Engine ro,nosuid,nodev,relatime - tmpfs faset-sdk-hidden ro,size=1024k,uid=1000,gid=1000,inode64"
],
"environment": {
"PATH": "/usr/bin:/bin",
"LANG": "C.UTF-8",
"HOME": "/tmp/faset-sdk-unavailable-fp5gnvp2/home",
"XDG_CACHE_HOME": "/tmp/faset-sdk-unavailable-fp5gnvp2/cache",
"XDG_CONFIG_HOME": "/tmp/faset-sdk-unavailable-fp5gnvp2/config",
"XDG_DATA_HOME": "/tmp/faset-sdk-unavailable-fp5gnvp2/data"
},
"timings_are_benchmark_evidence": false,
"projects": [
{
"name": "collect-2d",
"package": "/tmp/faset-playable-exports-rnbvn4tk/Faset Café 世界/collect-2d",
"player_sha256": "f7902f414ab64573a697a8f36b59e203370779990f63c63fbd90c88a7eb1397b",
"verified_package_files": 17,
"default_scene_and_assets": true,
"validation_passed": true,
"device": "NVIDIA GeForce RTX 2080 Ti",
"validation_enabled": true,
"validation_errors": 0,
"frames": 120,
"capture_sha256": "f9d74067d87c1ebf30c6db372a26d93b7285791c8cb0307a24af5adb23bf201a",
"capture_matches_final_release": true,
"status": "passed",
"trace_sdk_path_mentions": 0,
"traced_execve": [
"2514912 execve(\"/tmp/faset-playable-exports-rnbvn4tk/Faset Caf\\303\\251 \\344\\270\\226\\347\\225\\214/collect-2d/faset_player\", [\"/tmp/faset-playable-exports-rnbv\"..., \"--headless\", \"--frames\", \"120\", \"--capture\", \"/tmp/faset-sdk-unavailable-fp5gn\"..., \"--profile\", \"/tmp/faset-sdk-unavailable-fp5gn\"...], 0x7ffc06cf8bb0 /* 6 vars */) = 0"
],
"dependency_paths_outside_sdk": true,
"elf_has_rpath_or_runpath": false,
"dependencies": [
"\tlinux-vdso.so.1 (0x00007c4f9ae17000)",
"\tlibvulkan.so.1 => /usr/lib/x86_64-linux-gnu/libvulkan.so.1 (0x00007c4f9a7c5000)",
"\tlibm.so.6 => /usr/lib/x86_64-linux-gnu/libm.so.6 (0x00007c4f9a69f000)",
"\tlibstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007c4f9a200000)",
"\tlibgcc_s.so.1 => /usr/lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007c4f9a671000)",
"\tlibc.so.6 => /usr/lib/x86_64-linux-gnu/libc.so.6 (0x00007c4f99e00000)",
"\t/lib64/ld-linux-x86-64.so.2 (0x00007c4f9ae19000)"
]
},
{
"name": "collect-3d",
"package": "/tmp/faset-playable-exports-rnbvn4tk/Faset Café 世界/collect-3d",
"player_sha256": "5537942ff7cd999de044168420b730c772a6f21c3cf81ece389e9495c9eae974",
"verified_package_files": 22,
"default_scene_and_assets": true,
"validation_passed": true,
"device": "NVIDIA GeForce RTX 2080 Ti",
"validation_enabled": true,
"validation_errors": 0,
"frames": 120,
"capture_sha256": "914060401de93d08b33bf09a1e210859e491a0060c331e5e2044f517356f4980",
"capture_matches_final_release": true,
"status": "passed",
"trace_sdk_path_mentions": 0,
"traced_execve": [
"2514989 execve(\"/tmp/faset-playable-exports-rnbvn4tk/Faset Caf\\303\\251 \\344\\270\\226\\347\\225\\214/collect-3d/faset_player\", [\"/tmp/faset-playable-exports-rnbv\"..., \"--headless\", \"--frames\", \"120\", \"--capture\", \"/tmp/faset-sdk-unavailable-fp5gn\"..., \"--profile\", \"/tmp/faset-sdk-unavailable-fp5gn\"...], 0x7ffe821118e0 /* 6 vars */) = 0"
],
"dependency_paths_outside_sdk": true,
"elf_has_rpath_or_runpath": false,
"dependencies": [
"\tlinux-vdso.so.1 (0x000071bca491d000)",
"\tlibvulkan.so.1 => /usr/lib/x86_64-linux-gnu/libvulkan.so.1 (0x000071bca42ca000)",
"\tlibm.so.6 => /usr/lib/x86_64-linux-gnu/libm.so.6 (0x000071bca41a4000)",
"\tlibstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x000071bca3e00000)",
"\tlibgcc_s.so.1 => /usr/lib/x86_64-linux-gnu/libgcc_s.so.1 (0x000071bca4176000)",
"\tlibc.so.6 => /usr/lib/x86_64-linux-gnu/libc.so.6 (0x000071bca3a00000)",
"\t/lib64/ld-linux-x86-64.so.2 (0x000071bca491f000)"
]
}
],
"finished_utc": "2026-09-18T03:11:01.278406+00:00",
"outer_sdk_unchanged_after_namespace": true,
"outer_editor_sha256_after": "05e2b40c6c05282af71a03990c98c89c673c48a3cf1da0e17a59f911a947049a",
"retained_evidence_hashes": {
"collect-2d-dependencies.json": "c3882b5fec209be8e140b3a47bc4cd6e4c644baca4ce37308fb9420076fdd094",
"collect-2d-elf.json": "aa615e898a2ec9f7d68491d13f6bb5742a566ded87ce0d6c9c0ebe8fbe342de3",
"collect-2d-file-trace.log": "3fe847b2f8f8de66aeb04db69d02ba1b5bb311ffd64a057d405b527daa2e221d",
"collect-2d-profile.json": "5ab9c3e01d55e5f5a51d4e7d7d53bc1e39d3cac1d98544de246c8e1e05ef8a80",
"collect-2d-run.json": "b382c0582482d98c4c184949cb40d1c60939e18f772717b9c3d22f4ec94adba6",
"collect-2d-validate.json": "4fdf30c0fda6a275984a746b787f61c1ea50952558706da2232e97ca684a7bd1",
"collect-2d.ppm": "f9d74067d87c1ebf30c6db372a26d93b7285791c8cb0307a24af5adb23bf201a",
"collect-3d-dependencies.json": "0839c22cd4269a9461106b6f64c9d7255378639f571bec892ed189f0803cdfe6",
"collect-3d-elf.json": "c0f212cfec469cf493d517108f7859638b5aa4529216f42097170bfa0dbd49ba",
"collect-3d-file-trace.log": "3df4929e94c9b21e69f8a4dc3aed5d7182c60606af52840c9f945531cb54d6d0",
"collect-3d-profile.json": "e461b8c882f7ba487a0dc0704d20c96eafcbfe51785f50796bd70d611896ba27",
"collect-3d-run.json": "2a4703d596c1a3aa5450b205ee382cfed9578884bf63a965fde04e6e672c5ca8",
"collect-3d-validate.json": "15f128930142c0fc233eeb2b8e3b2a4d5d80e9e578f54f78538e6ce74c93d4cd",
"collect-3d.ppm": "914060401de93d08b33bf09a1e210859e491a0060c331e5e2044f517356f4980",
"inner.py": "a24ef1b36550a8f1944bda95b13c90eca5d2ddd81f49bfddbceba291c75d3857",
"outer-mount-namespace.txt": "b38992f03c046722594e95d6dcdbb4fb1b75a0f4f0156c26cb2059b1bafecdd4",
"prior.json": "d0d1ea7b6c4ffc8e6d1ac7dfe0e8dcfa1875780abd8eeb8377621db810c84883",
"run.sh": "dad0a2d008508eb2b2f2e0b4d7b86a6f38e3de1bdd6f0272d4d99bfcde201489"
}
}
@@ -0,0 +1,318 @@
{
"recorded_date": "2026-09-18",
"profiles": "Ubuntu 26.04 x86-64; Clang 21.1.8; NVIDIA RTX 2080 Ti 595.84; see docs/TOOLCHAINS.md",
"checks": [
{
"source_commit": "4cb82556de31268d2bde73948dd1ff1b6c02f162",
"description": "Full Debug with optional ImGui diagnostics",
"raw_log_sha256": "4f44fa2bc6b9cc560f89d0000b63cf87e200da237b432de87c5b64f299dc58a9",
"tests": [
{
"name": "authoring",
"status": "passed",
"seconds": 0.05
},
{
"name": "runtime_contracts",
"status": "passed",
"seconds": 0.12
},
{
"name": "assets_pipeline",
"status": "passed",
"seconds": 0.12
},
{
"name": "assets_blender_bundle",
"status": "passed",
"seconds": 0.07
},
{
"name": "render_graph",
"status": "passed",
"seconds": 0.01
},
{
"name": "render_offscreen",
"status": "passed",
"seconds": 0.35
},
{
"name": "render_sprite_alpha",
"status": "passed",
"seconds": 0.31
},
{
"name": "render_shader_reload",
"status": "passed",
"seconds": 2.47
},
{
"name": "render_window_lifecycle",
"status": "skipped",
"seconds": 4.02
},
{
"name": "player_scene_contracts",
"status": "passed",
"seconds": 0.01
},
{
"name": "player_shutdown_diagnostics",
"status": "passed",
"seconds": 0.37
},
{
"name": "editor_mcp",
"status": "passed",
"seconds": 0.02
},
{
"name": "process_and_cook",
"status": "passed",
"seconds": 0.29
},
{
"name": "build_schema_publication",
"status": "passed",
"seconds": 5.25
},
{
"name": "editor_plugins",
"status": "passed",
"seconds": 0.02
},
{
"name": "editor_session_settings",
"status": "passed",
"seconds": 0.01
},
{
"name": "ui_widgets",
"status": "passed",
"seconds": 0.12
},
{
"name": "ui_render",
"status": "passed",
"seconds": 0.43
},
{
"name": "editor_ui_import_conflicts",
"status": "passed",
"seconds": 0.71
},
{
"name": "editor_ui_project_settings",
"status": "passed",
"seconds": 0.83
},
{
"name": "editor_ui_reload",
"status": "passed",
"seconds": 3.9
},
{
"name": "editor_ui_launcher",
"status": "passed",
"seconds": 0.6
},
{
"name": "editor_ui_templates",
"status": "passed",
"seconds": 2.01
},
{
"name": "editor_ui_gizmos",
"status": "passed",
"seconds": 0.78
},
{
"name": "editor_ui_authoring",
"status": "passed",
"seconds": 1.2
},
{
"name": "editor_mcp_stdio",
"status": "passed",
"seconds": 0.28
},
{
"name": "editor_gui_mcp",
"status": "passed",
"seconds": 4.39
},
{
"name": "core",
"status": "passed",
"seconds": 0.1
},
{
"name": "tutorial_moving",
"status": "passed",
"seconds": 0.02
},
{
"name": "tutorial_following",
"status": "passed",
"seconds": 0.01
},
{
"name": "tutorial_spawning",
"status": "passed",
"seconds": 0.02
},
{
"name": "tutorial_physics",
"status": "passed",
"seconds": 0.06
},
{
"name": "playable_2d",
"status": "passed",
"seconds": 0.4
},
{
"name": "playable_3d",
"status": "passed",
"seconds": 0.88
},
{
"name": "editor_debug_overlay",
"status": "passed",
"seconds": 0.35
}
],
"counts": {
"passed": 34,
"skipped": 1,
"failed": 0
}
},
{
"source_commit": "4cb82556de31268d2bde73948dd1ff1b6c02f162",
"description": "Targeted ASan/UBSan after source-location correction",
"raw_log_sha256": "955813d41b1d28cf53df0af7bfe42d3b05009a1e00cfa266d75b8955f08003e1",
"tests": [
{
"name": "assets_pipeline",
"status": "passed",
"seconds": 0.59
},
{
"name": "process_and_cook",
"status": "passed",
"seconds": 0.54
}
],
"counts": {
"passed": 2,
"skipped": 0,
"failed": 0
}
},
{
"source_commit": "0f34b036313c011861dbfd5828ed45c4f7940b05",
"description": "Complete ASan/UBSan suite before the bounded source-location correction",
"raw_log_sha256": "fc34eb7195105c1cc7755eafa20580cdc23022c4edff86fe2f0571cb6cac8ba8",
"tests": [
{
"name": "authoring",
"status": "passed",
"seconds": 0.18
},
{
"name": "runtime_contracts",
"status": "passed",
"seconds": 0.76
},
{
"name": "assets_pipeline",
"status": "passed",
"seconds": 0.44
},
{
"name": "assets_blender_bundle",
"status": "passed",
"seconds": 0.07
},
{
"name": "editor_mcp",
"status": "passed",
"seconds": 0.08
},
{
"name": "process_and_cook",
"status": "passed",
"seconds": 0.53
},
{
"name": "build_schema_publication",
"status": "passed",
"seconds": 30.02
},
{
"name": "editor_plugins",
"status": "passed",
"seconds": 0.12
},
{
"name": "editor_session_settings",
"status": "passed",
"seconds": 0.09
},
{
"name": "ui_widgets",
"status": "passed",
"seconds": 0.35
},
{
"name": "editor_mcp_stdio",
"status": "passed",
"seconds": 0.58
},
{
"name": "core",
"status": "passed",
"seconds": 0.26
},
{
"name": "tutorial_moving",
"status": "passed",
"seconds": 0.14
},
{
"name": "tutorial_following",
"status": "passed",
"seconds": 0.06
},
{
"name": "tutorial_spawning",
"status": "passed",
"seconds": 0.14
},
{
"name": "tutorial_physics",
"status": "passed",
"seconds": 0.39
},
{
"name": "playable_2d",
"status": "passed",
"seconds": 2.16
},
{
"name": "playable_3d",
"status": "passed",
"seconds": 4.64
}
],
"counts": {
"passed": 18,
"skipped": 0,
"failed": 0
}
}
],
"skip_note": "The Wayland compositor declined programmatic restore. The XWayland lifecycle record is separate; this skipped invocation is not a passed native Wayland check."
}
@@ -15,14 +15,14 @@ Live Vulkan allocations stayed at 15,750,288 bytes (2D) and 15,787,008 bytes (3D
`render_call` includes GPU waits and synchronous readback; it is not CPU utilization. GPU timestamps cover submitted rendering. Frame timings exclude profile bookkeeping and final file writes. Synthetic 1/60-second simulation ticks run as fast as the offscreen loop allows. The resulting numbers are not display FPS or a real-time gameplay pacing test. Small sample scenes, one GPU, 240 frames and a static camera cannot establish scalability, a memory-leak guarantee, or broad performance claims. The imported 3D material also emits the recorded warning about unsupported material features in its manifest.
Proposed P1 budgets for these exact scenes on this reference-class Linux host, at the same resolution and validation/readback settings:
Initial P1 tracking budgets for these exact scenes on this reference-class Linux host, at the same resolution and validation/readback settings:
- Measured frame p95 ≤ 4 ms; GPU p95 ≤ 1 ms; readback p95 ≤ 1 ms.
- Simulation and snapshot p95 ≤ 0.5 ms each.
- Explicit live Vulkan memory ≤ 20 MiB, with no growth after warmup over a future 3,000-frame resource-lifecycle run.
- Startup from `main()` ≤ 500 ms for these tiny packages.
These initial thresholds leave headroom above the observed values. They are **proposed**, not enforced acceptance criteria. P1 should retain cold-start results separately, run repeated sessions, measure editor input latency and larger content, and collect independent Windows hardware baselines before adopting release gates.
These initial thresholds leave headroom above the observed values. They are tracking budgets for P1, not enforced MVP acceptance criteria or engine-wide guarantees. P1 should retain cold-start results separately, run repeated sessions, measure editor input latency and larger content, and collect independent Windows hardware baselines before adopting release gates.
Reproduce export validation from the repository with `python tools/verify_playable_exports.py --editor build/linux-debug/faset_editor --output <new-empty-directory>`. Run each retained standalone Player with `--headless --frames 240 --profile <output.json>` to repeat the longer measurement. A new run builds current sources and produces new immutable generations; use the stored hashes to distinguish it from this record.
+92
View File
@@ -0,0 +1,92 @@
# MVP acceptance dossier
Accepted engine source: **`4cb82556de31268d2bde73948dd1ff1b6c02f162`**. Recorded 18 September 2026. Release tag: **`v0.1.0-mvp`**, including the final documentation/evidence commit.
**Status: the C++ MVP is accepted for the recorded Linux and Windows profiles.** This dossier maps [PLAN M0–M9](../../PLAN.md) to implemented behavior and bounded evidence. The final publication changes documentation and the research viewer, not the tested engine/gameplay code. Test names below are CTest names, not claims of additional runs. Acceptance does not certify untested devices or turn the compatibility coverage limits below into passing checks.
## Evidence and revision boundaries
- **Final candidate Linux:** the integrated suite has **34 passed, 1 skipped, 0 failed** out of 35 tests, with optional ImGui diagnostics enabled. The skip is native Wayland programmatic restore. Both exact-candidate Release games also passed standalone relocation/validation and 120 frames each. See the [final Linux record](final-linux-2026-09-18/README.md) and [test outcomes](final-linux-2026-09-18/tests.json). The earlier XWayland lifecycle scenario is separate.
- **Final candidate native/manual CI:** [run 35301244366](https://github.com/emil28092005/Faset_Engine/actions/runs/35301244366) passed. This is separate from the full Windows graphics/export workflow.
- **Sanitizers:** the [recorded configurations](final-linux-2026-09-18/tests.json) show complete `0f34b03` ASan/UBSan **18/18** and final-candidate asset/cook **2/2** after the source-relocation correction. This is not a claim that the complete sanitizer suite was repeated on `4cb8255`.
- **Exact earlier Linux candidate:** [checkpoint 5 evidence](checkpoint5-linux-2026-09-18/README.md) records clean offline build, first project launch, standalone games, recovery and real Blender reimport at `0f34b036313c011861dbfd5828ed45c4f7940b05`. The Blender report separately identifies the updated verification harness. These reports retain their original provenance.
- **Final source Windows:** [run 35301244334](https://github.com/emil28092005/Faset_Engine/actions/runs/35301244334) passed the fresh Editor/Player build, all **35 tests with no skips**, real BuildService Release exports/incremental rebuilding, and both checked-in games after standalone Unicode relocation. The [Windows evidence](windows-software-vulkan-2026-09-18/README.md) separates this exact `4cb8255` result from the preceding successful `0f34b03` run. SwiftShader is software Vulkan; the Khronos validation layer was unavailable.
## Criterion map
### M0 — reproducible foundation
CMake separates Core, Runtime, Editor, Player and SchemaExporter; gameplay is static, and Player does not link Editor/MCP/import services. Presets, compiler/SDK/runtime requirements, dependency hashes, Slang and third-party notices are recorded in [toolchains](../TOOLCHAINS.md), [dependency pins](../../dependencies.lock.json) and [target definitions](../../cmake/Player.cmake). Core tests cover IDs, hashing, atomic IO, Unicode paths and subprocess behavior.
Evidence: `core`, `process_and_cook`, native CI, and the [clean offline build](checkpoint5-linux-2026-09-18/offline.json): committed `0f34b03`, empty build directory, external network disabled, prepared tools/dependencies, 20 CPU tests. A prepared offline build does not establish a fresh installation of every system prerequisite. Windows records the clean checkout, pinned tools, configure/build commands and native test execution separately.
### M1 — documents, metadata and commands
Persistent source IDs are separate from runtime handles. Typed metadata describes stable fields, constraints and versions; unknown data survives round-trip. Authoring transactions validate candidates before publication and share revision conflicts, retry keys, Undo/Redo, save and recovery. Explicit declarative migrations preserve IDs and unknown fields; missing/manual/incompatible rules fail without partial edits.
Evidence: `authoring`, `build_schema_publication`, `editor_session_settings`, `editor_mcp`, and migration actions in `editor_ui_authoring`; [authoring fixtures](../../tests/authoring_tests.cpp) and [metadata publication fixtures](../../tests/build_schema_tests.cpp). Opening older data does not migrate it automatically. Changing field units requires reviewing separately stored instance overrides, as described in the [schema API guide](../manual/scripting/api.md).
### M2 — platform and baseline graphics
SDL window/input/text/DPI feeds a direct Vulkan 1.3 backend with capability checks, explicit synchronization and resource lifetime handling. The serial Render Graph declares reads/writes, barriers and optional GPU labels. Pinned Slang produces SPIR-V and normalized reflection; incompatible or failed shader reload preserves the working pipeline. Baseline rendering includes sprite transparency/layers, static textured meshes, basic PBR, directional shadows, CPU culling and timing/resource counters.
Evidence: `render_graph`, `render_offscreen`, `render_sprite_alpha`, `render_shader_reload`, `render_window_lifecycle`, and `editor_debug_overlay`; [shader reload regression](../../tests/render_reload_tests.cpp) and [native window evidence](native-window-linux-2026-09-18.json). Shipping packages contain SPIR-V rather than requiring Slang. Native Wayland restore and physical-device limits are stated below.
### M3 — runtime, C++, physics and Player
EnTT-backed runtime APIs preserve source identity and validate handles. Deferred structural changes, input/fixed/physics/event phases, bounded catch-up, interpolation and lifecycle callbacks have contract tests. Box2D/Box3D support the demo bodies, layers/events, grounding and debug shapes. A separate statically linked Player receives an immutable resolved authoring snapshot, including unsaved edits; Play/Stop/pause/step do not write simulation state into the document. Failed C++ or metadata publication keeps the previous build and reports stale status.
Evidence: `runtime_contracts`, `player_scene_contracts`, `player_shutdown_diagnostics`, four `tutorial_*` tests, `playable_2d`, `playable_3d`, `build_schema_publication`, and the [seven recovery scenarios](checkpoint5-linux-2026-09-18/recovery.json). The supported physics profile remains root-level box bodies, not arbitrary mesh-collider cooking.
### M4 — retained editor UI
The main UI uses retained IDs, layout/styles, clipping/scroll, keyboard focus, editing/selection, drag/drop, one-window docking and dynamic metadata fields. One gesture creates one authoring Undo. FreeType/HarfBuzz and UTF-8 text support Cyrillic; focus reveals long/nested lists at 1×/2×. Layout/style reload validates candidates and preserves working state. Optional F12 ImGui diagnostics displays real renderer counters without replacing the main UI.
Evidence: `ui_widgets`, `ui_render`, `editor_ui_reload`, `editor_ui_gizmos`, `editor_ui_authoring`, `editor_debug_overlay`; [widget regression](../../tests/ui_tests.cpp) covers composition events, clipboard callbacks, DPI rasterization/hit testing and scroll/focus behavior. These deterministic events do **not** establish physical OS IME or mixed-monitor behavior.
### M5 — resources and Blender
The importer owns cooked geometry/material/texture data, persists source identity/settings, hashes inputs/dependencies/toolchain/profile and publishes complete generations atomically. Ordinary GLB and PNG/JPEG work without Blender. Freshness is visible through UI/MCP; stale sources cannot silently pass cook/export. Removal review pins both candidate and active generations; failed/cancelled imports retain the last good result. The final relocation correction updates logical source and payload together while keeping immutable generations and old pointers readable.
Evidence: `assets_pipeline`, `assets_blender_bundle`, `editor_ui_import_conflicts`; [asset fixtures](../../tests/assets_pipeline.cpp) cover cache rebuild, stable/unstable rename, removal, failure/cancel, freshness, moved PNG/bundle/external glTF and legacy pointers. The [real Blender report](checkpoint5-linux-2026-09-18/blender.json) shows unmodified Blender 4.5.3 updating two live Editor instances while preserving placement, tint, physics, opaque gameplay and revision. It used `0f34b03` binaries; relocation coverage is from the subsequent final-candidate tests. The [documented import profile](../manual/editor/assets.md) limits material/geometry transfer.
### M6 — scene editing and reusable scenes
Scene Tree, Inspector, Assets, viewport selection/gizmos, Console, project/simulation settings and Save/Play/Build are real authoring controls. Templates use stable nested instance/object/component addresses, sparse overrides, local additions/suppression and restricted local/world reparenting. Origin, Revert, source navigation and conflicts are visible. Schema changes invalidate resolved previews even without a document revision change.
Evidence: `editor_ui_launcher`, `editor_ui_project_settings`, `editor_ui_templates`, `editor_ui_gizmos`, `editor_ui_authoring`. The [template workflow](../../tests/editor_ui_templates.cpp) saves through UI, opens a new Session and compares nested IDs, origins and values. Cache clearing/reimport is covered separately by `assets_pipeline`. [First launch](checkpoint5-linux-2026-09-18/first-run.json) follows the Manual on the clean `0f34b03` Linux build. The [Windows record](windows-software-vulkan-2026-09-18/README.md) ties its fresh source build to actual launcher Create/Open and native Editor/MCP tests; it does not claim a retail installer or a human usability study.
### M7 — editor MCP and extensions
GUI and MCP share commands, canonical documents, revisions and history. Headless authoring/build, structured errors and cancellable jobs are separate from GPU screenshots. MCP is confined to editor services; Player and SchemaExporter contain no runtime-world MCP API. Startup DLL/SO extensions use exact SDK/build compatibility, dependencies and registration ownership.
Evidence: `editor_mcp`, `editor_mcp_stdio`, `editor_gui_mcp`, `editor_plugins`; [GUI/stdio regression](../../tests/editor_gui_mcp_test.py) covers actual captures and conflicting edits/Undo; [plugin fixtures](../../tests/plugin_tests.cpp) cover the runtime-component/editor-panel package, compatibility rejection and unknown-data preservation. This is the supported protocol/workflow scope, not certification of every MCP client.
### M8 — standalone games and export
BuildService validates metadata, builds C++, cooks resources/shaders, assembles a manifest/notices and validates the standalone Player before publishing. Editor/MCP/Blender/schema helpers/compiler are not required by the exported game. Both checked-in C++ games exercise controls, physics, pickups, a gate/exit and reset; the 3D game uses the Blender arch.
Evidence: `process_and_cook`, `build_schema_publication`, `playable_2d`, `playable_3d`, shader/template/Blender regressions, and [exact `4cb8255` Linux exports](final-linux-2026-09-18/playable-exports.json). Both packages ran 120 frames after Unicode-path relocation with source projects hidden and zero errors under active Khronos validation. [2D](final-linux-2026-09-18/collect-2d-manifest.json) and [3D](final-linux-2026-09-18/collect-3d-manifest.json) manifests retain package hashes. An additional [Linux namespace check](final-linux-2026-09-18/sdk-unavailable.json) hid the entire SDK/build/tool tree and repeated validation/120 frames with identical captures. [Final Windows exports](windows-software-vulkan-2026-09-18/README.md) passed the same project relocation checks on SwiftShader. **All four dimension/platform combinations passed.** The entire-SDK hiding check was Linux-only; it is not silently attributed to Windows.
### M9 — final acceptance
Existing evidence covers shared GUI/MCP authoring, revision conflicts, Undo/Redo, interrupted Editor recovery and failed save/import/build/Player startup. The [Release baseline](linux-release-2026-09-18/README.md) records scene/hardware/settings, startup and CPU/GPU/readback/memory data; the [implementation log](../IMPLEMENTATION.md) records build/import iteration measurements. Its scoped thresholds serve as initial P1 tracking budgets for the recorded scenes/reference host, not engine-wide guarantees or enforced MVP performance gates. Those measurements retain their original revision and workload limits and are not a benchmark of the final candidate or every platform.
The native build/export gates, first-project workflows and recovery checks are complete for the profiles below. PLAN and the Manual record current capabilities, with compatibility coverage limits retained explicitly. The first release is a source MVP for these small projects, not a claim of production readiness for arbitrary games.
## Final disposition
- **LINUX-EXPORT-4CB — passed:** [final report](final-linux-2026-09-18/README.md), source clean at `4cb8255` through completion; both Release packages validated and rendered 120 frames after Unicode relocation with source paths unavailable. RTX 2080 Ti, Khronos validation active, zero errors. Package hashes and per-game counters are linked from that record; the earlier `0f34b03` reports retain their provenance.
- **WINDOWS-TEST-4CB — passed:** [run 35301244334](https://github.com/emil28092005/Faset_Engine/actions/runs/35301244334), fresh build with optional diagnostics, 35/35 tests, no failures/skips. Exact outcomes and native UI/window observations are retained in the [Windows dossier](windows-software-vulkan-2026-09-18/README.md).
- **WINDOWS-EXPORT-4CB — passed:** both Release games, package hash checks, Unicode relocation, disposable source projects unavailable, 120 rendered frames each. Required system/runtime libraries and the software Vulkan driver remain part of the target environment. No physical Windows GPU or active Khronos layer is claimed.
- **FIRST-PROJECT — passed within the developer setup profile:** clean Linux SDK plus the Manual's create-project command; fresh Windows checkout/toolchain configure/build followed by actual Create/Open, native window and GUI/MCP execution. System prerequisites were prepared on Linux or provided/prepared by the Windows runner. No graphical retail installer was planned or tested.
- **RELEASE — `v0.1.0-mvp`:** M0–M9 are closed for the documented profiles. Remaining native IME, mixed-monitor, native Wayland restore and additional hardware-driver coverage stay visible below; Lua and subsequent feature work remain in P1–P6. The UI references guide appearance only; architecture, PLAN and working authoring contracts define behavior.
## Coverage limits that must remain visible
- **Physical OS IME:** synthetic preedit/commit and SDL input-area checks passed; an actual system IME/candidate window/composition workflow was not exercised. Clipboard checks do not substitute for it.
- **Physical mixed-monitor DPI:** tests cover 1×/2× layout, glyph density, input coordinates, focus and live-scale state preservation; moving a native window between actual monitors with different scales was not tested.
- **Native Wayland restore:** the compositor declined the programmatic restore scenario, which is explicitly skipped. XWayland passing is useful alternative-backend evidence, not native Wayland completion.
- **Platform and performance scope:** Linux hardware results do not certify physical Windows GPUs. Software-driver functional coverage does not establish hardware performance. Small fixed scenes and static frame profiles do not establish large-project responsiveness.
These are verification limits, not proof that the feature is broken; they also cannot be silently counted as verified. The supported MVP profile remains static glTF/UV0/basic PBR, root-level box physics, one editor window and C++ gameplay. Lua, advanced rendering/animation, live link, variants and C++ hot replacement remain outside this acceptance scope as specified by PLAN.
@@ -0,0 +1,18 @@
# Windows software Vulkan acceptance
[The final source record](final-source/evidence.json) documents a successful fresh-checkout build of `4cb82556de31268d2bde73948dd1ff1b6c02f162` in [GitHub Actions run 35301244334](https://github.com/emil28092005/Faset_Engine/actions/runs/35301244334). The hosted Windows Server 2025 image supplied Visual Studio tools and Clang 20.1.8 (`clang-cl`). The job prepared the pinned Vulkan loader/SwiftShader toolchain, fetched the verified Slang compiler, configured the Editor with `FASET_DEBUG_IMGUI=ON`, then built the Editor, Player and tests. All **35 CTests passed**, with no failures or skips.
The [test summary](final-source/tests.json) preserves the concrete acceptance cases and selected output:
- `editor_ui_launcher`: Create/Open, Unicode project paths, validation, recent projects, directory browsing and keyboard navigation through the instrumented retained UI. This is developer setup and first-project evidence, not a retail-installer test.
- `render_window_lifecycle`: native SDL window resize, Unicode clipboard roundtrip, text-input rectangle conversion, 12 fresh frames while minimized, and restore. Real OS IME composition was not exercised.
- `editor_gui_mcp`: a visible native Editor plus MCP stdio shared authoring, conflict/Undo handling and 12 fresh PNG captures without loss of responsiveness.
- `editor_debug_overlay`: the optional ImGui font/triangle rendering, F12 toggling and pointer-event isolation, including gestures crossing the overlay boundary.
Both the BuildService export/incremental-build integration and [the checked-in playable project verifier](final-source/playable-report.json) passed. Each game was built in **Release** from its own C++ scripts, packaged, checked against its manifest, moved outside the engine tree into a `Faset Café 世界` path, and run from an unrelated working directory with its disposable source project unavailable. Each standalone Player passed CPU scene validation and rendered 120 frames at 1280×720. The 3D case includes a real imported Blender arch bundle. Exact source input hashes, generations, packaged file hashes, process results and captures are retained.
The [toolchain](final-source/toolchain.json) and [probe](final-source/probe.json) identify **SwiftShader Vulkan 1.3, a CPU device**. The Khronos validation layer was absent (`validation_enabled=false`); zero reported validation errors must not be read as validation-layer coverage. These records establish functional behavior on a hosted Windows VM, not physical desktop GPU performance, a manually operated desktop, or a large-workload benchmark. Timing fields retained in the original verifier report are diagnostic data only.
Original detailed job logs and raw frame profiles are available in the linked CI artifact while its retention period lasts. This folder keeps the compact acceptance evidence and hashes of the original CTest/CI logs.
The earlier [checkpoint 5 record](checkpoint5/evidence.json) preserves the independently successful run of `0f34b036313c011861dbfd5828ed45c4f7940b05`. The final source run additionally covers the relocated importer metadata fix through `assets_pipeline`, followed by the complete export matrix again. [2D capture](final-source/collect-2d.png) · [3D capture](final-source/collect-3d.png).
@@ -0,0 +1,148 @@
{
"asset_generations": {},
"build_fingerprint": "1ce6b1c87ae16d24ae0df4da94b379805d976d550c31f3540cad9f64829542b7",
"configuration": "Release",
"executable": "faset_player.exe",
"files": [
{
"path": "faset_player.exe",
"sha256": "a9616a9547c599ecc27b88a3d5807c5c0a7daa1cd7ec7663743d286411520809",
"size": 4278272
},
{
"path": "Notices/box2d/LICENSE",
"sha256": "68a3e676d7e94093b102d5cba0d4e04af812040d6f230c3db67a6664574e43d2",
"size": 1067
},
{
"path": "Notices/box3d/LICENSE",
"sha256": "da5e31a26bf3cfd5ba5c96d6823e480128c81e76c107ef9d3ee5d94789184b90",
"size": 1067
},
{
"path": "Notices/dependencies.json",
"sha256": "a54639ad2d9ad13c2e5f1ec90d6775f5dfc493656789b78c7e44debe2a378fdc",
"size": 2206
},
{
"path": "Notices/entt/LICENSE",
"sha256": "0785027ce472d7c61f05fba664a1d3ba6639c1593ced351ad9e4bed868765d99",
"size": 1097
},
{
"path": "Notices/Faset-NOTICE.txt",
"sha256": "30c2e7e80f5153b31ab57675e07246aef9165965d64e36022b426a26449ece3f",
"size": 204
},
{
"path": "Notices/json/LICENSE.MIT",
"sha256": "46a65cffd1ea955132d95a8dd921640714a8d6b537d2e4e482d31145ae95b603",
"size": 1076
},
{
"path": "Notices/sdl3/LICENSE.txt",
"sha256": "97f35b302b361680ec1e891e95d2d52097bb95abff361434916d99dc1305f127",
"size": 884
},
{
"path": "Notices/stb/LICENSE",
"sha256": "bebfe904b14301657e4e5d655c811d51fd31b97c455b9cc2d8600d6bac6cff63",
"size": 2510
},
{
"path": "README.txt",
"sha256": "c62b83aa59e8eec3b3508f9e9ad5e78eb4554a2369242c830debe8d5ffbab924",
"size": 229
},
{
"path": "scene.fscene",
"sha256": "0d7b221abc1cae5e9f1d04357c684ac6993f5878d634b4b950eec66dd8e55b2a",
"size": 6214
},
{
"path": "shaders/fragmentMain.reflection.json",
"sha256": "ba43159da6ef0e8c305ba9a88414551ec273c487cdc7d324403998ba2d296f10",
"size": 2777
},
{
"path": "shaders/fragmentMain.spv",
"sha256": "f5c4f289917ecab5d00d543c053aac1d10e6cca311fd172daf06d315160a3505",
"size": 7436
},
{
"path": "shaders/shadowMain.reflection.json",
"sha256": "3f785e549f40abad75115d5dfc16b409f81bbb0eced6281c6eda33c2eb439249",
"size": 2772
},
{
"path": "shaders/shadowMain.spv",
"sha256": "7643b4d688492b5ee923b9606f0b0e70343ca05fa67673838206119a6f86d8d8",
"size": 784
},
{
"path": "shaders/vertexMain.reflection.json",
"sha256": "4459cc3113377b7fbc41b880529cf5ad474ad07d299e033eb9169ac7849894cf",
"size": 2768
},
{
"path": "shaders/vertexMain.spv",
"sha256": "1ad2631c35d654f48166321ae43d4165043e1b919b14f1dc61caf97b1ada0898",
"size": 1224
},
{
"path": "Windows-Runtime.txt",
"sha256": "1c53b7b6890d4a10408acfa7a27760ca1b176bb43dba9c25f3e5d2ac2d7d15db",
"size": 227
}
],
"format": "faset.export",
"generation": "16e0f91d-c1ab-4c66-b4d7-3dc88459caa9",
"platform": "windows",
"prerequisites": [
"Windows x64",
"Vulkan 1.3 driver",
"Microsoft Visual C++ x64 Redistributable (Visual Studio 2022 or newer)"
],
"renderer_profile": {
"api": "Vulkan 1.3",
"materials": [
"base-color factor and texture",
"metallic and roughness factors"
],
"required_features": [
"dynamicRendering",
"synchronization2"
],
"shadow_map": {
"resolution": 1024,
"world_extent": 40
},
"texture_sampling": "linear clamp, one mip level",
"unsupported_material_features": [
"normal maps",
"metallic-roughness maps",
"emissive and occlusion maps",
"alpha mode selection",
"unlit mode",
"per-material face culling"
]
},
"scene_hash": "fdfa1c49cf78a1cf52cd41b201c35bb0b5f852c90b7626b2a7f17da3e2e3e24c",
"simulation": {
"fixed_delta": 0.016666666666666666,
"gravity": [
0,
-9.81,
0
],
"max_catch_up_ticks": 4,
"physics_substeps": 4
},
"units": {
"angle": "radian",
"coordinates": "right-handed Y-up",
"distance": "metre"
},
"validation_log": "{\"dimension\":2,\"validated\":true}\r\n",
"version": 1
}
@@ -0,0 +1,18 @@
{
"arguments": [
"C:\\Users\\runneradmin\\AppData\\Local\\Temp\\faset-playable-exports-do2lq63x\\Faset Café 世界\\collect-2d\\faset_player.exe",
"--headless",
"--frames",
"120",
"--capture",
"C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-do2lq63x\\Faset Café 世界\\collect-2d\\verification.ppm",
"--profile",
"C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-do2lq63x\\Faset Café 世界\\collect-2d\\profile.json"
],
"cwd": "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-do2lq63x\\empty-working-directory",
"seconds": 0.5779999999999745,
"exit_code": 0,
"timed_out": false,
"stdout": "New round: collect the three gold cubes, then reach the green exit. E resets.\n{\"device\":\"SwiftShader Device (LLVM 10.0.0)\",\"dimension\":2,\"frames\":120,\"ticks\":120,\"validation_errors\":0}\n",
"stderr": "[Faset] Vulkan validation layer/debug-utils unavailable; validation disabled.\n"
}
@@ -0,0 +1,12 @@
{
"arguments": [
"C:\\Users\\runneradmin\\AppData\\Local\\Temp\\faset-playable-exports-do2lq63x\\Faset Café 世界\\collect-2d\\faset_player.exe",
"--validate"
],
"cwd": "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-do2lq63x\\empty-working-directory",
"seconds": 0.03199999999992542,
"exit_code": 0,
"timed_out": false,
"stdout": "{\"dimension\":2,\"validated\":true}\n",
"stderr": ""
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -0,0 +1,175 @@
{
"asset_generations": {
"5832763b-3ed0-44d6-9088-0b524f196a91": "e8eb8805673cc22596d1c5b48fc6b8fbdf600d1c6a15c9633d14014a7eb1d11f"
},
"build_fingerprint": "2df2988bbee68e7cb0819b84708006393d85217630475289df5ef603fac4e0c3",
"configuration": "Release",
"executable": "faset_player.exe",
"files": [
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/current.json",
"sha256": "32a37e4497f1129c0c78f3ccab4c53e802afe60cabc7a445a31114bf071ff90a",
"size": 134
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/e8eb8805673cc22596d1c5b48fc6b8fbdf600d1c6a15c9633d14014a7eb1d11f/manifest.json",
"sha256": "c13287d0ebbef75aa610af4a5d3e687e232b2450f3daaff6926fe6128250ead6",
"size": 4532
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/e8eb8805673cc22596d1c5b48fc6b8fbdf600d1c6a15c9633d14014a7eb1d11f/meshes/mesh-0961e2a3f92977836a3aa80cab09277e-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/e8eb8805673cc22596d1c5b48fc6b8fbdf600d1c6a15c9633d14014a7eb1d11f/meshes/mesh-93b5b3e52cb6bb120fefb80e2d8e222b-0.fmesh",
"sha256": "3875c16ea628d4126373ebfe07816621fbad3961596a2fcd112f1eec0e378071",
"size": 928
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/e8eb8805673cc22596d1c5b48fc6b8fbdf600d1c6a15c9633d14014a7eb1d11f/meshes/mesh-fc069a07a87e0f831fd92efdbc4efa1f-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "faset_player.exe",
"sha256": "4eb9cf7a6d0c2d5e274fb2fe5d6ce6048abda7775dd41927edd77250ecc3eb3a",
"size": 4292096
},
{
"path": "Notices/box2d/LICENSE",
"sha256": "68a3e676d7e94093b102d5cba0d4e04af812040d6f230c3db67a6664574e43d2",
"size": 1067
},
{
"path": "Notices/box3d/LICENSE",
"sha256": "da5e31a26bf3cfd5ba5c96d6823e480128c81e76c107ef9d3ee5d94789184b90",
"size": 1067
},
{
"path": "Notices/dependencies.json",
"sha256": "a54639ad2d9ad13c2e5f1ec90d6775f5dfc493656789b78c7e44debe2a378fdc",
"size": 2206
},
{
"path": "Notices/entt/LICENSE",
"sha256": "0785027ce472d7c61f05fba664a1d3ba6639c1593ced351ad9e4bed868765d99",
"size": 1097
},
{
"path": "Notices/Faset-NOTICE.txt",
"sha256": "30c2e7e80f5153b31ab57675e07246aef9165965d64e36022b426a26449ece3f",
"size": 204
},
{
"path": "Notices/json/LICENSE.MIT",
"sha256": "46a65cffd1ea955132d95a8dd921640714a8d6b537d2e4e482d31145ae95b603",
"size": 1076
},
{
"path": "Notices/sdl3/LICENSE.txt",
"sha256": "97f35b302b361680ec1e891e95d2d52097bb95abff361434916d99dc1305f127",
"size": 884
},
{
"path": "Notices/stb/LICENSE",
"sha256": "bebfe904b14301657e4e5d655c811d51fd31b97c455b9cc2d8600d6bac6cff63",
"size": 2510
},
{
"path": "README.txt",
"sha256": "c62b83aa59e8eec3b3508f9e9ad5e78eb4554a2369242c830debe8d5ffbab924",
"size": 229
},
{
"path": "scene.fscene",
"sha256": "21c364140669ec82c6886102667b255df5ccdf0ec77e0ca64060f84dd7b86632",
"size": 8639
},
{
"path": "shaders/fragmentMain.reflection.json",
"sha256": "ba43159da6ef0e8c305ba9a88414551ec273c487cdc7d324403998ba2d296f10",
"size": 2777
},
{
"path": "shaders/fragmentMain.spv",
"sha256": "f5c4f289917ecab5d00d543c053aac1d10e6cca311fd172daf06d315160a3505",
"size": 7436
},
{
"path": "shaders/shadowMain.reflection.json",
"sha256": "3f785e549f40abad75115d5dfc16b409f81bbb0eced6281c6eda33c2eb439249",
"size": 2772
},
{
"path": "shaders/shadowMain.spv",
"sha256": "7643b4d688492b5ee923b9606f0b0e70343ca05fa67673838206119a6f86d8d8",
"size": 784
},
{
"path": "shaders/vertexMain.reflection.json",
"sha256": "4459cc3113377b7fbc41b880529cf5ad474ad07d299e033eb9169ac7849894cf",
"size": 2768
},
{
"path": "shaders/vertexMain.spv",
"sha256": "1ad2631c35d654f48166321ae43d4165043e1b919b14f1dc61caf97b1ada0898",
"size": 1224
},
{
"path": "Windows-Runtime.txt",
"sha256": "1c53b7b6890d4a10408acfa7a27760ca1b176bb43dba9c25f3e5d2ac2d7d15db",
"size": 227
}
],
"format": "faset.export",
"generation": "e8819c35-cd05-45d9-b7f4-3de060f62836",
"platform": "windows",
"prerequisites": [
"Windows x64",
"Vulkan 1.3 driver",
"Microsoft Visual C++ x64 Redistributable (Visual Studio 2022 or newer)"
],
"renderer_profile": {
"api": "Vulkan 1.3",
"materials": [
"base-color factor and texture",
"metallic and roughness factors"
],
"required_features": [
"dynamicRendering",
"synchronization2"
],
"shadow_map": {
"resolution": 1024,
"world_extent": 40
},
"texture_sampling": "linear clamp, one mip level",
"unsupported_material_features": [
"normal maps",
"metallic-roughness maps",
"emissive and occlusion maps",
"alpha mode selection",
"unlit mode",
"per-material face culling"
]
},
"scene_hash": "ee12794145c14ba5b599c5d9ae21648cccdfa91c47ee7742549f60d5fe7bc3e0",
"simulation": {
"fixed_delta": 0.016666666666666666,
"gravity": [
0,
-9.81,
0
],
"max_catch_up_ticks": 4,
"physics_substeps": 4
},
"units": {
"angle": "radian",
"coordinates": "right-handed Y-up",
"distance": "metre"
},
"validation_log": "warning: material material-bb63b094febbf90bb7049c9bcf7587bc has features beyond the initial base-color/PBR renderer\r\n{\"dimension\":3,\"validated\":true}\r\n",
"version": 1
}
@@ -0,0 +1,18 @@
{
"arguments": [
"C:\\Users\\runneradmin\\AppData\\Local\\Temp\\faset-playable-exports-do2lq63x\\Faset Café 世界\\collect-3d\\faset_player.exe",
"--headless",
"--frames",
"120",
"--capture",
"C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-do2lq63x\\Faset Café 世界\\collect-3d\\verification.ppm",
"--profile",
"C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-do2lq63x\\Faset Café 世界\\collect-3d\\profile.json"
],
"cwd": "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-do2lq63x\\empty-working-directory",
"seconds": 2.2810000000001764,
"exit_code": 0,
"timed_out": false,
"stdout": "New round: collect the three gold cubes, then reach the green exit. E resets.\n{\"device\":\"SwiftShader Device (LLVM 10.0.0)\",\"dimension\":3,\"frames\":120,\"ticks\":120,\"validation_errors\":0}\n",
"stderr": "[Faset] Vulkan validation layer/debug-utils unavailable; validation disabled.\nwarning: material material-bb63b094febbf90bb7049c9bcf7587bc has features beyond the initial base-color/PBR renderer\n"
}
@@ -0,0 +1,12 @@
{
"arguments": [
"C:\\Users\\runneradmin\\AppData\\Local\\Temp\\faset-playable-exports-do2lq63x\\Faset Café 世界\\collect-3d\\faset_player.exe",
"--validate"
],
"cwd": "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-do2lq63x\\empty-working-directory",
"seconds": 0.03099999999994907,
"exit_code": 0,
"timed_out": false,
"stdout": "{\"dimension\":3,\"validated\":true}\n",
"stderr": "warning: material material-bb63b094febbf90bb7049c9bcf7587bc has features beyond the initial base-color/PBR renderer\n"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,235 @@
{
"format": "faset.validation-evidence",
"version": 1,
"status": "passed",
"scope": "Hosted Windows developer build, first-project UI contracts, software Vulkan and relocated Release games",
"source_checkout": {
"commit": "0f34b036313c011861dbfd5828ed45c4f7940b05",
"fresh_checkout": true,
"clean_commit_match": true,
"checkout_step": "actions/checkout on a fresh GitHub-hosted runner; engine and game targets configured and built in this job"
},
"ci": {
"run_url": "https://github.com/emil28092005/Faset_Engine/actions/runs/35300447535",
"job_url": "https://github.com/emil28092005/Faset_Engine/actions/runs/35300447535/job/105463031708",
"started_utc": "2026-09-18T02:49:42Z",
"finished_utc": "2026-09-18T03:04:40Z",
"conclusion": "success",
"steps": [
{
"name": "Set up job",
"conclusion": "success"
},
{
"name": "Run actions/checkout@11d5960a326750d5838078e36cf38b85af677262",
"conclusion": "success"
},
{
"name": "Run actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065",
"conclusion": "success"
},
{
"name": "Visual Studio x64 environment",
"conclusion": "success"
},
{
"name": "Enable the documented Windows long-path developer profile",
"conclusion": "success"
},
{
"name": "Identify pinned Vulkan source cache",
"conclusion": "success"
},
{
"name": "Restore pinned Vulkan test tools",
"conclusion": "success"
},
{
"name": "Build official Vulkan loader and SwiftShader from pinned sources",
"conclusion": "success"
},
{
"name": "Save successfully built Vulkan test tools",
"conclusion": "skipped"
},
{
"name": "Probe Vulkan loader and SwiftShader before compiling the engine",
"conclusion": "success"
},
{
"name": "Fetch checksum-verified Slang compiler",
"conclusion": "success"
},
{
"name": "Configure full editor and Player",
"conclusion": "success"
},
{
"name": "Build full editor and tests",
"conclusion": "success"
},
{
"name": "CPU contracts and software GPU pixel tests",
"conclusion": "success"
},
{
"name": "Real Release exports, 2D and 3D execution, incremental Debug rebuild",
"conclusion": "success"
},
{
"name": "Export and relocate both checked-in playable games",
"conclusion": "success"
},
{
"name": "Preserve graphics and export evidence",
"conclusion": "success"
},
{
"name": "Post Run actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065",
"conclusion": "success"
},
{
"name": "Post Run actions/checkout@11d5960a326750d5838078e36cf38b85af677262",
"conclusion": "success"
},
{
"name": "Complete job",
"conclusion": "success"
}
]
},
"environment": {
"runner_label": "windows-2025",
"observed_setup": [
"Microsoft Windows Server 2025",
"Image: windows-2025-vs2026",
"Version: 20260907.229.1",
"-- The CXX compiler identification is Clang 20.1.8 with MSVC-like command-line"
],
"compiler": "Clang 20.1.8 clang-cl, x64 MSVC ABI",
"editor_configuration": "Debug, FASET_DEBUG_IMGUI=ON",
"export_configuration": "Release, independent Ninja build with custom project C++ sources",
"windows_long_paths_enabled": true,
"device": "SwiftShader Device (LLVM 10.0.0)",
"validation_layer_available": false
},
"tests": {
"total": 35,
"passed": 35,
"failed": 0,
"skipped": 0,
"details": "tests.json"
},
"first_project_evidence": {
"test": "editor_ui_launcher",
"observed": "Create/Open through the instrumented launcher, Unicode paths, recents, directory browsing, validation and keyboard navigation; actual retained UI and software-rendered frame"
},
"games": [
{
"name": "collect-2d",
"generation": "16e0f91d-c1ab-4c66-b4d7-3dc88459caa9",
"configuration": "Release",
"frames": 120,
"validation_enabled": false,
"validation_errors": 0,
"source_paths_hidden": true,
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 7,
"sha256": "f9d74067d87c1ebf30c6db372a26d93b7285791c8cb0307a24af5adb23bf201a"
},
"export_compiler_evidence": [
"-- The C compiler identification is Clang 20.1.8 with MSVC-like command-line",
"-- The CXX compiler identification is Clang 20.1.8 with MSVC-like command-line"
]
},
{
"name": "collect-3d",
"generation": "e8819c35-cd05-45d9-b7f4-3de060f62836",
"configuration": "Release",
"frames": 120,
"validation_enabled": false,
"validation_errors": 0,
"source_paths_hidden": true,
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 43,
"sha256": "0edc88eef9f641a780ef44664f58c811a22bd201c8d29e1fcb7531b88f251900"
},
"export_compiler_evidence": [
"-- The C compiler identification is Clang 20.1.8 with MSVC-like command-line",
"-- The CXX compiler identification is Clang 20.1.8 with MSVC-like command-line"
]
}
],
"limitations": [
"Software Vulkan on a GitHub-hosted Windows Server VM, not a physical desktop GPU qualification or performance benchmark.",
"Khronos validation layer is absent. Zero reported validation errors does not establish validation-layer coverage.",
"Fresh developer-toolchain setup and first-project tests are covered; no retail installer or manual OS IME composition acceptance is claimed."
],
"original_ci_log_sha256": "5ef915923837881ffba85d03f73fb879acef2849d425fa6c90d6401781cc5937",
"retained_files": [
{
"path": "collect-2d-manifest.json",
"sha256": "f65466a16a53bad75cf758952cdb6169c1717e1995257333ca0e882aabda63b3",
"size": 4315
},
{
"path": "collect-2d-run.json",
"sha256": "9dda395b5012051bfa225be27577bde0eff9d56ccc5f7e64ae8ed7dbd9748d02",
"size": 1011
},
{
"path": "collect-2d-validate.json",
"sha256": "1789f7b2d8de3bd55be3974d6b1e8fbc76424b5c73633e0a2052fb07482ce474",
"size": 445
},
{
"path": "collect-2d.png",
"sha256": "c380583f2fd8bc09c5b0e1c6e755c35c3d64b5385ed66177b04d1bbdb532b236",
"size": 4525
},
{
"path": "collect-3d-manifest.json",
"sha256": "35350b5d34dd2d1caed5cbbc1216274479fcc2ed5139e2de53369df0f2a9b929",
"size": 5923
},
{
"path": "collect-3d-run.json",
"sha256": "0fd945b0ea7d8373832db5ef017b1d57155ba8a26fd245e43d45732a0aacb5f9",
"size": 1128
},
{
"path": "collect-3d-validate.json",
"sha256": "3c6f05871ac8d462505c0f4021cb18d9740060955bad22aead701a4dec8bebb0",
"size": 562
},
{
"path": "collect-3d.png",
"sha256": "48ef9a82378f74e8ad5a0c438bb730773d684cce809afcef461756a38476375e",
"size": 20366
},
{
"path": "playable-report.json",
"sha256": "d754700b5bfb23e3e5f6f78cc77336daea0503d71fe82bec7c80454f194ac293",
"size": 14529
},
{
"path": "probe.json",
"sha256": "0c85c12510dbb0d895faefb7d14f26935a5add675cf0477b29f543d72eed40d6",
"size": 1019
},
{
"path": "tests.json",
"sha256": "ac25ad1d3d9368e28cc9b1bd01275ee7ec154d8a654fbed95ceb6f2d91592c2e",
"size": 4730
},
{
"path": "toolchain.json",
"sha256": "969162d1f994724ff6f98d20353e62e3881477ebce98759ca325c8febb175176",
"size": 937
}
]
}
@@ -0,0 +1,435 @@
{
"format": "faset.playable-export-verification",
"version": 1,
"started_utc": "2026-09-18T03:00:26.437450+00:00",
"platform": "win32",
"engine": "D:\\a\\Faset_Engine\\Faset_Engine",
"editor": "D:\\a\\Faset_Engine\\Faset_Engine\\build\\windows-debug\\faset_editor.exe",
"standalone_root": "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-do2lq63x",
"frames_per_game": 120,
"status": "passed",
"projects": [
{
"name": "collect-2d",
"dimension": 2,
"source_inputs": [
{
"path": ".gitignore",
"sha256": "3bb936ff6f84f3db041c75d6e207138a9107997cc9ad372621f657456c9c0665"
},
{
"path": "project.faset.json",
"sha256": "27bd0e432381f8f884e78f6d68e9d0dc13d49222e70f3a5e34fef1d57d379b0b"
},
{
"path": "README.md",
"sha256": "7543b24c9ac87d6a7390d61d5289518e5f5725f3c0052675659f6da716883bdd"
},
{
"path": "Scenes/main.scene.json",
"sha256": "9e2ad049f60a9ff7f98e31061a8f107648fbf672e5b9292f53e6672e5bae66f1"
},
{
"path": "Scripts/Gameplay.cpp",
"sha256": "99fb965e6589b138049a2e6cf95c5c61baea70761ffab33082a8058c91a687ff"
},
{
"path": "Scripts/Gameplay.hpp",
"sha256": "9d1fa36da50886fa7d84f8a8b3d1ae42856ae74aae1e52be5015b60977634dc0"
}
],
"generation": "16e0f91d-c1ab-4c66-b4d7-3dc88459caa9",
"configuration": "Release",
"standalone_directory": "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-do2lq63x\\Faset Café 世界\\collect-2d",
"executable": "faset_player.exe",
"package_file_count": 18,
"asset_generations": {},
"device": "SwiftShader Device (LLVM 10.0.0)",
"validation_enabled": false,
"validation_errors": 0,
"completed_frames": 120,
"summary_ms": {
"gpu": {
"max": 128.739,
"min": 1.839,
"p50": 1.9465,
"p95": 2.362,
"samples": 120
},
"render_call": {
"max": 129.6447,
"min": 2.0406,
"p50": 2.1546,
"p95": 2.5774,
"samples": 120
},
"renderer_cpu": {
"max": 129.6266,
"min": 2.0373,
"p50": 2.1516,
"p95": 2.5745,
"samples": 120
},
"renderer_readback_cpu": {
"max": 0.7806,
"min": 0.1451,
"p50": 0.1478,
"p95": 0.1646,
"samples": 120
},
"simulation": {
"max": 0.3316,
"min": 0.2683,
"p50": 0.283,
"p95": 0.3148,
"samples": 120
},
"snapshot": {
"max": 0.2707,
"min": 0.1572,
"p50": 0.2213,
"p95": 0.2435,
"samples": 120
},
"wall": {
"max": 130.7666,
"min": 2.5456,
"p50": 2.6691,
"p95": 3.0846,
"samples": 120
}
},
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 7,
"sha256": "f9d74067d87c1ebf30c6db372a26d93b7285791c8cb0307a24af5adb23bf201a"
},
"source_project_paths_unavailable": true,
"status": "passed"
},
{
"name": "collect-3d",
"dimension": 3,
"source_inputs": [
{
"path": ".gitignore",
"sha256": "3bb936ff6f84f3db041c75d6e207138a9107997cc9ad372621f657456c9c0665"
},
{
"path": "Assets/exit-arch/create.py",
"sha256": "5d9dc30a3f939376d2474cf958065e6daab140c7220444cedecd3bf5edf6e3d1"
},
{
"path": "Assets/exit-arch/manifest.json",
"sha256": "d72d44184d665148109de74782bc695d79392e43be12782e48096a7d79e37c4f"
},
{
"path": "Assets/exit-arch/manifest.json.faset-import.json",
"sha256": "7639d4fa33f2ac52e10d1cf79280a93aeb680399d217fda167acec1c6edcae00"
},
{
"path": "Assets/exit-arch/payload/ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3.glb",
"sha256": "ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3"
},
{
"path": "Assets/exit-arch/source.blend",
"sha256": "ff2676bec97ab778e531e87a185ad716049ea3b05b4a4f0027a6345737735035"
},
{
"path": "project.faset.json",
"sha256": "9a0ee78fa0ea56982a99cf707948a33db37266ab84de9d3f12a24aabb98afda5"
},
{
"path": "README.md",
"sha256": "a2378adc9dbd5ffb6bfa65abb7770051827d08279e48761046f30d932a5eab8c"
},
{
"path": "Scenes/main.scene.json",
"sha256": "b1b668a2d633deda0074b475460c82019dbcf592b76502906192eaf1f2345a7e"
},
{
"path": "Scripts/Extensions/Beacon.hpp",
"sha256": "650e75bbc55a3339647bcf67ba10fd9b20cece3531bb688b4221155e97959d5e"
},
{
"path": "Scripts/Gameplay.cpp",
"sha256": "c76fbb0bf28a85cc88732a905f2bc9be3e24c58d02cbf2ecccbf77c99093c029"
},
{
"path": "Scripts/Gameplay.hpp",
"sha256": "9d1fa36da50886fa7d84f8a8b3d1ae42856ae74aae1e52be5015b60977634dc0"
}
],
"import": {
"asset_id": "5832763b-3ed0-44d6-9088-0b524f196a91",
"cache_hit": false,
"diagnostics": [],
"generation": "e8eb8805673cc22596d1c5b48fc6b8fbdf600d1c6a15c9633d14014a7eb1d11f",
"manifest": {
"asset_id": "5832763b-3ed0-44d6-9088-0b524f196a91",
"files": [
{
"path": "meshes/mesh-fc069a07a87e0f831fd92efdbc4efa1f-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "meshes/mesh-0961e2a3f92977836a3aa80cab09277e-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "meshes/mesh-93b5b3e52cb6bb120fefb80e2d8e222b-0.fmesh",
"sha256": "3875c16ea628d4126373ebfe07816621fbad3961596a2fcd112f1eec0e378071",
"size": 928
}
],
"generation": "e8eb8805673cc22596d1c5b48fc6b8fbdf600d1c6a15c9633d14014a7eb1d11f",
"importer": "faset-gltf-1/cgltf-1.15",
"input_key": {
"bundle_sha256": "d72d44184d665148109de74782bc695d79392e43be12782e48096a7d79e37c4f",
"dependencies": {},
"importer": "faset-gltf-1/cgltf-1.15",
"settings": {},
"source": "ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3",
"target_profile": "desktop-static-pbr-v1",
"toolchain": {
"cgltf": "360db1a95480fe102ae9c69b27c5d101167ff5ba",
"stb": "2c980bb59875b0d32144a71867fbdebb2f77cd20"
}
},
"kind": "scene",
"materials": [
{
"alpha_cutoff": 0.5,
"alpha_mode": "OPAQUE",
"base_color": [
0.2199999988079071,
0.28999999165534973,
0.3400000035762787,
1.0
],
"base_color_texture": -1,
"double_sided": true,
"emissive": [
0.0,
0.0,
0.0
],
"emissive_texture": -1,
"format": "faset.material",
"id": "material-bb63b094febbf90bb7049c9bcf7587bc",
"metallic": 0.07999999821186066,
"metallic_roughness_texture": -1,
"name": "Weathered stone",
"normal_texture": -1,
"occlusion_texture": -1,
"roughness": 0.800000011920929,
"unlit": false,
"version": 1
}
],
"meshes": [
{
"id": "mesh-fc069a07a87e0f831fd92efdbc4efa1f",
"name": "Cube.001",
"primitives": [
{
"material": 0,
"path": "meshes/mesh-fc069a07a87e0f831fd92efdbc4efa1f-0.fmesh"
}
]
},
{
"id": "mesh-0961e2a3f92977836a3aa80cab09277e",
"name": "Cube.002",
"primitives": [
{
"material": 0,
"path": "meshes/mesh-0961e2a3f92977836a3aa80cab09277e-0.fmesh"
}
]
},
{
"id": "mesh-93b5b3e52cb6bb120fefb80e2d8e222b",
"name": "Cube.003",
"primitives": [
{
"material": 0,
"path": "meshes/mesh-93b5b3e52cb6bb120fefb80e2d8e222b-0.fmesh"
}
]
}
],
"nodes": [
{
"id": "node-cec6e8cea80cf5a98ba3263a0156bee4",
"local_transform": [
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
1.4500000476837158,
1.2999999523162842,
1.0
],
"mesh": 0,
"name": "Left post",
"parent_id": "",
"stable_source_id": true
},
{
"id": "node-5f8a90a6c4d27020d20bd4fa81d1d958",
"local_transform": [
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
1.4500000476837158,
-1.2999999523162842,
1.0
],
"mesh": 1,
"name": "Right post",
"parent_id": "",
"stable_source_id": true
},
{
"id": "node-482463e486a8d1fd741340c653cd128f",
"local_transform": [
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
3.0,
0.0,
1.0
],
"mesh": 2,
"name": "Lintel",
"parent_id": "",
"stable_source_id": true
}
],
"outputs": [
"node-cec6e8cea80cf5a98ba3263a0156bee4",
"node-5f8a90a6c4d27020d20bd4fa81d1d958",
"node-482463e486a8d1fd741340c653cd128f",
"mesh-fc069a07a87e0f831fd92efdbc4efa1f",
"mesh-0961e2a3f92977836a3aa80cab09277e",
"mesh-93b5b3e52cb6bb120fefb80e2d8e222b",
"material-bb63b094febbf90bb7049c9bcf7587bc"
],
"payload_source": "D:\\a\\Faset_Engine\\Faset_Engine\\.cache\\windows-playable-exports\\Faset Café 世界\\collect-3d\\Assets\\exit-arch\\payload/ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3.glb",
"schema_version": 1,
"settings": {},
"source": "D:\\a\\Faset_Engine\\Faset_Engine\\.cache\\windows-playable-exports\\Faset Café 世界\\collect-3d\\Assets\\exit-arch\\manifest.json",
"source_sha256": "ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3",
"textures": []
},
"previous_generation": "",
"removed_output_ids": []
},
"generation": "e8819c35-cd05-45d9-b7f4-3de060f62836",
"configuration": "Release",
"standalone_directory": "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-do2lq63x\\Faset Café 世界\\collect-3d",
"executable": "faset_player.exe",
"package_file_count": 23,
"asset_generations": {
"5832763b-3ed0-44d6-9088-0b524f196a91": "e8eb8805673cc22596d1c5b48fc6b8fbdf600d1c6a15c9633d14014a7eb1d11f"
},
"device": "SwiftShader Device (LLVM 10.0.0)",
"validation_enabled": false,
"validation_errors": 0,
"completed_frames": 120,
"summary_ms": {
"gpu": {
"max": 191.1372,
"min": 14.892,
"p50": 15.2367,
"p95": 20.1033,
"samples": 120
},
"render_call": {
"max": 192.1372,
"min": 15.1555,
"p50": 15.5375,
"p95": 20.4063,
"samples": 120
},
"renderer_cpu": {
"max": 192.1253,
"min": 15.1507,
"p50": 15.5268,
"p95": 20.3959,
"samples": 120
},
"renderer_readback_cpu": {
"max": 0.7794,
"min": 0.1471,
"p50": 0.1602,
"p95": 0.187,
"samples": 120
},
"simulation": {
"max": 0.5079,
"min": 0.2905,
"p50": 0.4613,
"p95": 0.4892,
"samples": 120
},
"snapshot": {
"max": 2.8905,
"min": 0.1883,
"p50": 0.2104,
"p95": 0.3207,
"samples": 120
},
"wall": {
"max": 196.1393,
"min": 15.8274,
"p50": 16.2321,
"p95": 21.1248,
"samples": 120
}
},
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 43,
"sha256": "0edc88eef9f641a780ef44664f58c811a22bd201c8d29e1fcb7531b88f251900"
},
"source_project_paths_unavailable": true,
"status": "passed"
}
],
"finished_utc": "2026-09-18T03:04:35.903012+00:00"
}
@@ -0,0 +1,32 @@
{
"format": "faset.windows-vulkan-probe",
"version": 1,
"status": "passed",
"loader": "D:\\a\\Faset_Engine\\Faset_Engine\\.cache\\windows-graphics\\sdk\\bin\\vulkan-1.dll",
"driver_manifest": "D:\\a\\Faset_Engine\\Faset_Engine\\.cache\\windows-graphics\\driver\\vk_swiftshader_icd.json",
"driver_library": "D:\\a\\Faset_Engine\\Faset_Engine\\.cache\\windows-graphics\\driver\\vk_swiftshader.dll",
"manifest": {
"file_format_version": "1.0.0",
"ICD": {
"library_path": ".\\vk_swiftshader.dll",
"api_version": "1.0.5"
}
},
"loader_debug": "all",
"pointer_bits": 64,
"icd_negotiate_result": 0,
"icd_interface_version": 7,
"enumerate_version_result": 0,
"loader_api_version": "1.4.341",
"create_instance_result": 0,
"devices": [
{
"name": "SwiftShader Device (LLVM 10.0.0)",
"api_version": "1.3.0",
"driver_version": 20971520,
"vendor_id": 6880,
"device_id": 49374,
"device_type": 4
}
]
}
@@ -0,0 +1,206 @@
{
"format": "faset.ctest-summary",
"version": 1,
"source_commit": "0f34b036313c011861dbfd5828ed45c4f7940b05",
"passed": 35,
"failed": 0,
"skipped": 0,
"source_log_sha256": "784a057ec9067fc35e38e3eb87804f583b55745147e72c009f84fb87112361ba",
"tests": [
{
"name": "authoring",
"status": "passed",
"seconds": 1.76
},
{
"name": "runtime_contracts",
"status": "passed",
"seconds": 0.26
},
{
"name": "assets_pipeline",
"status": "passed",
"seconds": 0.95,
"observations": [
"assets: geometry/PBR/texture, GLB/glTF, cache, rename, deletion, overrides, failure, cancellation, standalone PNG/JPEG OK"
]
},
{
"name": "assets_blender_bundle",
"status": "passed",
"seconds": 0.42
},
{
"name": "render_graph",
"status": "passed",
"seconds": 0.25
},
{
"name": "render_offscreen",
"status": "passed",
"seconds": 0.62
},
{
"name": "render_sprite_alpha",
"status": "passed",
"seconds": 0.26
},
{
"name": "render_shader_reload",
"status": "passed",
"seconds": 2.65
},
{
"name": "render_window_lifecycle",
"status": "passed",
"seconds": 1.03,
"observations": [
"driver=windows stage=created",
"stage=resized width=360 height=300 resize_events=2",
"clipboard=unicode_roundtrip_passed",
"text_input_area=coordinate_conversion_passed",
"stage=minimized fresh_frames=12 minimize_events=1 focus_lost_events=1",
"stage=restored restored_events=1 activation_required=0 frames=54 validation_enabled=0 validation_errors=0"
]
},
{
"name": "player_scene_contracts",
"status": "passed",
"seconds": 0.16
},
{
"name": "player_shutdown_diagnostics",
"status": "passed",
"seconds": 0.17
},
{
"name": "editor_mcp",
"status": "passed",
"seconds": 0.06
},
{
"name": "process_and_cook",
"status": "passed",
"seconds": 0.64
},
{
"name": "build_schema_publication",
"status": "passed",
"seconds": 7.29
},
{
"name": "editor_plugins",
"status": "passed",
"seconds": 0.24
},
{
"name": "editor_session_settings",
"status": "passed",
"seconds": 0.19
},
{
"name": "ui_widgets",
"status": "passed",
"seconds": 0.14
},
{
"name": "ui_render",
"status": "passed",
"seconds": 0.48
},
{
"name": "editor_ui_import_conflicts",
"status": "passed",
"seconds": 0.93
},
{
"name": "editor_ui_project_settings",
"status": "passed",
"seconds": 1.23
},
{
"name": "editor_ui_reload",
"status": "passed",
"seconds": 4.26
},
{
"name": "editor_ui_launcher",
"status": "passed",
"seconds": 0.65,
"observations": [
"Launcher Unicode/create/open/validation/recents/directory browser/keyboard passed. C:\\Users\\runneradmin\\AppData\\Local\\Temp\\faset-проекты-1425bd32-d184-42d7-9ece-402897e8d0b2"
]
},
{
"name": "editor_ui_templates",
"status": "passed",
"seconds": 2.98
},
{
"name": "editor_ui_gizmos",
"status": "passed",
"seconds": 1.16
},
{
"name": "editor_ui_authoring",
"status": "passed",
"seconds": 1.98
},
{
"name": "editor_mcp_stdio",
"status": "passed",
"seconds": 0.59
},
{
"name": "editor_gui_mcp",
"status": "passed",
"seconds": 4.66,
"observations": [
"Native GUI + MCP shared authoring, revision conflict, Undo, 12 fresh PNG captures and continued responsiveness passed"
]
},
{
"name": "core",
"status": "passed",
"seconds": 0.16
},
{
"name": "tutorial_moving",
"status": "passed",
"seconds": 0.03
},
{
"name": "tutorial_following",
"status": "passed",
"seconds": 0.01
},
{
"name": "tutorial_spawning",
"status": "passed",
"seconds": 0.04
},
{
"name": "tutorial_physics",
"status": "passed",
"seconds": 0.1
},
{
"name": "playable_2d",
"status": "passed",
"seconds": 0.62
},
{
"name": "playable_3d",
"status": "passed",
"seconds": 1.34
},
{
"name": "editor_debug_overlay",
"status": "passed",
"seconds": 0.21,
"observations": [
"ImGui diagnostics, F12, font atlas, clipping and event isolation passed"
]
}
]
}
@@ -0,0 +1,23 @@
{
"sources": {
"headers": {
"repository": "KhronosGroup/Vulkan-Headers",
"commit": "0d3f509e57041fbd073a1ee84cd9ccd36d148446",
"sha256": "e82005a6bd3289ce213232ef41e0f8722d1365ed72bf5cc4be56911c56bc30e0"
},
"loader": {
"repository": "KhronosGroup/Vulkan-Loader",
"commit": "32fcb949e253cbeb40cda7ea76122b492db579ae",
"sha256": "610fa9017226c49bc4f19398d94fce9e3c9ff94c14f6420f9fe24822707feacf"
},
"swiftshader": {
"repository": "google/swiftshader",
"commit": "1e80438d2b93ef36a7c05f8d2b81233bac0e3d16",
"sha256": "1c3a1afc397c7aa4d3275790bc9b451e80b144a1db665135d5519838bacf44a8"
}
},
"validation_layer": false,
"vulkan_sdk": "D:\\a\\Faset_Engine\\Faset_Engine\\.cache\\windows-graphics\\sdk",
"driver": "D:\\a\\Faset_Engine\\Faset_Engine\\.cache\\windows-graphics\\driver\\vk_swiftshader_icd.json",
"loader_only": false
}
@@ -0,0 +1,148 @@
{
"asset_generations": {},
"build_fingerprint": "4f93c7bc328f4f1ec19cc143e94fca71cb3075f4fae8dc26a812d968426e015b",
"configuration": "Release",
"executable": "faset_player.exe",
"files": [
{
"path": "faset_player.exe",
"sha256": "4674615a5a89fb1151619d4cfc5a4e96743a036308739fdec95f72843bc9c586",
"size": 4278272
},
{
"path": "Notices/box2d/LICENSE",
"sha256": "68a3e676d7e94093b102d5cba0d4e04af812040d6f230c3db67a6664574e43d2",
"size": 1067
},
{
"path": "Notices/box3d/LICENSE",
"sha256": "da5e31a26bf3cfd5ba5c96d6823e480128c81e76c107ef9d3ee5d94789184b90",
"size": 1067
},
{
"path": "Notices/dependencies.json",
"sha256": "a54639ad2d9ad13c2e5f1ec90d6775f5dfc493656789b78c7e44debe2a378fdc",
"size": 2206
},
{
"path": "Notices/entt/LICENSE",
"sha256": "0785027ce472d7c61f05fba664a1d3ba6639c1593ced351ad9e4bed868765d99",
"size": 1097
},
{
"path": "Notices/Faset-NOTICE.txt",
"sha256": "30c2e7e80f5153b31ab57675e07246aef9165965d64e36022b426a26449ece3f",
"size": 204
},
{
"path": "Notices/json/LICENSE.MIT",
"sha256": "46a65cffd1ea955132d95a8dd921640714a8d6b537d2e4e482d31145ae95b603",
"size": 1076
},
{
"path": "Notices/sdl3/LICENSE.txt",
"sha256": "97f35b302b361680ec1e891e95d2d52097bb95abff361434916d99dc1305f127",
"size": 884
},
{
"path": "Notices/stb/LICENSE",
"sha256": "bebfe904b14301657e4e5d655c811d51fd31b97c455b9cc2d8600d6bac6cff63",
"size": 2510
},
{
"path": "README.txt",
"sha256": "c62b83aa59e8eec3b3508f9e9ad5e78eb4554a2369242c830debe8d5ffbab924",
"size": 229
},
{
"path": "scene.fscene",
"sha256": "0d7b221abc1cae5e9f1d04357c684ac6993f5878d634b4b950eec66dd8e55b2a",
"size": 6214
},
{
"path": "shaders/fragmentMain.reflection.json",
"sha256": "ba43159da6ef0e8c305ba9a88414551ec273c487cdc7d324403998ba2d296f10",
"size": 2777
},
{
"path": "shaders/fragmentMain.spv",
"sha256": "f5c4f289917ecab5d00d543c053aac1d10e6cca311fd172daf06d315160a3505",
"size": 7436
},
{
"path": "shaders/shadowMain.reflection.json",
"sha256": "3f785e549f40abad75115d5dfc16b409f81bbb0eced6281c6eda33c2eb439249",
"size": 2772
},
{
"path": "shaders/shadowMain.spv",
"sha256": "7643b4d688492b5ee923b9606f0b0e70343ca05fa67673838206119a6f86d8d8",
"size": 784
},
{
"path": "shaders/vertexMain.reflection.json",
"sha256": "4459cc3113377b7fbc41b880529cf5ad474ad07d299e033eb9169ac7849894cf",
"size": 2768
},
{
"path": "shaders/vertexMain.spv",
"sha256": "1ad2631c35d654f48166321ae43d4165043e1b919b14f1dc61caf97b1ada0898",
"size": 1224
},
{
"path": "Windows-Runtime.txt",
"sha256": "1c53b7b6890d4a10408acfa7a27760ca1b176bb43dba9c25f3e5d2ac2d7d15db",
"size": 227
}
],
"format": "faset.export",
"generation": "260a10cf-3fe6-4689-ac7b-69b577954cc2",
"platform": "windows",
"prerequisites": [
"Windows x64",
"Vulkan 1.3 driver",
"Microsoft Visual C++ x64 Redistributable (Visual Studio 2022 or newer)"
],
"renderer_profile": {
"api": "Vulkan 1.3",
"materials": [
"base-color factor and texture",
"metallic and roughness factors"
],
"required_features": [
"dynamicRendering",
"synchronization2"
],
"shadow_map": {
"resolution": 1024,
"world_extent": 40
},
"texture_sampling": "linear clamp, one mip level",
"unsupported_material_features": [
"normal maps",
"metallic-roughness maps",
"emissive and occlusion maps",
"alpha mode selection",
"unlit mode",
"per-material face culling"
]
},
"scene_hash": "fdfa1c49cf78a1cf52cd41b201c35bb0b5f852c90b7626b2a7f17da3e2e3e24c",
"simulation": {
"fixed_delta": 0.016666666666666666,
"gravity": [
0,
-9.81,
0
],
"max_catch_up_ticks": 4,
"physics_substeps": 4
},
"units": {
"angle": "radian",
"coordinates": "right-handed Y-up",
"distance": "metre"
},
"validation_log": "{\"dimension\":2,\"validated\":true}\r\n",
"version": 1
}
@@ -0,0 +1,18 @@
{
"arguments": [
"C:\\Users\\runneradmin\\AppData\\Local\\Temp\\faset-playable-exports-8qma4esl\\Faset Café 世界\\collect-2d\\faset_player.exe",
"--headless",
"--frames",
"120",
"--capture",
"C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-8qma4esl\\Faset Café 世界\\collect-2d\\verification.ppm",
"--profile",
"C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-8qma4esl\\Faset Café 世界\\collect-2d\\profile.json"
],
"cwd": "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-8qma4esl\\empty-working-directory",
"seconds": 0.5939999999999372,
"exit_code": 0,
"timed_out": false,
"stdout": "New round: collect the three gold cubes, then reach the green exit. E resets.\n{\"device\":\"SwiftShader Device (LLVM 10.0.0)\",\"dimension\":2,\"frames\":120,\"ticks\":120,\"validation_errors\":0}\n",
"stderr": "[Faset] Vulkan validation layer/debug-utils unavailable; validation disabled.\n"
}
@@ -0,0 +1,12 @@
{
"arguments": [
"C:\\Users\\runneradmin\\AppData\\Local\\Temp\\faset-playable-exports-8qma4esl\\Faset Café 世界\\collect-2d\\faset_player.exe",
"--validate"
],
"cwd": "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-8qma4esl\\empty-working-directory",
"seconds": 0.031000000000062755,
"exit_code": 0,
"timed_out": false,
"stdout": "{\"dimension\":2,\"validated\":true}\n",
"stderr": ""
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -0,0 +1,175 @@
{
"asset_generations": {
"5832763b-3ed0-44d6-9088-0b524f196a91": "e8eb8805673cc22596d1c5b48fc6b8fbdf600d1c6a15c9633d14014a7eb1d11f"
},
"build_fingerprint": "460296d2322c47856e907b7c7ca070004d2a94f53e40a9bfb791d4dd8e9e9e45",
"configuration": "Release",
"executable": "faset_player.exe",
"files": [
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/current.json",
"sha256": "32a37e4497f1129c0c78f3ccab4c53e802afe60cabc7a445a31114bf071ff90a",
"size": 134
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/e8eb8805673cc22596d1c5b48fc6b8fbdf600d1c6a15c9633d14014a7eb1d11f/manifest.json",
"sha256": "c13287d0ebbef75aa610af4a5d3e687e232b2450f3daaff6926fe6128250ead6",
"size": 4532
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/e8eb8805673cc22596d1c5b48fc6b8fbdf600d1c6a15c9633d14014a7eb1d11f/meshes/mesh-0961e2a3f92977836a3aa80cab09277e-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/e8eb8805673cc22596d1c5b48fc6b8fbdf600d1c6a15c9633d14014a7eb1d11f/meshes/mesh-93b5b3e52cb6bb120fefb80e2d8e222b-0.fmesh",
"sha256": "3875c16ea628d4126373ebfe07816621fbad3961596a2fcd112f1eec0e378071",
"size": 928
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/e8eb8805673cc22596d1c5b48fc6b8fbdf600d1c6a15c9633d14014a7eb1d11f/meshes/mesh-fc069a07a87e0f831fd92efdbc4efa1f-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "faset_player.exe",
"sha256": "cb5f4167643d33adb11770f6746a9c29259abe6ff41fba9acc55aeb062a8c449",
"size": 4292096
},
{
"path": "Notices/box2d/LICENSE",
"sha256": "68a3e676d7e94093b102d5cba0d4e04af812040d6f230c3db67a6664574e43d2",
"size": 1067
},
{
"path": "Notices/box3d/LICENSE",
"sha256": "da5e31a26bf3cfd5ba5c96d6823e480128c81e76c107ef9d3ee5d94789184b90",
"size": 1067
},
{
"path": "Notices/dependencies.json",
"sha256": "a54639ad2d9ad13c2e5f1ec90d6775f5dfc493656789b78c7e44debe2a378fdc",
"size": 2206
},
{
"path": "Notices/entt/LICENSE",
"sha256": "0785027ce472d7c61f05fba664a1d3ba6639c1593ced351ad9e4bed868765d99",
"size": 1097
},
{
"path": "Notices/Faset-NOTICE.txt",
"sha256": "30c2e7e80f5153b31ab57675e07246aef9165965d64e36022b426a26449ece3f",
"size": 204
},
{
"path": "Notices/json/LICENSE.MIT",
"sha256": "46a65cffd1ea955132d95a8dd921640714a8d6b537d2e4e482d31145ae95b603",
"size": 1076
},
{
"path": "Notices/sdl3/LICENSE.txt",
"sha256": "97f35b302b361680ec1e891e95d2d52097bb95abff361434916d99dc1305f127",
"size": 884
},
{
"path": "Notices/stb/LICENSE",
"sha256": "bebfe904b14301657e4e5d655c811d51fd31b97c455b9cc2d8600d6bac6cff63",
"size": 2510
},
{
"path": "README.txt",
"sha256": "c62b83aa59e8eec3b3508f9e9ad5e78eb4554a2369242c830debe8d5ffbab924",
"size": 229
},
{
"path": "scene.fscene",
"sha256": "21c364140669ec82c6886102667b255df5ccdf0ec77e0ca64060f84dd7b86632",
"size": 8639
},
{
"path": "shaders/fragmentMain.reflection.json",
"sha256": "ba43159da6ef0e8c305ba9a88414551ec273c487cdc7d324403998ba2d296f10",
"size": 2777
},
{
"path": "shaders/fragmentMain.spv",
"sha256": "f5c4f289917ecab5d00d543c053aac1d10e6cca311fd172daf06d315160a3505",
"size": 7436
},
{
"path": "shaders/shadowMain.reflection.json",
"sha256": "3f785e549f40abad75115d5dfc16b409f81bbb0eced6281c6eda33c2eb439249",
"size": 2772
},
{
"path": "shaders/shadowMain.spv",
"sha256": "7643b4d688492b5ee923b9606f0b0e70343ca05fa67673838206119a6f86d8d8",
"size": 784
},
{
"path": "shaders/vertexMain.reflection.json",
"sha256": "4459cc3113377b7fbc41b880529cf5ad474ad07d299e033eb9169ac7849894cf",
"size": 2768
},
{
"path": "shaders/vertexMain.spv",
"sha256": "1ad2631c35d654f48166321ae43d4165043e1b919b14f1dc61caf97b1ada0898",
"size": 1224
},
{
"path": "Windows-Runtime.txt",
"sha256": "1c53b7b6890d4a10408acfa7a27760ca1b176bb43dba9c25f3e5d2ac2d7d15db",
"size": 227
}
],
"format": "faset.export",
"generation": "66191118-07cc-477b-9dc7-1e870be5ebc3",
"platform": "windows",
"prerequisites": [
"Windows x64",
"Vulkan 1.3 driver",
"Microsoft Visual C++ x64 Redistributable (Visual Studio 2022 or newer)"
],
"renderer_profile": {
"api": "Vulkan 1.3",
"materials": [
"base-color factor and texture",
"metallic and roughness factors"
],
"required_features": [
"dynamicRendering",
"synchronization2"
],
"shadow_map": {
"resolution": 1024,
"world_extent": 40
},
"texture_sampling": "linear clamp, one mip level",
"unsupported_material_features": [
"normal maps",
"metallic-roughness maps",
"emissive and occlusion maps",
"alpha mode selection",
"unlit mode",
"per-material face culling"
]
},
"scene_hash": "ee12794145c14ba5b599c5d9ae21648cccdfa91c47ee7742549f60d5fe7bc3e0",
"simulation": {
"fixed_delta": 0.016666666666666666,
"gravity": [
0,
-9.81,
0
],
"max_catch_up_ticks": 4,
"physics_substeps": 4
},
"units": {
"angle": "radian",
"coordinates": "right-handed Y-up",
"distance": "metre"
},
"validation_log": "warning: material material-bb63b094febbf90bb7049c9bcf7587bc has features beyond the initial base-color/PBR renderer\r\n{\"dimension\":3,\"validated\":true}\r\n",
"version": 1
}
@@ -0,0 +1,18 @@
{
"arguments": [
"C:\\Users\\runneradmin\\AppData\\Local\\Temp\\faset-playable-exports-8qma4esl\\Faset Café 世界\\collect-3d\\faset_player.exe",
"--headless",
"--frames",
"120",
"--capture",
"C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-8qma4esl\\Faset Café 世界\\collect-3d\\verification.ppm",
"--profile",
"C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-8qma4esl\\Faset Café 世界\\collect-3d\\profile.json"
],
"cwd": "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-8qma4esl\\empty-working-directory",
"seconds": 2.312999999999988,
"exit_code": 0,
"timed_out": false,
"stdout": "New round: collect the three gold cubes, then reach the green exit. E resets.\n{\"device\":\"SwiftShader Device (LLVM 10.0.0)\",\"dimension\":3,\"frames\":120,\"ticks\":120,\"validation_errors\":0}\n",
"stderr": "[Faset] Vulkan validation layer/debug-utils unavailable; validation disabled.\nwarning: material material-bb63b094febbf90bb7049c9bcf7587bc has features beyond the initial base-color/PBR renderer\n"
}
@@ -0,0 +1,12 @@
{
"arguments": [
"C:\\Users\\runneradmin\\AppData\\Local\\Temp\\faset-playable-exports-8qma4esl\\Faset Café 世界\\collect-3d\\faset_player.exe",
"--validate"
],
"cwd": "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-8qma4esl\\empty-working-directory",
"seconds": 0.01599999999996271,
"exit_code": 0,
"timed_out": false,
"stdout": "{\"dimension\":3,\"validated\":true}\n",
"stderr": "warning: material material-bb63b094febbf90bb7049c9bcf7587bc has features beyond the initial base-color/PBR renderer\n"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,235 @@
{
"format": "faset.validation-evidence",
"version": 1,
"status": "passed",
"scope": "Hosted Windows developer build, first-project UI contracts, software Vulkan and relocated Release games",
"source_checkout": {
"commit": "4cb82556de31268d2bde73948dd1ff1b6c02f162",
"fresh_checkout": true,
"clean_commit_match": true,
"checkout_step": "actions/checkout on a fresh GitHub-hosted runner; engine and game targets configured and built in this job"
},
"ci": {
"run_url": "https://github.com/emil28092005/Faset_Engine/actions/runs/35301244334",
"job_url": "https://github.com/emil28092005/Faset_Engine/actions/runs/35301244334/job/105465851154",
"started_utc": "2026-09-18T03:04:44Z",
"finished_utc": "2026-09-18T03:19:49Z",
"conclusion": "success",
"steps": [
{
"name": "Set up job",
"conclusion": "success"
},
{
"name": "Run actions/checkout@11d5960a326750d5838078e36cf38b85af677262",
"conclusion": "success"
},
{
"name": "Run actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065",
"conclusion": "success"
},
{
"name": "Visual Studio x64 environment",
"conclusion": "success"
},
{
"name": "Enable the documented Windows long-path developer profile",
"conclusion": "success"
},
{
"name": "Identify pinned Vulkan source cache",
"conclusion": "success"
},
{
"name": "Restore pinned Vulkan test tools",
"conclusion": "success"
},
{
"name": "Build official Vulkan loader and SwiftShader from pinned sources",
"conclusion": "success"
},
{
"name": "Save successfully built Vulkan test tools",
"conclusion": "skipped"
},
{
"name": "Probe Vulkan loader and SwiftShader before compiling the engine",
"conclusion": "success"
},
{
"name": "Fetch checksum-verified Slang compiler",
"conclusion": "success"
},
{
"name": "Configure full editor and Player",
"conclusion": "success"
},
{
"name": "Build full editor and tests",
"conclusion": "success"
},
{
"name": "CPU contracts and software GPU pixel tests",
"conclusion": "success"
},
{
"name": "Real Release exports, 2D and 3D execution, incremental Debug rebuild",
"conclusion": "success"
},
{
"name": "Export and relocate both checked-in playable games",
"conclusion": "success"
},
{
"name": "Preserve graphics and export evidence",
"conclusion": "success"
},
{
"name": "Post Run actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065",
"conclusion": "success"
},
{
"name": "Post Run actions/checkout@11d5960a326750d5838078e36cf38b85af677262",
"conclusion": "success"
},
{
"name": "Complete job",
"conclusion": "success"
}
]
},
"environment": {
"runner_label": "windows-2025",
"observed_setup": [
"Microsoft Windows Server 2025",
"Image: windows-2025-vs2026",
"Version: 20260907.229.1",
"-- The CXX compiler identification is Clang 20.1.8 with MSVC-like command-line"
],
"compiler": "Clang 20.1.8 clang-cl, x64 MSVC ABI",
"editor_configuration": "Debug, FASET_DEBUG_IMGUI=ON",
"export_configuration": "Release, independent Ninja build with custom project C++ sources",
"windows_long_paths_enabled": true,
"device": "SwiftShader Device (LLVM 10.0.0)",
"validation_layer_available": false
},
"tests": {
"total": 35,
"passed": 35,
"failed": 0,
"skipped": 0,
"details": "tests.json"
},
"first_project_evidence": {
"test": "editor_ui_launcher",
"observed": "Create/Open through the instrumented launcher, Unicode paths, recents, directory browsing, validation and keyboard navigation; actual retained UI and software-rendered frame"
},
"games": [
{
"name": "collect-2d",
"generation": "260a10cf-3fe6-4689-ac7b-69b577954cc2",
"configuration": "Release",
"frames": 120,
"validation_enabled": false,
"validation_errors": 0,
"source_paths_hidden": true,
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 7,
"sha256": "f9d74067d87c1ebf30c6db372a26d93b7285791c8cb0307a24af5adb23bf201a"
},
"export_compiler_evidence": [
"-- The C compiler identification is Clang 20.1.8 with MSVC-like command-line",
"-- The CXX compiler identification is Clang 20.1.8 with MSVC-like command-line"
]
},
{
"name": "collect-3d",
"generation": "66191118-07cc-477b-9dc7-1e870be5ebc3",
"configuration": "Release",
"frames": 120,
"validation_enabled": false,
"validation_errors": 0,
"source_paths_hidden": true,
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 43,
"sha256": "0edc88eef9f641a780ef44664f58c811a22bd201c8d29e1fcb7531b88f251900"
},
"export_compiler_evidence": [
"-- The C compiler identification is Clang 20.1.8 with MSVC-like command-line",
"-- The CXX compiler identification is Clang 20.1.8 with MSVC-like command-line"
]
}
],
"limitations": [
"Software Vulkan on a GitHub-hosted Windows Server VM, not a physical desktop GPU qualification or performance benchmark.",
"Khronos validation layer is absent. Zero reported validation errors does not establish validation-layer coverage.",
"Fresh developer-toolchain setup and first-project tests are covered; no retail installer or manual OS IME composition acceptance is claimed."
],
"original_ci_log_sha256": "ada23530651f10bba0e4aec1e0bbe3f4ba243c6dbf9dd5416a744598b4988552",
"retained_files": [
{
"path": "collect-2d-manifest.json",
"sha256": "53b70e2ffc6e4c455fa5cad21eee2735f1c3182e83ed3051b6b5457cb11af615",
"size": 4315
},
{
"path": "collect-2d-run.json",
"sha256": "9dcc203502d2504ffcc9dcfed4ecde118bda954ee8d4604e0b6992839390b731",
"size": 1011
},
{
"path": "collect-2d-validate.json",
"sha256": "ffd23bb53b1d0f7da885ec40f6ab932f3aee3cc17afc3c620abc16ec4a661d4c",
"size": 446
},
{
"path": "collect-2d.png",
"sha256": "c380583f2fd8bc09c5b0e1c6e755c35c3d64b5385ed66177b04d1bbdb532b236",
"size": 4525
},
{
"path": "collect-3d-manifest.json",
"sha256": "4f86450eec32e773fbdbfa93d0da69abab3056cf869b6fa1b4e4647fe27ccc3d",
"size": 5923
},
{
"path": "collect-3d-run.json",
"sha256": "cd21db5286b96f4d4a9a3e381563797230d0b1d8cdd8fbfb644eb272e9b82af6",
"size": 1127
},
{
"path": "collect-3d-validate.json",
"sha256": "6dedaca4e4ffa573b7991856e5f3464bf93f6e825192ad4c4d35693d731531ad",
"size": 562
},
{
"path": "collect-3d.png",
"sha256": "48ef9a82378f74e8ad5a0c438bb730773d684cce809afcef461756a38476375e",
"size": 20366
},
{
"path": "playable-report.json",
"sha256": "3783533ba50b9b4b2fe29fa3e13cbaf7c43b96d29dce8365fa72ae014bc960f1",
"size": 14528
},
{
"path": "probe.json",
"sha256": "0c85c12510dbb0d895faefb7d14f26935a5add675cf0477b29f543d72eed40d6",
"size": 1019
},
{
"path": "tests.json",
"sha256": "1115633850520f66159d204210249bd5eacf57e5b0f9c5f5f70834cd149a0634",
"size": 4731
},
{
"path": "toolchain.json",
"sha256": "969162d1f994724ff6f98d20353e62e3881477ebce98759ca325c8febb175176",
"size": 937
}
]
}
@@ -0,0 +1,435 @@
{
"format": "faset.playable-export-verification",
"version": 1,
"started_utc": "2026-09-18T03:15:38.883680+00:00",
"platform": "win32",
"engine": "D:\\a\\Faset_Engine\\Faset_Engine",
"editor": "D:\\a\\Faset_Engine\\Faset_Engine\\build\\windows-debug\\faset_editor.exe",
"standalone_root": "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-8qma4esl",
"frames_per_game": 120,
"status": "passed",
"projects": [
{
"name": "collect-2d",
"dimension": 2,
"source_inputs": [
{
"path": ".gitignore",
"sha256": "3bb936ff6f84f3db041c75d6e207138a9107997cc9ad372621f657456c9c0665"
},
{
"path": "project.faset.json",
"sha256": "27bd0e432381f8f884e78f6d68e9d0dc13d49222e70f3a5e34fef1d57d379b0b"
},
{
"path": "README.md",
"sha256": "7543b24c9ac87d6a7390d61d5289518e5f5725f3c0052675659f6da716883bdd"
},
{
"path": "Scenes/main.scene.json",
"sha256": "9e2ad049f60a9ff7f98e31061a8f107648fbf672e5b9292f53e6672e5bae66f1"
},
{
"path": "Scripts/Gameplay.cpp",
"sha256": "99fb965e6589b138049a2e6cf95c5c61baea70761ffab33082a8058c91a687ff"
},
{
"path": "Scripts/Gameplay.hpp",
"sha256": "9d1fa36da50886fa7d84f8a8b3d1ae42856ae74aae1e52be5015b60977634dc0"
}
],
"generation": "260a10cf-3fe6-4689-ac7b-69b577954cc2",
"configuration": "Release",
"standalone_directory": "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-8qma4esl\\Faset Café 世界\\collect-2d",
"executable": "faset_player.exe",
"package_file_count": 18,
"asset_generations": {},
"device": "SwiftShader Device (LLVM 10.0.0)",
"validation_enabled": false,
"validation_errors": 0,
"completed_frames": 120,
"summary_ms": {
"gpu": {
"max": 129.9081,
"min": 1.8272,
"p50": 2.0341,
"p95": 2.6394,
"samples": 120
},
"render_call": {
"max": 131.02,
"min": 2.0275,
"p50": 2.2678,
"p95": 2.8731,
"samples": 120
},
"renderer_cpu": {
"max": 130.9907,
"min": 2.0248,
"p50": 2.2649,
"p95": 2.869,
"samples": 120
},
"renderer_readback_cpu": {
"max": 0.9709,
"min": 0.1456,
"p50": 0.155,
"p95": 0.1856,
"samples": 120
},
"simulation": {
"max": 0.4173,
"min": 0.267,
"p50": 0.2905,
"p95": 0.3238,
"samples": 120
},
"snapshot": {
"max": 0.4839,
"min": 0.1555,
"p50": 0.2226,
"p95": 0.2468,
"samples": 120
},
"wall": {
"max": 132.1731,
"min": 2.5367,
"p50": 2.7893,
"p95": 3.4346,
"samples": 120
}
},
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 7,
"sha256": "f9d74067d87c1ebf30c6db372a26d93b7285791c8cb0307a24af5adb23bf201a"
},
"source_project_paths_unavailable": true,
"status": "passed"
},
{
"name": "collect-3d",
"dimension": 3,
"source_inputs": [
{
"path": ".gitignore",
"sha256": "3bb936ff6f84f3db041c75d6e207138a9107997cc9ad372621f657456c9c0665"
},
{
"path": "Assets/exit-arch/create.py",
"sha256": "5d9dc30a3f939376d2474cf958065e6daab140c7220444cedecd3bf5edf6e3d1"
},
{
"path": "Assets/exit-arch/manifest.json",
"sha256": "d72d44184d665148109de74782bc695d79392e43be12782e48096a7d79e37c4f"
},
{
"path": "Assets/exit-arch/manifest.json.faset-import.json",
"sha256": "7639d4fa33f2ac52e10d1cf79280a93aeb680399d217fda167acec1c6edcae00"
},
{
"path": "Assets/exit-arch/payload/ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3.glb",
"sha256": "ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3"
},
{
"path": "Assets/exit-arch/source.blend",
"sha256": "ff2676bec97ab778e531e87a185ad716049ea3b05b4a4f0027a6345737735035"
},
{
"path": "project.faset.json",
"sha256": "9a0ee78fa0ea56982a99cf707948a33db37266ab84de9d3f12a24aabb98afda5"
},
{
"path": "README.md",
"sha256": "a2378adc9dbd5ffb6bfa65abb7770051827d08279e48761046f30d932a5eab8c"
},
{
"path": "Scenes/main.scene.json",
"sha256": "b1b668a2d633deda0074b475460c82019dbcf592b76502906192eaf1f2345a7e"
},
{
"path": "Scripts/Extensions/Beacon.hpp",
"sha256": "650e75bbc55a3339647bcf67ba10fd9b20cece3531bb688b4221155e97959d5e"
},
{
"path": "Scripts/Gameplay.cpp",
"sha256": "c76fbb0bf28a85cc88732a905f2bc9be3e24c58d02cbf2ecccbf77c99093c029"
},
{
"path": "Scripts/Gameplay.hpp",
"sha256": "9d1fa36da50886fa7d84f8a8b3d1ae42856ae74aae1e52be5015b60977634dc0"
}
],
"import": {
"asset_id": "5832763b-3ed0-44d6-9088-0b524f196a91",
"cache_hit": false,
"diagnostics": [],
"generation": "e8eb8805673cc22596d1c5b48fc6b8fbdf600d1c6a15c9633d14014a7eb1d11f",
"manifest": {
"asset_id": "5832763b-3ed0-44d6-9088-0b524f196a91",
"files": [
{
"path": "meshes/mesh-fc069a07a87e0f831fd92efdbc4efa1f-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "meshes/mesh-0961e2a3f92977836a3aa80cab09277e-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "meshes/mesh-93b5b3e52cb6bb120fefb80e2d8e222b-0.fmesh",
"sha256": "3875c16ea628d4126373ebfe07816621fbad3961596a2fcd112f1eec0e378071",
"size": 928
}
],
"generation": "e8eb8805673cc22596d1c5b48fc6b8fbdf600d1c6a15c9633d14014a7eb1d11f",
"importer": "faset-gltf-1/cgltf-1.15",
"input_key": {
"bundle_sha256": "d72d44184d665148109de74782bc695d79392e43be12782e48096a7d79e37c4f",
"dependencies": {},
"importer": "faset-gltf-1/cgltf-1.15",
"settings": {},
"source": "ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3",
"target_profile": "desktop-static-pbr-v1",
"toolchain": {
"cgltf": "360db1a95480fe102ae9c69b27c5d101167ff5ba",
"stb": "2c980bb59875b0d32144a71867fbdebb2f77cd20"
}
},
"kind": "scene",
"materials": [
{
"alpha_cutoff": 0.5,
"alpha_mode": "OPAQUE",
"base_color": [
0.2199999988079071,
0.28999999165534973,
0.3400000035762787,
1.0
],
"base_color_texture": -1,
"double_sided": true,
"emissive": [
0.0,
0.0,
0.0
],
"emissive_texture": -1,
"format": "faset.material",
"id": "material-bb63b094febbf90bb7049c9bcf7587bc",
"metallic": 0.07999999821186066,
"metallic_roughness_texture": -1,
"name": "Weathered stone",
"normal_texture": -1,
"occlusion_texture": -1,
"roughness": 0.800000011920929,
"unlit": false,
"version": 1
}
],
"meshes": [
{
"id": "mesh-fc069a07a87e0f831fd92efdbc4efa1f",
"name": "Cube.001",
"primitives": [
{
"material": 0,
"path": "meshes/mesh-fc069a07a87e0f831fd92efdbc4efa1f-0.fmesh"
}
]
},
{
"id": "mesh-0961e2a3f92977836a3aa80cab09277e",
"name": "Cube.002",
"primitives": [
{
"material": 0,
"path": "meshes/mesh-0961e2a3f92977836a3aa80cab09277e-0.fmesh"
}
]
},
{
"id": "mesh-93b5b3e52cb6bb120fefb80e2d8e222b",
"name": "Cube.003",
"primitives": [
{
"material": 0,
"path": "meshes/mesh-93b5b3e52cb6bb120fefb80e2d8e222b-0.fmesh"
}
]
}
],
"nodes": [
{
"id": "node-cec6e8cea80cf5a98ba3263a0156bee4",
"local_transform": [
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
1.4500000476837158,
1.2999999523162842,
1.0
],
"mesh": 0,
"name": "Left post",
"parent_id": "",
"stable_source_id": true
},
{
"id": "node-5f8a90a6c4d27020d20bd4fa81d1d958",
"local_transform": [
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
1.4500000476837158,
-1.2999999523162842,
1.0
],
"mesh": 1,
"name": "Right post",
"parent_id": "",
"stable_source_id": true
},
{
"id": "node-482463e486a8d1fd741340c653cd128f",
"local_transform": [
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
3.0,
0.0,
1.0
],
"mesh": 2,
"name": "Lintel",
"parent_id": "",
"stable_source_id": true
}
],
"outputs": [
"node-cec6e8cea80cf5a98ba3263a0156bee4",
"node-5f8a90a6c4d27020d20bd4fa81d1d958",
"node-482463e486a8d1fd741340c653cd128f",
"mesh-fc069a07a87e0f831fd92efdbc4efa1f",
"mesh-0961e2a3f92977836a3aa80cab09277e",
"mesh-93b5b3e52cb6bb120fefb80e2d8e222b",
"material-bb63b094febbf90bb7049c9bcf7587bc"
],
"payload_source": "D:\\a\\Faset_Engine\\Faset_Engine\\.cache\\windows-playable-exports\\Faset Café 世界\\collect-3d\\Assets\\exit-arch\\payload/ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3.glb",
"schema_version": 1,
"settings": {},
"source": "D:\\a\\Faset_Engine\\Faset_Engine\\.cache\\windows-playable-exports\\Faset Café 世界\\collect-3d\\Assets\\exit-arch\\manifest.json",
"source_sha256": "ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3",
"textures": []
},
"previous_generation": "",
"removed_output_ids": []
},
"generation": "66191118-07cc-477b-9dc7-1e870be5ebc3",
"configuration": "Release",
"standalone_directory": "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\faset-playable-exports-8qma4esl\\Faset Café 世界\\collect-3d",
"executable": "faset_player.exe",
"package_file_count": 23,
"asset_generations": {
"5832763b-3ed0-44d6-9088-0b524f196a91": "e8eb8805673cc22596d1c5b48fc6b8fbdf600d1c6a15c9633d14014a7eb1d11f"
},
"device": "SwiftShader Device (LLVM 10.0.0)",
"validation_enabled": false,
"validation_errors": 0,
"completed_frames": 120,
"summary_ms": {
"gpu": {
"max": 203.9357,
"min": 14.9149,
"p50": 15.2496,
"p95": 18.9976,
"samples": 120
},
"render_call": {
"max": 205.0598,
"min": 15.1706,
"p50": 15.5334,
"p95": 19.2678,
"samples": 120
},
"renderer_cpu": {
"max": 205.0432,
"min": 15.166,
"p50": 15.5286,
"p95": 19.2578,
"samples": 120
},
"renderer_readback_cpu": {
"max": 0.8953,
"min": 0.1466,
"p50": 0.1597,
"p95": 0.2174,
"samples": 120
},
"simulation": {
"max": 0.5335,
"min": 0.3274,
"p50": 0.4679,
"p95": 0.5036,
"samples": 120
},
"snapshot": {
"max": 3.3124,
"min": 0.1893,
"p50": 0.2114,
"p95": 0.2532,
"samples": 120
},
"wall": {
"max": 209.6105,
"min": 15.8408,
"p50": 16.2166,
"p95": 19.948,
"samples": 120
}
},
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 43,
"sha256": "0edc88eef9f641a780ef44664f58c811a22bd201c8d29e1fcb7531b88f251900"
},
"source_project_paths_unavailable": true,
"status": "passed"
}
],
"finished_utc": "2026-09-18T03:19:42.166192+00:00"
}
@@ -0,0 +1,32 @@
{
"format": "faset.windows-vulkan-probe",
"version": 1,
"status": "passed",
"loader": "D:\\a\\Faset_Engine\\Faset_Engine\\.cache\\windows-graphics\\sdk\\bin\\vulkan-1.dll",
"driver_manifest": "D:\\a\\Faset_Engine\\Faset_Engine\\.cache\\windows-graphics\\driver\\vk_swiftshader_icd.json",
"driver_library": "D:\\a\\Faset_Engine\\Faset_Engine\\.cache\\windows-graphics\\driver\\vk_swiftshader.dll",
"manifest": {
"file_format_version": "1.0.0",
"ICD": {
"library_path": ".\\vk_swiftshader.dll",
"api_version": "1.0.5"
}
},
"loader_debug": "all",
"pointer_bits": 64,
"icd_negotiate_result": 0,
"icd_interface_version": 7,
"enumerate_version_result": 0,
"loader_api_version": "1.4.341",
"create_instance_result": 0,
"devices": [
{
"name": "SwiftShader Device (LLVM 10.0.0)",
"api_version": "1.3.0",
"driver_version": 20971520,
"vendor_id": 6880,
"device_id": 49374,
"device_type": 4
}
]
}
@@ -0,0 +1,206 @@
{
"format": "faset.ctest-summary",
"version": 1,
"source_commit": "4cb82556de31268d2bde73948dd1ff1b6c02f162",
"passed": 35,
"failed": 0,
"skipped": 0,
"source_log_sha256": "1d2038ce1751098d5fe99cf3a8da9e743ce4bf1c0fa014613abbbd5dd6cedbe7",
"tests": [
{
"name": "authoring",
"status": "passed",
"seconds": 0.89
},
{
"name": "runtime_contracts",
"status": "passed",
"seconds": 0.21
},
{
"name": "assets_pipeline",
"status": "passed",
"seconds": 1.03,
"observations": [
"assets: geometry/PBR/texture, GLB/glTF, cache, rename, deletion, overrides, failure, cancellation, standalone PNG/JPEG OK"
]
},
{
"name": "assets_blender_bundle",
"status": "passed",
"seconds": 0.28
},
{
"name": "render_graph",
"status": "passed",
"seconds": 0.14
},
{
"name": "render_offscreen",
"status": "passed",
"seconds": 0.64
},
{
"name": "render_sprite_alpha",
"status": "passed",
"seconds": 0.26
},
{
"name": "render_shader_reload",
"status": "passed",
"seconds": 2.56
},
{
"name": "render_window_lifecycle",
"status": "passed",
"seconds": 1.03,
"observations": [
"driver=windows stage=created",
"stage=resized width=360 height=300 resize_events=2",
"clipboard=unicode_roundtrip_passed",
"text_input_area=coordinate_conversion_passed",
"stage=minimized fresh_frames=12 minimize_events=1 focus_lost_events=1",
"stage=restored restored_events=1 activation_required=0 frames=54 validation_enabled=0 validation_errors=0"
]
},
{
"name": "player_scene_contracts",
"status": "passed",
"seconds": 0.14
},
{
"name": "player_shutdown_diagnostics",
"status": "passed",
"seconds": 0.19
},
{
"name": "editor_mcp",
"status": "passed",
"seconds": 0.05
},
{
"name": "process_and_cook",
"status": "passed",
"seconds": 0.62
},
{
"name": "build_schema_publication",
"status": "passed",
"seconds": 6.47
},
{
"name": "editor_plugins",
"status": "passed",
"seconds": 0.17
},
{
"name": "editor_session_settings",
"status": "passed",
"seconds": 0.18
},
{
"name": "ui_widgets",
"status": "passed",
"seconds": 0.15
},
{
"name": "ui_render",
"status": "passed",
"seconds": 0.49
},
{
"name": "editor_ui_import_conflicts",
"status": "passed",
"seconds": 0.94
},
{
"name": "editor_ui_project_settings",
"status": "passed",
"seconds": 1.16
},
{
"name": "editor_ui_reload",
"status": "passed",
"seconds": 4.25
},
{
"name": "editor_ui_launcher",
"status": "passed",
"seconds": 0.67,
"observations": [
"Launcher Unicode/create/open/validation/recents/directory browser/keyboard passed. C:\\Users\\runneradmin\\AppData\\Local\\Temp\\faset-проекты-427259f4-0109-4b0c-b901-4af129291d3a"
]
},
{
"name": "editor_ui_templates",
"status": "passed",
"seconds": 2.88
},
{
"name": "editor_ui_gizmos",
"status": "passed",
"seconds": 1.15
},
{
"name": "editor_ui_authoring",
"status": "passed",
"seconds": 1.94
},
{
"name": "editor_mcp_stdio",
"status": "passed",
"seconds": 0.57
},
{
"name": "editor_gui_mcp",
"status": "passed",
"seconds": 4.77,
"observations": [
"Native GUI + MCP shared authoring, revision conflict, Undo, 12 fresh PNG captures and continued responsiveness passed"
]
},
{
"name": "core",
"status": "passed",
"seconds": 0.16
},
{
"name": "tutorial_moving",
"status": "passed",
"seconds": 0.03
},
{
"name": "tutorial_following",
"status": "passed",
"seconds": 0.02
},
{
"name": "tutorial_spawning",
"status": "passed",
"seconds": 0.04
},
{
"name": "tutorial_physics",
"status": "passed",
"seconds": 0.11
},
{
"name": "playable_2d",
"status": "passed",
"seconds": 0.62
},
{
"name": "playable_3d",
"status": "passed",
"seconds": 1.33
},
{
"name": "editor_debug_overlay",
"status": "passed",
"seconds": 0.22,
"observations": [
"ImGui diagnostics, F12, font atlas, clipping and event isolation passed"
]
}
]
}
@@ -0,0 +1,23 @@
{
"sources": {
"headers": {
"repository": "KhronosGroup/Vulkan-Headers",
"commit": "0d3f509e57041fbd073a1ee84cd9ccd36d148446",
"sha256": "e82005a6bd3289ce213232ef41e0f8722d1365ed72bf5cc4be56911c56bc30e0"
},
"loader": {
"repository": "KhronosGroup/Vulkan-Loader",
"commit": "32fcb949e253cbeb40cda7ea76122b492db579ae",
"sha256": "610fa9017226c49bc4f19398d94fce9e3c9ff94c14f6420f9fe24822707feacf"
},
"swiftshader": {
"repository": "google/swiftshader",
"commit": "1e80438d2b93ef36a7c05f8d2b81233bac0e3d16",
"sha256": "1c3a1afc397c7aa4d3275790bc9b451e80b144a1db665135d5519838bacf44a8"
}
},
"validation_layer": false,
"vulkan_sdk": "D:\\a\\Faset_Engine\\Faset_Engine\\.cache\\windows-graphics\\sdk",
"driver": "D:\\a\\Faset_Engine\\Faset_Engine\\.cache\\windows-graphics\\driver\\vk_swiftshader_icd.json",
"loader_only": false
}
+1 -1
View File
@@ -41,7 +41,7 @@ The Player window title and console identify the controls and objective; the gam
`project.faset.json` chooses `Scenes/main.scene.json`. The scene stores object/component IDs, transforms, physics settings, and entity references in the collector component. `Scripts/Gameplay.cpp` owns the round's transient C++ state and registers its editable schema. `speed` and `jump_speed` use metres per second.
Change JSON and restart Play to see new level data. Change C++ or its schema, then stop, build, and restart; C++ hot reload is not implemented. Runtime pickup progress does not rewrite the authoring scene or create Undo entries.
Edit the scene through the Inspector and restart Play to see new level data. Change C++ or its schema, then stop, build, and restart; C++ hot reload is not implemented. Runtime pickup progress does not rewrite the authoring scene or create Undo entries.
## Verify
+3
View File
@@ -66,6 +66,9 @@ class AssetPipeline : public AssetStore {
explicit AssetPipeline(std::filesystem::path cache_root);
ImportResult import_asset(const ImportRequest& request, ImportJob& job);
ImportResult import_asset(const ImportRequest& request);
// Inspect source/recipe/dependency hashes without importing or changing the
// active generation. Only editor tools need source freshness; Player does not.
Json freshness(const std::string& asset_id) const;
// Overrides are authoring data beside the source, never generated cache contents.
Json overrides(const std::string& asset_id) const;
void set_overrides(const std::string& asset_id, const Json& overrides);
+3
View File
@@ -56,5 +56,8 @@ template <class T> class TypeRegistration {
Json schema_;
};
SchemaRegistry builtin_schemas();
// Validate a gameplay schema array/types manifest, reserving native TypeIds and
// rejecting repeated gameplay TypeIds. The result includes built-in schemas.
SchemaRegistry gameplay_schemas(const Json& manifest);
void validate_field(const Json& value, const Json& descriptor);
} // namespace faset::authoring
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <faset/render/renderer.hpp>
#include <memory>
#include <span>
namespace faset::editor {
// Optional developer diagnostics. This module never edits authoring or runtime state.
class DebugOverlay {
public:
DebugOverlay();
~DebugOverlay();
DebugOverlay(const DebugOverlay&) = delete;
DebugOverlay& operator=(const DebugOverlay&) = delete;
bool visible() const;
void set_visible(bool);
// F12 toggles the overlay; events captured by its widgets are removed for this frame.
std::vector<render::Event> process_events(std::span<const render::Event>);
void append(render::Snapshot&, const render::Renderer&, float delta_seconds);
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace faset::editor
+9
View File
@@ -62,6 +62,12 @@ struct Quad {
std::shared_ptr<const Texture> texture{};
std::array<float, 4> uv_rect{0, 0, 1, 1};
};
struct UiTriangles {
// Non-indexed triangle list in drawable pixels, rendered after UI quads/text.
std::vector<Vertex> vertices;
std::shared_ptr<const Texture> texture{};
std::array<float, 4> clip_rect{}; // x/y/width/height; zero size uses the full target
};
struct Text {
float x{}, y{};
std::string value;
@@ -81,6 +87,7 @@ struct Snapshot {
// UI coordinates are drawable pixels, top-left origin. Order is preserved per list.
std::vector<Quad> ui_quads;
std::vector<Text> ui_text;
std::vector<UiTriangles> ui_triangles;
};
// CPU-only validation used before publishing a game or creating Vulkan pipelines.
void validate_shader_bundle(const std::filesystem::path& directory);
@@ -119,6 +126,8 @@ struct Event {
struct FrameStats {
std::uint64_t frame{};
bool validation_enabled{};
bool gpu_labels_enabled{};
std::uint32_t gpu_label_count{};
// Live VkDeviceMemory allocation sizes, including alignment; excludes driver internals.
std::uint64_t gpu_allocated_bytes{};
std::uint32_t texture_count{};
+2
View File
@@ -170,6 +170,8 @@ class Context {
void draw(render::Snapshot&);
bool update_text(const std::string& id, const std::string& value, bool force = false);
bool update_number(const std::string& id, double value, bool force = false);
// Reveal the target in enclosing vertical scrollers without changing text
// edits; a previous field must commit successfully before focus moves.
bool focus(const std::string& id);
const std::string& focused_id() const;
void clear_focus(bool commit = true);
+1
View File
@@ -45,6 +45,7 @@ nav:
- Assets and Blender: editor/assets.md
- Build, Play, and export: editor/export.md
- Profiling and measurements: editor/profiling.md
- Optional developer diagnostics: editor/diagnostics.md
- MCP and command line: editor/mcp.md
- Native extensions: editor/extensions.md
- Contributing to this manual: contributing.md
+21 -1
View File
@@ -133,7 +133,27 @@ Json AssetStore::current_manifest(const std::string& id) const {
if (manifest.at("asset_id").get<std::string>() != id ||
manifest.at("generation").get<std::string>() != generation)
throw std::runtime_error("Cooked manifest identity does not match its generation");
// One pointer snapshot prevents mixing two concurrently published generations.
// Locations belong to the active pointer, not the immutable content recipe:
// moving sources can reuse an existing generation whose paths are historical.
// One pointer snapshot keeps logical source and payload in the same publication.
if (pointer.contains("payload_source"))
manifest["payload_source"] = pointer.at("payload_source");
else {
// Earlier pointers only stored source. Ordinary files are their own
// payload; a bundle payload keeps its path relative to the manifest.
const auto previous_source =
faset::path_from_utf8(manifest.at("source").get<std::string>());
const auto previous_payload = faset::path_from_utf8(
manifest.value("payload_source", faset::path_to_utf8(previous_source)));
const auto current_source = faset::path_from_utf8(pointer.at("source").get<std::string>());
const auto current_payload =
previous_payload == previous_source
? current_source
: (current_source.parent_path() /
previous_payload.lexically_relative(previous_source.parent_path()))
.lexically_normal();
manifest["payload_source"] = faset::path_to_utf8(current_payload);
}
manifest["source"] = pointer.at("source");
return manifest;
}
+72 -1
View File
@@ -828,7 +828,8 @@ ImportResult AssetPipeline::import_asset(const ImportRequest& request, ImportJob
throw Cancelled{};
atomic_json(asset_root / "current.json", {{"schema_version", 1},
{"generation", result.generation},
{"source", faset::path_to_utf8(logical_source)}});
{"source", faset::path_to_utf8(logical_source)},
{"payload_source", faset::path_to_utf8(source)}});
result.status = ImportStatus::succeeded;
job.report(1, "complete");
} catch (const Cancelled&) {
@@ -844,6 +845,76 @@ ImportResult AssetPipeline::import_asset(const ImportRequest& request, ImportJob
}
return result;
}
Json AssetPipeline::freshness(const std::string& id) const {
Json result{{"state", "current"}, {"reasons", Json::array()}};
auto stale = [&](const std::string& code, const fs::path& path, const std::string& message) {
result["state"] = "stale";
result["reasons"].push_back(
{{"code", code}, {"path", faset::path_to_utf8(path)}, {"message", message}});
};
try {
const auto manifest = current_manifest(id);
result["generation"] = manifest.at("generation");
const auto source = faset::path_from_utf8(manifest.at("source").get<std::string>());
const auto payload =
faset::path_from_utf8(manifest.at("payload_source").get<std::string>());
const auto& key = manifest.at("input_key");
auto check_file = [&](const fs::path& path, const std::string& digest,
const std::string& code) {
try {
if (faset::sha256_file(path) != digest)
stale(code, path, "Input changed; reimport to update the active generation");
} catch (const std::exception& error) {
stale("input.unavailable", path, error.what());
}
};
check_file(payload, manifest.at("source_sha256"), "source.changed");
if (key.contains("bundle_sha256")) {
check_file(source, key.at("bundle_sha256"), "bundle.changed");
// The bundle can declare more than its selected GLB. Validate every
// declared payload, as import does, even when the manifest is unchanged.
try {
const auto bundle = read_json(source);
for (const auto& file : bundle.at("files")) {
const auto relative = faset::path_from_utf8(file.at("path").get<std::string>());
if (relative.is_absolute() ||
faset::generic_path_to_utf8(relative).find("..") != std::string::npos)
throw std::runtime_error("Invalid bundle payload path");
check_file(source.parent_path() / relative, file.at("sha256"),
"bundle.payload_changed");
}
} catch (const std::exception& error) {
stale("bundle.unavailable", source, error.what());
}
}
for (const auto& [uri, digest] : key.at("dependencies").items())
check_file(external_path(payload, uri), digest, "dependency.changed");
const auto sidecar =
faset::path_from_utf8(faset::path_to_utf8(source) + ".faset-import.json");
try {
const auto metadata = read_json(sidecar);
if (metadata.value("schema_version", 0) != 1 || metadata.value("asset_id", "") != id ||
metadata.value("settings", Json::object()) != manifest.at("settings"))
stale("settings.changed", sidecar,
"Import identity or recipe changed; reimport required");
} catch (const std::exception& error) {
stale("settings.unavailable", sidecar, error.what());
}
const bool image = manifest.value("kind", "scene") == "image";
const std::string recipe = image ? "faset-image-1/stb-2.30" : importer_version;
const std::string profile = image ? "desktop-image-v1" : "desktop-static-pbr-v1";
const Json toolchain{{"cgltf", FASET_CGLTF_COMMIT}, {"stb", FASET_STB_COMMIT}};
if (key.at("importer") != recipe || key.at("target_profile") != profile ||
key.at("toolchain") != toolchain)
stale("importer.changed", source,
"Importer, toolchain or target profile changed; reimport required");
} catch (const std::exception& error) {
result["state"] = "unavailable";
result["reasons"].push_back({{"code", "manifest.unavailable"}, {"message", error.what()}});
}
return result;
}
Json AssetPipeline::overrides(const std::string& id) const {
const auto source = current_manifest(id).at("source").get<std::string>();
const auto path = faset::path_from_utf8(source + ".faset-overrides.json");
+74 -3
View File
@@ -1,6 +1,7 @@
#include <array>
#include <cmath>
#include <faset/authoring/schema.hpp>
#include <limits>
#include <set>
namespace faset::authoring {
@@ -52,13 +53,25 @@ void SchemaRegistry::register_schema(const Json& value) {
Json normalized = value;
const auto id = value.at("id").get<std::string>();
require(!id.empty(), "schema.invalid", "TypeId cannot be empty");
require(value.value("version", 1) > 0, "schema.invalid", "Schema version must be positive");
if (value.contains("version")) {
const auto& version = value.at("version");
require(version.is_number_integer() && version > 0 &&
version <= std::numeric_limits<int>::max(),
"schema.invalid", "Schema version must be a positive supported integer");
}
for (auto& [key, field] : normalized["fields"].items()) {
require(field.is_object() && field.contains("default"), "schema.invalid",
"Each field requires a typed default");
require(field.value("id", key) == key, "schema.field_id",
"Field map keys must be stable FieldIds");
field["id"] = key;
for (const auto* limit : {"min", "max"})
if (field.contains(limit))
require(field.at(limit).is_number() && std::isfinite(field.at(limit).get<double>()),
"schema.invalid", "Field limits must be finite numbers");
if (field.contains("enum"))
require(field.at("enum").is_array(), "schema.invalid",
"Field enum choices must be an array");
validate_field(field["default"], field);
}
if (auto found = schemas_.find(id); found != schemas_.end())
@@ -73,6 +86,41 @@ void SchemaRegistry::register_schemas(const Json& values) {
candidate.register_schema(schema);
*this = std::move(candidate);
}
SchemaRegistry gameplay_schemas(const Json& manifest) {
require(manifest.is_array() || (manifest.is_object() && manifest.contains("types") &&
manifest.at("types").is_array()),
"schema.invalid", "Gameplay schemas require an array or a types manifest");
const auto& types = manifest.is_array() ? manifest : manifest.at("types");
auto result = builtin_schemas();
for (const auto& schema : types) {
require(schema.is_object() && schema.contains("id") && schema.at("id").is_string(),
"schema.invalid", "Gameplay schema requires a TypeId");
const auto id = schema.at("id").get<std::string>();
require(!result.contains(id), "schema.duplicate_type",
"Gameplay schema duplicates a builtin or gameplay TypeId: " + id);
result.register_schema(schema);
}
for (const auto& schema : types) {
if (!schema.contains("migrations"))
continue;
require(schema.at("migrations").is_array(), "migration.invalid",
"Migrations must be an array of version steps");
for (const auto& step : schema.at("migrations")) {
require(
step.is_object() && step.contains("from_version") &&
step.at("from_version").is_number_integer() && step.at("from_version") > 0 &&
step.at("from_version") < schema.value("version", 1) &&
step.contains("fields") && step.at("fields").is_object(),
"migration.invalid", "Migration requires a supported earlier version and fields");
for (const auto& [key, unused] : step.items())
require(key == "from_version" || key == "fields", "migration.invalid",
"Unsupported migration step property: " + key);
result.add_migration(schema.at("id"), step.at("from_version").get<int>(),
step.at("fields"));
}
}
return result;
}
bool SchemaRegistry::contains(const std::string& type) const {
return schemas_.contains(type);
}
@@ -110,9 +158,25 @@ void SchemaRegistry::validate_component(const Json& component) const {
validate_field(value, metadata["fields"][id]);
}
void SchemaRegistry::add_migration(const std::string& type, int from_version, Json rules) {
require(from_version > 0 && rules.is_object(), "migration.invalid", "Invalid migration");
require(contains(type) && from_version > 0 && from_version < schema(type).value("version", 1) &&
rules.is_object(),
"migration.invalid", "Migration requires a registered type and an earlier version");
require(!migrations_.contains({type, from_version}), "migration.duplicate",
"Migration already exists");
for (const auto& [field, rule] : rules.items()) {
require(!field.empty() && rule.is_object(), "migration.invalid",
"Migration fields require nonempty IDs and rule objects");
for (const auto& [operation, value] : rule.items()) {
require(operation == "default" || operation == "scale" || operation == "require_manual",
"migration.invalid", "Unsupported migration operation: " + operation);
if (operation == "scale")
require(value.is_number() && std::isfinite(value.get<double>()),
"migration.invalid", "Migration scale must be a finite number");
else if (operation == "require_manual")
require(value.is_boolean(), "migration.invalid",
"Migration require_manual must be a boolean");
}
}
migrations_[{type, from_version}] = std::move(rules);
}
Json SchemaRegistry::migrate_component(const Json& source) const {
@@ -121,6 +185,10 @@ Json SchemaRegistry::migrate_component(const Json& source) const {
if (!contains(type))
return result;
const auto current = schema(type).value("version", 1);
if (result.contains("version"))
require(result.at("version").is_number_integer() && result.at("version") > 0 &&
result.at("version") <= std::numeric_limits<int>::max(),
"migration.invalid", "Component version must be a positive supported integer");
auto version = result.value("version", 1);
if (version > current)
return result;
@@ -134,8 +202,11 @@ Json SchemaRegistry::migrate_component(const Json& source) const {
if (rule.contains("scale") && result["fields"].contains(field)) {
require(result["fields"][field].is_number(), "migration.type",
"Cannot scale a nonnumeric field");
result["fields"][field] =
const auto scaled =
result["fields"][field].get<double>() * rule["scale"].get<double>();
require(std::isfinite(scaled), "migration.nonfinite",
"Migration produced a non-finite field value");
result["fields"][field] = scaled;
}
if (rule.value("require_manual", false) && result["fields"].contains(field))
throw Error("migration.manual", "Field requires explicit manual migration",
+19 -9
View File
@@ -288,9 +288,6 @@ Json AuthoringService::open(const std::filesystem::path& relative, bool recover)
value.data = recovered.at("scene");
value.revision = recovered.value("revision", 0u);
}
for (auto& item : value.data["entities"])
for (auto& component : item["components"])
component = schemas_.migrate_component(component);
documents_.emplace(id, std::move(value));
return summary(state(id));
}
@@ -311,9 +308,7 @@ void AuthoringService::register_schemas(const Json& manifest) {
}
void AuthoringService::replace_external_schemas(const Json& manifest) {
std::lock_guard lock(mutex_);
auto candidate = builtin_schemas();
candidate.register_schemas(manifest);
schemas_ = std::move(candidate);
schemas_ = gameplay_schemas(manifest);
}
void AuthoringService::apply(Json& scene, const Json& command) {
require(command.is_object() && command.contains("op") && command["op"].is_string(),
@@ -377,6 +372,24 @@ void AuthoringService::apply(Json& scene, const Json& command) {
[&](const Json& value) { return value.at("id") == id; });
require(found != values.end(), "component.missing", "Component does not exist");
values.erase(found);
} else if (op == "component.migrate") {
Json* object = nullptr;
if (command.contains("instance")) {
const auto instance_id = command.at("instance").get<std::string>();
for (auto& instance : scene.at("instances"))
if (instance.at("id") == instance_id && instance.contains("additions"))
for (auto& addition : instance.at("additions"))
if (addition.at("id") == command.at("entity"))
object = &addition;
require(object, "template.addition_missing", "Instance-local entity is unavailable");
} else
object = &entity(scene, command.at("entity").get<std::string>());
auto& value = component(*object, command.at("component").get<std::string>());
const auto type = value.at("type").get<std::string>();
require(schemas_.contains(type), "schema.missing", "Component schema unavailable: " + type);
require(value.value("version", 1) <= schemas_.schema(type).value("version", 1),
"migration.future", "Cannot migrate a component from a newer schema");
value = schemas_.migrate_component(value);
} else if (op == "component.set") {
auto& value = component(entity(scene, command.at("entity").get<std::string>()),
command.at("component").get<std::string>());
@@ -603,9 +616,6 @@ Json AuthoringService::recover(const std::string& id,
"recovery.disk_conflict",
"Scene file changed or disappeared since recovery was written");
}
for (auto& item : candidate.data["entities"])
for (auto& component : item["components"])
component = schemas_.migrate_component(component);
validate_scene(candidate.data, schemas_);
if (documents_.contains(id)) {
const auto& current = state(id);
+1 -1
View File
@@ -17,7 +17,7 @@ struct Resolver {
const SceneLoader& loader;
std::string root;
Json conflicts = Json::array();
std::set<std::string> sources;
std::set<std::string> sources{};
void conflict(const Json& path, std::string code, const Json& record) {
conflicts.push_back(
{{"instance_path", path}, {"code", std::move(code)}, {"record", record}});
+22 -17
View File
@@ -4,6 +4,8 @@
#include <condition_variable>
#include <deque>
#include <faset/assets/asset_data.hpp>
#include <faset/assets/asset_pipeline.hpp>
#include <faset/authoring/schema.hpp>
#include <faset/core/hash.hpp>
#include <faset/core/io.hpp>
#include <faset/core/process.hpp>
@@ -33,23 +35,14 @@ void validate_scene(const Json& scene) {
throw std::runtime_error("Scene dimension must be 2 or 3");
}
void validate_component_types(const Json& scene, const Json& schema) {
std::map<std::string, int> versions;
for (const auto* type : {"faset.transform", "faset.sprite", "faset.mesh", "faset.camera",
"faset.light", "faset.rigid_body_2d", "faset.rigid_body_3d"})
versions[type] = 1;
for (const auto& type : schema.value("types", Json::array())) {
auto id = type.at("id").get<std::string>();
if (versions.contains(id))
throw std::runtime_error("Gameplay schema duplicates a component type: " + id);
versions[id] = type.value("version", 1);
}
const auto registry =
schema.is_null() ? authoring::builtin_schemas() : authoring::gameplay_schemas(schema);
for (const auto& entity : scene.at("entities"))
for (const auto& component : entity.value("components", Json::array())) {
const auto id = component.at("type").get<std::string>();
auto found = versions.find(id);
if (found == versions.end())
if (!registry.contains(id))
throw std::runtime_error("Cannot cook unresolved component type: " + id);
if (component.value("version", 1) != found->second)
if (component.value("version", 1) != registry.schema(id).value("version", 1))
throw std::runtime_error("Migrate component '" + id +
"' to the current gameplay schema before cooking");
}
@@ -233,9 +226,16 @@ struct BuildService::Impl {
}
}
void validate_assets(const Json& scene) {
assets::AssetStore pipeline(config.cache_root);
for (const auto& id : asset_references(scene))
assets::AssetPipeline pipeline(config.cache_root);
for (const auto& id : asset_references(scene)) {
pipeline.load_asset(id);
const auto freshness = pipeline.freshness(id);
if (freshness.value("state", std::string("unavailable")) != "current")
throw std::runtime_error("Asset " + id +
" is stale or unavailable. Reimport before cooking or "
"exporting. Details: " +
freshness.dump());
}
}
Json build(Job& job, bool exporting = false) {
const auto& configuration = exporting ? config.export_configuration : config.configuration;
@@ -298,6 +298,9 @@ struct BuildService::Impl {
if (schema.value("format", "") != "faset.schema" || schema.value("version", 0) != 1 ||
!schema.contains("types") || !schema.at("types").is_array())
throw std::runtime_error("SchemaExporter returned an invalid manifest");
// Validate the complete metadata before publishing either the schema or
// its Player generation. Session uses this same authoring contract.
(void)authoring::gameplay_schemas(schema);
std::string fingerprint = sha256_file(player) + sha256_file(exporter) +
read_text(native_directory / "CMakeCache.txt");
for (const auto& file : {"Gameplay.cpp", "Gameplay.hpp"})
@@ -393,7 +396,7 @@ struct BuildService::Impl {
checkpoint(job, "Validating scene and assets", .1);
validate_scene(job.scene);
validate_assets(job.scene);
Json schema = Json::object();
Json schema;
if (fs::exists(config.cache_root / "last_build.json")) {
auto pointer = read_json(config.cache_root / "last_build.json");
auto schema_path = project_path(
@@ -403,7 +406,9 @@ struct BuildService::Impl {
}
validate_component_types(job.scene, schema);
auto source_hash = sha256(job.scene.dump());
auto schema_fingerprint = schema.value("build_fingerprint", std::string("builtin-v1"));
auto schema_fingerprint =
schema.is_null() ? std::string("builtin-v1")
: schema.value("build_fingerprint", std::string("builtin-v1"));
auto digest = sha256(source_hash + schema_fingerprint);
auto directory = config.cache_root / "cooked" / digest;
auto staging = config.cache_root / "cooked" / (".staging-" + job.status.id);
+2 -1
View File
@@ -114,7 +114,8 @@ Commands::Commands(authoring::AuthoringService& authoring) : authoring_(authorin
true);
add("faset_scene_edit",
"Apply one atomic authoring batch with optimistic revision checking and one Undo step. "
"Operations: entity.create/rename/delete/duplicate/reparent; component.add/remove/set; "
"Operations: entity.create/rename/delete/duplicate/reparent; "
"component.add/remove/set/migrate; "
"scene.rename/simulation; "
"template.instance/override/revert/suppress/restore/add/addition_set/reparent/remove/"
"source_set. Use "
+265
View File
@@ -0,0 +1,265 @@
#include <algorithm>
#include <cmath>
#include <faset/editor/debug_overlay.hpp>
#include <imgui.h>
#include <stdexcept>
#include <string_view>
namespace faset::editor {
namespace {
struct CurrentContext {
ImGuiContext* previous{ImGui::GetCurrentContext()};
explicit CurrentContext(ImGuiContext* context) {
ImGui::SetCurrentContext(context);
}
~CurrentContext() {
ImGui::SetCurrentContext(previous);
}
};
ImGuiKey key(std::string_view name) {
if (name.size() == 1 && name[0] >= 'A' && name[0] <= 'Z')
return static_cast<ImGuiKey>(ImGuiKey_A + name[0] - 'A');
if (name.size() == 1 && name[0] >= '0' && name[0] <= '9')
return static_cast<ImGuiKey>(ImGuiKey_0 + name[0] - '0');
const std::pair<std::string_view, ImGuiKey> names[] = {
{"Tab", ImGuiKey_Tab}, {"Left", ImGuiKey_LeftArrow},
{"Right", ImGuiKey_RightArrow}, {"Up", ImGuiKey_UpArrow},
{"Down", ImGuiKey_DownArrow}, {"PageUp", ImGuiKey_PageUp},
{"PageDown", ImGuiKey_PageDown}, {"Home", ImGuiKey_Home},
{"End", ImGuiKey_End}, {"Insert", ImGuiKey_Insert},
{"Delete", ImGuiKey_Delete}, {"Backspace", ImGuiKey_Backspace},
{"Space", ImGuiKey_Space}, {"Return", ImGuiKey_Enter},
{"Escape", ImGuiKey_Escape}};
for (const auto& [label, value] : names)
if (label == name)
return value;
return ImGuiKey_None;
}
} // namespace
struct DebugOverlay::Impl {
ImGuiContext* context{};
bool visible{}, freeze{};
std::uint32_t overlay_buttons{}, editor_buttons{};
std::array<float, 2> pointer{-1, -1};
float scale{};
std::array<float, 4> window_rect{};
render::FrameStats displayed;
std::shared_ptr<render::Texture> atlas;
Impl() {
IMGUI_CHECKVERSION();
auto* previous = ImGui::GetCurrentContext();
context = ImGui::CreateContext();
auto& io = ImGui::GetIO();
io.IniFilename = nullptr;
io.LogFilename = nullptr;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
io.BackendPlatformName = "faset_events";
io.BackendRendererName = "faset_ui_triangles";
unsigned char* pixels{};
int width{}, height{};
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
atlas = std::make_shared<render::Texture>();
atlas->width = width;
atlas->height = height;
atlas->rgba.assign(pixels, pixels + std::size_t(width) * height * 4);
io.Fonts->SetTexID(ImTextureID{1});
ImGui::SetCurrentContext(previous);
}
~Impl() {
ImGui::DestroyContext(context);
}
};
DebugOverlay::DebugOverlay() : impl_(std::make_unique<Impl>()) {}
DebugOverlay::~DebugOverlay() = default;
bool DebugOverlay::visible() const {
return impl_->visible;
}
void DebugOverlay::set_visible(bool value) {
impl_->visible = value;
}
std::vector<render::Event> DebugOverlay::process_events(std::span<const render::Event> events) {
CurrentContext current(impl_->context);
auto& io = ImGui::GetIO();
std::vector<render::Event> forwarded;
const auto pointer_in_overlay = [&] {
return impl_->visible && impl_->pointer[0] >= impl_->window_rect[0] &&
impl_->pointer[1] >= impl_->window_rect[1] &&
impl_->pointer[0] < impl_->window_rect[0] + impl_->window_rect[2] &&
impl_->pointer[1] < impl_->window_rect[1] + impl_->window_rect[3];
};
const auto capture_pointer = [&] {
if (impl_->overlay_buttons)
return true;
if (impl_->editor_buttons)
return false;
return pointer_in_overlay();
};
for (const auto& event : events) {
using Type = render::Event::Type;
if ((event.type == Type::KeyDown || event.type == Type::KeyUp) && event.key == "F12") {
if (event.type == Type::KeyDown && !event.repeat)
impl_->visible = !impl_->visible;
continue;
}
io.AddKeyEvent(ImGuiMod_Ctrl, event.control);
io.AddKeyEvent(ImGuiMod_Shift, event.shift);
io.AddKeyEvent(ImGuiMod_Alt, event.alt);
bool captured{};
bool pointer_event{};
switch (event.type) {
case Type::MouseMove:
io.AddMousePosEvent(event.x, event.y);
impl_->pointer = {event.x, event.y};
pointer_event = true;
captured = capture_pointer();
break;
case Type::MouseDown:
case Type::MouseUp:
io.AddMousePosEvent(event.x, event.y);
impl_->pointer = {event.x, event.y};
pointer_event = true;
captured = capture_pointer();
if (event.button >= 1 && event.button <= 5) {
const int buttons[] = {0, 2, 1, 3, 4};
io.AddMouseButtonEvent(buttons[event.button - 1], event.type == Type::MouseDown);
const auto mask = std::uint32_t{1} << (event.button - 1);
if (event.type == Type::MouseDown) {
if (captured)
impl_->overlay_buttons |= mask;
else
impl_->editor_buttons |= mask;
} else {
if (impl_->overlay_buttons & mask)
captured = true;
else if (impl_->editor_buttons & mask)
captured = false;
impl_->overlay_buttons &= ~mask;
impl_->editor_buttons &= ~mask;
}
}
break;
case Type::Wheel:
io.AddMouseWheelEvent(event.x, event.y);
pointer_event = true;
captured = capture_pointer();
break;
case Type::KeyDown:
case Type::KeyUp:
if (const auto mapped = key(event.key); mapped != ImGuiKey_None)
io.AddKeyEvent(mapped, event.type == Type::KeyDown);
captured = io.WantCaptureKeyboard;
break;
case Type::TextInput:
io.AddInputCharactersUTF8(event.text.c_str());
captured = io.WantCaptureKeyboard;
break;
case Type::FocusGained:
case Type::FocusLost:
io.AddFocusEvent(event.type == Type::FocusGained);
if (event.type == Type::FocusLost)
impl_->overlay_buttons = impl_->editor_buttons = 0;
break;
default:
break;
}
if (!captured || (!pointer_event && !impl_->visible))
forwarded.push_back(event);
}
return forwarded;
}
void DebugOverlay::append(render::Snapshot& output, const render::Renderer& renderer, float delta) {
CurrentContext current(impl_->context);
auto& state = *impl_;
auto& io = ImGui::GetIO();
io.DisplaySize = {float(renderer.width()), float(renderer.height())};
io.DisplayFramebufferScale = {1, 1}; // Faset events and geometry already use drawable pixels.
io.DeltaTime = std::isfinite(delta) && delta > 0 ? std::clamp(delta, .0001f, .1f) : 1.f / 60.f;
const float scale = renderer.display_scale();
if (state.scale != scale) {
auto& style = ImGui::GetStyle();
style = ImGuiStyle{};
ImGui::StyleColorsDark();
style.ScaleAllSizes(scale);
style.FontScaleMain = scale;
if (state.window_rect[2] == 0)
state.window_rect = {16 * scale, 44 * scale, 390 * scale, 310 * scale};
state.scale = scale;
}
// Complete an ImGui frame while hidden too, so queued input cannot accumulate.
ImGui::NewFrame();
if (state.visible) {
ImGui::SetNextWindowPos({16 * scale, 44 * scale}, ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize({390 * scale, 310 * scale}, ImGuiCond_FirstUseEver);
if (ImGui::Begin("Faset diagnostics (F12)", &state.visible,
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoCollapse)) {
const auto position = ImGui::GetWindowPos(), size = ImGui::GetWindowSize();
state.window_rect = {position.x, position.y, size.x, size.y};
if (!state.freeze)
state.displayed = renderer.stats();
const auto& stats = state.displayed;
ImGui::TextUnformatted("Previous completed frame");
ImGui::TextWrapped("%s", stats.device.c_str());
ImGui::Checkbox("Freeze counters", &state.freeze);
ImGui::Separator();
ImGui::Text("Frame: %llu", static_cast<unsigned long long>(stats.frame));
ImGui::Text("Render call (wall): %.3f ms", stats.cpu_ms);
if (stats.gpu_ms > 0)
ImGui::Text("GPU: %.3f ms", stats.gpu_ms);
else
ImGui::TextUnformatted("GPU timestamps: unavailable");
ImGui::Text("Readback (wall): %.3f ms", stats.readback_cpu_ms);
ImGui::Text("Draws: %u Packed vertices: %u", stats.draw_calls, stats.vertices);
ImGui::Text("Culled meshes: %u Textures: %u", stats.culled_meshes,
stats.texture_count);
ImGui::Text("Vulkan allocations: %.2f MiB",
double(stats.gpu_allocated_bytes) / 1048576.0);
ImGui::Text("Validation: %s Errors: %u",
stats.validation_enabled ? "on" : "unavailable/off",
stats.validation_errors);
ImGui::Text("GPU pass labels: %s (%u)", stats.gpu_labels_enabled ? "on" : "unavailable",
stats.gpu_label_count);
}
ImGui::End();
}
ImGui::Render();
const auto* data = ImGui::GetDrawData();
if (!data || !data->Valid)
return;
for (const auto* list : data->CmdLists) {
for (const auto& command : list->CmdBuffer) {
if (command.UserCallback) {
if (command.UserCallback != ImDrawCallback_ResetRenderState)
command.UserCallback(list, &command);
continue;
}
if (command.GetTexID() != ImTextureID{1})
throw std::runtime_error("Unsupported texture in Faset diagnostic overlay");
const float x = command.ClipRect.x - data->DisplayPos.x;
const float y = command.ClipRect.y - data->DisplayPos.y;
const float width = command.ClipRect.z - command.ClipRect.x;
const float height = command.ClipRect.w - command.ClipRect.y;
if (width <= 0 || height <= 0)
continue;
render::UiTriangles batch;
batch.texture = state.atlas;
batch.clip_rect = {x, y, width, height};
batch.vertices.reserve(command.ElemCount);
for (unsigned i = 0; i < command.ElemCount; ++i) {
const auto index = list->IdxBuffer[command.IdxOffset + i] + command.VtxOffset;
const auto& vertex = list->VtxBuffer[index];
const auto channel = [&](unsigned shift) {
return float((vertex.col >> shift) & 0xffu) / 255.f;
};
batch.vertices.push_back(
{{vertex.pos.x - data->DisplayPos.x, vertex.pos.y - data->DisplayPos.y, 0},
{},
{channel(IM_COL32_R_SHIFT), channel(IM_COL32_G_SHIFT),
channel(IM_COL32_B_SHIFT), channel(IM_COL32_A_SHIFT)},
{vertex.uv.x, vertex.uv.y}});
}
output.ui_triangles.push_back(std::move(batch));
}
}
}
} // namespace faset::editor
+59 -4
View File
@@ -847,6 +847,24 @@ struct EditorUI::Impl {
transaction(Json::array(
{{{"op", "component.remove"}, {"entity", selected}, {"component", cid}}}));
}
void migrate_component(const std::string& cid) {
const auto* object = entity(resolved, selected);
if (!object)
return;
Json operation = {{"op", "component.migrate"}, {"entity", selected}, {"component", cid}};
if (owned_addition(*object)) {
operation["instance"] = object->at("origin").at("path").front();
operation["entity"] = object->at("origin").at("object");
for (const auto& c : object->at("components"))
if (c.at("id") == cid)
operation["component"] = c.at("source_id");
} else if (inherited(*object)) {
open_template_source(object->at("origin").at("path"),
object->at("origin").at("object"));
return;
}
transaction(Json::array({operation}));
}
Json relative_address(const Json& object, const std::string& component_id = {},
const std::string& field = {}) const {
const auto& origin = object.at("origin");
@@ -1119,7 +1137,9 @@ struct EditorUI::Impl {
return;
current = session.authoring().query(document);
schemas = session.authoring().schemas().manifest();
const auto document_stamp = session.authoring().documents().dump();
// Resolution also depends on schemas: a rebuilt component version may
// invalidate an override or change how entity references are remapped.
const auto document_stamp = Json::array({session.authoring().documents(), schemas}).dump();
if (shown_revision != current.at("revision").get<std::uint64_t>() ||
resolved_stamp != document_stamp) {
resolved_stamp = document_stamp;
@@ -1440,6 +1460,24 @@ struct EditorUI::Impl {
if (!known) {
label(body, "opaque-note-" + cid, "Schema unavailable. Data is preserved.");
keep.insert("opaque-note-" + cid);
if (has_schema &&
c.value("version", 1) <
session.authoring().schemas().schema(type).value("version", 1)) {
const auto version =
session.authoring().schemas().schema(type).value("version", 1);
label(body, "opaque-note-" + cid,
"Stored v" + std::to_string(c.value("version", 1)) + " / schema v" +
std::to_string(version) + ". Migration is explicit and undoable.");
const auto action_id = "component-migrate-" + cid;
auto& action = button(body, action_id,
source_object && !local_addition
? "Open source to migrate"
: "Migrate to v" + std::to_string(version),
[this, cid] { migrate_component(cid); });
action.tooltip =
"Apply the schema's declared migration rules. Errors keep all stored data.";
keep.insert(action_id);
}
auto& raw =
body.add(Kind::TextField, "opaque-fields-" + cid, c.at("fields").dump());
raw.enabled = false;
@@ -1617,13 +1655,30 @@ struct EditorUI::Impl {
asset.contains("manifest")
? path_to_utf8(path_from_utf8(asset["manifest"].value("source", id)).filename())
: id;
auto& row = list.add(Kind::TreeRow, "asset-" + id, "Imported / " + name);
const auto freshness = asset.value("freshness", Json::object());
const auto state = freshness.value("state", std::string("unavailable"));
const auto prefix = state == "current" ? "Imported"
: state == "stale" ? "Stale"
: "Unavailable";
auto& row =
list.add(Kind::TreeRow, "asset-" + id, std::string(prefix) + " / " + name);
row.layout.height = 25;
row.indent = 1;
row.drag_payload = {{"kind", "asset"}, {"id", id}, {"label", name}};
row.on_click = [this, id](Widget&) {
row.on_click = [this, id, asset, freshness, state](Widget&) {
renderer.set_clipboard(id);
status = "Asset ID copied; drag to viewport or an asset field";
if (asset.contains("manifest")) {
source_file = generic_path_to_utf8(std::filesystem::relative(
path_from_utf8(asset.at("manifest").at("source").get<std::string>()),
session.config().project_root));
assets_dirty = true;
}
status = state == "current"
? "Asset ID copied; drag to viewport or an asset field"
: "Reimport required; select Import to refresh the selected source";
for (const auto& reason : freshness.value("reasons", Json::array()))
report(reason.value("message", "Input changed") + ": " +
reason.value("path", ""));
};
keep.insert(row.id);
}
+3 -2
View File
@@ -120,7 +120,8 @@ Json Session::assets_list() const {
try {
const auto id = path_to_utf8(entry.path().filename());
const auto manifest = assets_.current_manifest(id);
list.push_back({{"id", id}, {"manifest", manifest}});
list.push_back(
{{"id", id}, {"manifest", manifest}, {"freshness", assets_.freshness(id)}});
} catch (const std::exception& error) {
list.push_back(
{{"id", path_to_utf8(entry.path().filename())}, {"error", error.what()}});
@@ -375,7 +376,7 @@ void Session::register_commands() {
return Json{{"settings", value}, {"revision", sha256(value.dump())}};
});
commands_.add(
"faset_assets", "List imported asset manifests and resource identities.",
"faset_assets", "List imported assets with source/dependency/recipe freshness and reasons.",
schema(Json::object()), [&](const Json&) { return assets_list(); }, true);
commands_.add(
"faset_import",
+99 -10
View File
@@ -55,12 +55,14 @@ struct Image {
struct Batch {
std::uint32_t first{}, count{};
const Texture* texture{};
std::array<float, 4> clip_rect{};
};
constexpr std::uint32_t shadow_size = 1024;
} // namespace
struct Renderer::Impl {
RendererConfig config;
SDL_Window* window{};
std::string offscreen_clipboard;
bool sdl{}, close{}, dirty_swapchain{};
std::uint32_t width{}, height{};
VkInstance instance{};
@@ -90,6 +92,8 @@ struct Renderer::Impl {
VkSampler shadow_sampler{}, color_sampler{};
VkPipelineLayout pipeline_layout{};
VkPipeline pipeline{}, ui_pipeline{}, shadow_pipeline{}, sprite_pipeline{};
PFN_vkCmdBeginDebugUtilsLabelEXT begin_gpu_label{};
PFN_vkCmdEndDebugUtilsLabelEXT end_gpu_label{};
struct GpuTexture {
Image image;
VkDescriptorSet descriptor{};
@@ -359,13 +363,24 @@ struct Renderer::Impl {
check(vkEnumerateInstanceLayerProperties(&count, nullptr), "Enumerate layers");
std::vector<VkLayerProperties> layers(count);
check(vkEnumerateInstanceLayerProperties(&count, layers.data()), "Enumerate layers");
bool validation = c.validation && std::any_of(layers.begin(), layers.end(), [](auto& p) {
return std::strcmp(p.layerName, "VK_LAYER_KHRONOS_validation") == 0;
});
check(vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr),
"Enumerate instance extensions");
std::vector<VkExtensionProperties> instance_extensions(count);
check(vkEnumerateInstanceExtensionProperties(nullptr, &count, instance_extensions.data()),
"Enumerate instance extensions");
const bool debug_utils =
std::any_of(instance_extensions.begin(), instance_extensions.end(), [](const auto& p) {
return std::strcmp(p.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0;
});
bool validation =
c.validation && debug_utils && std::any_of(layers.begin(), layers.end(), [](auto& p) {
return std::strcmp(p.layerName, "VK_LAYER_KHRONOS_validation") == 0;
});
statistics.validation_enabled = validation;
if (c.validation && !validation)
std::cerr << "[Faset] Vulkan validation layer not installed; diagnostics disabled.\n";
if (validation)
std::cerr << "[Faset] Vulkan validation layer/debug-utils unavailable; validation "
"disabled.\n";
if (debug_utils)
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
VkApplicationInfo app{};
app.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
@@ -473,6 +488,13 @@ struct Renderer::Impl {
}
check(vkCreateDevice(physical, &di, nullptr, &device), "Create Vulkan device");
vkGetDeviceQueue(device, queue_family, 0, &queue);
if (debug_utils) {
begin_gpu_label = reinterpret_cast<PFN_vkCmdBeginDebugUtilsLabelEXT>(
vkGetDeviceProcAddr(device, "vkCmdBeginDebugUtilsLabelEXT"));
end_gpu_label = reinterpret_cast<PFN_vkCmdEndDebugUtilsLabelEXT>(
vkGetDeviceProcAddr(device, "vkCmdEndDebugUtilsLabelEXT"));
}
statistics.gpu_labels_enabled = begin_gpu_label && end_gpu_label;
VkCommandPoolCreateInfo pi{};
pi.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
pi.queueFamilyIndex = queue_family;
@@ -1000,7 +1022,7 @@ struct Renderer::Impl {
}
void render(const Snapshot& snapshot) {
auto start = std::chrono::steady_clock::now();
statistics.draw_calls = statistics.culled_meshes = 0;
statistics.draw_calls = statistics.culled_meshes = statistics.gpu_label_count = 0;
bool can_present = surface != VK_NULL_HANDLE;
if (surface) {
// A capture may render between normal event-loop iterations. Keep the window
@@ -1033,6 +1055,9 @@ struct Renderer::Impl {
for (const auto& sprite : snapshot.sprites)
if (sprite.texture)
upload_texture(sprite.texture);
for (const auto& triangles : snapshot.ui_triangles)
if (triangles.texture)
upload_texture(triangles.texture);
std::vector<GpuVertex> data;
std::vector<Batch> scene_batches, shadow_batches, sprite_batches, ui_batches;
for (const auto& item : snapshot.draws) {
@@ -1123,6 +1148,24 @@ struct Renderer::Impl {
if (data.size() > text_first)
ui_batches.push_back(
{text_first, static_cast<std::uint32_t>(data.size() - text_first), white.get()});
for (const auto& triangles : snapshot.ui_triangles) {
if (triangles.vertices.size() % 3 != 0)
throw std::invalid_argument("UI triangle list must contain complete triangles");
const auto first = static_cast<std::uint32_t>(data.size());
for (const auto& source : triangles.vertices) {
GpuVertex vertex{};
vertex.clip[0] = source.position[0] / float(width) * 2 - 1;
vertex.clip[1] = source.position[1] / float(height) * 2 - 1;
vertex.clip[3] = 1;
std::copy(source.color.begin(), source.color.end(), vertex.color);
std::copy(source.uv.begin(), source.uv.end(), vertex.uv);
vertex.material[0] = triangles.texture && triangles.texture->srgb ? 1.f : 0.f;
data.push_back(vertex);
}
ui_batches.push_back({first, static_cast<std::uint32_t>(triangles.vertices.size()),
triangles.texture ? triangles.texture.get() : white.get(),
triangles.clip_rect});
}
statistics.vertices = static_cast<std::uint32_t>(data.size());
auto byte_count = std::max<std::size_t>(sizeof(GpuVertex), data.size() * sizeof(GpuVertex));
if (vertices.size < byte_count) {
@@ -1185,7 +1228,33 @@ struct Renderer::Impl {
vkCmdSetScissor(command, 0, 1, &scissor);
};
RenderGraph graph;
graph.add("ShadowMap", {}, {"shadow"}, [&] {
auto add_pass = [&](std::string name, std::vector<std::string> reads,
std::vector<std::string> writes, RenderGraph::Callback callback) {
const auto label_name = name;
graph.add(std::move(name), std::move(reads), std::move(writes),
[this, label_name, callback = std::move(callback)] {
if (statistics.gpu_labels_enabled) {
VkDebugUtilsLabelEXT label{};
label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
label.pLabelName = label_name.c_str();
label.color[0] = .25f;
label.color[1] = .65f;
label.color[2] = .9f;
label.color[3] = 1.f;
begin_gpu_label(command, &label);
++statistics.gpu_label_count;
}
struct EndLabel {
Impl& renderer;
~EndLabel() {
if (renderer.statistics.gpu_labels_enabled)
renderer.end_gpu_label(renderer.command);
}
} end{*this};
callback();
});
};
add_pass("ShadowMap", {}, {"shadow"}, [&] {
transition(command, shadow, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
VK_IMAGE_ASPECT_DEPTH_BIT);
VkRenderingAttachmentInfo attachment{};
@@ -1214,7 +1283,7 @@ struct Renderer::Impl {
transition(command, shadow, VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL,
VK_IMAGE_ASPECT_DEPTH_BIT);
});
graph.add("ForwardAndUI", {"shadow"}, {"color", "depth"}, [&] {
add_pass("ForwardAndUI", {"shadow"}, {"color", "depth"}, [&] {
transition(command, color, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_ASPECT_COLOR_BIT);
transition(command, depth, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
@@ -1281,6 +1350,20 @@ struct Renderer::Impl {
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(push), &push);
for (auto batch : ui_batches) {
VkRect2D scissor{{0, 0}, {width, height}};
if (batch.clip_rect[2] > 0 && batch.clip_rect[3] > 0) {
const auto& clip = batch.clip_rect;
const float x = std::clamp(clip[0], 0.f, float(width));
const float y = std::clamp(clip[1], 0.f, float(height));
const float right = std::clamp(clip[0] + clip[2], x, float(width));
const float bottom = std::clamp(clip[1] + clip[3], y, float(height));
scissor.offset = {static_cast<int>(x), static_cast<int>(y)};
scissor.extent = {static_cast<unsigned>(right) - static_cast<unsigned>(x),
static_cast<unsigned>(bottom) - static_cast<unsigned>(y)};
}
if (!scissor.extent.width || !scissor.extent.height)
continue;
vkCmdSetScissor(command, 0, 1, &scissor);
auto descriptor = textures.at(batch.texture).descriptor;
vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout,
0, 1, &descriptor, 0, nullptr);
@@ -1289,7 +1372,7 @@ struct Renderer::Impl {
}
vkCmdEndRendering(command);
});
graph.add("Readback", {"color"}, {"capture"}, [&] {
add_pass("Readback", {"color"}, {"capture"}, [&] {
transition(command, color, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_IMAGE_ASPECT_COLOR_BIT);
VkBufferImageCopy copy{};
@@ -1299,7 +1382,7 @@ struct Renderer::Impl {
readback.handle, 1, &copy);
});
if (swap_index)
graph.add("Presentation", {"color"}, {"swapchain"}, [&] {
add_pass("Presentation", {"color"}, {"swapchain"}, [&] {
auto index = *swap_index;
transition(command, swap_images[index], swap_layouts[index],
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_ASPECT_COLOR_BIT);
@@ -1487,10 +1570,16 @@ void Renderer::set_text_input_area(float x, float y, float width, float height)
throw std::runtime_error(SDL_GetError());
}
void Renderer::set_clipboard(const std::string& text) {
if (!impl_->window) {
impl_->offscreen_clipboard = text;
return;
}
if (!SDL_SetClipboardText(text.c_str()))
throw std::runtime_error(SDL_GetError());
}
std::string Renderer::clipboard() const {
if (!impl_->window)
return impl_->offscreen_clipboard;
char* text = SDL_GetClipboardText();
if (!text)
return {};
+35 -1
View File
@@ -420,15 +420,49 @@ struct Context::Impl {
if (ime_enabled)
ime_enabled(false);
}
void reveal(Widget& widget) {
// Reveal inside-out: an offscreen nested scroller still needs its own
// contents positioned before an outer scroller brings it into view.
auto* branch = &widget;
for (auto* parent = widget.parent; parent; branch = parent, parent = parent->parent) {
if (!parent->layout.scroll || parent->kind == Kind::Row || branch->layout.absolute)
continue;
const float padding = parent->layout.padding * scale;
const float top = parent->rect.y + padding;
const float extent = std::max(0.f, parent->rect.height - 2 * padding);
if (extent <= 0)
continue;
float delta = 0;
if (widget.rect.y < top || widget.rect.height > extent)
delta = widget.rect.y - top;
else if (widget.rect.y + widget.rect.height > top + extent)
delta = widget.rect.y + widget.rect.height - top - extent;
const float scroll = std::clamp(parent->scroll_y + delta, 0.f,
std::max(0.f, parent->content_height - extent));
if (scroll != parent->scroll_y) {
parent->scroll_y = scroll;
arrange(root, {0, 0, width, height}, {0, 0, width, height});
}
}
}
bool focus(const std::string& id) {
auto* widget = find(id);
if (!widget || !focusable(*widget))
return false;
if (focused == id)
if (focused == id) {
reveal(*widget);
if (field(*widget) && ime_rectangle)
ime_rectangle(widget->rect);
return true;
}
if (!commit())
return false;
// A commit callback may reconcile the retained tree.
widget = find(id);
if (!widget || !focusable(*widget))
return false;
focused = id;
reveal(*widget);
if (field(*widget)) {
auto& edit = edits[id];
const auto text = widget->kind == Kind::NumberField
+135
View File
@@ -101,6 +101,95 @@ void success(const ImportResult& result) {
throw std::runtime_error(text);
}
}
void relocated_inputs(AssetPipeline& pipeline, const fs::path& root) {
const auto before = root / faset::path_from_utf8("Источники до переноса");
const auto after = root / faset::path_from_utf8("Источники после переноса");
fs::create_directories(before / "payload/nested");
glb(before / "payload/nested/mesh.glb", "Moved bundle");
const Json bundle{
{"schema_version", 1},
{"asset_id", "relocated-bundle"},
{"files",
Json::array({{{"path", "payload/nested/mesh.glb"},
{"sha256", faset::sha256_file(before / "payload/nested/mesh.glb")}}})}};
save(before / "manifest.json", bundle.dump());
const auto first = pipeline.import_asset({before / "manifest.json"});
success(first);
const auto immutable = pipeline.generation_directory(first.asset_id) / "manifest.json";
const auto original_digest = faset::sha256_file(immutable);
fs::rename(before, after);
const auto moved = pipeline.import_asset({after / "manifest.json"});
success(moved);
require(moved.cache_hit && moved.generation == first.generation &&
moved.asset_id == first.asset_id,
"Bundle directory relocation must reuse the content generation and identity");
require(pipeline.freshness(first.asset_id).at("state") == "current" &&
pipeline.current_manifest(first.asset_id).at("payload_source") ==
faset::path_to_utf8(after / "payload/nested/mesh.glb"),
"Moved bundle freshness must inspect the current payload location");
require(faset::sha256_file(immutable) == original_digest,
"Relocation must not rewrite an immutable generation manifest");
// Earlier pointers only recorded logical source; they must remain readable
// after a cache-hit relocation without consulting missing old source files.
const auto pointer = pipeline.cache_root() / "assets" / first.asset_id / "current.json";
auto old_pointer = faset::read_json(pointer);
old_pointer.erase("payload_source");
faset::atomic_write_json(pointer, old_pointer);
require(pipeline.freshness(first.asset_id).at("state") == "current" &&
pipeline.load_asset(first.asset_id).generation == first.generation,
"Legacy bundle pointer must resolve payload relative to the moved source");
glb(after / "payload/nested/mesh.glb", "Moved bundle", true, false, .8f);
require(pipeline.freshness(first.asset_id).at("state") == "stale" &&
pipeline.load_asset(first.asset_id).generation == first.generation,
"Changed relocated bundle payload must be stale without changing cooked data");
const auto external_before = root / "external-before";
const auto external_after = root / faset::path_from_utf8("Внешний glTF после переноса");
fs::create_directories(external_before / "deps");
auto gltf = document("Moved external", true, false);
gltf["buffers"][0]["uri"] = "deps/geometry.bin";
gltf["images"] = Json::array({{{"uri", "deps/picture.png"}, {"mimeType", "image/png"}}});
gltf["textures"] = Json::array({{{"source", 0}}});
gltf["materials"][0]["pbrMetallicRoughness"]["baseColorTexture"] = {{"index", 0}};
save(external_before / "model.gltf", gltf.dump());
save(external_before / "deps/geometry.bin", geometry(0));
save(external_before / "deps/picture.png", faset::test_images::png_red_green);
const auto external = pipeline.import_asset({external_before / "model.gltf"});
success(external);
const auto external_immutable =
pipeline.generation_directory(external.asset_id) / "manifest.json";
const auto external_digest = faset::sha256_file(external_immutable);
fs::rename(external_before, external_after);
const auto relocated = pipeline.import_asset({external_after / "model.gltf"});
success(relocated);
require(relocated.cache_hit && relocated.generation == external.generation &&
relocated.asset_id == external.asset_id &&
pipeline.freshness(external.asset_id).at("state") == "current",
"Moving glTF and external dependencies together must preserve a current cache hit");
const auto external_pointer =
pipeline.cache_root() / "assets" / external.asset_id / "current.json";
auto legacy = faset::read_json(external_pointer);
legacy.erase("payload_source");
faset::atomic_write_json(external_pointer, legacy);
require(pipeline.freshness(external.asset_id).at("state") == "current",
"Legacy ordinary glTF pointer must treat the current source as payload");
save(external_after / "deps/geometry.bin", geometry(.4f));
auto freshness = pipeline.freshness(external.asset_id);
require(freshness.at("state") == "stale" &&
freshness.at("reasons")[0].at("code") == "dependency.changed" &&
freshness.at("reasons")[0].at("path") ==
faset::path_to_utf8((external_after / "deps/geometry.bin").lexically_normal()),
"Freshness must hash external geometry at its new location after directory move");
save(external_after / "deps/geometry.bin", geometry(0));
save(external_after / "deps/picture.png", faset::test_images::png_blue_white);
freshness = pipeline.freshness(external.asset_id);
require(freshness.at("state") == "stale" &&
freshness.at("reasons")[0].at("path") ==
faset::path_to_utf8((external_after / "deps/picture.png").lexically_normal()),
"Freshness must inspect moved external images too");
require(faset::sha256_file(external_immutable) == external_digest,
"Freshness/relocation must leave the published glTF generation immutable");
}
} // namespace
int main() {
const auto root =
@@ -115,6 +204,8 @@ int main() {
glb(source);
auto first = pipeline.import_asset({source});
success(first);
require(pipeline.freshness(first.asset_id).at("state") == "current",
"new import must be current");
require(first.manifest.at("input_key").at("target_profile") == "desktop-static-pbr-v1" &&
first.manifest.at("input_key")
.at("toolchain")
@@ -151,8 +242,13 @@ int main() {
require(unchanged.cache_hit && unchanged.generation == first.generation,
"content cache hit");
glb(source, "Renamed panel", true, false, .25f);
require(pipeline.freshness(first.asset_id).at("state") == "stale" &&
pipeline.current_manifest(first.asset_id).at("generation") == first.generation,
"source edit must mark stale without publishing a generation");
auto modified = pipeline.import_asset({source});
success(modified);
require(pipeline.freshness(first.asset_id).at("state") == "current",
"successful reimport must clear stale state");
require(modified.asset_id == first.asset_id && modified.generation != first.generation,
"stable asset identity and changed generation");
asset = pipeline.load_asset(first.asset_id);
@@ -243,11 +339,36 @@ int main() {
save(root / "external.gltf", external.dump());
auto ext = pipeline.import_asset({root / "external.gltf"});
success(ext);
const auto sidecar = root / "external.gltf.faset-import.json";
const auto recipe = faset::read_json(sidecar);
auto changed_recipe = recipe;
changed_recipe["settings"]["target"] = "changed-profile";
faset::atomic_write_json(sidecar, changed_recipe);
require(pipeline.freshness(ext.asset_id).at("state") == "stale",
"changed sidecar must mark imported asset stale");
faset::atomic_write_json(sidecar, recipe);
const auto image_path = root / faset::path_from_utf8("пиксель.png");
const auto original_time = fs::last_write_time(image_path);
auto altered_image = png;
altered_image.back() ^= 1;
save(image_path, altered_image);
fs::last_write_time(image_path, original_time);
require(pipeline.freshness(ext.asset_id).at("state") == "stale",
"external dependency content change with same size/time must be detected");
fs::remove(image_path);
require(pipeline.freshness(ext.asset_id).at("state") == "stale",
"missing source dependency must be diagnosed");
save(image_path, png);
require(pipeline.freshness(ext.asset_id).at("state") == "current",
"restoring exact recipe and dependency must clear stale state");
auto ext_asset = pipeline.load_asset(ext.asset_id);
require(ext_asset.textures.size() == 1 && ext_asset.textures[0].bytes.size() == png.size(),
"external image not extracted");
require(ext_asset.materials[0].base_color_texture == 0, "material texture reference lost");
save(root / faset::path_from_utf8("геометрия.bin"), geometry(.3f));
require(pipeline.freshness(ext.asset_id).at("reasons")[0].at("code") ==
"dependency.changed",
"external geometry change must expose its reason");
auto dependent = pipeline.import_asset({root / "external.gltf"});
success(dependent);
require(dependent.generation != ext.generation, "buffer dependency not invalidated");
@@ -263,12 +384,16 @@ int main() {
save(bundle_dir / "manifest.json", bundle.dump());
auto bundle_first = pipeline.import_asset({bundle_dir / "manifest.json"});
success(bundle_first);
require(pipeline.freshness(bundle_first.asset_id).at("state") == "current",
"new Blender bundle must be current");
require(bundle_first.asset_id == "bundle-asset", "bundle identity lost");
glb(bundle_dir / "payload" / "second.glb", "Bundle renamed", true, false, .4f);
bundle["files"][0] = {
{"path", "payload/second.glb"},
{"sha256", faset::sha256_file(bundle_dir / "payload" / "second.glb")}};
save(bundle_dir / "manifest.json", bundle.dump());
require(pipeline.freshness(bundle_first.asset_id).at("state") == "stale",
"new bundle publication must invalidate freshness before reimport");
auto bundle_second = pipeline.import_asset({bundle_dir / "manifest.json"});
success(bundle_second);
require(bundle_second.asset_id == bundle_first.asset_id &&
@@ -374,6 +499,16 @@ int main() {
"Image rename retained old source pointer");
require(pipeline.overrides(image_first.asset_id).contains(texture_id),
"Image rename/reimport lost overrides");
require(pipeline.freshness(image_first.asset_id).at("state") == "current",
"Renamed and reimported PNG must be current, not stale at its old payload path");
const auto image_pointer =
pipeline.cache_root() / "assets" / image_first.asset_id / "current.json";
auto old_image_pointer = faset::read_json(image_pointer);
old_image_pointer.erase("payload_source");
faset::atomic_write_json(image_pointer, old_image_pointer);
require(pipeline.freshness(image_first.asset_id).at("state") == "current",
"Legacy image pointer must follow the renamed logical source");
relocated_inputs(pipeline, root);
const auto jpeg = root / "sprite.JPEG";
save(jpeg, faset::test_images::jpeg_red_green);
auto jpeg_import = pipeline.import_asset({jpeg});
+274
View File
@@ -0,0 +1,274 @@
#include <faset/authoring/service.hpp>
#include <faset/core/hash.hpp>
#include <faset/core/io.hpp>
#include <faset/editor/build_service.hpp>
#include <faset/editor/commands.hpp>
#include <iostream>
#include <limits>
using namespace faset;
namespace fs = std::filesystem;
namespace {
void check(bool value, std::string_view message) {
if (!value)
throw std::runtime_error(std::string(message));
}
Json manifest() {
Json type{
{"id", "game.mover"},
{"version", 2},
{"fields",
{{"speed", {{"type", "number"}, {"default", 2.5}, {"min", 0}, {"max", 10}}},
{"enabled", {{"type", "boolean"}, {"default", true}}}}},
{"migrations",
Json::array(
{{{"from_version", 1},
{"fields", {{"speed", {{"scale", 0.01}}}, {"enabled", {{"default", true}}}}}}})}};
return {{"format", "faset.schema"}, {"version", 1}, {"types", Json::array({type})}};
}
void migration_contracts(const fs::path& root, const Json& metadata) {
auto old_scene = authoring::make_scene("Legacy movement", 2);
auto object = authoring::make_entity(authoring::builtin_schemas(), "Mover");
object["id"] = "mover";
object["components"].push_back({{"id", "movement"},
{"type", "game.mover"},
{"version", 1},
{"fields", {{"speed", 300}, {"unknown", "preserve me"}}}});
old_scene["entities"].push_back(object);
atomic_write_json(root / "Scenes/legacy.scene.json", old_scene);
const auto original = read_text(root / "Scenes/legacy.scene.json");
authoring::AuthoringService service(root);
service.replace_external_schemas(metadata);
editor::Commands commands(service);
const auto opened =
commands.call("faset_document_open", {{"path", "Scenes/legacy.scene.json"}});
const auto id = opened.at("id").get<std::string>();
check(opened.at("scene") == old_scene && !opened.at("dirty").get<bool>(),
"Opening an older schema preserves opaque data without implicit migration");
auto apply = [&] {
return commands.call("faset_scene_edit",
{{"document", id},
{"revision", service.query(id).at("revision")},
{"operations", Json::array({{{"op", "entity.rename"},
{"entity", "mover"},
{"name", "Migrated mover"}},
{{"op", "component.migrate"},
{"entity", "mover"},
{"component", "movement"}}})}});
};
const auto migrated = apply();
const auto& value = migrated.at("scene").at("entities")[0].at("components")[1];
check(value.at("id") == "movement" && value.at("version") == 2 &&
value.at("fields").at("speed") == 3.0 && value.at("fields").at("enabled") == true &&
value.at("fields").at("unknown") == "preserve me",
"Exported declarative steps convert values and retain IDs and unknown fields");
check(read_text(root / "Scenes/legacy.scene.json") == original,
"Explicit migration remains unsaved until Save");
const auto undone =
commands.call("faset_undo", {{"document", id}, {"revision", migrated.at("revision")}});
check(undone.at("scene") == old_scene, "One Undo restores the entire migration transaction");
authoring::AuthoringService recovered(root);
recovered.replace_external_schemas(metadata);
check(recovered.recover(id).at("scene") == old_scene,
"Recovery preserves the journal's older version without automatic migration");
commands.call("faset_redo", {{"document", id}, {"revision", undone.at("revision")}});
check(service.query(id).at("scene") == migrated.at("scene"), "Redo restores migrated values");
commands.call("faset_undo", {{"document", id}, {"revision", service.query(id).at("revision")}});
for (const auto* failure : {"missing", "manual", "overflow"}) {
auto rules = metadata;
if (std::string_view(failure) == "missing")
rules["types"][0].erase("migrations");
else if (std::string_view(failure) == "manual")
rules["types"][0]["migrations"][0]["fields"]["speed"] = {{"require_manual", true}};
else
rules["types"][0]["migrations"][0]["fields"]["speed"]["scale"] =
std::numeric_limits<double>::max();
service.replace_external_schemas(rules);
const auto before = service.query(id);
bool rejected{};
try {
(void)apply();
} catch (const Error&) {
rejected = true;
}
check(rejected && service.query(id) == before &&
read_text(root / "Scenes/legacy.scene.json") == original,
"Missing/manual/overflow migration preserves document, revision, Undo and source");
authoring::AuthoringService reopened(root);
reopened.replace_external_schemas(rules);
check(reopened.open("Scenes/legacy.scene.json").at("scene") == old_scene,
"An unavailable migration never prevents opening old data");
}
service.replace_external_schemas(metadata);
auto addition = object;
addition["id"] = "local-mover";
addition["components"][0]["id"] = "local-transform";
addition["components"][1]["id"] = "local-movement";
auto current = service.query(id);
current = service.transact(id, current.at("revision"),
Json::array({{{"op", "template.instance"},
{"instance",
{{"id", "local-instance"},
{"source", "Scenes/unused.scene.json"},
{"additions", Json::array({addition})}}}}}));
const auto local_before = current;
current = commands.call("faset_scene_edit",
{{"document", id},
{"revision", current.at("revision")},
{"operations", Json::array({{{"op", "component.migrate"},
{"instance", "local-instance"},
{"entity", "local-mover"},
{"component", "local-movement"}}})}});
check(current.at("scene")
.at("instances")[0]
.at("additions")[0]
.at("components")[1]
.at("fields")
.at("speed") == 3.0,
"Instance-local additions use the same migration command");
check(service.undo(id, current.at("revision")).at("scene") == local_before.at("scene"),
"Local-addition migration is one undoable edit");
}
int test_main(int argc, char** argv) {
const auto root =
fs::temp_directory_path() / path_from_utf8("Faset schema Café 世界 " + new_id());
try {
check(argc == 3, "Expected native fixture tool and engine root");
editor::BuildConfig config;
config.project_root = root / "project";
config.engine_root = fs::absolute(path_from_utf8(argv[2]));
config.cmake = path_to_utf8(fs::absolute(path_from_utf8(argv[1])));
config.configure_arguments = {"-DCMAKE_CXX_COMPILER=fixture-does-not-compile"};
editor::BuildService builds(config);
builds.scaffold("Schema publication", 2);
authoring::AuthoringService authoring(config.project_root, authoring::builtin_schemas());
const auto valid = manifest();
atomic_write_json(config.project_root / "schema-fixture.json", valid);
const auto first = builds.wait(builds.start_build());
check(first.state == "succeeded", "Valid custom schema v2 publishes: " + first.error);
const auto directory = path_from_utf8(first.result.at("directory").get<std::string>());
const auto player = path_from_utf8(first.result.at("player").get<std::string>());
const auto schema = path_from_utf8(first.result.at("schema").get<std::string>());
const auto last_build = builds.config().cache_root / "last_build.json";
const auto previous_pointer = read_text(last_build);
const auto previous_player = sha256_file(player);
const auto previous_schema = read_text(schema);
const auto previous_manifest = read_text(directory / "manifest.json");
authoring.replace_external_schemas(read_json(schema));
const auto previous_registry = authoring.schemas().manifest();
check(authoring.schemas().schema("game.mover").at("version") == 2,
"Matching custom v2 metadata reaches authoring");
migration_contracts(config.project_root / "migration-contracts", read_json(schema));
Json scene{
{"format", "faset.scene"},
{"version", 1},
{"id", "fixture-scene"},
{"dimension", 2},
{"instances", Json::array()},
{"entities",
Json::array({{{"id", "mover"},
{"components", Json::array({{{"id", "behavior"},
{"type", "game.mover"},
{"version", 2},
{"fields", {{"speed", 2.5}}}}})}}})}};
check(builds.wait(builds.start_cook(scene)).state == "succeeded",
"Matching component v2 cooks against the published schema");
std::vector<Json> invalid;
auto candidate = valid;
candidate["types"][0]["fields"]["speed"]["default"] = "not a number";
invalid.push_back(candidate);
candidate = valid;
candidate["types"][0]["fields"]["speed"]["type"] = "unsupported-kind";
invalid.push_back(candidate);
candidate = valid;
candidate["types"][0]["fields"]["speed"]["min"] = "not a number";
invalid.push_back(candidate);
candidate = valid;
candidate["types"][0]["fields"]["speed"]["enum"] = 2.5;
invalid.push_back(candidate);
candidate = valid;
candidate["types"][0]["fields"]["speed"]["id"] = "different-field-id";
invalid.push_back(candidate);
candidate = valid;
candidate["types"].push_back(candidate["types"][0]);
invalid.push_back(candidate);
candidate = valid;
candidate["types"] = Json::array({authoring::builtin_schemas().schema("faset.transform")});
invalid.push_back(candidate);
for (const Json& version : {Json(0), Json(2.5), Json("2")}) {
candidate = valid;
candidate["types"][0]["version"] = version;
invalid.push_back(candidate);
}
candidate = valid;
candidate["types"][0].erase("fields");
invalid.push_back(candidate);
candidate = valid;
candidate["types"] = Json::object();
invalid.push_back(candidate);
for (const auto& step : Json::array(
{{{"from_version", 2}, {"fields", Json::object()}},
{{"from_version", 0}, {"fields", Json::object()}},
{{"from_version", 1.5}, {"fields", Json::object()}},
{{"from_version", 1}, {"fields", {{"speed", {{"execute", "unsafe"}}}}}},
{{"from_version", 1}, {"fields", {{"speed", {{"scale", "bad"}}}}}},
{{"from_version", 1}, {"fields", {{"speed", {{"scale", nullptr}}}}}},
{{"from_version", 1}, {"fields", {{"speed", {{"require_manual", 1}}}}}},
{{"from_version", 1}, {"fields", Json::object()}, {"unsupported", true}}})) {
candidate = valid;
candidate["types"][0]["migrations"] = Json::array({step});
invalid.push_back(candidate);
}
candidate = valid;
candidate["types"][0]["migrations"].push_back(candidate["types"][0]["migrations"][0]);
invalid.push_back(candidate);
for (std::size_t index = 0; index < invalid.size(); ++index) {
atomic_write_json(config.project_root / "schema-fixture.json", invalid[index]);
atomic_write(config.project_root / "Scripts/Gameplay.cpp",
"// Invalid metadata fixture " + std::to_string(index));
const auto failed = builds.wait(builds.start_build());
check(failed.state == "failed" && failed.result.empty() && !failed.error.empty(),
"Malformed schema must fail before a successful build result is published");
check(read_text(last_build) == previous_pointer,
"Last good build pointer is preserved");
check(sha256_file(player) == previous_player && read_text(schema) == previous_schema &&
read_text(directory / "manifest.json") == previous_manifest,
"Last good binary, schema and build manifest remain unchanged");
std::size_t generations{};
for (const auto& entry : fs::directory_iterator(directory.parent_path())) {
++generations;
check(entry.path() == directory, "Invalid build leaves no staging or generation");
}
check(generations == 1, "Only the validated build generation remains");
bool rejected{};
try {
authoring.replace_external_schemas(invalid[index]);
} catch (const std::exception&) {
rejected = true;
}
check(rejected && authoring.schemas().manifest() == previous_registry,
"Cached-schema validation uses the same contract and preserves the registry");
}
std::cout << "Valid v2 schema and atomic rejection of " << invalid.size()
<< " malformed metadata generations passed\n";
fs::remove_all(root);
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
std::error_code ignored;
fs::remove_all(root, ignored);
return 1;
}
}
} // namespace
#ifdef _WIN32
int wmain(int argc, wchar_t** argv) {
return run_utf8_main(argc, argv, test_main);
}
#else
int main(int argc, char** argv) {
return test_main(argc, argv);
}
#endif

Some files were not shown because too many files have changed in this diff Show More