33 Commits
Author SHA1 Message Date
Emil 2c1cf2fecc Record final combined P1 Release reference and memory miss
Native and manual checks / native (ubuntu-24.04) (push) Failing after 36s
Native and manual checks / manual (push) Successful in 30s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
2026-09-24 05:18:03 +03:00
Emil 331815d458 Describe staged source verification boundaries accurately 2026-09-24 04:04:23 +03:00
Emil 54d1bae962 Reverify staged gameplay sources before build publication
Native and manual checks / native (ubuntu-24.04) (push) Failing after 35s
Native and manual checks / manual (push) Successful in 28s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 04:01:46 +03:00
Emil 798aa0e550 Keep warning-flood regression within one fixture build
Native and manual checks / native (ubuntu-24.04) (push) Failing after 36s
Native and manual checks / manual (push) Successful in 26s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 03:53:05 +03:00
Emil 822b44f1d7 Archive P1 iteration workflow and manual checkpoint 2026-09-24 03:45:18 +03:00
Emil d7a7a7c3a4 Keep real staged compile errors navigable under warning floods
Native and manual checks / native (ubuntu-24.04) (push) Failing after 30s
Native and manual checks / manual (push) Successful in 28s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 03:34:18 +03:00
Emil abd8c21273 Record configured workflow toolchain binary provenance
Native and manual checks / native (ubuntu-24.04) (push) Failing after 32s
Native and manual checks / manual (push) Successful in 27s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 03:16:26 +03:00
Emil 4ac02ee44a Keep Windows export integration outside engine checkout
Native and manual checks / native (ubuntu-24.04) (push) Failing after 32s
Native and manual checks / manual (push) Successful in 27s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 03:13:27 +03:00
Emil 6c2e8fa800 Stop workflow tool after validated version 2 report
Native and manual checks / native (ubuntu-24.04) (push) Failing after 40s
Native and manual checks / manual (push) Successful in 32s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 02:59:48 +03:00
Emil 1ea0ce6ba9 Use UTF-8 path conversion in autosave UI fixture 2026-09-24 02:59:09 +03:00
Emil 62059e154d Compare source-open Unicode argv against canonical paths
Native and manual checks / native (ubuntu-24.04) (push) Failing after 30s
Native and manual checks / manual (push) Successful in 28s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 02:46:07 +03:00
Emil a68fb8e7fd Collect repeatable P1 workflow evidence 2026-09-24 02:44:51 +03:00
Emil 93946c1d24 Refresh Lua Release evidence after verifier review
Native and manual checks / native (ubuntu-24.04) (push) Failing after 34s
Native and manual checks / manual (push) Successful in 29s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 02:35:14 +03:00
Emil 7c777eba7c Harden Lua export verification against stale or incomplete evidence 2026-09-24 02:35:14 +03:00
Emil af836785d3 Qualify Windows UTF-8 test entry point 2026-09-24 02:34:12 +03:00
Emil ba8bf45dbc Add repeatable synthetic Editor input latency probe 2026-09-24 02:30:41 +03:00
Emil 9e921a5b3b Record relocated Lua Release execution on Linux
Native and manual checks / native (ubuntu-24.04) (push) Failing after 33s
Native and manual checks / manual (push) Successful in 29s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 02:26:10 +03:00
Emil 6446f8917e Verify relocated Lua Release games with checked rendering 2026-09-24 02:26:10 +03:00
Emil 45c5d2d188 Show and configure safe Editor autosave 2026-09-24 02:25:55 +03:00
Emil 77c145fa02 Fix Windows diagnostic paths and schema fixture timeout 2026-09-24 02:19:09 +03:00
Emil 4a434f7855 Autosave named scenes with revision and disk conflict safety 2026-09-24 02:11:26 +03:00
Emil d3217838cb Show source-linked diagnostics and expandable build logs 2026-09-24 02:01:47 +03:00
Emil cf5b702233 Record Linux starter project validation
Native and manual checks / native (ubuntu-24.04) (push) Failing after 36s
Native and manual checks / manual (push) Successful in 29s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 01:59:22 +03:00
Emil 6459d69cc7 Offer runnable C++ and Lua project starters 2026-09-24 01:59:17 +03:00
Emil d4c1f0e86b Open project C++ and Lua sources at diagnostic locations 2026-09-24 01:58:59 +03:00
Emil abbb71d0c1 Protect native cache paths and snapshot gameplay sources 2026-09-24 01:55:50 +03:00
Emil 4ec5a3cd8a Expose structured gameplay build diagnostics 2026-09-24 01:46:38 +03:00
Emil 4a59ca1629 Reuse verified gameplay schema and build generations 2026-09-24 01:41:26 +03:00
Emil deb37c308b Capture complete gameplay and toolchain build inputs 2026-09-24 01:35:02 +03:00
Emil bf89ba6f3d Plan P1 iteration and P3 rendering milestones
Native and manual checks / native (ubuntu-24.04) (push) Failing after 32s
Native and manual checks / manual (push) Successful in 27s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 01:25:23 +03:00
Emil d18c5d77b3 Validate and document P2 GPU visibility milestone
Native and manual checks / native (ubuntu-24.04) (push) Failing after 31s
Native and manual checks / manual (push) Successful in 29s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 00:48:54 +03:00
Emil 22012c1a80 Report effective visibility and stable GPU instance identity
Native and manual checks / native (ubuntu-24.04) (push) Failing after 36s
Native and manual checks / manual (push) Successful in 28s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 00:28:12 +03:00
Emil 433bce08c0 Run GPU visibility without optional draw parameters feature
Native and manual checks / native (ubuntu-24.04) (push) Failing after 42s
Native and manual checks / manual (push) Successful in 32s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-23 23:50:02 +03:00
321 changed files with 162290 additions and 333 deletions
+1
View File
@@ -96,3 +96,4 @@ jobs:
python-version: '3.12'
- run: python -m pip install -r docs/requirements.txt
- run: python -m mkdocs build --strict
- run: python tests/verify_playable_exports_test.py
+6 -6
View File
@@ -71,9 +71,9 @@ jobs:
VK_LOADER_DEBUG: error,warn,driver
run: ctest --preset windows-debug --timeout 180
- name: Real Release exports, 2D and 3D execution, incremental Debug rebuild
run: build/windows-debug/faset_build_service_tests.exe --integration .cache/windows-export-e2e
- name: Export and relocate both checked-in playable games
run: python tools/verify_playable_exports.py --editor build/windows-debug/faset_editor.exe --output .cache/windows-playable-exports
run: build/windows-debug/faset_build_service_tests.exe --integration "${{ runner.temp }}/faset-windows-export-e2e"
- name: Export and relocate C++ and Lua playable games
run: python tools/verify_playable_exports.py --editor build/windows-debug/faset_editor.exe --output .cache/windows-playable-exports --include-lua
- name: Preserve graphics and export evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
@@ -89,8 +89,8 @@ jobs:
.cache/windows-graphics/sdk/source-identity.json
build/windows-debug/Testing/Temporary/LastTest.log
build/windows-debug/*test.ppm
.cache/windows-export-e2e/result-*.json
.cache/windows-export-e2e/export-*/generations/*/manifest.json
.cache/windows-export-e2e/export-*/generations/*/verification.ppm
${{ runner.temp }}/faset-windows-export-e2e/result-*.json
${{ runner.temp }}/faset-windows-export-e2e/export-*/generations/*/manifest.json
${{ runner.temp }}/faset-windows-export-e2e/export-*/generations/*/verification.ppm
.cache/windows-playable-exports/report.json
.cache/windows-playable-exports/evidence/*
+4 -1
View File
@@ -74,13 +74,16 @@ if(TARGET faset_assets AND TARGET faset_authoring AND EXISTS "${PROJECT_SOURCE_D
endif()
if(TARGET faset_editor_commands AND TARGET faset_build_service)
include(cmake/Plugins.cmake)
add_library(faset_editor_session STATIC src/editor/session.cpp)
add_library(faset_editor_session STATIC src/editor/session.cpp src/editor/autosave.cpp)
target_link_libraries(faset_editor_session PUBLIC faset_editor_commands faset_build_service faset_assets faset_editor_plugins)
if(BUILD_TESTING)
add_executable(faset_editor_session_tests tests/editor_session_tests.cpp)
target_link_libraries(faset_editor_session_tests PRIVATE faset_editor_session)
target_compile_definitions(faset_editor_session_tests PRIVATE FASET_TEST_ENGINE="${PROJECT_SOURCE_DIR}")
add_test(NAME editor_session_settings COMMAND faset_editor_session_tests)
add_executable(faset_editor_autosave_tests tests/editor_autosave_tests.cpp)
target_link_libraries(faset_editor_autosave_tests PRIVATE faset_editor_session)
add_test(NAME editor_autosave COMMAND faset_editor_autosave_tests)
endif()
endif()
foreach(module UI EditorUI Editor Applications)
+21 -6
View File
@@ -2,7 +2,7 @@
Версия 1.3 · 23 сентября 2026 года.
**Статус:** C++ MVP реализован и принят; первый tag — **v0.1.0-mvp**. Его исходники проверены на `4cb82556de31268d2bde73948dd1ff1b6c02f162`; [досье M0–M9](docs/validation/mvp-acceptance.md) связывает этапы с проверками и revisions. После MVP реализованы Lua-модуль и P2 GPU visibility/mesh LOD. Для P2 сохранён direct-эталон; [досье Linux-проверок](docs/validation/p2-gpu-visibility-2026-09-23/README.md), [протокол приёмки](docs/studies/19-p2-gpu-visibility-acceptance.md), [первое измерение](docs/studies/20-p2-gpu-visibility-benchmark-2026-09-23.md) и [повтор после оптимизации](docs/studies/21-p2-gpu-visibility-optimization-2026-09-23.md) фиксируют функциональную и измерительную область. Первый Debug-профиль обнаружил дорогой MainCull; перенос GPU-выходов в device-local память и bounded atomic append устранили эту стоимость в повторном синтетическом тесте. Это не доказывает ускорение любой игры. Windows P2 и дополнительные семейства физических GPU/драйверов не подтверждены этими проверками. Для MVP Linux проверен на RTX 2080 Ti, Windows — в native CI через SwiftShader; это не сертификация всех GPU/драйверов. Системный IME и физические переходы между мониторами не проверены, native Wayland restore имеет явный skip; XWayland и Windows lifecycle прошли. Контракты находятся в [ARCHITECTURE.md](docs/ARCHITECTURE.md), история — в [журнале реализации](docs/IMPLEMENTATION.md).
**Статус:** C++ MVP реализован и принят; первый tag — **v0.1.0-mvp**. Его исходники проверены на `4cb82556de31268d2bde73948dd1ff1b6c02f162`; [досье M0–M9](docs/validation/mvp-acceptance.md) связывает этапы с проверками и revisions. После MVP реализованы Lua-модуль и P2 GPU visibility/mesh LOD. Для P2 сохранён direct-эталон; [досье Linux-проверок](docs/validation/p2-gpu-visibility-2026-09-23/README.md), [проверка Windows SwiftShader](docs/validation/p2-swiftshader-2026-09-23/README.md), [протокол приёмки](docs/studies/19-p2-gpu-visibility-acceptance.md), [первое измерение](docs/studies/20-p2-gpu-visibility-benchmark-2026-09-23.md) и [повтор после оптимизации](docs/studies/21-p2-gpu-visibility-optimization-2026-09-23.md) фиксируют функциональную и измерительную область. Первый Debug-профиль обнаружил дорогой MainCull; перенос GPU-выходов в device-local память и bounded atomic append устранили эту стоимость в повторном синтетическом тесте. Это не доказывает ускорение любой игры. Windows P2 функционально проверен в native CI через SwiftShader; физический Windows GPU и другие семейства драйверов остаются без проверки. Для MVP Linux проверен на RTX 2080 Ti, Windows — в native CI через SwiftShader; это не сертификация всех GPU/драйверов. Системный IME и физические переходы между мониторами не проверены, native Wayland restore имеет явный skip; XWayland и Windows lifecycle прошли. Контракты находятся в [ARCHITECTURE.md](docs/ARCHITECTURE.md), история — в [журнале реализации](docs/IMPLEMENTATION.md).
## 1. Результат MVP
@@ -179,13 +179,28 @@ Lua, C++ hot reload, Blender live link, встроенное изображен
Начальные бюджеты отслеживания для конкретных демо и 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 development reload; сохранение состояния при reload проектируется отдельно. [Проверка Lua-модуля](docs/validation/lua-module.md) подтверждает Linux CPU-контракты и отключение модуля для C++-only проекта; Windows и независимый graphical/release профиль модуля остаются отдельными проверками. Таким образом, Lua-часть P1 появилась после MVP, но это не закрывает весь этап скорости итераций.
Улучшать schema/build cache, сообщения компилятора, шаблоны проектов, переход к коду, autosave и измеренное время «изменение → результат». Dynamic gameplay loading рассматривать при подтверждённой проблеме линковки.
Реализованы необязательный Lua runtime/editor модуль, development reload без
сохранения runtime-состояния, проверяемые schema/package cache и snapshots C++
исходников, структурированная диагностика, переход к исходнику, четыре C++/Lua
2D/3D starter-проекта и безопасный autosave именованных сцен. [Датированный
отчёт P1](docs/validation/p1-iteration-2026-09-24/README.md) содержит raw
повторные измерения Debug-итераций, 3000 кадров без роста явных GPU allocations,
синтетическую расширенную сцену и реальные найденные ограничения. [Lua Release
пакет](docs/validation/lua-release-2026-09-24/README.md) на Linux проверен после
переноса и скрытия исходного проекта. Windows native/manual, 61 SwiftShader
CTest и перенос всех трёх Release-игр, включая Lua-only, прошли на
[чистой ревизии `4ac02ee`](https://github.com/emil28092005/Faset_Engine/actions/runs/35937433040).
Это функциональная проверка software Vulkan без активного validation layer.
Исторический Release benchmark точных демо нельзя
подменять новым Debug workflow; свежий Release reference и физический Windows GPU
остаются отдельным покрытием. Dynamic gameplay loading рассматривать при
подтверждённой проблеме линковки, без обещания C++ hot reload в P1.
### P2. GPU-driven visibility и LOD
**Реализовано для opaque static meshes на Linux reference GPU; остальные платформы/устройства требуют отдельной проверки.** Direct renderer остался выбираемым эталоном. Реализованы устойчивые instance IDs с generation, GPU frustum culling, фиксированные indirect bins, current HZB и его редакторский preview, main/post occlusion с проверяемой историей, выбор заранее подготовленного mesh LOD по проецируемому размеру и hysteresis. Редактор переключает режимы через diagnostics, Player — через явный флаг `--visibility direct|gpu-frustum|gpu-occlusion`; по умолчанию остаётся Direct. Прозрачные meshes, спрайты, UI и shadow pass сохраняют свои упорядоченные/независимые пути. Система не генерирует LOD-модели из исходного mesh автоматически.
**Реализовано для opaque static meshes; проверено на Linux reference GPU и функционально на Windows SwiftShader. Физический Windows GPU и другие драйверы требуют отдельной проверки.** Direct renderer остался выбираемым эталоном. Реализованы устойчивые instance IDs с generation, GPU frustum culling, фиксированные indirect bins, current HZB и его редакторский preview, main/post occlusion с проверяемой историей, выбор заранее подготовленного mesh LOD по проецируемому размеру и hysteresis. Редактор переключает режимы через diagnostics, Player — через явный флаг `--visibility direct|gpu-frustum|gpu-occlusion`; по умолчанию остаётся Direct. Прозрачные meshes, спрайты, UI и shadow pass сохраняют свои упорядоченные/независимые пути. Система не генерирует LOD-модели из исходного mesh автоматически.
GPU instance record содержит стабильные slot/generation; плотный индекс кандидата остаётся адресом в буфере текущего кадра. Диагностика отдельно показывает запрошенный и фактический режим: при отсутствии HZB запрос occlusion явно исполняется как frustum, при отсутствии GPU culling — как Direct. Проверка этих контрактов добавлена после независимого ревью.
- [x] GPU instance records, frustum culling и fixed indirect draws без CPU feedback для решения видимости.
- [x] Current HZB, двухпроходное исправление ошибочной previous-frame occlusion и инвалидация истории при cut, resize, смене view/projection/instance.
@@ -193,7 +208,7 @@ Lua runtime/editor пакет реализован как необязатель
- [x] Автоматические adversarial-сценарии: пустота, граница ёмкости, дверь/телепорт, тени, near plane, resize, несколько views, lifecycle, прозрачность и открытая сцена.
- [x] Профиль direct/frustum/occlusion на закрытой и открытой сценах с raw samples и без обещания универсального ускорения.
Основой проектирования было [исследование 15](docs/studies/15-renderer-implementation-notes.md); фактическая проверка — в [Linux-досье](docs/validation/p2-gpu-visibility-2026-09-23/README.md) и [P2 acceptance](docs/studies/19-p2-gpu-visibility-acceptance.md), методика и значения — в [исходном benchmark](docs/studies/20-p2-gpu-visibility-benchmark-2026-09-23.md) и [повторе после оптимизации](docs/studies/21-p2-gpu-visibility-optimization-2026-09-23.md), реализация — в [журнале](docs/IMPLEMENTATION.md). Счётчики GPU и HZB preview включаются только для диагностики; существующий framebuffer capture по-прежнему синхронен, поэтому end-to-end benchmark отражает этот путь. В первом измерении MainCull оказался дорогим; device-local выходные буферы и atomic add уменьшили его p50 до 0,030–0,042 мс в повторном Debug/validation тесте на reference GPU. Direct остаётся начальным режимом: до изменения default нужны Release-профиль и реальные игровые сцены. Открытые сцены и дополнительная стоимость HZB публикуются наравне с закрытыми.
Основой проектирования было [исследование 15](docs/studies/15-renderer-implementation-notes.md); фактическая проверка — в [Linux-досье](docs/validation/p2-gpu-visibility-2026-09-23/README.md), [Windows SwiftShader CI](docs/validation/p2-swiftshader-2026-09-23/README.md) и [P2 acceptance](docs/studies/19-p2-gpu-visibility-acceptance.md), методика и значения — в [исходном benchmark](docs/studies/20-p2-gpu-visibility-benchmark-2026-09-23.md) и [повторе после оптимизации](docs/studies/21-p2-gpu-visibility-optimization-2026-09-23.md), реализация — в [журнале](docs/IMPLEMENTATION.md). Счётчики GPU и HZB preview включаются только для диагностики; существующий framebuffer capture по-прежнему синхронен, поэтому end-to-end benchmark отражает этот путь. В первом измерении MainCull оказался дорогим; device-local выходные буферы и atomic add уменьшили его p50 до 0,030–0,042 мс в повторном Debug/validation тесте на reference GPU. Direct остаётся начальным режимом: до изменения default нужны Release-профиль и реальные игровые сцены. Открытые сцены и дополнительная стоимость HZB публикуются наравне с закрытыми.
### P3. Освещение, тени и temporal reconstruction
+2 -2
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: the C++ MVP is accepted for the recorded Linux and Windows test profiles; Lua and P2 GPU visibility were added afterward.** The MVP 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. P2 adds optional GPU frustum and two-pass HZB occlusion modes, fixed indirect mesh bins, prepared mesh LOD selection, and an Editor HZB diagnostic view. Direct remains the default and comparison reference; an exported Player can explicitly select a mode with `--visibility direct|gpu-frustum|gpu-occlusion`. P2 GPU acceptance and measurements cover the Linux reference device, with a separate software-Vulkan functional check; Windows P2 and additional physical GPUs need separate validation. The first Debug/validation benchmark exposed slow GPU culling; device-local outputs and a bounded atomic append improved the same synthetic workloads in the repeat. Neither run establishes a general game-speed benefit. Windows MVP graphics acceptance used software Vulkan. See the [MVP acceptance dossier](docs/validation/mvp-acceptance.md), [P2 Linux evidence](docs/validation/p2-gpu-visibility-2026-09-23/README.md), [P2 acceptance protocol](docs/studies/19-p2-gpu-visibility-acceptance.md), [initial benchmark](docs/studies/20-p2-gpu-visibility-benchmark-2026-09-23.md), [optimized benchmark](docs/studies/21-p2-gpu-visibility-optimization-2026-09-23.md), and [implementation checkpoints](docs/IMPLEMENTATION.md) for exact scope and limits.
**Current status: the C++ MVP is accepted for the recorded Linux and Windows test profiles; Lua and P2 GPU visibility were added afterward.** The MVP 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. P2 adds optional GPU frustum and two-pass HZB occlusion modes, fixed indirect mesh bins, prepared mesh LOD selection, and an Editor HZB diagnostic view. Direct remains the default and comparison reference; an exported Player can explicitly select a mode with `--visibility direct|gpu-frustum|gpu-occlusion`. P2 GPU acceptance and measurements cover the Linux reference device, with a separate software-Vulkan functional check; Windows P2 functional checks passed on pinned SwiftShader; physical Windows GPUs and broader driver families remain untested. The first Debug/validation benchmark exposed slow GPU culling; device-local outputs and a bounded atomic append improved the same synthetic workloads in the repeat. Neither run establishes a general game-speed benefit. Windows MVP graphics acceptance used software Vulkan. See the [MVP acceptance dossier](docs/validation/mvp-acceptance.md), [P2 Linux evidence](docs/validation/p2-gpu-visibility-2026-09-23/README.md), [P2 SwiftShader compatibility](docs/validation/p2-swiftshader-2026-09-23/README.md), [P2 acceptance protocol](docs/studies/19-p2-gpu-visibility-acceptance.md), [initial benchmark](docs/studies/20-p2-gpu-visibility-benchmark-2026-09-23.md), [optimized benchmark](docs/studies/21-p2-gpu-visibility-optimization-2026-09-23.md), and [implementation checkpoints](docs/IMPLEMENTATION.md) for exact scope and limits.
## Start here
@@ -39,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.
The MVP provides two small games, one 2D and one 3D, with scene editing, C++ behavior, physics, Play and standalone export. A [Lua-only example](examples/lua) demonstrates the optional scripting module. P2 GPU visibility applies to opaque static meshes; ordered sprites/UI and the shadow pass keep their separate rendering paths. Its LOD policy chooses among meshes supplied by the project; automatic LOD generation, advanced shadows, temporal reconstruction and dynamic global illumination remain future work. See [profiling guidance](docs/manual/editor/profiling.md) before interpreting full-frame measurements.
The MVP provides two small games, one 2D and one 3D, with scene editing, C++ behavior, physics, Play and standalone export. A [Lua-only example](examples/lua) demonstrates the optional scripting module. P2 GPU visibility applies to opaque static meshes; ordered sprites/UI and the shadow pass keep their separate rendering paths. Its LOD policy chooses among meshes supplied by the project; the Editor and Player report the effective visibility mode if device capabilities force a fallback. Automatic LOD generation, advanced shadows, temporal reconstruction and dynamic global illumination remain future work. See [profiling guidance](docs/manual/editor/profiling.md) before interpreting full-frame measurements.
## Run the research map
+50 -7
View File
@@ -4,6 +4,7 @@
#include <faset/editor/mcp.hpp>
#include <faset/editor/session.hpp>
#include <iostream>
#include <memory>
#include <thread>
#ifdef FASET_HAS_EDITOR_UI
#include <faset/editor/editor_ui.hpp>
@@ -40,7 +41,7 @@ void help() {
std::cout
<< "Faset Editor\n"
" faset_editor Open the project launcher\n"
" faset_editor --project PATH [--new NAME --dimension 2|3] [--scene RELATIVE_PATH]\n"
" faset_editor --project PATH [--new NAME --dimension 2|3 [--language cpp|lua]] [--scene RELATIVE_PATH]\n"
" faset_editor --project PATH --mcp [--gui]\n"
" faset_editor --project PATH --command JSON [--wait]\n"
"Options: --engine SDK_SOURCE, --headless, --frames N, --capture PATH.ppm\n"
@@ -52,9 +53,10 @@ int editor_main(int argc, char** argv) {
using namespace faset::editor;
try {
std::filesystem::path project, engine = path_from_utf8(FASET_ENGINE_SOURCE), scene, capture;
std::string new_name, command;
std::string new_name, command, language = "cpp";
int dimension = 3;
bool mcp = false, gui = true, explicit_gui = false, wait = false;
bool explicit_language = false;
std::uint64_t frames = 0;
for (int i = 1; i < argc; ++i) {
const std::string arg = argv[i];
@@ -74,6 +76,10 @@ int editor_main(int argc, char** argv) {
new_name = value();
else if (arg == "--dimension")
dimension = std::stoi(value());
else if (arg == "--language") {
language = value();
explicit_language = true;
}
else if (arg == "--scene")
scene = path_from_utf8(value());
else if (arg == "--mcp")
@@ -100,30 +106,65 @@ int editor_main(int argc, char** argv) {
throw Error("cli.option", "Unknown option: " + arg);
}
require(!(mcp && !command.empty()), "cli.mode", "Choose MCP or a single command");
require(!explicit_language || !new_name.empty(), "cli.language",
"--language requires --new NAME");
require(language == "cpp" || language == "lua", "cli.language",
"Language must be cpp or lua");
if (mcp && !explicit_gui)
gui = false;
require(!project.empty() || (gui && !mcp && new_name.empty()), "cli.project",
"Use --project PATH for command, MCP, or --new modes");
auto previous_project = project;
#ifdef FASET_HAS_EDITOR_UI
std::optional<ProjectSelection> retry_create;
std::string creation_error;
#endif
for (;;) {
if (project.empty()) {
#ifdef FASET_HAS_EDITOR_UI
const auto selection =
run_project_launcher(engine, previous_project, frames, capture);
run_project_launcher(engine, previous_project, frames, capture,
retry_create, creation_error);
retry_create.reset();
creation_error.clear();
if (!selection)
return 0;
project = selection->path;
new_name = selection->create ? selection->name : std::string();
dimension = selection->dimension;
language = selection->language;
explicit_language = selection->create;
#else
throw Error("editor.gui_unavailable",
"This build has no graphical project launcher; use --project PATH");
#endif
}
Session session({std::filesystem::absolute(project), std::filesystem::absolute(engine),
executable_directory(argv[0])});
if (!new_name.empty())
session.scaffold(new_name, dimension);
std::unique_ptr<Session> session_holder;
try {
session_holder = std::make_unique<Session>(SessionConfig{
std::filesystem::absolute(project), std::filesystem::absolute(engine),
executable_directory(argv[0])});
if (!new_name.empty()) {
if (explicit_language)
session_holder->scaffold(new_name, dimension, language);
else
session_holder->scaffold(new_name, dimension);
}
} catch (const std::exception& error) {
#ifdef FASET_HAS_EDITOR_UI
if (gui && !new_name.empty()) {
retry_create = ProjectSelection{std::filesystem::absolute(project),
new_name, dimension, true, language};
creation_error = error.what();
project.clear();
new_name.clear();
scene.clear();
continue;
}
#endif
throw;
}
Session& session = *session_holder;
const auto settings = session.project();
#ifdef FASET_HAS_EDITOR_UI
if (gui && std::filesystem::exists(project / "project.faset.json"))
@@ -167,6 +208,8 @@ int editor_main(int argc, char** argv) {
project.clear();
scene.clear();
new_name.clear();
explicit_language = false;
language = "cpp";
continue;
#else
throw Error(
+26 -6
View File
@@ -28,6 +28,17 @@ using Json = nlohmann::json;
double milliseconds(Clock::time_point begin, Clock::time_point end) {
return std::chrono::duration<double, std::milli>(end - begin).count();
}
const char* visibility_mode_name(faset::render::VisibilityMode mode) {
switch (mode) {
case faset::render::VisibilityMode::Direct:
return "direct";
case faset::render::VisibilityMode::GpuFrustum:
return "gpu-frustum";
case faset::render::VisibilityMode::GpuOcclusion:
return "gpu-occlusion";
}
return "unknown";
}
struct ProfileSample {
double wall{}, simulation{}, snapshot{}, render{}, rendererCpu{}, gpu{}, readbackCpu{};
faset::runtime::FrameStats runtime;
@@ -36,6 +47,7 @@ struct ProfileSample {
std::uint32_t textureCount{};
bool physicsDebug{};
bool gpuVisibilityActive{};
faset::render::VisibilityMode effectiveVisibilityMode{faset::render::VisibilityMode::Direct};
};
Json distribution(std::vector<double> values) {
if (values.empty())
@@ -82,7 +94,9 @@ Json profileFrames(const std::vector<ProfileSample>& samples) {
{"gpu_allocated_bytes", sample.gpuAllocatedBytes},
{"texture_count", sample.textureCount},
{"physics_debug", sample.physicsDebug},
{"gpu_visibility_active", sample.gpuVisibilityActive}});
{"gpu_visibility_active", sample.gpuVisibilityActive},
{"effective_visibility_mode",
visibility_mode_name(sample.effectiveVisibilityMode)}});
}
return {{"samples", std::move(frames)},
{"summary_ms",
@@ -555,10 +569,12 @@ int player_main(int argc, char** argv) {
printGameplayLogs();
const auto renderStarted = Clock::now();
renderer.render(snapshot);
if (frames == 0 && visibilityMode != faset::render::VisibilityMode::Direct &&
!renderer.stats().gpu_visibility_active)
std::cerr << "Requested GPU visibility is unavailable on this device; "
"using Direct rendering.\n";
if (frames == 0 &&
visibilityMode != renderer.stats().effective_visibility_mode)
std::cerr << "Requested " << visibilityName << " visibility is unavailable; "
<< "using "
<< visibility_mode_name(renderer.stats().effective_visibility_mode)
<< " rendering.\n";
const auto frameFinished = Clock::now();
if (frames == 0)
firstFrameMs = milliseconds(started, frameFinished);
@@ -571,7 +587,7 @@ int player_main(int argc, char** argv) {
milliseconds(renderStarted, frameFinished), measured.cpu_ms, measured.gpu_ms,
measured.readback_cpu_ms, runtimeStats, measured.draw_calls, measured.vertices,
measured.gpu_allocated_bytes, measured.texture_count, debugPhysics,
measured.gpu_visibility_active});
measured.gpu_visibility_active, measured.effective_visibility_mode});
}
++frames;
}
@@ -601,6 +617,8 @@ int player_main(int argc, char** argv) {
{"validation_errors", stats.validation_errors},
{"presentation_mode", headless ? "offscreen" : "windowed"},
{"visibility_mode", visibilityName},
{"effective_visibility_mode",
visibility_mode_name(stats.effective_visibility_mode)},
{"simulation_mode", "synthetic_fixed_timestep"},
{"fixed_delta_seconds", config.fixedDelta},
{"percentile_method", "nearest_rank_all_completed_frames_no_warmup_exclusion"},
@@ -627,6 +645,8 @@ int player_main(int argc, char** argv) {
{"dimension", document.value("dimension", 3)},
{"device", stats.device},
{"visibility_mode", visibilityName},
{"effective_visibility_mode",
visibility_mode_name(stats.effective_visibility_mode)},
{"gpu_visibility_active", stats.gpu_visibility_active},
{"validation_errors", stats.validation_errors}}
.dump()
+52 -10
View File
@@ -75,6 +75,22 @@ ProjectSelection open_project(const fs::path& path) {
throw std::runtime_error("Project name or scene type is invalid.");
return {path, std::move(name), dimension, false};
}
bool fresh_destination(const fs::path& path) {
if (!fs::exists(path))
return true;
if (!fs::is_directory(path) || fs::is_symlink(path))
return false;
for (const auto& entry : fs::directory_iterator(path)) {
if (entry.path().filename() != ".faset" || !entry.is_directory() ||
entry.is_symlink())
return false;
for (const auto& internal : fs::directory_iterator(entry.path()))
if (internal.path().filename() != "cache" || !internal.is_directory() ||
internal.is_symlink() || !fs::is_empty(internal.path()))
return false;
}
return true;
}
Json recent_records(const fs::path& file) {
try {
auto j = read_json(file);
@@ -124,9 +140,11 @@ struct ProjectLauncher::Impl {
std::optional<ProjectSelection> selected;
bool create = false, cancelled = false, browsing = false, browser_valid = false;
int dimension = 3;
std::string language = "cpp";
std::string error;
Impl(render::Renderer& r, const fs::path& engine, const fs::path& initial,
const fs::path& recents)
const fs::path& recents, const std::optional<ProjectSelection>& retry,
std::string creation_error)
: renderer(r), ui(engine / "assets/fonts/NotoSans.ttf"),
recents_file(recents.empty() ? recent_path() : recents) {
auto theme = ui::Theme::load(engine / "assets/ui/dark.json");
@@ -147,13 +165,22 @@ struct ProjectLauncher::Impl {
ui.set_ime(
[this](bool enabled) { renderer.set_text_input(enabled); },
[this](ui::Rect r) { renderer.set_text_input_area(r.x, r.y, r.width, r.height); });
const auto start = initial.empty() ? user_home() / "FasetProjects/MyGame" : initial;
const auto start = retry ? retry->path
: initial.empty() ? user_home() / "FasetProjects/MyGame" : initial;
ui.update_text("launcher-path", path_to_utf8(start));
create = initial.empty();
create = retry ? retry->create : initial.empty();
if (retry) {
ui.update_text("launcher-name", retry->name);
dimension = retry->dimension;
language = retry->language;
error = std::move(creation_error);
}
ui.find("launcher-open")->on_click = [this](Widget&) { set_mode(false); };
ui.find("launcher-create")->on_click = [this](Widget&) { set_mode(true); };
ui.find("launcher-2d")->on_click = [this](Widget&) { dimension = 2; };
ui.find("launcher-3d")->on_click = [this](Widget&) { dimension = 3; };
ui.find("launcher-cpp")->on_click = [this](Widget&) { language = "cpp"; };
ui.find("launcher-lua")->on_click = [this](Widget&) { language = "lua"; };
ui.find("launcher-submit")->on_click = [this](Widget&) { submit(); };
ui.find("launcher-cancel")->on_click = [this](Widget&) { cancelled = true; };
ui.find("launcher-name")->on_preview = [this](Widget&) { error.clear(); };
@@ -209,14 +236,14 @@ struct ProjectLauncher::Impl {
throw std::runtime_error("Enter a project name.");
if (name.size() > 256 || name.find_first_of("\r\n\t") != std::string::npos)
throw std::runtime_error("Use a short project name on one line.");
if (fs::exists(path) && (!fs::is_directory(path) || !fs::is_empty(path)))
if (!fresh_destination(path))
throw std::runtime_error("Create requires a new or empty directory.");
auto parent = path.parent_path();
while (!parent.empty() && !fs::exists(parent))
parent = parent.parent_path();
if (parent.empty() || !fs::is_directory(parent))
throw std::runtime_error("Project parent directory is unavailable.");
selected = ProjectSelection{path, name, dimension, true};
selected = ProjectSelection{path, name, dimension, true, language};
} else
selected = open_project(path);
error.clear();
@@ -351,14 +378,24 @@ struct ProjectLauncher::Impl {
ui.find("launcher-create")->selected = create;
ui.find("launcher-2d")->selected = dimension == 2;
ui.find("launcher-3d")->selected = dimension == 3;
ui.find("launcher-cpp")->selected = language == "cpp";
ui.find("launcher-lua")->selected = language == "lua";
ui.find("launcher-2d")->appearance =
dimension == 2 ? Appearance::Quiet : Appearance::Default;
ui.find("launcher-3d")->appearance =
dimension == 3 ? Appearance::Quiet : Appearance::Default;
ui.find("launcher-cpp")->appearance =
language == "cpp" ? Appearance::Quiet : Appearance::Default;
ui.find("launcher-lua")->appearance =
language == "lua" ? Appearance::Quiet : Appearance::Default;
ui.find("launcher-language-hint")->text =
language == "cpp" ? "Compiled gameplay. Add Lua later if you need quick reloads."
: "Lua gameplay reloads during development without a C++ rebuild.";
for (const auto* id : {"launcher-name-label", "launcher-name", "launcher-dimension-label",
"launcher-dimensions", "launcher-name-group",
"launcher-name-space", "launcher-path-space",
"launcher-dimension-group"})
"launcher-dimension-group", "launcher-language-group",
"launcher-language-space"})
ui.find(id)->visible = create;
ui.find("launcher-heading")->text = create ? "Create project" : "Open project";
ui.find("launcher-submit")->text = create ? "Create project" : "Open project";
@@ -424,8 +461,11 @@ struct ProjectLauncher::Impl {
}
};
ProjectLauncher::ProjectLauncher(render::Renderer& r, const fs::path& engine,
const fs::path& initial, const fs::path& recents)
: impl_(std::make_unique<Impl>(r, engine, initial, recents)) {}
const fs::path& initial, const fs::path& recents,
const std::optional<ProjectSelection>& retry,
std::string creation_error)
: impl_(std::make_unique<Impl>(r, engine, initial, recents, retry,
std::move(creation_error))) {}
ProjectLauncher::~ProjectLauncher() = default;
void ProjectLauncher::frame(const std::vector<render::Event>& events) {
impl_->frame(events);
@@ -445,9 +485,11 @@ bool ProjectLauncher::cancelled() const {
std::optional<ProjectSelection> run_project_launcher(const fs::path& engine,
const fs::path& initial,
std::uint64_t max_frames,
const fs::path& capture) {
const fs::path& capture,
const std::optional<ProjectSelection>& retry,
std::string creation_error) {
render::Renderer renderer({1100, 720, "Faset Engine — Projects", false, true});
ProjectLauncher launcher(renderer, engine, initial);
ProjectLauncher launcher(renderer, engine, initial, {}, retry, std::move(creation_error));
std::uint64_t frames = 0;
while (!renderer.should_close() && !launcher.cancelled() && !launcher.selection() &&
(max_frames == 0 || frames < max_frames)) {
+9
View File
@@ -41,6 +41,15 @@
{"id": "launcher-3d", "kind": "button", "text": "3D", "layout": {"width": 116}}
]}
]},
{"id": "launcher-language-space", "kind": "row", "layout": {"height": 18}},
{"id": "launcher-language-group", "kind": "column", "layout": {"gap": 6}, "children": [
{"id": "launcher-language-label", "kind": "label", "text": "Gameplay language", "layout": {"height": 24}},
{"id": "launcher-languages", "kind": "row", "layout": {"height": 42, "gap": 4}, "children": [
{"id": "launcher-cpp", "kind": "button", "text": "C++", "layout": {"width": 116}},
{"id": "launcher-lua", "kind": "button", "text": "Lua", "layout": {"width": 116}}
]},
{"id": "launcher-language-hint", "kind": "label", "text": "Compiled gameplay. Add Lua later if you need quick reloads.", "font_size": 12, "layout": {"height": 30}}
]},
{"id": "launcher-hint", "kind": "label", "text": "Open a directory containing project.faset.json.", "font_size": 13, "layout": {"height": 36}},
{"id": "launcher-actions-space", "kind": "row", "layout": {"height": 22}},
{"id": "launcher-actions-rule", "kind": "row", "layout": {"height": 1}},
+19 -2
View File
@@ -1,8 +1,18 @@
add_library(faset_build_service STATIC ${PROJECT_SOURCE_DIR}/src/editor/build_service.cpp)
add_library(faset_build_service STATIC
${PROJECT_SOURCE_DIR}/src/editor/build_service.cpp
${PROJECT_SOURCE_DIR}/src/editor/build_cache.cpp
${PROJECT_SOURCE_DIR}/src/editor/build_diagnostics.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 faset_scripting_project PRIVATE faset_assets faset_authoring Threads::Threads)
if(BUILD_TESTING)
add_executable(faset_build_diagnostics_tests ${PROJECT_SOURCE_DIR}/tests/build_diagnostics_tests.cpp)
target_link_libraries(faset_build_diagnostics_tests PRIVATE faset_build_service)
add_test(NAME build_diagnostics COMMAND faset_build_diagnostics_tests)
add_executable(faset_build_cache_tests ${PROJECT_SOURCE_DIR}/tests/build_cache_tests.cpp)
target_link_libraries(faset_build_cache_tests PRIVATE faset_build_service)
target_compile_definitions(faset_build_cache_tests PRIVATE FASET_ENGINE_SOURCE="${PROJECT_SOURCE_DIR}")
add_test(NAME build_cache COMMAND faset_build_cache_tests)
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}")
@@ -13,5 +23,12 @@ if(BUILD_TESTING)
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)
# This fixture performs many full cache invalidations and process launches.
# Windows runner startup and antivirus overhead exceed 60 seconds even when
# individual native fixture commands finish normally.
if(WIN32)
set_tests_properties(build_schema_publication PROPERTIES TIMEOUT 240)
else()
set_tests_properties(build_schema_publication PROPERTIES TIMEOUT 60)
endif()
endif()
+3
View File
@@ -47,5 +47,8 @@ if(TARGET faset_ui AND TARGET faset_editor_session AND TARGET faset_scene_view)
set_tests_properties(editor_ui_gizmos PROPERTIES LABELS "gpu")
add_test(NAME editor_ui_authoring COMMAND faset_editor_ui_tests)
set_tests_properties(editor_ui_authoring PROPERTIES LABELS "gpu")
add_executable(faset_editor_ui_latency ${PROJECT_SOURCE_DIR}/tests/editor_ui_latency.cpp)
target_link_libraries(faset_editor_ui_latency PRIVATE faset_editor_ui)
target_compile_definitions(faset_editor_ui_latency PRIVATE FASET_TEST_ENGINE="${PROJECT_SOURCE_DIR}")
endif()
endif()
+1
View File
@@ -9,4 +9,5 @@ if(TARGET faset_render AND BUILD_TESTING)
"${PROJECT_SOURCE_DIR}/tests/render_gpu_visibility_tests.cpp")
target_link_libraries(faset_render_gpu_visibility_tests PRIVATE faset_render)
add_test(NAME gpu_visibility COMMAND faset_render_gpu_visibility_tests)
set_tests_properties(gpu_visibility PROPERTIES LABELS "gpu;p2")
endif()
+42 -2
View File
@@ -1,6 +1,6 @@
# Faset Engine — архитектура
Редакция 1.2 · 23 сентября 2026 · **MVP принят; реализация P2 описана отдельно от исходных решений**.
Редакция 1.3 · 24 сентября 2026 · **MVP принят; реализация P1/P2 описана отдельно от исходных решений**.
Этот файл фиксирует решения пользователя. **Принято** означает выбранное направление реализации, а не автоматически завершённую возможность движка. Работающий код, выполненные проверки и текущие ограничения перечислены в [журнале реализации](IMPLEMENTATION.md). Этапы до MVP и после него, зависимости работ и критерии готовности находятся в [PLAN.md](../PLAN.md).
@@ -83,14 +83,18 @@ Catch-up ограничен числом ticks за проход главног
2D и 3D имеют отдельные миры и spatial-типы. Для динамического тела итоговым transform владеет физика; teleport и кинематическое управление — отдельные операции. Физическое тело связано с entity через проверяемый handle. Миры 2D и 3D не сталкиваются автоматически. Внутренние substeps solver настраиваются отдельно от частоты gameplay ticks; начальная настройка — четыре substeps с проверкой на сценах проекта.
Собственный Vulkan 1.3 renderer получает подготовленный snapshot, не обходит изменяемый EnTT registry с render thread. RenderGraph описывает reads/writes ресурсов, порядок проходов, barriers и lifetimes. MVP создал проверяемый raster-путь 2D/3D без обязательных RT/mesh shaders. P2 добавил выбираемые GPU frustum и GPU occlusion режимы для opaque static meshes; Direct остаётся начальным режимом и эталоном. Scene extraction задаёт устойчивые keys для постоянных объектов; renderer отслеживает generation и консервативные bounds. `DrawItem` может содержать заранее подготовленные LOD meshes. CPU формирует фиксированные совместимые bins, GPU заполняет visible IDs и `instanceCount` для indirect draws. В occlusion-режиме previous HZB даёт предварительное решение, а current HZB и PostCull возвращают объекты, которые открылись в этом кадре. Camera cut, несовместимые view/projection/extent и смена instance generation инвалидируют историю. Тени, прозрачные meshes, спрайты и UI не теряют независимый порядок из-за camera culling. Точная реализованная область и проверка приведены в [P2 acceptance](studies/19-p2-gpu-visibility-acceptance.md).
Собственный Vulkan 1.3 renderer получает подготовленный snapshot, не обходит изменяемый EnTT registry с render thread. RenderGraph описывает reads/writes ресурсов, порядок проходов, barriers и lifetimes. MVP создал проверяемый raster-путь 2D/3D без обязательных RT/mesh shaders. P2 добавил выбираемые GPU frustum и GPU occlusion режимы для opaque static meshes; Direct остаётся начальным режимом и эталоном. Scene extraction задаёт устойчивые keys для постоянных объектов; renderer отслеживает generation и консервативные bounds. GPU instance record несёт стабильные slot и generation, а компактный индекс в candidate/visible буферах служит адресом записи только текущего кадра. `DrawItem` может содержать заранее подготовленные LOD meshes. CPU формирует фиксированные совместимые bins, GPU заполняет visible IDs и `instanceCount` для indirect draws. В occlusion-режиме previous HZB даёт предварительное решение, а current HZB и PostCull возвращают объекты, которые открылись в этом кадре. Camera cut, несовместимые view/projection/extent и смена instance generation инвалидируют историю. Тени, прозрачные meshes, спрайты и UI не теряют независимый порядок из-за camera culling. Точная реализованная область и проверка приведены в [P2 acceptance](studies/19-p2-gpu-visibility-acceptance.md).
**Vulkan API вызывается напрямую внутри собственного backend Faset.** Он владеет Vulkan handles, созданием GPU-ресурсов и pipelines, записью команд, синхронизацией и отправкой в очереди. Renderer и RenderGraph используют небольшой внутренний интерфейс ресурсов и команд Faset; Vulkan-типы и вызовы `vk*` не входят в gameplay API или команды редактора. SDL3 обеспечивает окно и создание Vulkan surface, но не заменяет графический backend; Slang отвечает за компиляцию шейдеров. Универсальная абстракция нескольких графических API не является задачей MVP.
Slang компилирует шейдеры в SPIR-V и выдаёт сведения для согласования CPU/GPU данных. Совместимый HLSL проходит выбранный pipeline; поддержка любого существующего HLSL-кода не обещается. Cook учитывает compiler/version, includes, defines и GPU profile. Nanite/Lumen-подобные системы остаются исследовательскими направлениями, не готовыми возможностями MVP.
Граница зависимости проходит через **готовый пакет SPIR-V + нормализованную reflection-метаинформацию Faset**. В текущей реализации CMake требует `slangc` для сборки этого пакета, а `compile_shader.py` преобразует Slang reflection в формат `faset.shader-reflection` с hash и layout fingerprint. Сам Vulkan backend создаёт pipelines из SPIR-V и проверяет этот контракт; он не вызывает Slang во время игры и не линкует его runtime. Однако структура descriptor sets, push constants, входов вершин и соглашение о матрицах сейчас явно согласованы с существующими Slang-шейдерами. Поэтому новый компилятор возможен без переписывания команд Vulkan, если он выдаёт эквивалентный SPIR-V и тот же проверяемый контракт. При появлении второго компилятора отдельным шагом вводится интерфейс shader package producer и тесты эквивалентности ABI; преждевременная абстракция компиляторов в MVP/P2 не нужна.
P2 не требует синхронного чтения GPU-счётчиков для решения видимости: readback включается редакторской диагностикой. Current HZB preview также читается только по запросу. Существующий путь полного framebuffer capture всё ещё ждёт GPU, поэтому измерения полной длительности кадра включают эту стоимость; `gpu_ms` и времена отдельных проходов не заменяют полную CPU/GPU-профилировку. [Первое измерение](studies/20-p2-gpu-visibility-benchmark-2026-09-23.md) обнаружило дорогой MainCull на Linux reference GPU в Debug/validation. [Повтор после оптимизации](studies/21-p2-gpu-visibility-optimization-2026-09-23.md) отдельно измерил эффект device-local выходных буферов и замены CAS-цикла на atomic add: MainCull p50 уменьшился до 0,030–0,042 мс в тех же синтетических сценах. Direct остаётся performance default до проверки Release-сборки и игровых нагрузок. Mesh LOD выбирается среди заранее подготовленных вариантов с hysteresis; генерация LOD, streaming и cluster geometry пока не реализованы. GPU-режимы дополнительно проверяют необходимые limits/formats устройства и не считаются доступными на любом Vulkan 1.3 GPU без такой проверки.
Диагностика различает запрошенный и фактически выполненный visibility mode в каждом кадре. Если GPU culling недоступен, фактический режим Direct; если доступен frustum, но нет подходящего HZB, запрос GPU occlusion явно понижается до GPU frustum. Player записывает оба режима в профиль и сообщает о fallback в stderr, редактор показывает Effective path и факт расхождения с выбранным режимом.
## 8. Blender и ассеты — принято
Используется **обычный Blender**, без обязательного форка или установленного MCP-плагина. Базовый обмен — **GLB и manifest со стабильными asset/subasset IDs**. Обычный экспорт принимается движком; удобный add-on для публикации, UUID и повторного экспорта остаётся необязательным помощником. Если источник не предоставляет устойчивые subasset IDs, importer не обещает надёжно угадать соответствие после произвольного rename/reparent: сохраняет mapping и показывает конфликты.
@@ -107,6 +111,41 @@ Development и release Player содержат runtime, выбранные иг
Критерии [плана](../PLAN.md) включают цикл сцена → Inspector → MCP authoring edit → Undo → save → build → отдельный Player, повторный импорт GLB с сохранением overrides, lifecycle/handle tests и запуск на обеих ОС без Editor/Blender/MCP/SDK. Выполненные проверки и незакрытые условия перечислены в [журнале реализации](IMPLEMENTATION.md); наличие теста само по себе не означает прохождения на обеих ОС.
## 10. P1 — проверяемая итерация gameplay
Для native-сборки источником истины остаются CMake/Ninja и их dependency graph.
BuildService сохраняет проверяемую копию `Scripts` по хешу содержимого,
отделяет Debug от Release и
запоминает версионированный fingerprint рецепта, исходников, Lua-деклараций,
toolchain, шейдерных и runtime-входов. Повторное использование schema/package
разрешено лишь после проверки полного manifest и схемы; испорченная или устаревшая
генерация не выдаётся за cache hit. Переключение указателя на успешную генерацию
атомарно; сбой сборки оставляет предыдущую доступной, но Inspector помечает
метаданные устаревшими. Исходный каталог и копия повторно хешируются после native
build и перед повторным использованием кэша или публикацией. Это обнаруживает сохранившееся изменение;
запись и восстановление прежних байтов другим процессом того же пользователя во
время чтения компилятором не исключены. Экспорт отдельно проверяет asset generations.
Структурированные сообщения компилятора/Lua дополняют сырой лог. Для исходников
проекта Editor и MCP вызывают один `faset_source_open` с относительным путём и
позициями; внешний редактор запускается аргументами без shell. Четыре стартовых
проекта комбинируют C++/Lua и 2D/3D. C++ остаётся rebuild-and-restart, а Lua
development reload проверяет candidate и перезапускает script/world state, не
сохраняя произвольное runtime-состояние.
Autosave относится только к именованным dirty сценам Editor после двух секунд
покоя. Он использует ревизию документа и прежний disk-hash conflict check, не
перезаписывает внешний файл и не создаёт Undo-операцию. Неназванная сцена требует
Save As; recovery journal работает независимо от autosave. Статусы видны в UI и
через Editor MCP. Player и экспортная игра по-прежнему не имеют MCP.
Измерения P1 разделяют Debug-время итераций, offscreen первый кадр и старый
Release reference baseline точных демо. Исходный отчёт, raw-профили и отдельное
дополнение о версиях/хешах исполняемых инструментов находятся в
[датированном dossier](validation/p1-iteration-2026-09-24/README.md). Большая
синтетическая сцена выявляет стоимость CPU simulation/snapshot; её время нельзя
переносить на 2D/3D демо или выдавать за обещание кадровой частоты.
## История решений
- **17.09.2026:** выбраны имя Faset Engine, Linux/Windows desktop 2D/3D, ручная работа и MCP, интеграция с Blender, Box2D и Box3D; подготовлены исследования.
@@ -114,5 +153,6 @@ Development и release Player содержат runtime, выбранные иг
- **18.09.2026:** утверждены процессы и linkage, явная metadata registration/schema export, JSON/binary formats, вложенные шаблоны/sparse overrides, lifecycle, фиксированный tick и последовательный scheduler; граница MCP ограничена Editor/headless authoring services.
- **18.09.2026:** английский принят основным языком интерфейса, диагностик и публичного API; уточнена граница прямых Vulkan-вызовов внутри собственного backend.
- **23.09.2026:** после принятого MVP выполнен P2 GPU visibility/LOD на Linux reference GPU; direct renderer сохранён как начальный режим и эталон, ограничения и измерения вынесены в отдельный [протокол](studies/19-p2-gpu-visibility-acceptance.md).
- **24.09.2026:** P1 итерация фиксирует проверяемое повторное использование schema/package, структурированную диагностику, стартовые C++/Lua проекты, безопасный autosave и измерительный протокол без C++ hot reload.
Реализация идёт по [PLAN.md](../PLAN.md). Последующие изменения принятых контрактов фиксируются здесь с причиной и способом проверки.
+95 -1
View File
@@ -406,7 +406,7 @@ LOD example. Its full CTest run reported 57 registered tests, zero failures and
one existing native-window lifecycle skip. The focused Release Player CLI test
selected all three visibility modes, verified active GPU status in the profile,
and rejected an invalid mode. Release build/test success establishes functional
coverage; it does not replace a Release performance comparison or a Windows P2 run.
coverage; at that checkpoint, Windows P2 was still unverified.
As an additional Linux software-Vulkan check, Lavapipe ran all 15 labelled P2
acceptance cases plus the standalone GPU visibility test and both example modes.
@@ -425,3 +425,97 @@ pixels, with no missing geometry. Direct CPU vertex transformation and GPU shade
vertex transformation round differently at subpixel triangle boundaries. The
Debug benchmarks above and this Release functional record have different purposes;
neither establishes physical Windows GPU coverage.
Compatibility checkpoint `433bce0` followed the first red Windows P2 CI run. The
pinned SwiftShader device does not expose `shaderDrawParameters`; the Slang
`SV_InstanceID` builtin had emitted a SPIR-V `DrawParameters` capability, so the
renderer rejected the GPU route. Both GPU vertex entries now use raw
`SV_VulkanInstanceID` while their fixed indirect commands keep `firstInstance = 0`.
The renderer no longer requests that optional feature. The GPU smoke case now has
the `gpu;p2` CTest labels, so CPU-only native checks exclude it. A reflection test
rejects `DrawParameters` in the two generated vertex modules. On matching Linux
SwiftShader, all 16 P2 cases passed with the GPU path active; a deliberately
driverless CPU-only selection passed 26/26.
At this revision, [Windows graphics CI](https://github.com/emil28092005/Faset_Engine/actions/runs/35918597688)
passed all 58 tests on pinned SwiftShader, including the P2 route and Windows
window/editor tests. Its two relocated Release example games each rendered 120
frames in the default Direct mode. [Native/manual CI](https://github.com/emil28092005/Faset_Engine/actions/runs/35918597686)
also passed. The [SwiftShader compatibility dossier](validation/p2-swiftshader-2026-09-23/README.md)
records the cause, local commands, CI run and export report. Windows did not have
the Khronos validation layer, and no physical Windows GPU or driver-family
performance claim follows from this software-Vulkan result.
Independent P2 source review then found two contract gaps. Requested GPU occlusion
could silently run GPU frustum when HZB was unavailable, and stable instance
slot/generation lived only in the CPU tracker while GPU candidates used dense
frame-local indices. Follow-up `22012c1` records the requested and effective
visibility mode per frame in renderer statistics, Player profiles/output and
Editor diagnostics, with a CPU capability-policy regression. GPU instance records
now carry stable slot and all 64 generation bits in previously reserved metadata;
dense indices remain addresses into the current frame's instance buffer. A focused
reorder/replacement/anonymous-instance test covers the metadata packing. The
reviewer rechecked both fixes and found no remaining load-bearing issue in those
paths. On this revision, full Linux Debug and Release CTest each reported 57
registered tests, zero failures and one existing native-window lifecycle skip. The dedicated ImGui overlay test passed
1/1; pinned Linux SwiftShader passed all 16 P2 cases and Player diagnostics; a
driverless CPU-only Release selection passed 26/26. The final
[Windows graphics CI run](https://github.com/emil28092005/Faset_Engine/actions/runs/35922643226)
passed all 58 tests without skips on SwiftShader; both relocated Release games
rendered 120 frames in Direct mode. The corresponding
[native/manual CI run](https://github.com/emil28092005/Faset_Engine/actions/runs/35922643004)
passed on Linux and Windows. Unsupported-HZB integration and physical Windows
GPU coverage remain untested.
## Post-MVP checkpoint — P1 gameplay iteration
P1 adds a complete-input native build stamp and a content-addressed `Scripts` copy.
The package key binds configuration, recipe, source, Lua declaration, toolchain,
shader and runtime artifacts. A second unchanged build reuses a verified schema
and package generation without invoking SchemaExporter; CMake/Ninja still verify
the native graph. Malformed/corrupt generations, modified headers/tools and
detected source mismatches cannot publish a false hit. The live tree and staged
copy are rehashed after native build and before reuse/publication; a same-user
write-and-restore during compiler reads remains outside this check. Failed jobs
preserve the previous successful pointer, while Inspector metadata becomes stale.
Compiler and Lua output now produce bounded structured Console diagnostics with
raw logs retained. Source opening accepts only project `Scripts` files and passes
literal file/line/column arguments to the configured external editor. A real
compile failure revealed that compiler paths pointed to the staged copy;
`d7a7a7c` maps only the verified snapshot subtree back to project-relative
source. The same correction retains a later actionable error after 250 unrelated
warnings. Four runnable C++/Lua × 2D/3D starters are available in launcher and
CLI; the existing scaffold default remains compatible. Named dirty scenes
autosave after two idle seconds by default; unnamed scenes require Save As, and
disk/revision conflicts retain the journal and never overwrite external edits.
The [P1 dated dossier](validation/p1-iteration-2026-09-24/README.md) retains a
clean-revision `6c2e8fa` Debug workflow with every raw command/profile. On the
Ryzen 7 1700/RTX 2080 Ti Linux host, five unchanged builds had median 11.53 s,
five changed-C++ builds 28.16 s, five changed-header builds 23.89 s, and five
watched Lua reloads 0.502 s. All five unchanged builds reported a verified
generation reuse. The 3,000-frame profile showed no growth in the explicit
Vulkan allocation counter (15,787,008 bytes). A seeded 527-object synthetic
scene reached 149.39 ms p95 wall time, dominated by CPU simulation/snapshot;
this is a visible scalability issue, not a tiny-demo Release budget comparison.
The historical exact-scene Release budgets were met in the earlier 2026-09-18
record, but that evolving working-tree baseline does not certify the current
revision. A quiet-host current Release rerun is pending.
Linux focused cache, diagnostics, schema and workflow-math tests pass; a real
failed-build replay now reports navigable `Scripts/Gameplay.cpp:73:2`. The
[Lua-only Release result](validation/lua-release-2026-09-24/README.md) validated
and rendered after source-hidden relocation on the physical Linux GPU. At
`6c2e8fa`, [native/manual CI](https://github.com/emil28092005/Faset_Engine/actions/runs/35936310287)
passed on Linux and Windows. Its Windows graphics job passed all 61 CTests on
SwiftShader, then hit an invalid integration fixture under the engine checkout;
`4ac02ee` moved the fixture to runner temporary storage. The subsequent
[Windows graphics run](https://github.com/emil28092005/Faset_Engine/actions/runs/35937433040)
passed all 61 CTests and the real export integration. Two C++ and one Lua-only
Release package validated and rendered 120 frames each after relocation with
source paths unavailable; the [raw Windows report](validation/p1-iteration-2026-09-24/windows-4ac02ee/playable-report.json)
records hashes and software Vulkan device identity. The later `d7a7a7c`
real-diagnostic-navigation assertion still awaits its Windows CI result.
Windows had no active Khronos validation layer, and no physical Windows GPU
performance result is claimed.
+17
View File
@@ -0,0 +1,17 @@
# P1 editor visual references
These image prototypes were generated from the recorded native editor screenshot in
`docs/validation/checkpoint5-linux-2026-09-18/editor-blender.png` before changing the
P1 interface. They are visual references for the existing compact dark treatment,
not specifications of behavior, layout measurements, copy, or platform paths.
- `p1-iteration-console-reference.png` explores structured build diagnostics,
source navigation, a cache indicator, and autosave status in the existing editor.
- `p1-project-template-reference.png` explores a compact 2D/3D and C++/Lua project
chooser. Its example Windows path is illustrative; the implementation must use
native paths on Linux and Windows.
Both were created with the built-in image generation tool using the cited screenshot
as the edit reference. The accepted behavior and validation criteria are in the
[P1 design](../superpowers/specs/2026-09-24-p1-iteration-design.md) and
[`PLAN.md`](../../PLAN.md).
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

+24 -1
View File
@@ -1,5 +1,28 @@
# Developer diagnostics
## Build errors and source navigation
Build and export jobs report compiler, clang-cl/MSVC, and Lua errors as structured
Console rows. Each row shows severity, message, and a one-based source location.
Choose **Open source** to open a file under the current project's `Scripts/`
directory at that line and column. Diagnostics from engine or external files remain
visible but have no source action. The **Jobs** tab shows whether a verified build
generation was reused, the elapsed phases, and an expandable raw process log;
**Copy full log** retains the complete available output for troubleshooting.
The same action is available over Editor MCP:
```json
{"command":"faset_source_open","arguments":{"path":"Scripts/Gameplay.cpp","line":17,"column":4}}
```
`faset_script_open` remains Lua-only. Both commands use the project's optional
`editor.script_editor` argv array, or Zed by default. The tokens `{file}`,
`{line}`, `{column}`, and `{project}` may appear within one argument, for
example `["zed", "{file}:{line}:{column}"]`. Faset passes each argument
literally to the editor process without shell expansion. An invalid editor
command produces a source-editor error without changing the build result.
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
@@ -10,7 +33,7 @@ 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.
Use the **Visibility** selector to compare **Direct**, **GPU frustum**, and **GPU occlusion** on the same open scene. This is a live renderer setting for the Editor viewport; it does not change the scene or exported game. The selected mode is independent of **Freeze counters**. The counters describe the previous completed frame, so render one more frame after changing modes before reading them. **Path: active** under GPU visibility confirms that the GPU path actually ran; a selected GPU mode by itself is not evidence that it ran.
Use the **Visibility** selector to compare **Direct**, **GPU frustum**, and **GPU occlusion** on the same open scene. This is a live renderer setting for the Editor viewport; it does not change the scene or exported game. The selected mode is independent of **Freeze counters**. The counters describe the previous completed frame, so render one more frame after changing modes before reading them. **Effective path** names the algorithm that actually ran. A **Fallback from** line appears when device or target capabilities prevent the selected mode; for example, GPU occlusion may use GPU frustum if HZB is unavailable.
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. It also shows whether GPU visibility ran, submitted indirect bins, visible instances, frustum rejects, deferred and post-pass visible instances, HZB history validity, counts per prepared LOD level, and GPU pass timings where available. GPU counts are explicitly marked unavailable until the first frame rendered with diagnostics open; only a displayed zero is a measured zero. **Previous HZB history: invalid** is expected after a camera cut or resize until compatible depth history is available. A current HZB preview can still exist after that first frame because it was built from the current depth. 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.
+23 -4
View File
@@ -9,8 +9,10 @@ Player process. **Stop** leaves the authoring document unchanged.
1. Edit `Scripts/Gameplay.cpp` and its explicit schema declarations.
2. Stop the running Player.
3. Choose **Build C++**. The compiler and SchemaExporter run outside the Editor.
4. Read compiler errors in the Console. A failed build retains the previous schema
3. Choose **Build**. CMake/Ninja build native gameplay and the separate
SchemaExporter refreshes the Inspector schema when inputs changed.
4. Read compiler errors in the Console. Select a project-source diagnostic to open
its line in your external editor. A failed build retains the previous schema
and binary; the schema status reports that it is stale.
5. After a successful build, edit the behavior's exposed fields in the Inspector.
6. Choose **Play**. It builds if necessary and launches the captured scene.
@@ -23,6 +25,22 @@ The Player supports pause and single-step. Editor controls use a private session
control file; they do not expose runtime entity queries through MCP. Closing the
Player is observed by the Editor, which keeps its logs and authoring state.
An unchanged second build still asks CMake/Ninja to verify their dependency graph.
When the native outputs, gameplay sources, toolchain, shaders and runtime inputs
match the validated package, **Jobs** reports `schema_cache_hit` and
`generation_reused` as true and returns the same verified generation without a
second SchemaExporter run or package copy. A changed `.cpp`, header, Lua declaration,
build recipe or relevant tool invalidates that reuse. A damaged cached package is
not treated as a hit. The job also exposes elapsed phases and a build fingerprint;
see [Developer diagnostics](diagnostics.md) for structured errors and raw logs.
If a build fails, correct the listed source and build again. The prior playable
generation remains available, but stale Inspector metadata is not evidence that
the failed edit built successfully. Save the scene separately: named dirty scenes
autosave after two idle seconds by default, while an unnamed scene needs **Save As**.
External file changes produce a visible conflict instead of an overwrite; the
[workspace guide](workspace.md#create-and-save-a-scene) explains recovery.
## Export a standalone game
Use **Export** in the Editor for the current scene. Resolve template conflicts and
@@ -46,9 +64,10 @@ Development builds use **Debug**. Exports default to **Release** and use a separ
CMake cache. The BuildService API also accepts `RelWithDebInfo` for exports. Exporting
does not change the configuration of your development Player.
Each successful export creates an immutable directory under
Each successful export creates a versioned directory under
`Exports/MyGame/generations/<generation>`. `current.json` points to the active
generation. Distribute the **whole generation directory**, not just the executable.
generation. The Editor leaves published generations untouched. Distribute the
**whole generation directory**, not just the executable.
If building, cooking or verification fails, the previous pointer and package remain
available. Cancelling a job does not delete earlier exports.
+14
View File
@@ -67,6 +67,20 @@ resume and single-step process controls; it cannot read or modify game entities.
scenes that have never been saved. Restoring an already open document requires its
current revision. Recovery refuses to overwrite an externally changed scene file.
Long-lived headless and graphical Editor sessions also poll the same autosave
controller. With `editor.autosave` enabled (the default), a named dirty scene is
saved after two seconds idle. `faset_autosave_status {}` reports `enabled` and each
open document's `state`, `revision`, `path`, and `error`. States include `saved`,
`pending`, `saving`, `conflict`, `failed`, `save_as_required`, and `disabled`.
An unnamed scene remains in recovery until you give it an explicit path. An
external disk edit produces `conflict` without overwriting that edit. For scripted
workflows, use `faset_document_save` with `expected_revision` when you need a
definite save boundary instead of waiting for idle autosave. If a conflict occurs,
save to a new project-relative path and compare the two files. Change the
preference through `faset_project_settings_get` and `faset_project_settings_set`
with the returned project revision and `{"editor":{"autosave":false}}`; the
new value applies to the current session and persists for the next Editor launch.
## Single-command CLI
The CLI is useful for scripts that do not need an MCP session:
+45 -15
View File
@@ -39,29 +39,58 @@ Use `--debug-physics` or press **F3** to show current physics box colliders. Deb
geometry increases draw count, so record whether it was enabled. The collider view
uses simulation poses; normal visuals can use interpolated poses.
## Measure Editor and C++ workflows
## Measure Editor, C++ and Lua iteration
From the engine repository:
```sh
cmake --build --preset linux-debug --target faset_editor faset_editor_ui_latency --parallel 2
python3 tools/measure_workflows.py \
--editor build/linux-debug/faset_editor \
--project examples/projects/collect-3d \
--output .cache/my-workflow-measurement
--output /tmp/faset-workflow-measurement
```
Use a new output directory. The tool copies the project, preserving your original,
and records command startup, two-frame GUI startup/shutdown, first/cached Blender
bundle import, initial/no-change/changed Debug builds and a subsequent Player frame.
It checks that editing gameplay makes the schema stale and successful building
clears that state. The initial build uses available dependency archives and OS
caches; it is not a measurement of internet download speed.
The UI probe must be built beside the Editor first. The default Lua input is
`examples/lua`; use `--lua-project` to select another declared Lua project and
`--ui-latency-binary` when the probe is elsewhere. Use a **new** output directory;
the tool refuses to overwrite evidence. It copies both projects and never edits
the checked-in examples.
`report.json` version 2 separates the initial cold project configure/build,
unchanged builds, changed `.cpp` builds, changed-header builds, deliberate compile
failure/recovery, Lua edit-to-reload, first offscreen Player frame and synthetic
Editor input-to-visible-state. Each warm/changed case has one warm-up and five
measured repetitions. Each sample remains in `raw/` with command, stdout/stderr,
duration and, on Linux, peak RSS; the report includes median and nearest-rank p95.
The file hashes identify every changed source variant. Five unchanged builds must
return a verified schema/package cache hit. The cold case begins with empty project
`.faset` caches, **not** a cold OS file cache, newly downloaded dependencies or a
fresh engine toolchain. It is a development `Debug` workflow.
The first-frame value comes from the Player's `main_to_first_frame` profile after
five one-frame offscreen runs. It excludes OS process loading and says nothing about
window presentation latency. The separate UI probe applies keyboard input through
the retained Editor UI, then measures 100 post-warm-up offscreen frames; it is not
native desktop input-to-photon latency. A watched Lua Player records five
edit-to-successful-reload times, including its polling and log notification. A
3,000-frame Player profile compares explicit Vulkan allocation and texture maxima
in frames 101–200 with frames 2901–3000. Zero growth over that interval is useful
but does not prove the absence of all leaks. The tool also adds 512 deterministic
objects to a disposable scene and profiles 240 frames; this stresses scene
simulation/snapshot work, not representative game content.
On Linux, GNU `time` records peak RSS for each command and its waited-for children.
This is a maximum, not the sum of simultaneous compiler processes. Other platforms
report this field as null unless equivalent measurement support is added. The tool
keeps raw stdout/stderr, durations, hardware and revision information alongside its
report. A dirty source checkout is explicitly identified.
report this field as null unless equivalent measurement support is added. The report
keeps raw stdout/stderr, profiles, hardware, driver, build setting, source hashes,
Git revision and dirty-tree state. New runs also hash the exact CMake, Ninja, C/C++
compiler, Slang, Editor and UI-probe binaries selected by the disposable project's
`CMakeCache.txt`. Compare profiles only with the same scene, configuration,
resolution, validation/readback settings and hardware class. The
[dated P1 workflow record](https://github.com/emil28092005/Faset_Engine/blob/main/docs/validation/p1-iteration-2026-09-24/README.md)
retains a successful Debug run and its exact raw files. Its post-run binary
provenance supplement is explicitly separate from the original report.
`tools/verify_playable_exports.py` separately verifies the two sample games in
relocated Release packages and records their Player profiles. Its assertions test
@@ -80,10 +109,11 @@ bounded run:
```
Accepted values are `direct`, `gpu-frustum`, and `gpu-occlusion`; Direct is the
default. The profile records the requested `visibility_mode` and each frame's
`gpu_visibility_active` state. Check that state when interpreting a GPU run: a
requested mode can fall back if the required device profile is unavailable. The
Editor reports the same distinction as **Path: active**. See
default. The profile records the requested `visibility_mode`, the run's and each
frame's `effective_visibility_mode`, and each frame's `gpu_visibility_active` state.
Compare requested and effective modes before interpreting a GPU run: a missing GPU
profile falls back to Direct, while missing HZB can reduce GPU occlusion to GPU
frustum. The Editor shows the **Effective path** and any **Fallback from** line. See
[Diagnostics](diagnostics.md) for the counters and HZB preview.
For a repeatable offscreen comparison, build and run the P2 benchmark harness:
+30 -7
View File
@@ -14,7 +14,8 @@ process; stopping it leaves the authoring scene unchanged.
Launch `faset_editor` without arguments to open the project launcher.
1. Choose **Create project**, enter a name and a new or empty project directory,
then select **2D** or **3D** for the initial scene.
then select **C++** or **Lua** and **2D** or **3D**. Each combination creates a
runnable start scene and a small behavior for that language.
2. Choose **Create project** to create the project files and open the Editor.
3. To return later, choose **Open project**, select its directory with **Browse**,
and confirm **Open project**. The directory must contain `project.faset.json`.
@@ -33,6 +34,12 @@ was launched in MCP mode so the connection keeps a single project context.
For a direct launch, use `faset_editor --project /path/to/MyGame` on Linux or
`faset_editor.exe --project C:\Projects\MyGame` on Windows.
For scripted project creation, pass `--new MyGame --dimension 2|3
--language cpp|lua` with `--project` pointing to the new directory. Omitting
`--language` retains the earlier C++ scaffold without a runnable start scene;
select a language explicitly for a complete starter.
The [C++ first behavior](../scripting/first-behavior.md) and
[Lua gameplay](../scripting/lua.md) guides show the first editing steps.
## Create and save a scene
@@ -50,6 +57,19 @@ outside the Editor, ordinary Save reports a conflict instead of overwriting it.
Save your in-memory work to a new path and compare the two versions before replacing
anything.
Autosave is enabled by default. After a named scene has been idle for two seconds,
the Editor saves its current revision to that scene file. The status bar shows
**Pending autosave**, **Saving**, or **Saved**. An unnamed scene shows **Save As
required** until you choose its path. The status bar's **Save As...** action opens
an editable, new project-relative path; it never overwrites the conflicting file.
If the disk file changes outside Faset, autosave stops for that revision and shows
**Save conflict**. Use **Save As...** to keep both versions, then compare or reload
the original. **Save failed** preserves the unsaved scene in the recovery journal;
correct the write error or use Save As. Autosave does not clear Undo history or
replace the [recovery journal](#recover-unsaved-work). Recovery still protects
unnamed scenes and edits before the idle deadline.
## Navigate and transform
- **Right drag:** orbit the 3D view; pan in a 2D scene.
@@ -119,13 +139,16 @@ for the C++ iteration loop.
## Project settings
Open **File → Project settings** to change the project name, initial **2D / 3D** type,
and start-scene path. Save a scene first, then choose it from the saved-scene list or
enter its project-relative path. Choose **Save project** to update
`project.faset.json` explicitly. A missing start scene is rejected and the dialog
stays open.
start-scene path, or **Autosave named scenes** preference. Save a scene first, then
choose it from the saved-scene list or enter its project-relative path. Choose
**Save project** to update `project.faset.json` explicitly. A missing start scene is
rejected and the dialog stays open. The Autosave preference applies immediately;
when disabled, the status bar says **Autosave off** and you can still use Ctrl+S or
Save As. The project file stores it as `editor.autosave`; omitting it means enabled.
These settings are used when the project opens again; they do not convert the active
scene, switch its dimension, restart the Player, or add a scene Undo entry.
The project name, default dimension, and start scene are used when the project opens
again; they do not convert the active scene, switch its dimension, restart the Player,
or add a scene Undo entry.
**Cancel** or **Escape** discards this form's draft. If another writer changes the
project settings while the dialog is open, Save reports a revision conflict and
keeps your draft. **Reload saved** deliberately discards it and reloads the latest
+13 -3
View File
@@ -31,17 +31,27 @@ cmake --build --preset linux-debug --parallel
ctest --preset linux-debug
```
Create a project and open the native Editor:
Create a runnable C++ or Lua starter and open the native Editor:
```sh
build/linux-debug/faset_editor --project "$PWD/MyGame" --new MyGame --dimension 3
build/linux-debug/faset_editor --project "$PWD/MyGame" --new MyGame --dimension 3 --language cpp
build/linux-debug/faset_editor --project "$PWD/LuaGame" --new LuaGame --dimension 2 --language lua
```
You can also run `build/linux-debug/faset_editor` without arguments to open the
project launcher and create or select a project using the native interface.
project launcher. Its Create view offers C++ 2D, C++ 3D, Lua 2D, and Lua 3D.
Each explicit starter includes `Scenes/main.scene.json` with a player, ground,
and a behavior bound to `Scripts/Gameplay.cpp` or `Scripts/main.lua`. 3D starters
also include a camera and light. Existing files in a destination directory are
never replaced by starter creation. The older `--new NAME --dimension 2|3`
command without `--language` retains its original scene-free C++ scaffold.
Use **Build** after changing `MyGame/Scripts/Gameplay.cpp`, then **Play**.
The Player runs separately. Stop it before changing and rebuilding C++ gameplay.
Lua starters declare their entry script in `project.faset.json`, install LuaLS
annotations/configuration, and need no `Gameplay.cpp`. Use **Refresh Lua** after
editing a Lua behavior declaration; development **Reload Lua** can apply a valid
script edit to a running Player without recompiling C++.
See [MCP and CLI](../editor/mcp.md) for headless authoring and automation.
For an optimized build use `linux-release`. The `linux-sanitize` preset enables
+14 -2
View File
@@ -56,14 +56,26 @@ The example scene is a complete, loadable document:
The sprite is visible because it has `faset.sprite`. It moves because it also has `tutorial.move_x`. The configuration field is `speed`; the type string must match the registration exactly. `rotation` uses radians, and the default coordinate system is Y-up.
After **Build C++** succeeds in the Editor, choose **Add component** in the Inspector
After **Build** succeeds in the Editor, choose **Add component** in the Inspector
and select the registered type. Its schema supplies editable fields, defaults and
constraints. The direct Player command above is useful for testing the same behavior
independently of an editor session.
## Make a change and verify it
Change the scene's `speed` to `-2`: the object moves left. Change the C++ callback or schema: stop the Player, rebuild, regenerate the schema, then launch a new session. There is no automatic C++ hot reload.
Change the scene's `speed` to `-2`: the object moves left. Change the C++ callback
or schema: stop the Player, choose **Build**, then launch a new Play session. The
Build job regenerates metadata when its inputs changed; an unchanged second build
can reuse the verified schema/package generation. It still asks CMake/Ninja to check
the native dependency graph. There is no automatic C++ hot reload.
To try compiler navigation in a project opened by the Editor, add
`#error Check navigation` to its `Scripts/Gameplay.cpp`, choose **Build**, and
select the resulting Console diagnostic. **Open source** passes the file and
one-based location to the configured external editor. Remove the line and build
again; the failed attempt keeps the previous valid generation. See
[Developer diagnostics](../editor/diagnostics.md) for editor command setup and
[Build, Play, and export](../editor/export.md) for the full iteration loop.
The `tutorial_moving` CTest checks that both 30 Hz and 60 Hz frame sequences move the object two metres in one second. It checks the resulting pose, rather than only checking that the program starts.
+40 -7
View File
@@ -5,6 +5,23 @@ behaviors share the same runtime lifecycle, typed entity operations, scene compo
and Inspector metadata. The Editor does not run gameplay code in its own process.
There is no built-in script editor: edit `.lua` files in Zed or another external editor.
## Start with a runnable Lua project
Choose **Create project → Lua → 2D or 3D** in the native launcher, or run:
```sh
build/linux-debug/faset_editor --project "$PWD/LuaGame" --new LuaGame --dimension 2 --language lua
```
The starter creates `Scripts/main.lua`, a scene with a player and ground, and a
manifest entry for that script. The player moves horizontally and jumps with the
default input actions. The 3D choice also provides a camera and directional light.
Open `Scripts/main.lua` in your external editor and change `speed` or
`fixed_update`. **Refresh Lua** updates declared Inspector fields; **Reload Lua**
updates a development Player after a valid edit without rebuilding native gameplay.
The starter installs LuaLS declarations and `.luarc.json`; reopening it in Zed or
another LuaLS-enabled editor provides completions for the Faset API.
## Enable Lua in a project
Add explicit entry scripts to `project.faset.json`:
@@ -19,7 +36,7 @@ Add explicit entry scripts to `project.faset.json`:
This is a manifest fragment, not a complete project file. Each entry must return one
`faset.behavior` table with a unique custom TypeId. All sources live beneath `Scripts`
and are captured as an immutable build/export snapshot. Paths must be project-relative;
and are copied into a content-verified build/export snapshot. Paths must be project-relative;
symlinks and paths outside `Scripts` are rejected. Auxiliary modules do not need to
appear in the entry list.
@@ -193,14 +210,18 @@ The Editor command palette exposes:
| `faset_lua_refresh` | Build if needed, extract schemas, refresh Inspector metadata |
| `faset_lua_reload` | Request a Lua reload in a development Player |
| `faset_lua_setup` | Install Faset LuaLS declarations/configuration |
| `faset_script_open` | Open a script in an external editor |
| `faset_source_open` | Open a C++, header or Lua source at a one-based line/column |
| `faset_script_open` | Lua-only compatibility alias for source opening |
The external-editor default is `zed`. Set `editor.script_editor` in
`project.faset.json` to an argument array such as `["code", "--goto", "{file}"]`,
or pass an `editor` argument array to `faset_script_open`. Exact `{file}` and
`{project}` arguments are substituted; a missing file argument is appended. The
command launches the executable directly, without a shell. The Assets panel lists
Lua sources under `Scripts` and provides **Open Script**.
`project.faset.json` to an argument array such as
`["zed", "{file}:{line}:{column}"]`, or pass an `editor` argument array to
`faset_source_open`. `{file}`, `{line}`, `{column}` and `{project}` are substituted
inside arguments; a missing file argument is appended. The command launches the
executable directly, without a shell. Only regular source files under this
project's `Scripts` can be opened. The Assets panel lists those files and the
Console's structured Lua error can open its source location. A missing external
editor is an actionable navigation error, not a failed gameplay build.
Development Play watches Lua changes. The Player's `--watch-lua` option enables this
for direct development runs. A candidate source generation is loaded and validated
@@ -209,6 +230,18 @@ Successful reload **restarts the scene**, invalidates old handles, and resets al
script state. This is not state-preserving hot swapping. C++ source changes still
require a rebuild and a new Player process.
For a short iteration, change `speed` in `Scripts/main.lua`, save it, and watch the
Console for **Lua reloaded**. The running development Player picks up a valid
snapshot without native recompilation. Change a field declaration or TypeId, then
choose **Refresh Lua** to validate metadata and update Inspector choices. If the
candidate has a syntax/schema error, the previous running generation remains in
place; use the structured diagnostic to return to the source, correct it and retry.
Lua module declaration, gameplay source, compiler/toolchain or shader changes can
invalidate a later build's verified schema/package cache. The **Jobs** result
distinguishes `schema_cache_hit` and `generation_reused`; those flags do not imply
that the native build graph was skipped. See the [build loop](../editor/export.md)
and [profiling guide](../editor/profiling.md).
Export captures the declared entry list and Lua modules with the game. The exported
Player runs without the Editor or a separate Lua installation; development watching
is not enabled by ordinary exported-game launch. Exported Lua remains readable source,
@@ -30,7 +30,7 @@
---
**Implementation note, 23 September 2026:** this checklist records the proposed execution sequence. A checked item has direct code/test/commit evidence; an unchecked item may be an unrecorded test-first step or a narrower validation/documentation gap, even where the corresponding P2 feature works. The [acceptance protocol](../../studies/19-p2-gpu-visibility-acceptance.md), [initial benchmark](../../studies/20-p2-gpu-visibility-benchmark-2026-09-23.md) and [optimized repeat](../../studies/21-p2-gpu-visibility-optimization-2026-09-23.md) report the observed Linux scope. Other platforms and real-game performance require separate evidence.
**Implementation note, 23 September 2026:** this checklist records the proposed execution sequence. A checked item has direct code/test/commit evidence; an unchecked item may be an unrecorded test-first step or a narrower validation/documentation gap, even where the corresponding P2 feature works. The [acceptance protocol](../../studies/19-p2-gpu-visibility-acceptance.md), [initial benchmark](../../studies/20-p2-gpu-visibility-benchmark-2026-09-23.md), [optimized repeat](../../studies/21-p2-gpu-visibility-optimization-2026-09-23.md) and [SwiftShader compatibility record](../../validation/p2-swiftshader-2026-09-23/README.md) report their tested configurations. Real-game performance and physical Windows GPUs require separate evidence.
### Task 1: Stable instances, conservative bounds, and LOD policy
@@ -59,7 +59,7 @@
- [x] **Step 1: Write failing reflection/package tests.** A storage-buffer/storage-image Slang reflection fixture must normalize to a typed descriptor; missing or tampered P2 SPIR-V/metadata must fail validation; a packaged Player must contain all required P2 shaders.
- [ ] **Step 2: Run focused tests and verify the expected rejection or missing-artifact failure.** `ctest --test-dir build/linux-debug --output-on-failure -R 'render_shader_reload|build_schema_publication'` plus the new reflection test target.
- [x] **Step 3: Extend the compiler's descriptor normalization and add the P2 shader entries.** Vertex resolves `visibleIds[binBase + SV_InstanceID]`; compute writes bounded per-bin IDs/counts; HZB computes max of valid children and far depth for padding. Keep C++/Slang record strides explicit and checked.
- [x] **Step 3: Extend the compiler's descriptor normalization and add the P2 shader entries.** Vertex resolves `visibleIds[binBase + SV_VulkanInstanceID]`; compute writes bounded per-bin IDs/counts; HZB computes max of valid children and far depth for padding. Keep C++/Slang record strides explicit and checked.
- [x] **Step 4: Validate generated reflection and package.** Rebuild `faset_shaders`, run the focused tests and inspect each generated metadata stage/binding/fingerprint.
- [x] **Step 5: Commit** `Add checked Slang shaders for GPU visibility and HZB`.
@@ -118,7 +118,7 @@
**Interfaces:** Close P2 only with exact revision, compiler/driver/GPU/OS, scene/camera paths, commands, full-frame direct/GPU timings, pass counters, image comparison and stated coverage limits.
- [ ] **Step 1: Add adversarial fixtures for mass deletion/reuse, near-plane, camera inside bounds, odd/offset viewport, door/wall, open scene and offscreen shadow caster; confirm at least one fails before its corresponding fix.**
- [ ] **Step 2: Run Debug and Release build/test suites, GPU validation, shader reload, two sample-game export/relaunch checks, and available Windows CI.** Record exact outputs; a platform without executed GPU coverage remains explicitly unverified.
- [ ] **Step 3: Profile the same closed and open scenes in direct and GPU modes.** Record CPU extraction/upload/submission, GPU pass and full-frame time, readback conditions, memory and culling counters. Do not turn a scene-specific result into a universal performance claim.
- [ ] **Step 4: Review all PLAN P2 criteria against evidence, update docs, run `graphify update .`, request independent code review and fix load-bearing findings.**
- [ ] **Step 5: Commit** `Validate and document P2 GPU visibility milestone`; publish only after all checks are green.
- [x] **Step 2: Run Debug and Release build/test suites, GPU validation, shader reload, two sample-game export/relaunch checks, and available Windows CI.** Record exact outputs; a platform without executed GPU coverage remains explicitly unverified.
- [x] **Step 3: Profile the same closed and open scenes in direct and GPU modes.** Record CPU extraction/upload/submission, GPU pass and full-frame time, readback conditions, memory and culling counters. Do not turn a scene-specific result into a universal performance claim.
- [x] **Step 4: Review all PLAN P2 criteria against evidence, update docs, run `graphify update .`, request independent code review and fix load-bearing findings.**
- [x] **Step 5: Commit** `Validate and document P2 GPU visibility milestone`; publish only after all checks are green.
@@ -0,0 +1,253 @@
# P1 Gameplay Iteration Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Close PLAN P1 with reliable build/schema reuse, useful diagnostics and source navigation, four runnable project starters, safe scene autosave, repeatable iteration evidence, and Linux/Windows Lua validation.
**Architecture:** Keep CMake/Ninja responsible for native dependency analysis and retain immutable last-good build generations. Add a content-checked schema/package cache after native build, normalize diagnostics into the existing job API, run revision-aware autosave in `Session::poll`, and expose all new state through Editor commands shared by GUI and MCP.
**Tech Stack:** C++20, CMake/Ninja, Clang/clang-cl, optional Lua 5.4, SDL3/Vulkan 1.3 for graphical tests, Python 3 for workflow measurements, MkDocs Material.
**Spec:** `docs/superpowers/specs/2026-09-24-p1-iteration-design.md`
## Global Constraints
- Linux and Windows x86-64 desktop 2D/3D are the supported P1 targets; all user-facing Editor copy and Manual pages are English.
- C++ is compiled into the Player; Lua is optional and development reload resets state. No C++ hot reload or dynamic gameplay loading is required.
- MCP addresses Editor authoring/build services, never the live Player world.
- Build and export failures preserve the last successful generation and schema; Debug and Release native trees stay separate.
- CMake/Ninja always run for a requested native build; cache reuse starts only after their success and artifact verification.
- The existing recovery journal protects every authoring transaction; autosave never overwrites a disk conflict or gives an unnamed scene an implicit path.
- The two `docs/design/p1-*-reference.png` images guide visual quality only; `PLAN.md` and the spec define behavior. Paths are cross-platform.
- Keep `BuildService::scaffold(name, dimension)` and `faset_script_open` working for existing callers.
- Before every RED CTest run, register a new named test if needed, reconfigure and rebuild its executable; `ctest --no-tests=error` prevents an empty match from passing.
## Review Focus
- An included project header changes while a build runs: reject the mixed candidate and keep the last-good pointer; Tasks 1–2 pin this.
- A toolchain executable changes in place while its path remains the same: invalidate the native build tree/package key; Task 1 pins this.
- A valid cached manifest points to a truncated schema or shader: do not return a hit; Task 2 pins this.
- An external editor or another MCP client changes a scene before autosave: never overwrite it or a newer revision; Task 6 pins this.
- A Windows diagnostic contains a drive colon, Unicode directories and a line/column: parse the right location and reject navigation outside `Scripts`; Tasks 3–4 pin this.
---
### Task 1: Capture complete gameplay inputs and toolchain identity
**Files:** Create `include/faset/editor/build_cache.hpp`, `src/editor/build_cache.cpp`, `tests/build_cache_tests.cpp`; modify `cmake/BuildService.cmake`, `src/editor/build_service.cpp`, `src/editor/session.cpp`.
**Interfaces:** `BuildInputs capture_build_inputs(const BuildConfig&, const scripting::LuaProject&)` records `source_hash` over all regular files under project `Scripts` plus Lua declaration/fingerprint, and separate recipe/toolchain hashes over normalized configure args and executables. `BuildInputs::fingerprint()` combines these fields deterministically. `ensure_native_toolchain_stamp(native_directory, inputs)` forces a fresh native tree if the compiler/CMake/Slang identity changed in place. Extract the common Scripts snapshot so `Session::source_signature()` and `BuildService` use the same content set, while allowing their intended extra fields to differ.
- [ ] **Step 1: Write failing source/identity tests.** In `tests/build_cache_tests.cpp`, construct a temporary project with `Gameplay.cpp`, `Gameplay.hpp` and `Scripts/Extensions/Extra.hpp`; change only the nested header, Lua declaration and then bytes of a fake compiler at the same path. Each change must alter the corresponding fingerprint. A symlink escaping `Scripts` must be rejected. Test the toolchain stamp with a disposable build directory. Register `faset_build_cache_tests` and CTest name `build_cache` in `cmake/BuildService.cmake` before the red run.
```cpp
const auto before = capture_build_inputs(config, lua);
atomic_write(root / "Scripts/Extensions/Extra.hpp", "#define SPEED 2\n");
require(capture_build_inputs(config, lua).fingerprint() != before.fingerprint(),
"Nested gameplay header invalidates the source snapshot");
```
- [ ] **Step 2: Build the red test.** Run `cmake --preset linux-debug`, then `cmake --build --preset linux-debug --target faset_build_cache_tests --parallel 4`. The test target is registered; compilation must fail specifically on the missing `capture_build_inputs` interface, not on an unknown target.
- [ ] **Step 3: Implement the input scanner and toolchain stamp.** Sort project-relative UTF-8 paths, hash file bytes, reject escaping symlinks, include explicit recipe/version values, and hash resolved tool executables. Do not use modification time alone. Compare/persist the stamp before native configure; on a changed stamp clear only the generated native tree for that configuration, never published generations or source files. Replace the narrower two-file post-build race check with the captured complete Scripts snapshot.
```cpp
const auto submitted = capture_build_inputs(config, job.lua);
ensure_native_toolchain_stamp(native_directory, submitted);
// After native build and schema extraction, before publication:
if (capture_build_inputs(config, scripting::loadLuaProject(config.project_root))
.source_hash != submitted.source_hash)
throw std::runtime_error("Gameplay sources changed during the build; build again");
```
- [ ] **Step 4: Rebuild and run focused tests.** Build `faset_build_cache_tests faset_build_schema_tests faset_editor_session_tests`, then run `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^(build_cache|build_schema_publication|editor_session_settings)$'`. All three named tests pass; a changed nested header during the fixture build rejects publication.
- [ ] **Step 5: Commit** `Capture complete gameplay and toolchain build inputs`.
### Task 2: Reuse only verified schema/package generations
**Files:** Modify `include/faset/editor/build_service.hpp`, `src/editor/build_cache.cpp`, `src/editor/build_service.cpp`, `tests/build_cache_tests.cpp`, `tests/build_schema_tests.cpp`, `tests/build_service_tests.cpp`.
**Interfaces:** `build_package_key(inputs, native_directory, configuration)` hashes the post-build Player, SchemaExporter, required SPIR-V/reflection files, runtime libraries and CMake cache. `validate_build_generation(directory, key)` verifies every recorded file hash and validates the schema. Successful `BuildService::build()` returns `schema_cache_hit`, `generation_reused`, `fingerprint`, and phase times without changing existing result paths.
- [ ] **Step 1: Write failing cache and invalidation tests.** The fixture `tests/build_schema_tests.cpp` must count SchemaExporter invocations: two unchanged builds return the same generation and count one export; a changed C++ header, Lua source, configure option, shader bytes or runtime artifact produces a new generation; corrupt or missing cached schema/shader is never a hit. Extend `tests/build_service_tests.cpp` so a failed compile preserves `last_build.json`, and export after changed asset source fails until reimport, then packages the new asset generation despite a gameplay cache hit.
```cpp
const auto first = builds.wait(builds.start_build());
const auto again = builds.wait(builds.start_build());
check(again.result.at("generation") == first.result.at("generation") &&
again.result.at("schema_cache_hit") == true &&
again.result.at("generation_reused") == true,
"Unchanged build reuses a verified generation");
```
- [ ] **Step 2: Run rebuilt red tests.** Build `faset_build_cache_tests faset_build_schema_tests faset_build_service_tests`, then run `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^(build_cache|build_schema_publication|process_and_cook)$'`. At least the newly added cache assertion must fail on missing flags or duplicate schema export; an old binary passing does not count.
- [ ] **Step 3: Implement post-native cache lookup and manifest hashes.** Continue to run configure/build first. Compute the package key, verify the previous generation's manifest/files/schema, then return it without SchemaExporter/copy if valid. Otherwise export schema into staging from the captured Lua snapshot, copy files, write per-file hashes and key, recheck source snapshot, and atomically update `last_build.json`. Never return a damaged generation. Keep the raw job log and phase timing for a hit as well as a miss.
```cpp
if (auto previous = verified_generation(config.cache_root, key))
return result_for(*previous, /*schema_cache_hit=*/true,
/*generation_reused=*/true);
// Only a fully validated staging directory may become last_build.json.
```
- [ ] **Step 4: Rebuild and run focused/integration tests.** Build `faset_build_cache_tests faset_build_schema_tests faset_build_service_tests faset_editor_session_tests`, then run `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^(build_cache|build_schema_publication|process_and_cook|editor_session_settings)$'`. All four pass. Inspect one real no-op build's log: Ninja does no native compile/link; SchemaExporter is absent on the second request. Do not call this a native cache hit unless the log confirms it.
- [ ] **Step 5: Commit** `Reuse verified gameplay schema and build generations`.
### Task 3: Parse structured compiler and Lua diagnostics
**Files:** Create `include/faset/editor/build_diagnostics.hpp`, `src/editor/build_diagnostics.cpp`, `tests/build_diagnostics_tests.cpp`; modify `cmake/BuildService.cmake`, `include/faset/editor/build_service.hpp`, `src/editor/build_service.cpp`.
**Interfaces:** `parse_build_diagnostics(raw_log, phase, project_root)` returns a bounded JSON array of `{severity, phase, message, file?, line?, column?, code?}`. `JobStatus::diagnostics` is serialized by `JobStatus::json()` and returned by `faset_job`; `log` and `error` remain unchanged for old clients.
- [ ] **Step 1: Write failing parser fixtures.** Cover `/project/Scripts/Game.cpp:17:4: error:`, `C:\\Café\\Scripts\\Game.cpp(17,4): error`, `C:\\Café\\Scripts\\Game.cpp:17:4: warning:`, `Scripts/player.lua:6: unexpected symbol`, ANSI escapes, multiline note/caret output, an engine/external path, and a nonzero process with only unparseable text. Assert one-based positive locations and normalized project-relative files only for files under `Scripts`. Register `faset_build_diagnostics_tests` and CTest name `build_diagnostics` in `cmake/BuildService.cmake`.
```cpp
const auto rows = parse_build_diagnostics(
"Scripts/Game.cpp:17:4: error: bad field\n", "compile", project);
require(rows.size() == 1 && rows[0].at("file") == "Scripts/Game.cpp" &&
rows[0].at("line") == 17 && rows[0].at("severity") == "error",
"Clang location is structured");
```
- [ ] **Step 2: Build the red parser test.** Run `cmake --preset linux-debug`, then `cmake --build --preset linux-debug --target faset_build_diagnostics_tests --parallel 4`. Compilation must fail on the missing parser API, not an unknown test target.
- [ ] **Step 3: Implement incremental job collection.** Parse completed log lines as process output arrives, cap row count/message size, strip ANSI, and preserve every raw line in the bounded existing log. Use phase names from `BuildService` checkpoints. On a failed process with no parsed error, add a generic diagnostic with the exit code and `see job log`; never fabricate a navigable file.
```cpp
job.status.diagnostics = parse_build_diagnostics(job.status.log, job.status.stage,
config.project_root);
if (exit_code != 0 && job.status.diagnostics.empty())
job.status.diagnostics.push_back(generic_process_error(exit_code));
```
- [ ] **Step 4: Run parser and real failure tests.** Build `faset_build_diagnostics_tests faset_build_service_tests faset_build_schema_tests`, then run `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^(build_diagnostics|process_and_cook|build_schema_publication)$'`. All three pass; a deliberate `#error` in `Gameplay.cpp` yields both a structured row and the original log, with previous build retained.
- [ ] **Step 5: Commit** `Expose structured gameplay build diagnostics`.
### Task 4: Open project source at a diagnostic location
**Files:** Modify `src/editor/session.cpp`, `src/editor/editor_ui.cpp`, `tests/editor_session_tests.cpp`, `tests/editor_ui_tests.cpp`, `docs/manual/editor/diagnostics.md`.
**Interfaces:** New command `faset_source_open({path, line?, column?, editor?})` validates project code source and opens it with an argv template. Existing `faset_script_open` delegates to the same safe launcher while retaining Lua-only validation. `editor.script_editor` accepts `{file}`, `{line}`, `{column}` and `{project}` tokens. The Console uses Task 3 `diagnostics` and displays cache/timing fields from Task 2.
- [ ] **Step 1: Write failing command/UI tests.** A valid `.cpp` and `.lua` under `Scripts` produce the configured argv with line/column; use a disposable probe executable to record arguments without launching a real editor. A drive/Unicode project path stays one argv element; `../`, symlink escape, directory, `.exe` and nonexistent targets fail with structured `source.*` errors. Clicking a parsed diagnostic issues `faset_source_open` with its location; an external diagnostic has no enabled Open action. Existing `faset_script_open` rejects `.cpp` as before.
```cpp
const auto opened = commands.call("faset_source_open",
{{"path", "Scripts/Gameplay.cpp"}, {"line", 17}, {"column", 4},
{"editor", {path_to_utf8(probe_executable), "{file}:{line}:{column}"}}});
require(opened.at("line") == 17 && opened.at("path") == "Scripts/Gameplay.cpp",
"Project source opens at the diagnostic position");
```
- [ ] **Step 2: Run rebuilt red command/UI tests.** Build `faset_editor_session_tests faset_editor_ui_tests`, then run `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^(editor_session_settings|editor_ui_authoring)$'` with the configured Vulkan ICD. At least the new source-open assertion must fail; a missing GPU or old binary is not the intended red result.
- [ ] **Step 3: Implement the safe source launcher and Console action.** Use `project_path` and extension/regular-file checks, exact argv substitution with no shell, and Zed's verified `path:line:column` default. Keep raw output expandable, show severity/file/line first, and expose a clear editor-launch error without changing build state. Make rows keyboard reachable and update the English diagnostics Manual.
```cpp
commands_.add("faset_source_open", description, source_schema,
[&](const Json& args) { return open_project_source(args, /*lua_only=*/false); });
// UI: call("faset_source_open", {{"path", row.at("file")},
// {"line", row.value("line", 1)}});
```
- [ ] **Step 4: Run focused tests with software Vulkan for UI.** Rebuild `faset_editor_session_tests faset_editor_ui_tests`, then run `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^(editor_session_settings|editor_ui_authoring)$'` on an equipped GPU/software ICD. Both pass; `faset_job` JSON still exposes raw log and normalized rows headlessly.
- [ ] **Step 5: Commit** `Navigate from build diagnostics to project source`.
### Task 5: Four runnable C++/Lua new-project choices
**Files:** Modify `include/faset/editor/build_service.hpp`, `src/editor/build_service.cpp`, `include/faset/editor/project_launcher.hpp`, `src/editor/project_launcher.cpp`, `apps/editor_main.cpp`, `tests/build_service_tests.cpp`, `tests/editor_ui_launcher.cpp`; create `tools/project_templates/lua-main.lua`; update `docs/manual/getting-started/build.md`.
**Interfaces:** New overload `BuildService::scaffold(name, dimension, language)` accepts `cpp|lua` and creates a runnable template; the legacy two-argument call retains its existing C++ scaffold behavior without implicitly creating a scene. `ProjectSelection::language` and `faset_editor --new NAME --dimension 2|3 --language cpp|lua` select the same templates. Explicit template creation adds a minimal `Scenes/main.scene.json`; Lua-only templates receive a declared `Scripts/main.lua` without `Gameplay.cpp/.hpp`.
- [ ] **Step 1: Write failing scaffold/launcher tests.** Create all four explicit combinations under disposable Unicode project paths. Assert dimension/language, scene validity, declared Lua schemas, no C++ stub in Lua projects, ability to `--validate` or launch a built Player, and that existing files are never overwritten. Test legacy `scaffold(name, dimension)` still creates C++ without unexpectedly creating a scene.
```cpp
builds.scaffold("Lua 2D", 2, "lua");
require(fs::exists(root / "Scripts/main.lua") &&
!fs::exists(root / "Scripts/Gameplay.cpp"),
"Lua starter is genuinely Lua-only");
```
- [ ] **Step 2: Run rebuilt red tests.** Build `faset_build_service_tests faset_project_launcher_tests`, then run `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^(process_and_cook|editor_ui_launcher)$'` with the configured Vulkan ICD. Compilation or a new assertion must fail specifically on language selection; old binaries or a missing GPU do not establish red.
- [ ] **Step 3: Add starter files and cross-platform chooser.** Generate the start scene from authoring schema helpers; use source templates for behavior text and LuaLS defaults; reject an existing conflicting destination before writing any starter file. Maintain the dark Editor style, keyboard selection and platform-native path display. The PNG reference guides layout only. Extend CLI help and Manual with exact commands.
```cpp
void BuildService::scaffold(const std::string& name, int dimension,
std::string_view language);
// Existing two-argument overload keeps the legacy scaffold without a scene.
```
- [ ] **Step 4: Run focused and real template builds.** Rebuild `faset_build_service_tests faset_project_launcher_tests faset_player`, then run `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^(process_and_cook|editor_ui_launcher|lua_cli_contracts)$'` with the configured Vulkan ICD. All three pass; build/validate one generated C++ and one generated Lua project for each dimension.
- [ ] **Step 5: Commit** `Offer runnable C++ and Lua project starters`.
### Task 6: Revision-aware autosave controller and MCP status
**Files:** Create `include/faset/editor/autosave.hpp`, `src/editor/autosave.cpp`, `tests/editor_autosave_tests.cpp`; modify `CMakeLists.txt`, `include/faset/authoring/service.hpp`, `src/authoring/service.cpp`, `src/editor/commands.cpp`, `include/faset/editor/session.hpp`, `src/editor/session.cpp`.
**Interfaces:** `AuthoringService::save(document, path={}, expected_revision=std::nullopt)` rejects a stale expected revision. `AutosaveController` takes `SaveFn = std::function<Json(const std::string&, std::uint64_t)>` in its constructor; `observe(documents, now, enabled)` tracks each document's revision/idle deadline and invokes that callback after 2 seconds. `Session::poll()` calls it in both GUI and long-lived MCP modes. Read-only `faset_autosave_status` returns per-document `state`, `revision`, `path`, `error` and enabled flag.
- [ ] **Step 1: Write failing fake-clock and authoring tests.** Coalesce three edits within 2 seconds into one save; unnamed dirty scene stays in recovery; disabling autosave leaves only recovery; an external disk edit fails with `save.disk_conflict`; a newer revision fails with `revision.conflict` then gets a new deadline; a permission/write error remains visible without frame-by-frame retries; Undo after save still restores the prior scene; a Play snapshot captured before autosave remains byte-identical. Register `faset_editor_autosave_tests` and CTest name `editor_autosave` in `CMakeLists.txt`.
```cpp
service.transact(id, revision, rename_ops);
controller.observe(service.documents(), start + 1900ms, true);
require(read_json(scene_path).at("name") == "Before", "Idle timer has not fired");
controller.observe(service.documents(), start + 2100ms, true);
require(read_json(scene_path).at("name") == "After", "Named scene autosaved once");
```
- [ ] **Step 2: Build the red autosave test.** Run `cmake --preset linux-debug`, then `cmake --build --preset linux-debug --target faset_editor_autosave_tests --parallel 4`. Compilation must fail on the missing controller/revision-aware save API, not an unknown target.
- [ ] **Step 3: Implement deterministic scheduling and status.** Use monotonic time injected into the controller, reset deadline only when revision changes, save with expected revision, suppress identical failure repeats until another edit or explicit retry, and never assign a path to an unnamed scene. Keep recovery journaling and disk-hash guard unchanged. Add `expected_revision` as an optional JSON field to `faset_document_save`; preserve existing callers.
```cpp
if (summary.at("dirty") && !summary.at("path").get<std::string>().empty() &&
now >= state.deadline)
authoring.save(id, {}, state.observed_revision);
```
- [ ] **Step 4: Run authoring/session/MCP tests.** Rebuild `faset_authoring_tests faset_editor_autosave_tests faset_editor_session_tests faset_mcp_tests faset_editor`, then run `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^(authoring|editor_autosave|editor_session_settings|editor_mcp|editor_mcp_stdio)$'`. All five pass; a headless MCP session that stays open autosaves a named scene after idle.
- [ ] **Step 5: Commit** `Autosave named scenes with revision and disk conflict safety`.
### Task 7: Autosave settings and visible Editor state
**Files:** Modify `src/editor/session.cpp`, `src/editor/editor_ui.cpp`, `tests/editor_session_tests.cpp`, `tests/editor_ui_project_settings.cpp`, `tests/editor_ui_tests.cpp`, `docs/manual/editor/workspace.md`, `docs/manual/editor/mcp.md`.
**Interfaces:** `project.faset.json` may contain `editor.autosave: bool`, defaulting to `true`; `faset_project_settings_set` updates that property with its existing project revision check, applies it immediately to the current session and preserves `editor.script_editor`. Project settings provide an Autosave toggle. Status bar maps `faset_autosave_status` to Saved, Pending autosave, Saving, Save conflict, Save failed and Save As required. `Session` caches the setting and updates it on the command rather than rereading the project file every polling frame.
- [ ] **Step 1: Write failing settings/UI tests.** Missing setting reads as enabled; toggling off/on survives project reopen and does not change `editor.script_editor`; stale settings revision is rejected; UI shows pending then saved after a deterministic tick, conflict persists with Save As/reload guidance, unnamed dirty scene shows Save As required. Keyboard focus reaches the toggle and conflict action.
```cpp
auto settings = commands.call("faset_project_settings_get", Json::object());
auto changed = commands.call("faset_project_settings_set",
{{"revision", settings.at("revision")},
{"settings", {{"editor", {{"autosave", false}}}}}});
require(changed.at("settings").at("editor").at("autosave") == false,
"Project autosave preference persists");
```
- [ ] **Step 2: Run rebuilt red tests.** Build `faset_editor_session_tests faset_editor_project_settings_ui_tests faset_editor_ui_tests`, then run `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^(editor_session_settings|editor_ui_project_settings|editor_ui_authoring)$'` with the configured Vulkan ICD. At least the new settings/status assertion must fail for the expected behavior.
- [ ] **Step 3: Implement settings validation and UI.** Merge only the `editor.autosave` field into existing `editor` settings; reject non-Boolean values. Poll the read-only status instead of inferring save from dirty flags. Keep the dark compact reference style while making actual status and actions correct; document autosave vs recovery and MCP explicit save.
```cpp
require(changes["editor"]["autosave"].is_boolean(), "project.autosave",
"Autosave must be enabled or disabled");
value["editor"]["autosave"] = changes["editor"]["autosave"];
```
- [ ] **Step 4: Run UI and Manual checks.** Rebuild `faset_editor_session_tests faset_editor_project_settings_ui_tests faset_editor_ui_tests`, then run `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^(editor_session_settings|editor_ui_project_settings|editor_ui_authoring)$'` with the configured Vulkan ICD; all three pass. `python3 -m mkdocs build --strict` from the documentation virtual environment passes. Capture one Editor screenshot to inspect legibility; the image reference is not a pixel-perfect target.
- [ ] **Step 5: Commit** `Show and configure safe Editor autosave`.
### Task 8: Windows and Release Lua execution evidence
**Files:** Create `tools/verify_lua_release_export.py`, `tests/verify_lua_release_export_test.py`; modify `.github/workflows/ci.yml`, `.github/workflows/windows-graphics.yml`; update `docs/validation/lua-module.md` after results exist. `lua_player_reload` is already registered in `cmake/Player.cmake`.
**Interfaces:** The verifier copies `examples/lua` to a disposable Unicode project path, exports a Release Lua-only package with the real Editor, relocates the generation, hides the source project, runs `--validate` and 120 headless rendered frames, and writes a JSON report containing exact revision/build profile/VM presence/source hashes/device/driver/capture hash. It never modifies the checked-in sample.
- [ ] **Step 1: Write failing verifier contract tests.** In `tests/verify_lua_release_export_test.py`, import the verifier's package/report validator and test that it rejects a package containing project `Gameplay.cpp`, a missing declared Lua source, a package that only works with the source project present, or fewer than 120 rendered frames. Its report must distinguish physical GPU and software Vulkan.
```python
assert report["configuration"] == "Release"
assert report["lua_enabled"] is True
assert report["relocated"] is True
assert report["rendered_frames"] == 120
```
- [ ] **Step 2: Run and implement the verifier.** Run `python3 -m unittest discover -s tests -p 'verify_lua_release_export_test.py' -v`; confirm it discovers at least four tests and fails on the absent verifier API. Implement the disposable-project export/relocation/validation/frame runner and report validator in `tools/verify_lua_release_export.py`, then rerun the same test until it passes. Run the verifier on Linux with the real Release Editor and record its exact output. A deliberately incomplete disposable package must still be rejected.
```sh
python3 tools/verify_lua_release_export.py --editor build/linux-release/faset_editor --output .cache/p1-lua-release-check
```
- [ ] **Step 3: Run actual platform jobs.** Linux native CI runs existing Lua CPU tests; Windows native CI runs them without a GPU. Windows graphics CI runs `lua_player_reload` under its pinned SwiftShader ICD and the Release export verifier. Run the same verifier on Linux with a real GPU when available; exact commands and environment go in the report. A failed CI job is not described as covered. Keep `FASET_ENABLE_LUA=OFF` for C++-only projects, preserve Lua source snapshot hashing, and package required notices; any observed integration defect gets a failing regression before its fix.
```sh
ctest --test-dir build/linux-release --no-tests=error --output-on-failure -R '^(lua_contracts|lua_safety_contracts|lua_cli_contracts|lua_player_reload|build_schema_publication)$'
```
- [ ] **Step 4: Store exact evidence and update the Lua record.** Include CI run links, `ctest` totals, report JSON, renderer/driver details and honest unsupported-hardware notes. A functional Windows SwiftShader result is not a physical-Windows-GPU claim.
- [ ] **Step 5: Commit** `Validate Lua gameplay in Windows and Release exports`.
### Task 9: Measure iteration and close P1 documentation
**Files:** Modify `tools/measure_workflows.py`, `docs/manual/editor/export.md`, `docs/manual/editor/profiling.md`, `docs/manual/scripting/lua.md`, `docs/manual/scripting/first-behavior.md`, `docs/ARCHITECTURE.md`, `docs/IMPLEMENTATION.md`, `PLAN.md`; create `tests/measure_workflows_test.py`, `docs/validation/p1-iteration-2026-09-24/README.md` and raw evidence files.
**Interfaces:** Workflow report version 2 retains raw samples with labels and adds median/nearest-rank p95, exact host/toolchain/driver/revision/source-hash metadata, separate cold and warm paths, changed C++ and Lua-to-reload time, Play-to-first-rendered-frame, synthetic Editor input-to-visible-state latency, and a larger generated scene. Timing values are observations, not CI pass/fail thresholds.
- [ ] **Step 1: Write failing report/math tests.** With sample durations `[1, 2, 3, 4, 5]`, median is 3 and nearest-rank p95 is 5; the report rejects an absent revision, mixed Debug/Release samples under one label, missing source hashes, and a sample labeled windowed first frame when the Player profile says `presentation_mode=offscreen`. Fixture generation must be deterministic from a seed.
```python
assert summarize([1, 2, 3, 4, 5]) == {"median": 3, "p95": 5}
assert len(report["samples"]["warm_unchanged_build"]) >= 5
```
- [ ] **Step 2: Run the red Python test.** `python3 -m unittest discover -s tests -p 'measure_workflows_test.py' -v` discovers at least four tests and fails on the missing summary/report behavior before implementation; a zero-test run does not count.
- [ ] **Step 3: Extend the disposable-project workflow and Manual.** Keep raw stdout/stderr and per-run JSON, warm up then repeat each warm/changed case at least five times, record toolchain/GPU/driver and validation/readback settings, and compare reference budgets only for the exact checked-in sample scenes. Measure a 3,000-frame resource-lifecycle run and report explicit memory growth. Document C++/Lua iteration, cache flags, source navigation, templates, autosave/conflict recovery and profile interpretation in English.
```python
def summarize(values):
ordered = sorted(values)
return {"median": statistics.median(ordered),
"p95": ordered[math.ceil(0.95 * len(ordered)) - 1]}
```
- [ ] **Step 4: Run full validation and record dated evidence.** `ctest --preset linux-debug --no-tests=error --output-on-failure`, `ctest --test-dir build/linux-release --no-tests=error --output-on-failure`, available Linux GPU tests, Windows native/SwiftShader CI, `python3 -m mkdocs build --strict` from the documentation virtual environment and the workflow script pass or have exact documented failures. Update `PLAN.md` to P1 complete only after every acceptance-matrix row in the spec has evidence; physical Windows GPU remains marked unverified if unavailable. Run `graphify update .` after code changes where `graphify-out/graph.json` exists, then request independent review and fix load-bearing findings.
- [ ] **Step 5: Commit** `Measure and document P1 gameplay iteration`; publish only after required checks and review are complete.
@@ -0,0 +1,213 @@
# P3 Lighting and Shadows Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Deliver PLAN P3's multi-light shading, four-cascade sun shadows, bounded point/spot shadow atlas, independent shadow-view visibility, explicit budgets and diagnostics, and a measured Forward+ decision.
**Architecture:** An immutable `Snapshot` carries one sun, local lights, and an unjittered camera frustum from scene extraction. Both Direct and P2 GPU graphics paths use the existing material set 0 and shared `fragmentMain`, a new lighting set 1, and, only for P2 GPU vertices, scene set 2. CPU shadow planning produces atomic sun/spot/point views and caster lists; Vulkan renders those views into two bounded D32 atlases before forward shading. Temporal reconstruction has a separate plan.
**Tech Stack:** C++20, Vulkan 1.3 dynamic rendering, Slang/SPIR-V and Faset reflection v1, CMake/Ninja, SDL3, Catch-style existing C++ test executables, Python measurement scripts, MkDocs Material.
**Spec:** `docs/superpowers/specs/2026-09-24-p3-lighting-design.md`
## Global Constraints
- Linux and Windows desktop 3D; exported Player remains independent of Slang, Editor, and MCP.
- Preserve selectable Direct, GPU frustum, and GPU occlusion paths and their equivalent opaque lighting.
- Preserve material descriptor set 0 bindings 0–3; add lighting set 1; move GPU graphics scene bindings to set 2; leave GPU compute sets unchanged.
- Preserve 96-byte baseline and 112-byte GPU graphics push constants; no optional Vulkan feature may be assumed without a capability check.
- Sun atlas: four 1024² tiles in 2048² D32; local atlas: sixteen 512² tiles in 2048² D32; fallback to half resolution or explicitly unshadowed lighting.
- At most 128 submitted local lights, sixteen local shadow faces, and 4096 caster draws per frame. Overflow is deterministic and visible; a skipped view is entirely unshadowed.
- Shadow caster selection is independent of camera/P2 culling and camera-selected mesh LOD. No incomplete point-light cubemap.
- Sun shadows use unjittered camera data; lighting work must not mutate velocity/TAA history owned by the temporal plan.
- Register each new CTest case before its red run; verify it appears in `ctest --test-dir build/linux-debug -N`, use `--no-tests=error` with exact `-R` names, and treat an absent build target as a failed setup rather than a passing test.
## Review Focus
- A distant, offscreen caster overlapping a visible receiver's sun cascade must still cast a shadow; Task 3's CPU test and Task 4's GPU fixture pin this.
- A point light with fewer than six free atlas tiles must be fully unshadowed, never show partial faces; Task 3's scheduler test and Task 5's image case pin this.
- A view exceeding the caster draw budget must not render only some casters; Task 3's budget test and Task 5's overflow image pin this.
- A material with no local lights, including UI/sprite pixels, must not read an uninitialized storage descriptor or change tint; Tasks 2 and 4 pin this.
- Shader hot reload with a changed light-buffer stride/binding must reject the candidate and retain the displayed frame; Task 2 pins this.
---
## File map and integration order
`include/faset/render/renderer.hpp` owns public light/camera snapshot fields and statistics. New `include/faset/render/lighting.hpp` and `src/render/lighting.cpp` own pure CPU split, shadow-view, tile-allocation, caster-culling, and budget policy, independently testable without Vulkan. `src/player/SceneView.cpp` and `src/authoring/schema.cpp` translate version-1 authoring fields into those typed records. `shaders/baseline.slang`, `shaders/gpu_scene.slang`, `src/render/shader_contract.cpp`, and `src/render/renderer.cpp` own the exact graphics ABI and Vulkan implementation. `src/editor/debug_overlay.cpp`, Player diagnostics, the Manual, and validation studies consume statistics after rendering.
Tasks 1–3 define the shared interfaces. Integrate Task 2's descriptor ABI before concurrent temporal shader changes; Tasks 4–5 then implement Vulkan shadow rendering. The temporal plan may proceed independently in its own files, but `renderer.hpp`, `renderer.cpp`, and `baseline.slang` changes must be sequenced or reconciled with a focused Direct/P2/temporal regression pass. Make a checkpoint commit after every green task; do not mark P3 complete until the acceptance record exists.
### Task 1: Authoring schema and typed light extraction
**Files:** Modify `include/faset/render/renderer.hpp`, `src/authoring/schema.cpp`, `src/player/SceneView.cpp`, `tests/authoring_tests.cpp`, `tests/runtime_player_tests.cpp`, and relevant Manual authoring examples.
**Interfaces:** Introduce `SunLight { stable_id, direction, color, intensity, casts_shadow }`, `LocalLight { Kind::Point|Spot, stable_id, position, direction, color, intensity, range, inner_angle, outer_angle, casts_shadow, shadow_priority }`, and `CameraFrustum { view, projection, near_plane, far_plane, perspective }`. Add `std::optional<SunLight> Snapshot::sun`, `std::vector<LocalLight> Snapshot::local_lights`, and `std::optional<CameraFrustum> Snapshot::camera_frustum` after existing aggregate fields; retain `Snapshot::light_direction`. `SceneView::build` fills camera data for 3D scenes and picks the first enabled directional by stable ID.
- [ ] **Step 1: Write failing schema/extraction tests.** Construct a version-1 scene with directional, point, and spot entities in one order and reversed order. Assert all local IDs/properties agree; authored sun color/intensity are preserved; no-light scene retains the default legacy sun; extra directionals emit a diagnostic; invalid `range <= 0`, `inner_angle > outer_angle`, nonfinite color/transform, and unknown kind identify the entity/field. An essential assertion is:
```cpp
auto a = view.build(scene_with_three_lights(), 16.f / 9.f);
auto b = view.build(reordered_scene_with_three_lights(), 16.f / 9.f);
check(a.local_lights.size() == 2 && b.local_lights.size() == 2,
"Point and spot lights survive scene extraction");
check(a.local_lights[0].stable_id == b.local_lights[0].stable_id,
"Light ordering follows stable IDs, not entity array order");
```
- [ ] **Step 2: Run the focused tests red.** Run `cmake --preset linux-debug` and `cmake --build --preset linux-debug --target faset_authoring_tests faset_player_tests --parallel 4`; missing typed fields should fail compilation. If compilation succeeds, `ctest --test-dir build/linux-debug --output-on-failure --no-tests=error -R '^(authoring|player_scene_contracts)$'` must fail on a new behavioral assertion. Record the expected failure rather than assuming the build itself must be red.
- [ ] **Step 3: Add schema defaults and extraction.** Keep builtin version 1; use `fields.value` for additive fields, normalize directions after the world transform, validate finite/color/range/cone values, sort by stable ID, and preserve the legacy fallback. Set `camera_frustum` from the same unjittered view/projection used to form `view_projection`. Update direct C++ API examples to construct one point and one spot light.
```cpp
struct CameraFrustum {
Mat4 view{identity}, projection{identity};
float near_plane{0.1f}, far_plane{1000.f};
bool perspective{true};
};
// Snapshot::light_direction remains available to callers without Snapshot::sun.
```
- [ ] **Step 4: Run focused tests green.** `ctest --test-dir build/linux-debug --output-on-failure -R '^(authoring|player_scene_contracts)$'` passes, including old version-1 scenes and exported-scene decoding.
- [ ] **Step 5: Commit** `Expose authored sun, point, and spot lights in render snapshots`.
### Task 2: Shared lighting shader ABI and unshadowed local PBR
**Files:** Modify `shaders/baseline.slang`, `shaders/gpu_scene.slang`, `src/render/shader_contract.cpp`, `src/render/renderer.cpp`, `include/faset/render/renderer.hpp`, `tests/test_shader_reflection.py`, `tests/render_gpu_shader_contract_tests.cpp`, `tests/render_reload_tests.cpp`, `tests/render_tests.cpp`, `cmake/Renderer.cmake` if a new CPU-only ABI target is useful.
**Interfaces:** Keep set 0 bindings 0–3 and `Push`/`ScenePush` sizes. Define set 1 bindings 0=`StructuredBuffer<LightingHeader>` (one record), 1=`StructuredBuffer<LocalLightGpu>` (minimum one allocated record even when count zero), 2=`StructuredBuffer<ShadowViewGpu>` (minimum one record), 3=`Texture2D<float>` local atlas. `LightingHeader` is five 16-byte lanes (counts/flags, sun direction+intensity, sun color, camera forward+shadow distance, four split depths); `LocalLightGpu` is five 16-byte lanes (position+range, direction+cosOuter, color+intensity, cone/type/shadow-view indices, reserved); `ShadowViewGpu` is seven 16-byte lanes (matrix, tile scale/offset, guarded clamp, bias/flags). GPU vertex buffers move to set 2 bindings 0–2. Put host mirrors in `src/render/renderer.cpp` with exact `static_assert` size/offsets and validate Slang element strides in reflection. These 80/80/112-byte records are renderer ABI, not scene-file schema.
- [ ] **Step 1: Write failing ABI and image tests.** Extend the existing registered `render_shader_reflection`, `render_gpu_shader_contract`, `render_shader_reload`, and `render_offscreen` cases; do not rely on an unregistered new test. Assert exact set/binding/type/stride for baseline fragment and GPU vertex entries; corrupt a lighting stride or GPU set number in a copied reflection file and require validation rejection. Render a zero-local-light scene with unchanged sprite/UI tint; place red and blue point lights at different ranges and assert the correct receiver regions brighten without NaNs. Run in Direct and GPU frustum modes.
```python
fragment = json.loads((shader_dir / "fragmentMain.reflection.json").read_text())
gpu_vertex = json.loads((shader_dir / "gpuVertexMain.reflection.json").read_text())
assert next(d for d in fragment["layout"]["descriptors"]
if (d["set"], d["binding"]) == (1, 1))["element_stride"] == 80
assert next(d for d in gpu_vertex["layout"]["descriptors"]
if (d["set"], d["binding"]) == (2, 0))["element_stride"] == 224
```
- [ ] **Step 2: Run focused tests red.** Reconfigure and build the modified test targets, then run `ctest --test-dir build/linux-debug --output-on-failure --no-tests=error -R '^(render_shader_reflection|render_gpu_shader_contract|render_shader_reload|render_offscreen)$'`. The new ABI assertion or the point/spot fixture inside `render_offscreen` must reject missing set 1/2 behavior. Confirm all four expected cases in `ctest --test-dir build/linux-debug -N`.
- [ ] **Step 3: Implement the checked ABI and shading.** Allocate/bind one frame lighting set in Direct and GPU graphics pipelines; update shader contracts and P2 graphics set indices only. Accumulate each direct-light BRDF in linear RGB before tone mapping; keep the zero-normal UI/sprite early return. Use finite-safe inverse-square-like distance attenuation with smooth range cutoff and a smooth spot cone. Keep sun/default image reference close to the old baseline. Reject incompatible runtime shader reload while preserving previous pipelines.
```slang
[[vk::binding(0,1)]] StructuredBuffer<LightingHeader> lightingFrame;
[[vk::binding(1,1)]] StructuredBuffer<LocalLightGpu> localLights;
[[vk::binding(2,1)]] StructuredBuffer<ShadowViewGpu> shadowViews;
[[vk::binding(3,1)]] Texture2D<float> localShadowAtlas;
// GPU scene vertex descriptors change from binding(*,1) to binding(*,2).
```
- [ ] **Step 4: Rebuild shaders and run reflection, reload, offscreen, and Direct/P2 image tests green.** `cmake --build --preset linux-debug --target faset_shaders faset_render_tests faset_render_gpu_shader_contract_tests faset_render_reload_tests --parallel 4`; then the focused `ctest` regex above and `ctest --test-dir build/linux-debug -L p2 --output-on-failure`. Check zero Vulkan validation errors.
- [ ] **Step 5: Commit** `Share typed multi-light shading across Direct and GPU paths`.
### Task 3: Pure CPU shadow planning, caster visibility, and capacity policy
**Files:** Create `include/faset/render/lighting.hpp`, `src/render/lighting.cpp`, `tests/render_lighting_policy_tests.cpp`; modify `cmake/Renderer.cmake` and `src/render/renderer.cpp` only to call the policy after it is tested.
**Interfaces:** `build_shadow_plan(const Snapshot&, std::span<const ShadowCasterBounds>, ShadowBudget) -> ShadowPlan` returns ordered `ShadowView` records (sun cascade 0–3, spot one, point six), stable tile indices, caster indices, per-view update reason, requested/effective counts, and dropped-reason counters. `ShadowBudget` defaults to four sun views, sixteen local tiles, 4096 caster draws, 128 local lights, and 2048² atlas dimensions. `ShadowCasterBounds` contains source-mesh world AABB and draw index. No Vulkan handle enters this module.
- [ ] **Step 1: Write and register failing policy tests.** Add `faset_render_lighting_policy_tests` and `add_test(NAME render_lighting_policy ...)` in `cmake/Renderer.cmake`. Assert practical split endpoints are increasing and end at `min(far,80)`; translating a camera by less than one cascade texel keeps the snapped projection origin fixed; a caster outside camera view but upstream of a receiver is included; a caster outside the shadow XY footprint is excluded. Fill 15 local tiles, then request one point light and assert no faces are scheduled while the light remains in the submitted lighting list with shadow validity false. Give a shadow view 4097 casters and assert it is skipped whole. Reverse input light order and assert allocations are unchanged.
```cpp
auto plan = build_shadow_plan(snapshot, casters, ShadowBudget{});
require(plan.sun_views.size() == 4, "Explicit camera gets four cascades");
require(plan.local_faces_used <= 16 && plan.caster_draws <= 4096,
"Shadow work stays within the configured budget");
require(plan.dropped_point_faces == 6 || plan.dropped_point_faces == 0,
"Point shadow allocation is all-or-none");
```
- [ ] **Step 2: Run policy test red.** Reconfigure, verify `render_lighting_policy` appears in `ctest --test-dir build/linux-debug -N`, build `faset_render_lighting_policy_tests`, then run `ctest --test-dir build/linux-debug --output-on-failure --no-tests=error -R '^render_lighting_policy$'` if compilation succeeds. Expected failure is the absent `lighting.hpp` interface or the first failing new assertion.
- [ ] **Step 3: Implement the planner.** Use unjittered frustum corners and fixed λ=0.5 splits, enclosing-sphere square extents, two-texel guard, texel-snapped light XY, and conservative caster-derived light Z. Sort by explicit priority, projected influence, and stable ID. Choose entire views under tile/draw limits; never reuse an old tile if its owner/generation changes. Legacy Snapshot without `CameraFrustum` produces one reported sun view. If no atlas profile is usable, return an unshadowed plan with a reason.
```cpp
ShadowPlan build_shadow_plan(const Snapshot& frame,
std::span<const ShadowCasterBounds> casters,
const ShadowBudget& budget);
```
- [ ] **Step 4: Run focused policy and P2 visibility tests green.** `ctest --test-dir build/linux-debug --output-on-failure -R 'render_lighting_policy|visibility_policy|render_gpu_shadow'`. Preserve P2's offscreen caster fixture.
- [ ] **Step 5: Commit** `Plan stable cascades and bounded shadow views independently of camera culling`.
### Task 4: Vulkan sun atlas and cascade sampling
**Files:** Modify `src/render/renderer.cpp`, `shaders/baseline.slang`, `tests/render_tests.cpp`, `tests/render_gpu_acceptance_tests.cpp`; create `tests/render_lighting_gpu_tests.cpp` and register `render_lighting_sun` as `gpu;p3` in `cmake/Renderer.cmake` or a focused `cmake/LightingAcceptance.cmake`.
**Interfaces:** `ShadowPlan::sun_views` supplies four 1024² tile viewports and matrices to one `ShadowAtlases` RenderGraph pass. `LightingHeader` and `ShadowViewGpu` provide split depths, tile transforms, and valid flags to `fragmentMain`; set 0 binding 0 points to the sun atlas. `FrameStats` reports requested/effective cascade count, sun tiles, caster draws, atlas bytes, and aggregated `gpu_sun_shadow_ms`.
- [ ] **Step 1: Write and register failing sun image tests.** Register `render_lighting_sun` with `LABELS "gpu;p3"` in CMake before its red run. Make a receiver cross the first two split distances; require shadow continuity across the blend band. Move the camera by subtexel and whole-texel steps; require stable then updated shadow edges. Move/disable an upstream offscreen caster and require affected receiver pixels to change. Assert four effective cascades for explicit camera and one reported fallback for a legacy low-level Snapshot. Run Direct and GPU frustum; compare the final frames within the existing P2 image tolerance.
```cpp
require(renderer.stats().effective_sun_cascades == 4,
"Explicit 3D camera uses four sun cascades");
std::size_t darker = 0;
for (std::size_t i = 0; i < with_caster.size(); i += 4)
darker += without_caster[i] > with_caster[i] + 12;
require(darker > 20, "Offscreen caster affects a visible cascade receiver");
```
- [ ] **Step 2: Run new GPU test red.** Reconfigure, verify `render_lighting_sun` appears in `ctest --test-dir build/linux-debug -N`, and build its test executable. Run `ctest --test-dir build/linux-debug --output-on-failure --no-tests=error -R '^render_lighting_sun$'`; an old single fixed shadow projection must fail the new count/image assertions. A missing target is setup failure, not green.
- [ ] **Step 3: Render and sample the sun atlas.** Check D32 sampled/depth-attachment format support and image limits; allocate 2048² or 1024² fallback. Transition to depth attachment once, loop tile rendering with per-tile `renderArea`, clear, viewport and scissor, push each matrix, draw only that view's caster list; transition once to depth read-only. Map projected XY into guarded tile UV, clamp every PCF tap, apply slope-aware bias, choose/blend cascades from unjittered depth. When recreating the atlas, rewrite set-0 binding 0 for every live material descriptor before retiring the old image view. Increase the fixed timestamp query capacity before adding pass labels so aggregate timing does not silently disappear.
```cpp
graph.add("ShadowAtlases", {}, {"sun_shadow", "local_shadow"}, [&] {
// For each scheduled view: clear only its renderArea, set tile viewport/scissor,
// push its view_projection, draw exactly plan.caster_indices.
});
```
- [ ] **Step 4: Run sun, existing shadow, reload, P2, and Vulkan validation tests green.** `ctest --test-dir build/linux-debug --output-on-failure -R 'render_lighting_sun|render_offscreen|render_shader_reload|render_gpu_shadow'` and the full `-L p2` suite. Save difference frames before adjusting any threshold.
- [ ] **Step 5: Commit** `Render stable cascaded sun shadows into a bounded atlas`.
### Task 5: Point/spot atlas, face scheduling, and observability
**Files:** Modify `src/render/renderer.cpp`, `shaders/baseline.slang`, `src/editor/debug_overlay.cpp`, Player profile/diagnostics source, `include/faset/render/renderer.hpp`, `tests/render_lighting_gpu_tests.cpp`, `tests/editor_debug_overlay.cpp`, `tests/player_diagnostics_test.py`, and acceptance CMake registration.
**Interfaces:** `ShadowPlan::local_views` supplies one spot or six point faces per assigned light. The shader resolves a point face from the dominant light-to-fragment axis and samples only a valid assigned tile. `FrameStats`/Player/editor expose submitted and omitted local lights, requested and effective faces, tile occupancy, dropped-reason counts, caster draws, atlas bytes, shadow GPU time, and actual lighting path. The common fallback is unshadowed local lighting.
- [ ] **Step 1: Write and register failing image/diagnostic tests.** Register `render_lighting_local` with `LABELS "gpu;p3"`; extend existing Player diagnostics assertions. A spotlight lights inside its outer cone but not outside; a point light lights six cube-face directions and has no seam-caused bright leak at a face boundary. A caster darkens a nearby receiver; disabling its `casts_shadow` restores light. Fill all local tiles and verify the overflow light still contributes unshadowed. Exercise 15 occupied tiles plus one point; assert six faces are dropped and no stale tile is sampled. Verify editor/Player labels use actual effective counts and explicit reasons.
```cpp
require(stats.local_shadow_faces <= 16 && stats.shadow_caster_draws <= 4096,
"Rendered local shadow work obeys both budgets");
require(stats.dropped_shadow_faces == 6,
"Insufficient atlas room disables a whole point shadow");
require(over_capacity_point_pixels[receiver_pixel] > no_point_pixels[receiver_pixel] + 12,
"Unshadowed point light still illuminates its receiver");
```
- [ ] **Step 2: Run the registered GPU and Player cases red.** Reconfigure and verify `render_lighting_local` appears in `ctest --test-dir build/linux-debug -N`; build the test executable and run `ctest --test-dir build/linux-debug --output-on-failure --no-tests=error -R '^(render_lighting_local|player_shutdown_diagnostics)$'`. For the optional overlay, which is absent from the default `linux-debug` preset, configure `cmake -S . -B build/p3-debug-overlay -G Ninja -DFASET_DEBUG_IMGUI=ON -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++`, build `faset_debug_overlay_tests`, verify it appears in that build's `ctest -N`, then run `ctest --test-dir build/p3-debug-overlay --output-on-failure --no-tests=error -R '^editor_debug_overlay$'`. Red must come from the new assertions, not from an absent optional target.
- [ ] **Step 3: Render local faces and sample them.** Allocate/clear local atlas tiles as in Task 4; assign six 90° views atomically for point lights and one cone view for spots. Bind the local atlas at set 1 binding 3. Apply per-tile PCF guard/clamp and correct face projection; skip sampling on `valid=false`. Report fallback when optional sampled D32 or atlas allocation is unavailable. Record conservative redraw reasons rather than claiming cached depth without invalidation proof.
```slang
if (light.shadowFaceCount == 6 && light.shadowValid != 0)
visibility = samplePointAtlas(light, worldPosition, normal);
// An unassigned light always uses visibility = 1, but still illuminates.
```
- [ ] **Step 4: Run local image and diagnostics tests under Khronos validation.** `ctest --test-dir build/linux-debug --output-on-failure --no-tests=error -R '^(render_lighting_local|player_shutdown_diagnostics|render_shader_reload)$'`; `ctest --test-dir build/p3-debug-overlay --output-on-failure --no-tests=error -R '^editor_debug_overlay$'`; then `ctest --test-dir build/linux-debug -L p3 --no-tests=error --output-on-failure`. Run the point-face seam fixture on Linux physical GPU and pinned SwiftShader.
- [ ] **Step 5: Commit** `Add bounded point and spot shadows with explicit fallback diagnostics`.
### Task 6: Release measurement and conditional Forward+
**Files:** Create `examples/renderer/p3_lighting_benchmark.cpp`, `tools/benchmark_p3_lighting.py`, `tests/test_p3_lighting_benchmark.py`, raw CSV under `docs/studies/data/`, and `docs/studies/22-p3-lighting-benchmark-2026-09-24.md`; modify `cmake/Renderer.cmake` and, only if the gate triggers, `shaders/gpu_scene.slang` or a focused new Slang file, `src/render/renderer.cpp`, `src/render/shader_contract.cpp`, `src/editor/build_service.cpp`, shader/package tests, and `tests/render_lighting_gpu_tests.cpp`.
**Interfaces:** Benchmark `--lights 0|4|16|32|64|128 --shadows on|off --visibility direct|gpu-frustum|gpu-occlusion --csv PATH` at 1920×1080, fixed scene/camera, ten warm-up and thirty measured frames, three independent runs. The Python wrapper has `--list-runs` for a fast configuration-contract test and `--sweep` for the full offline measurement; the registered CTest schema smoke invokes one 64×64, one-frame benchmark, not the full sweep. CSV includes commit, device/driver, mode, light count, GPU forward/shadow/total milliseconds, CPU render/readback milliseconds, atlas use, draw counts, validation errors. If the spec threshold triggers, tiled Forward+ uses 16×16 screen tiles with an overflow flag; an overflowing tile evaluates all submitted lights.
- [ ] **Step 1: Write and register benchmark/overflow contract checks.** Register `render_lighting_benchmark_schema` in CMake to run `tests/test_p3_lighting_benchmark.py` against the benchmark executable. Use `--list-runs` to verify the 0/4/16/32/64/128 sweep yields 18 mode×light configurations and 54 independent runs per shadow setting; use one 64×64 frame to verify every CSV row includes the effective lighting path. If tiled mode is needed, write an image case with more than the per-tile index capacity and compare against the full-light reference to ensure no missing illumination.
```cpp
double absolute_error = 0;
std::size_t large_error = 0;
for (std::size_t i = 0; i < tiled_pixels.size(); i += 4)
for (std::size_t channel = 0; channel < 3; ++channel) {
const auto delta = std::abs(int(tiled_pixels[i + channel]) -
int(full_scan_pixels[i + channel]));
absolute_error += delta;
large_error += delta > 16;
}
const double samples = 3.0 * (tiled_pixels.size() / 4);
require(absolute_error / samples <= 2.0 && large_error / samples <= 0.005,
"Forward+ overflow scans all local lights instead of dropping any");
```
- [ ] **Step 2: Run the new checks red.** Reconfigure; verify `render_lighting_benchmark_schema` appears in `ctest --test-dir build/linux-debug -N`; build `faset_p3_lighting_benchmark` and run `ctest --test-dir build/linux-debug --output-on-failure --no-tests=error -R '^render_lighting_benchmark_schema$'`. Expected failure is a missing benchmark interface/output or effective-path CSV column, never an empty CTest selection.
- [ ] **Step 3: Implement and run the fixed-scene benchmark.** Record Linux Release physical GPU and pinned SwiftShader functional runs separately. Do not use whole-render CPU time as a proxy for fragment cost because `Renderer::render` always performs synchronous framebuffer readback.
- [ ] **Step 4: Apply the objective gate.** If at 32, 64, or 128 local lights the median main-raster overhead versus zero lights is at least 1.0 ms or at least 15% of the light-free GPU frame on the Linux reference GPU, implement and verify depth-free 16×16 tiled Forward+ from conservative projected light volumes and record before/after *build + raster* time. If the threshold is not reached, keep the simple path, record the measured reason, and keep the CSV harness. New compute shader entries require exact reflection validation, CMake outputs, Editor build-service copy lists, and exported Player bundle tests.
```text
gate = max_over_32_64_128(Δmain_raster_p50 >= 1.0 ms
OR Δmain_raster_p50 >= 0.15 × gpu_frame_zero_lights_p50)
```
- [ ] **Step 5: Re-run both paths on the same frames.** Compare image output, zero validation errors, and measured GPU construction+raster cost. Keep Direct and P2 modes correct regardless of chosen default. Retain all raw CSV and methodology in the study; never claim universal speedup from one device.
- [ ] **Step 6: Commit** `Measure P3 light scaling and select a verified lighting path`.
### Task 7: End-to-end acceptance, Manual, and public evidence
**Files:** Modify `docs/manual/editor/diagnostics.md`, `docs/manual/editor/profiling.md`, `mkdocs.yml`, `PLAN.md`, `docs/IMPLEMENTATION.md`, `docs/validation/README.md`; create `docs/manual/editor/lighting.md`, `docs/validation/p3-lighting-2026-09-24/README.md`, raw test logs/report files. Update Windows CI files only if the existing GPU-labeled suite does not pick up P3 cases.
**Interfaces:** The Manual explains light kinds/properties, sun shadow distance, atlas face cost and overflow, shadow/camera behavior, editor and MCP authoring examples, and how to read actual diagnostics. The validation dossier names commit, exact Linux/Windows platform and driver, test counts, benchmark raw CSV paths, known limitations, and links to CI. P3 acceptance is an evidence claim, not a checkbox based only on compiling code.
- [ ] **Step 1: Write acceptance cases before the final run.** Use fixed scenes for 0/1/many lights, moving sun/caster, camera pan/cut/resize, a thin receiver at a cascade split, offscreen caster, six point faces, 16-tile and 4096-draw capacity, an unsupported-atlas fallback, Direct/GPU frustum/occlusion equivalence, shader reload, and independent 2D/UI output.
- [ ] **Step 2: Run Linux Debug and Release checks.** `cmake --build --preset linux-debug --parallel 4`; `ctest --preset linux-debug --output-on-failure`; `cmake --build --preset linux-release --parallel 4`; `ctest --test-dir build/linux-release --output-on-failure`. Run pinned Linux SwiftShader `ctest -L p3` and the same cases on a physical Vulkan GPU with validation enabled; record actual skips and layer availability.
- [ ] **Step 3: Run Windows native and software-Vulkan CI.** Verify every `gpu;p3` test executes on pinned SwiftShader, shader reflection/package tests pass, and relocated 2D/3D exported Release Players run at least 120 frames. Publish the exact GitHub Actions run links, test logs, and exported-game report. Do not describe this as physical Windows-GPU validation unless that device was run.
- [ ] **Step 4: Finish the English Manual and evidence.** Link `docs/manual/editor/lighting.md` in `mkdocs.yml`; include authoring JSON/Inspector and script examples, priorities, budgets, actual fallback and default path. Run `python -m mkdocs build --strict`, `git diff --check`, and the updated validation index link check. Update `PLAN.md` only for features whose stated acceptance evidence is present.
- [ ] **Step 5: Review and commit** `Validate and document P3 lighting and shadows`; after independent code review, publish the completed checkpoint to GitHub and Gitea as previously authorized.
@@ -0,0 +1,202 @@
# P3 Temporal Reconstruction Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Deliver selectable 1:1 TAA and, after it passes moving-image acceptance, output-resolution-history temporal upscaling for Direct and P2 visibility paths, with explicit fallback and measurable image quality.
**Architecture:** Keep the existing Off render branch as a reference. Add independent temporal history and per-instance previous transforms; raster the scene to internal color/depth/velocity, resolve against output-resolution history, then composite sharp UI into the existing final color image. HZB and temporal history share no validity flag, while using the same current scene depth and jittered raster projection.
**Tech Stack:** C++20, Vulkan 1.3 dynamic rendering and synchronization2, Slang/SPIR-V, CMake/Ninja, SDL3, headless GPU image tests, Khronos validation, Linux/Windows software-Vulkan CI.
**Spec:** `docs/superpowers/specs/2026-09-24-p3-temporal-design.md`
**Prerequisite checkpoint:** Complete Task 2 of `docs/superpowers/plans/2026-09-24-p3-lighting.md` through the green commit `Share typed multi-light shading across Direct and GPU paths` before the temporal plan changes `renderer.hpp`, `renderer.cpp`, `baseline.slang`, `gpu_scene.slang` or shader-contract validation. Pure temporal policy/test drafting can happen earlier; shared renderer integration cannot. Re-run Direct/P2 lighting reflection, hot-reload and image tests after each temporal shader or pipeline change.
## Global Constraints
- `TemporalMode::Off` and output-resolution `color` remain the defaults and reference output; Direct visibility must work with TAA and Upscale.
- Preserve the lighting graphics ABI: material set 0 bindings 0–3, frame-lighting set 1, GPU graphics scene set 2 bindings 0–2, unchanged 96/112-byte graphics push blocks; P2 compute sets remain set 0.
- `Snapshot::view_projection`, projection, `scene_rect`, picking and UI coordinates remain unjittered and in output pixels.
- HZB history and temporal color history have independent validity; GPU occlusion remains correct through temporal mode/scale switches.
- A missing previous transform, anonymous draw, mesh/LOD identity change, camera cut, changed view/projection/scene rectangle, resize or incompatible shader generation cannot reuse stale color history.
- World transparency/sprites retain depth and order, but their composited pixels reject temporal color history; UI is rendered at output resolution after resolve.
- Shadow views and light-space matrices are never jittered or scaled by temporal rendering.
- Player bundles contain complete checked SPIR-V and reflection metadata; no Slang compiler or Editor/MCP service is required at runtime.
- Unsupported temporal capabilities must use Off with an exposed effective mode and reason, never a silent path change.
- Every new pass receives a GPU label and timing; full-frame measurements include the existing synchronous readback cost unless explicitly excluded in a matched experiment.
## Review Focus
- Direct mode while TAA is enabled: a stable opaque instance must gain a valid prior transform on frame two without requiring HZB; Task 1 and Task 4 test this.
- GPU PostRaster after an object emerges from occlusion: its color and velocity must exist before temporal resolve so the same final frame shows it; Tasks 3 and 4 test this.
- Editor scene rectangle and UI: an offset/odd-sized scene viewport may scale internally, but text and buttons must remain pixel-exact in final output; Tasks 3 and 5 test this.
- A translucent object moving across an opaque surface: its pixels must not borrow the underlying opaque motion/history; Tasks 3 and 4 test this.
- Shader reload, format fallback and relocated exports: a partial bundle or unsupported format cannot produce a half-active temporal path; Task 6 tests this.
---
### Task 1: Independent temporal history policy and public mode
**Files:** Create `include/faset/render/temporal.hpp`, `src/render/temporal.cpp`, `tests/render_temporal_policy_tests.cpp`; modify `include/faset/render/renderer.hpp`, `src/render/renderer.cpp`, `cmake/Renderer.cmake`.
**Interfaces:** Add `enum class TemporalMode { Off, TAA, Upscale };`, `RendererConfig::temporal_mode`, `RendererConfig::render_scale`, `Renderer::set_temporal_mode(TemporalMode, float)`, `Renderer::temporal_mode()`, `FrameStats::requested_temporal_mode`, `effective_temporal_mode`, `temporal_history_valid`, `temporal_reset_reason`, and `temporal_internal_width/height`. Define `TemporalResetReason { None, FirstFrame, CameraCut, CameraDiscontinuity, ViewChanged, ViewportChanged, ProjectionChanged, Resize, ModeChanged, ScaleChanged, ShaderReload, Unsupported }` and `TemporalCapabilities { bool compute, formats, extent; }`. A pure `evaluate_temporal_history(previous, current) -> TemporalHistoryDecision` compares `TemporalHistoryKey` values containing view ID, output/internal extent, scene rectangle, unjittered projection, mode, shader generation, camera eye/current VP and explicit cut. `temporal_jitter(frameIndex, viewportWidth, viewportHeight)` returns a deterministic Halton(2,3) clip offset. Add `select_effective_temporal_mode(requested, capabilities)` as a pure policy function. Shared graphics descriptors remain material set 0, lighting set 1, GPU scene set 2; this task does not reallocate those bindings.
- [ ] **Step 1: Write failing policy tests and register their CMake target.** In a Direct visibility configuration, a same-view second frame returns `valid=true` even when HZB is absent. Test first frame, cut, view switch, changed projection, odd scene rectangle, resize, mode/scale change, shader generation, a camera teleport and unsupported format/compute capabilities. Add `faset_render_temporal_policy_tests` / `render_temporal_policy` to `cmake/Renderer.cmake` before the RED build. Example contract:
```cpp
auto decision = evaluate_temporal_history(previous, current);
require(decision.valid && decision.reason == TemporalResetReason::None);
current.camera_cut = true;
require(!evaluate_temporal_history(previous, current).valid);
require(select_effective_temporal_mode(TemporalMode::TAA, {false, true}) == TemporalMode::Off);
```
- [ ] **Step 2: Run the focused target and record the intended missing-interface failure.** Reconfigure with `cmake --preset linux-debug`, then run `cmake --build --preset linux-debug --target faset_render_temporal_policy_tests -j 4`; it must fail compiling the new test against the absent temporal API, not with `unknown target`. After implementation, `ctest --test-dir build/linux-debug --no-tests=error -R '^render_temporal_policy$'` runs its behavioral assertions.
- [ ] **Step 3: Implement the pure policy and attach it to rendered-frame completion.** Keep `scene.hzb_history_valid` and temporal history fields separate; do not call `InstanceTracker::invalidate_view()` merely because P2 occlusion is inactive. Reject invalid scale (`TAA` requires `1`, `Upscale` requires `[0.5, 1)`) with `std::invalid_argument`. Define camera teleport conservatively using eye displacement and an unjittered VP discontinuity, and expose the reason through `FrameStats`.
```cpp
const bool hzb_compatible = evaluate_hzb_history(...);
const auto temporal = evaluate_temporal_history(previous_temporal, current_temporal);
gpu_frame.view.flags[0] = hzb_compatible ? 1u : 0u;
statistics.temporal_history_valid = temporal.valid;
```
- [ ] **Step 4: Run the CPU tests and existing P2 policy tests.** `ctest --test-dir build/linux-debug --output-on-failure -R 'render_temporal_policy|render_visibility_policy|render_gpu_shader_contract'` must pass. Check that the default constructor still selects Off.
- [ ] **Step 5: Commit.** `git add include/faset/render/temporal.hpp src/render/temporal.cpp tests/render_temporal_policy_tests.cpp include/faset/render/renderer.hpp src/render/renderer.cpp cmake/Renderer.cmake && git commit -m "Define independent temporal history and mode policy"`.
### Task 2: Previous transforms and checked motion-vector shaders
**Files:** Modify `src/render/renderer.cpp`, `include/faset/render/visibility.hpp`, `shaders/baseline.slang`, `shaders/gpu_scene.slang`, `cmake/Renderer.cmake`, `src/render/shader_contract.hpp`, `src/render/shader_contract.cpp`, `tests/test_shader_reflection.py`, `tests/render_gpu_shader_contract_tests.cpp`; create `tests/render_temporal_motion_tests.cpp`.
**Interfaces:** Extend the GPU `SceneInstance/InstanceRecord` from 224 to 288 bytes by appending `previousModel` at offset 224; retain slot/generation metadata at offsets 208–223. `metadata.x & 1` means prior HZB eligibility; `metadata.x & 2` means prior temporal transform eligibility. A temporal Direct vertex carries `previousClip` and validity alongside existing current clip and material data. Temporal Direct/GPU vertex entries feed one temporal fragment entry that writes scene color and an `R16G16B16A16_SFLOAT` target: `.xy = currentUV - previousUV`, `.z = previous clip depth`, `.w = 1` for valid opaque motion and `0` for reactive/invalid pixels. Add a pure `project_motion(current_clip, previous_clip) -> Vec2` CPU oracle with the same sign/space convention. Preserve the post-lighting baseline Off shader entry points and their layout fingerprints; GPU P2 fingerprints change with the checked record stride. Temporal fragment binds material set 0 and lighting set 1; temporal GPU vertex reads instance/visible-ID/view from set 2. Do not move cull/HZB compute off set 0 or increase 96/112-byte graphics push constants.
- [ ] **Step 1: Write failing shader and motion tests and register their target.** Assert the reflected 288-byte storage stride, current/previous clip varyings, velocity attachment output, lighting set 1/GPU scene set 2, and rejection of a tampered SPIR-V/reflection pair. Add `faset_render_temporal_motion_tests` / `render_temporal_motion` to `cmake/Renderer.cmake`. Test `project_motion` against a known current/prior clip pair and `InstanceTracker` through a two-frame rigid object move; include a replaced mesh and anonymous draw whose motion validity is false. GPU image-level motion tests belong to Task 4 after MRT resources exist.
```cpp
auto previous = tracker.update("cube", mesh, model_a, bounds_a, "main");
tracker.finish_frame();
auto moved = tracker.update("cube", mesh, model_b, bounds_b, "main");
require(moved.previous_valid && moved.previous_model == model_a);
```
- [ ] **Step 2: Observe the expected pre-implementation failure.** Reconfigure, then run `cmake --build --preset linux-debug --target faset_render_temporal_motion_tests -j 4`; the new test fails to compile against missing motion interfaces. Build the changed existing tests with `cmake --build --preset linux-debug --target faset_render_gpu_shader_contract_tests faset_shaders -j 4`, then `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^(render_shader_reflection|render_gpu_shader_contract)$'` must reject the new temporal ABI assertions rather than pass only old cases.
- [ ] **Step 3: Implement both geometry paths and shader contracts.** Retain `InstanceUpdate` in selected Direct draws; compute the previous clip position from its previous model and previous jittered VP. Extend `GpuVertex` only as needed by temporal shader input locations. GPU temporal vertex multiplies `previousViewProjection * previousModel * localVertex`. Current/previous scene clip and validity reach the fragment; encode current-minus-prior local-scene UV, prior clip depth and validity. Make P2 culling read `(metadata.x & 1u) != 0`, not `metadata.x != 0`. Update C++ static assertions and reflection checks together.
```slang
float4 previousClip = mul(view.previousViewProjection,
mul(instance.previousModel, float4(vertex.position, 1)));
bool temporalValid = (instance.metadata.x & 2u) != 0u && previousClip.w > 0;
// Off still uses vertexMain / gpuVertexMain / fragmentMain.
```
- [ ] **Step 4: Run reflection, motion and P2 GPU suites under validation.** `ctest --test-dir build/linux-debug --output-on-failure -R 'render_temporal_motion|render_shader_reflection|render_gpu_shader_contract|render_gpu_visibility'` must pass with zero validation errors. Verify the Off direct/GPU image comparison remains within its established tolerance.
- [ ] **Step 5: Commit.** `git add src/render/renderer.cpp include/faset/render/visibility.hpp shaders/baseline.slang shaders/gpu_scene.slang cmake/Renderer.cmake src/render/shader_contract.hpp src/render/shader_contract.cpp tests/test_shader_reflection.py tests/render_gpu_shader_contract_tests.cpp tests/render_temporal_motion_tests.cpp && git commit -m "Emit checked motion vectors for direct and GPU scenes"`.
### Task 3: Scene targets, post-cull ordering and sharp UI boundary
**Files:** Create `shaders/temporal.slang`, `tests/render_temporal_graph_tests.cpp`; modify `src/render/renderer.cpp`, `src/render/shader_contract.hpp`, `src/render/shader_contract.cpp`, `shaders/baseline.slang`, `shaders/gpu_scene.slang`, `cmake/Renderer.cmake`, `tests/render_tests.cpp`, `tests/render_gpu_acceptance_tests.cpp`.
**Interfaces:** Add temporal-only internal `sceneColor` (`R8G8B8A8_UNORM`, sampled/color attachment), `sceneDepth` (`D32_SFLOAT`, sampled/depth attachment) and `sceneVelocity` (sampled floating-point/color attachment). Keep existing full-resolution `color` for capture and presentation. Temporal opaque pipeline variants use color+velocity MRT; transparent/sprite pixels overwrite velocity validity with invalid/reactive state. Split `draw_sprites_and_ui` so world sprites join the internal scene and UI draws only after resolve. Record names in `FrameStats::graph_passes` for ordering diagnostics. Main and post-raster graphics pipelines bind material set 0 and lighting set 1, plus scene set 2 only for GPU instances; shadow planning remains unjittered. For TAA, internal and output extents are identical; the scaling of Task 5 follows later.
- [ ] **Step 1: Write failing graph/image tests and register their target.** Add `faset_render_temporal_graph_tests` / `render_temporal_graph` to `cmake/Renderer.cmake`. Render an odd, offset Editor `scene_rect`, a textured sprite behind/in front of a mesh, moving translucent geometry and bright UI text/quad. Assert `TemporalResolve` occurs after `PostRasterScene` in GPU occlusion mode, `UI` occurs after resolve, viewport clipping holds, and an unchanged UI pixel matches Off exactly. Check depth and velocity attachment store/load behavior with validation.
```cpp
require(position(pass_names, "PostRasterScene") < position(pass_names, "TemporalResolve"));
require(position(pass_names, "TemporalResolve") < position(pass_names, "TemporalComposite"));
require(position(pass_names, "TemporalComposite") < position(pass_names, "UI"));
require(taa_frame.ui_pixel == off_frame.ui_pixel);
```
- [ ] **Step 2: Run the focused test and confirm the missing scene/UI split.** Reconfigure/build `faset_render_temporal_graph_tests`, then `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^render_temporal_graph$'` must fail at the new pass-order/UI assertion. A compile failure on an absent `FrameStats::graph_passes` field is also a valid RED result; an unregistered test is not.
- [ ] **Step 3: Refactor the render branch without changing Off output.** In the temporal branch, MainRaster clears color/depth/velocity, HZB reads stored scene depth, PostRaster loads all attachments, transparent/sprites retain their scene-depth test and invalidate velocity/reactive pixels, then `TemporalResolve`, `TemporalComposite` and `UI` are separate named passes. First make Resolve a spatial copy into a temporary `resolvedColor` and Composite a fullscreen draw to `color`; Task 4 replaces the spatial result with temporal accumulation. Explicitly transition color/depth/velocity from attachment to sampled layouts before compute, and resolved output to sampled before composite.
```cpp
// Temporal branch only; Off keeps the established ForwardAndUI path.
graph.add("PostRasterScene", {"sceneColor", "sceneDepth", "post_indirect"},
{"sceneColor", "sceneDepth", "sceneVelocity"}, draw_post_scene);
graph.add("TemporalResolve", {"sceneColor", "sceneDepth", "sceneVelocity"},
{"resolvedColor"}, resolve_spatial);
graph.add("TemporalComposite", {"resolvedColor"}, {"color"}, draw_resolved_scene);
graph.add("UI", {"color"}, {"color"}, draw_output_ui);
```
- [ ] **Step 4: Run GPU image/validation and sprite/UI suites.** `ctest --test-dir build/linux-debug --output-on-failure -R 'render_temporal_graph|render_offscreen|render_sprite_alpha|render_gpu_visibility|ui_render'` passes. Compare Off captures before/after this commit to ensure no unrequested shading/UI change.
- [ ] **Step 5: Commit.** `git add src/render/renderer.cpp src/render/shader_contract.hpp src/render/shader_contract.cpp shaders/baseline.slang shaders/gpu_scene.slang shaders/temporal.slang cmake/Renderer.cmake tests/render_tests.cpp tests/render_gpu_acceptance_tests.cpp tests/render_temporal_graph_tests.cpp && git commit -m "Separate temporal scene raster from output UI"`.
### Task 4: Stable 1:1 TAA with disocclusion rejection
**Files:** Create `tests/render_temporal_acceptance_tests.cpp`; modify `shaders/temporal.slang`, `src/render/renderer.cpp`, `cmake/Renderer.cmake`, `src/render/shader_contract.hpp`, `src/render/shader_contract.cpp`, `tests/test_shader_reflection.py`, `tests/render_temporal_graph_tests.cpp`.
**Interfaces:** Allocate two output-resolution `R16G16B16A16_SFLOAT` sampled/storage color histories and two output-resolution `R32_SFLOAT` sampled/storage depth histories. `temporalResolveMain` reads current scene color/depth/velocity and prior history, writes current history. `temporalComposite*` draws the resolved result to the existing RGBA8 final `color`; it does not reapply tone mapping/gamma because scene shading is already display-referred. Add GPU pass time and a history accepted/rejected diagnostic counter that is read only in diagnostic mode.
- [ ] **Step 1: Write failing deterministic frame-sequence tests and register their target.** Add `faset_render_temporal_acceptance_tests` / `render_temporal_acceptance` to `cmake/Renderer.cmake`. On Direct and P2 GPU modes, require first-frame rejection and second-frame acceptance. Test static diagonal/wire variance after 16 jitter phases, slow camera pan, moving rigid cube, opening a door, explicit cut, unmarked teleport, mesh/LOD identity change, and UI opacity. Compare each reveal/cut frame to Off at the same camera; old foreground colors must not trail into exposed background. Record tolerances per fixture in the test, not a universal image-perfect claim.
```cpp
auto cut = render_frame(taa, scene_with_cut);
auto fresh = render_frame(off, scene_without_history);
require(!cut.stats.temporal_history_valid);
require(mean_rgb_error(cut.rgba, fresh.rgba, reveal_roi) <= 8.0);
```
- [ ] **Step 2: Run the acceptance target to see the expected lack of accumulation/rejection.** Reconfigure/build the new target, then `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^render_temporal_acceptance$'` must fail in its new history/variance assertions; a zero-tests pass is not evidence.
- [ ] **Step 3: Implement TAA resolve and real history lifetime.** Use deterministic Halton jitter, local-scene normalized motion and 3×3 nearest-depth velocity dilation. Reject invalid/out-of-bounds motion and sampled prior depth mismatches, clamp previous color to a current 3×3 neighborhood, reduce its bounded weight for high motion/reactive pixels, and use current color on first/rejected pixels. Write current color/depth to the next history only after a successful submitted frame. Grow the 12-query timestamp pool so every graph pass is timed, and expose separate temporal resolve/composite timings.
```slang
float2 previousUV = currentUV - selectedMotion.xy;
bool accept = historyValid && selectedMotion.w > 0 && inBounds(previousUV) &&
depthAgrees(selectedMotion.z, historyDepth.SampleLevel(sampler, previousUV, 0));
float3 resolved = accept ? lerp(current.rgb, clamp(history.rgb, neighborhoodMin,
neighborhoodMax), historyWeight)
: current.rgb;
```
- [ ] **Step 4: Run policy, shader and GPU acceptance under validation.** `ctest --test-dir build/linux-debug --output-on-failure -R 'render_temporal|render_shader_reflection|render_gpu_visibility|render_offscreen'` passes, including Direct/P2. Capture repeatable Off/TAA comparison images and per-pass timing for review; a TAA frame with zero validation errors is not sufficient without the moving-image assertions.
- [ ] **Step 5: Commit.** `git add shaders/temporal.slang tests/render_temporal_acceptance_tests.cpp src/render/renderer.cpp cmake/Renderer.cmake src/render/shader_contract.hpp src/render/shader_contract.cpp tests/test_shader_reflection.py tests/render_temporal_graph_tests.cpp && git commit -m "Resolve scene with depth-rejected temporal AA"`.
### Task 5: Lower-resolution scene and output-resolution history
**Files:** Modify `src/render/renderer.cpp`, `include/faset/render/renderer.hpp`, `include/faset/render/temporal.hpp`, `src/render/temporal.cpp`, `shaders/temporal.slang`, `tests/render_temporal_policy_tests.cpp`, `tests/render_temporal_acceptance_tests.cpp`, `tests/render_gpu_acceptance_tests.cpp`.
**Interfaces:** `Upscale` computes internal width/height by `ceil(outputExtent * renderScale)` with a minimum of one pixel; stores history at output extent. It transforms `scene_rect` into a clipped internal viewport once per frame. P2 HZB, current/post depth and `SceneView` viewport use these internal values. Resolve maps each output-scene pixel to the internal scene signal and handles depth/velocity boundaries before history reuse. Output UI and capture remain full resolution.
- [ ] **Step 1: Write failing scale tests.** Exercise 320×240 at 0.67, 319×241 with an offset scene rectangle at 0.5, resize, 2D sprites, and toggling 1.0 TAA → 0.67 Upscale → Off. Check reported internal extent, exact UI pixels, HZB reset, history reset reason and no stale edge pixels. Compare a static thin-wire and slow-pan sequence to a full-resolution spatial reference; require measured temporal variance to improve over a nearest-neighbor 0.67 spatial baseline, while reporting image error rather than asserting that all scenes improve.
```cpp
require(upscaled.stats.temporal_internal_width == 215);
require(upscaled.stats.temporal_internal_height == 161);
require(upscaled.rgba.size() == 320u * 240u * 4u);
require(upscaled.stats.temporal_reset_reason == TemporalResetReason::ScaleChanged);
```
- [ ] **Step 2: Run the scale test and observe the missing reduced-resolution path.** Rebuild `faset_render_temporal_acceptance_tests` with the new assertions, then `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^render_temporal_acceptance$'` must fail at the internal extent/scale transition assertion.
- [ ] **Step 3: Implement internal target recreation and upscale sampling.** Keep `color`/readback output-sized, resize scene color/depth/velocity and HZB to internal size, scale only scene viewport/scissor, and include internal extent in both history keys. Use output-resolution history and depth-aware current sampling at output pixel centers; keep motion in local-scene normalized coordinates. Recreate descriptors after target changes and invalidate both temporal/HZB histories on scale/resize.
```cpp
const auto internal_w = std::max(1u, static_cast<unsigned>(std::ceil(width * scale)));
const auto internal_h = std::max(1u, static_cast<unsigned>(std::ceil(height * scale)));
// scene_rect is mapped to this extent; UI remains at width x height.
```
- [ ] **Step 4: Run Direct/frustum/occlusion GPU sequences and baseline UI/resize tests.** `ctest --test-dir build/linux-debug --output-on-failure -R 'render_temporal|render_gpu_visibility|render_offscreen|render_sprite_alpha|editor_ui'` passes with zero Vulkan validation errors where the validation layer exists. Inspect side-by-side captures on physical GPU and software Vulkan.
- [ ] **Step 5: Commit.** `git add src/render/renderer.cpp include/faset/render/renderer.hpp include/faset/render/temporal.hpp src/render/temporal.cpp shaders/temporal.slang tests/render_temporal_policy_tests.cpp tests/render_temporal_acceptance_tests.cpp tests/render_gpu_acceptance_tests.cpp && git commit -m "Reconstruct lower-resolution scenes at output resolution"`.
### Task 6: Editor/Player control, shader reload, package and graceful fallback
**Files:** Modify `src/editor/debug_overlay.cpp`, `src/editor/build_service.cpp`, `apps/player_main.cpp`, `src/render/renderer.cpp`, `src/render/shader_contract.cpp`, `cmake/Renderer.cmake`, `tests/render_reload_tests.cpp`, `tests/build_service_tests.cpp`, `tests/player_diagnostics_test.py`, `docs/manual/editor/profiling.md`; add temporal mode usage to the appropriate scripting/manual rendering page.
**Interfaces:** Editor and Player select Off/TAA/Upscale with scale; the Player profile reports requested/effective mode, fallback/reset reason, internal/output extent, jitter, temporal GPU ms and memory. A complete shader bundle includes all temporal entry points and metadata. Reload is transactional: failure preserves the active set and history, success replaces pipelines and resets history. Device feature checks are separate from P2 `scene.available`, then `select_effective_temporal_mode` exposes fallback.
- [ ] **Step 1: Write failing integration tests.** A missing/tampered temporal `.spv` or reflection rejects the bundle and exported game; a failed hot reload leaves previous rendered pixels/actual mode intact; successful reload resets history; a pure unsupported-capability fixture selects Off with a specific reason. A relocated 2D and 3D Player profile contains temporal requested/effective and reset fields. Check Editor control state matches `Renderer::temporal_mode()`.
```cpp
const auto before = renderer.stats().effective_temporal_mode;
require(!renderer.reload_shaders(error) && !error.empty());
renderer.render(scene);
require(renderer.stats().effective_temporal_mode == before);
```
- [ ] **Step 2: Run focused integration tests for the expected package/control failure.** Build `faset_render_reload_tests`, `faset_build_service_tests`, and `faset_player_diagnostics`; then `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^(render_shader_reload|process_and_cook|player_shutdown_diagnostics)$'` must execute registered tests and fail in the new temporal assertions. For the optional overlay, configure `cmake -S . -B build/p3-temporal-ui -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DBUILD_TESTING=ON -DFASET_DEBUG_IMGUI=ON`, build `faset_debug_overlay_tests`, and run `ctest --test-dir build/p3-temporal-ui --no-tests=error --output-on-failure -R '^editor_debug_overlay$'`. Record the specific failing assertions; an absent overlay test is not a pass.
- [ ] **Step 3: Implement controls and packaging.** Compile/install temporal shaders in `cmake/Renderer.cmake`; update both explicit shader-copy lists in `src/editor/build_service.cpp`; validate reflection/strides; stage temporal pipelines before replacing live ones. Query sampled/color/storage/filter format support and compute queue support, report Off fallback explicitly, and avoid allocating history on Off. Add concise English Manual examples for mode choice and reading the profile.
```cpp
stats.requested_temporal_mode = config.temporal_mode;
stats.effective_temporal_mode = select_effective_temporal_mode(config.temporal_mode, caps);
stats.temporal_status_reason = stats.effective_temporal_mode == TemporalMode::Off
? missing_capability_name(caps) : std::string{};
```
- [ ] **Step 4: Run focused tests, both sample exports, strict Manual build and native Windows CI.** `ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R 'render_shader_reload|process_and_cook|player_shutdown_diagnostics|render_temporal'`, `ctest --test-dir build/p3-temporal-ui --no-tests=error --output-on-failure -R '^editor_debug_overlay$'`, and `python -m mkdocs build --strict` pass; relocated exports launch without compiler/source checkout. Record any Windows GPU/validation coverage gap explicitly.
- [ ] **Step 5: Commit.** `git add src/editor/debug_overlay.cpp src/editor/build_service.cpp apps/player_main.cpp src/render/renderer.cpp src/render/shader_contract.cpp cmake/Renderer.cmake tests/render_reload_tests.cpp tests/build_service_tests.cpp tests/player_diagnostics_test.py docs/manual && git commit -m "Expose and package temporal reconstruction"`.
### Task 7: Adversarial quality gate, performance record and P3 integration
**Files:** Add `docs/validation/p3-temporal-2026-09-24/README.md` and image/measurement artifacts; modify `PLAN.md`, `docs/IMPLEMENTATION.md`, `docs/ARCHITECTURE.md`, `docs/manual/editor/profiling.md`, temporal tests as findings require.
**Interfaces:** The evidence dossier binds revision, OS/GPU/driver, exact test commands, mode/scale, output/internal extent, scene sequence, raw image differences, static variance, disocclusion trail length, pass/full-frame time, allocated GPU bytes and feature limits. Lighting/shadow work may use the final `sceneColor/depth/velocity` boundary but does not mark temporal acceptance by itself.
- [ ] **Step 1: Run every adversarial sequence against Off and 1:1 TAA before claiming upscaling readiness.** A newly exposed surface and camera cut must show current color immediately; thin static geometry should have lower temporal variance without unacceptable trail length. Repeat with 0.67 Upscale against 0.67 spatial and 1.0 full-res references on closed/open scenes. Keep the raw captures and metric script with the dossier.
```text
Sequences: wire-static-16, pan-16, moving-cube-16, door-open-4,
cut-2, teleport-2, projection/resize/view-switch, UI+alpha.
Modes: Off, TAA 1.0, Upscale 0.67; visibility: Direct, Frustum, Occlusion.
```
- [ ] **Step 2: Run Debug/Release tests, validation, shader reload, strict docs, software GPU and native CI.** `cmake --build --preset linux-debug --parallel 4`, `ctest --preset linux-debug --output-on-failure`, `cmake --build --preset linux-release --parallel 4`, `ctest --test-dir build/linux-release --output-on-failure`, and strict MkDocs must pass. Run the selected SwiftShader ICD with temporal GPU tests and relocated exports. If a physical Windows GPU is unavailable, say so.
- [ ] **Step 3: Record matched profiling and image metrics.** Use the same scene/camera path and resolution for Off/TAA/Upscale. Report pass GPU ms, full GPU ms, CPU extraction/readback, and live allocation, with raw samples and p50/p95. Report quality metrics on fixed ROIs and representative frames, plus captures for moving thin geometry, disocclusion, UI and transparency; do not turn fixture-specific results into a universal speed/quality claim.
- [ ] **Step 4: Review the final diff independently and close load-bearing findings.** Confirm HZB correctness, history invalidation, shader package/reload atomicity, Direct/P2 equivalence and UI sharpness; run `graphify update .` after final code edits, then update PLAN/Manual/architecture and evidence links to match actual tested scope.
- [ ] **Step 5: Commit and publish only verified work.** `git add PLAN.md docs/IMPLEMENTATION.md docs/ARCHITECTURE.md docs/manual docs/validation/p3-temporal-2026-09-24 tests && git commit -m "Validate P3 temporal reconstruction"`; publish checkpoint and final commits to the authorized remotes once local verification and CI are green.
@@ -0,0 +1,74 @@
# P1 gameplay iteration design
This design closes [PLAN P1](../../../PLAN.md#p1-lua-и-скорость-итераций) for Linux and Windows desktop 2D/3D authoring. It improves the path from changing gameplay or a scene to seeing the result in the Player. C++ remains the compiled gameplay language; Lua remains an optional module with development reload. MCP controls the Editor and authoring/build services, not the live Player world.
The Editor's dark visual treatment should be consistent with [the iteration console reference](../../design/p1-iteration-console-reference.png) and [the new-project reference](../../design/p1-project-template-reference.png). These images are visual references only. The behavior, data contracts, platform paths, accessibility and available actions in this specification and `PLAN.md` are the source of truth. The Windows path shown in the project image is illustrative; the real chooser must use each platform's native paths.
## Existing baseline and scope
The optional Lua VM, LuaLS annotations, component schemas, Inspector fields, atomic development reload and rollback already exist. `BuildService` already serializes GUI/MCP builds, uses incremental CMake/Ninja native trees separated by Debug and Release, publishes immutable successful generations, and preserves the last good generation after failure. The authoring service atomically journals every transaction under `.faset/recovery` and refuses to save over an externally changed scene. A disposable workflow script measures one changed C++ build and first Player frame. These remain the foundation, not new work to replace.
P1 adds verifiable reuse of unchanged schema/build packages, useful compiler diagnostics, convenient new-project choices and code navigation, autosave of named scenes, and repeatable iteration measurements. It also closes the recorded Lua validation gap on Windows and in a graphical Release export. P1 does not add C++ hot reload, dynamic gameplay loading, Lua state preservation across reload, Player-world MCP access, a built-in code editor, or broad performance guarantees. Dynamic gameplay loading is reconsidered only if measured native link time is a material bottleneck.
## User workflows
### C++ edit, build and Play
The developer edits `Scripts/Gameplay.cpp` or a header, presses **Build**, and sees a named job with phase, progress, elapsed time and diagnostics. A compiler error shows severity, a short message, project-relative file and one-based line/column when the tool supplies them. Selecting it opens the source at that location in the configured external editor. Raw compiler output remains available. A failed build leaves the prior Player/schema generation intact and marks Inspector metadata stale. An unchanged second build reports a cache hit and reuses the same validated generation. **Play** still captures the current authoring scene, invokes the build service, then launches an isolated Player; editing after capture does not silently change that snapshot.
### Lua edit and reload
A Lua project declares scripts in `project.faset.json` and can start with no project C++ files. Script edits mark schema metadata stale when declarations change; **Refresh Lua** validates schemas without recompiling unrelated C++ gameplay. The running development Player watches or explicitly reloads scripts; successful reload resets its world/script state, while an invalid edit preserves the old running version. The Editor and MCP surface parseable diagnostics with source locations where available. A packaged Release Player includes only the declared Lua snapshot and needs neither `slangc`, LuaLS nor the Editor.
### New project and source navigation
The new-project chooser offers four clear combinations: C++ 2D, C++ 3D, Lua 2D and Lua 3D. The existing CLI `--new NAME --dimension 2|3` and two-argument `BuildService::scaffold(name, dimension)` retain their C++ scaffold behavior for compatibility. The chooser and an explicit `--language cpp|lua` select a runnable template that adds a minimal start scene and documented starter behavior for the chosen dimension. Template creation never overwrites existing user files and rejects nonempty conflicting destinations. Lua starters include the module declaration and language-server setup without a C++ gameplay stub. The source browser opens C++, headers and Lua under `Scripts`; diagnostic rows open the exact project source line/column when supported by the configured editor.
`faset_source_open` accepts a project-relative path and optional one-based line/column, validates that the target is a regular `.cpp`, `.hpp`, `.h` or `.lua` file beneath `Scripts`, and launches an argv array without a shell. It rejects traversal, symlinks escaping the project, directories and executable files. The existing `faset_script_open` Lua-only command remains a compatibility alias. Editor configuration supports `{file}`, `{line}`, `{column}` and `{project}` tokens. The default Zed invocation uses its `path:line:column` syntax; custom commands can substitute the tokens. Missing editor executables yield a concise actionable error and never make a build fail.
### Autosave
Autosave is enabled by default for named, dirty scene documents while a persistent Editor session is running, including headless MCP sessions. It waits 2 seconds after the last document revision change and saves through `AuthoringService::save`; rapid edits coalesce. A project-level `editor.autosave` Boolean in `project.faset.json` controls it, with a visible toggle in Project settings and a read-only `faset_autosave_status` command. Changing the toggle takes effect immediately in the current session and persists for the next open. `Ctrl+S` and `faset_document_save` remain immediate explicit saves. An unnamed scene is never assigned an implicit path: the UI says **Save As required**, and the existing recovery journal protects its edits.
Autosave carries the document revision it observed into an optional `expected_revision` on the save API. A concurrent edit makes that save fail with `revision.conflict`, after which the newer revision is scheduled normally. Existing `disk_hash` comparison still rejects external modifications; autosave never silently overwrites them. A failed autosave leaves the document dirty and its journal intact, shows a persistent conflict/error with Save As or reload guidance, and does not log the same failure every frame. Another edit or an explicit retry permits a new attempt. Saving creates no Undo operation; Undo/Redo remain valid after autosave. Scene snapshots already captured for Play/export remain immutable. The recovery journal remains active even when autosave is disabled or before its timer expires.
## Build cache and schema correctness
The native build always runs CMake configure and Ninja/selected generator build. Those tools own source/header dependency analysis; a shortcut based only on `Session::source_signature()` is unsafe because that signature covers project `Scripts` but not engine sources, shaders, CMake recipes or toolchain changes. A no-op native build should do no compile or link work, but the UI must not call it a native cache hit merely because the later schema/package stage was reused.
After a successful native build, `BuildService` computes a versioned package key from the selected configuration, normalized build recipe and configure arguments, `CMakeCache.txt` and toolchain identity, complete project `Scripts` content snapshot, Lua declaration/fingerprint, Player and SchemaExporter hashes, all required SPIR-V/reflection hashes, and copied runtime-library hashes. The toolchain identity includes resolved compiler/CMake/Slang executable identity or content hash; changing a toolchain in place must force the native tree to be reconfigured/rebuilt or make the service refuse reuse. Asset source files are not gameplay-build inputs.
If the key matches the last successful immutable build, and every required file passes the stored manifest hash and schema validation, the service returns that generation with explicit `schema_cache_hit=true` and `generation_reused=true`. It skips SchemaExporter and package copying. A missing, malformed or corrupt cached file is never returned as a hit; the service attempts a fresh candidate or fails while retaining the prior pointer. A changed key runs SchemaExporter against the same captured Lua source snapshot, validates the complete schema, stages all files, rechecks the project source snapshot, then atomically publishes the new generation and pointer. Do not publish a mixed snapshot if any `Scripts` source changes during the build. Debug and Release have distinct keys and native trees. Export always revalidates referenced assets and packages their current generations even if gameplay build reuse succeeds; a stale asset must block export until reimport.
`JobStatus` and `faset_job` expose the reused-generation flag, schema hit flag, elapsed phase times and final build fingerprint. Existing `result.directory`, `result.player`, `result.schema` and the last-good pointer remain compatible. Cache results must be understandable in the GUI and MCP without parsing logs.
## Diagnostics and UI behavior
The build service keeps its bounded raw log and adds a structured `diagnostics` array: `{severity, phase, message, file?, line?, column?, code?}`. The parser handles Clang/clang-cl and Lua source-location formats on Linux and Windows, including drive letters and Unicode paths. Project files are normalized to project-relative paths; outside-project diagnostics remain visible as text but cannot be passed to `faset_source_open`. ANSI escape sequences do not contaminate messages. Unrecognized output stays in the raw log; nonzero exit without a parsed error still creates a generic job failure rather than an empty error panel. Multiline notes remain associated with the triggering error where practical.
The Console shows current and recent build jobs, counts by severity, phase, elapsed time, the first actionable error and expandable raw output. Selecting a diagnostic invokes the same `faset_source_open` command available to MCP. Disabled source actions explain why a location cannot be opened. The status bar distinguishes **Saved**, **Pending autosave**, **Saving**, **Save conflict**, **Save failed** and **Save As required**. Controls remain keyboard reachable, compact, high contrast and consistent with the Editor's dark theme. Layout and copy may evolve from the image reference as the actual controls are implemented.
## Measurement and validation
`tools/measure_workflows.py` retains disposable projects and machine-readable raw results. It records cold configure/build, warm unchanged build, changed C++ source/header build, build failure/recovery, Play-to-first-rendered-frame, Lua edit-to-successful-reload, and repeated Editor event-to-visible-state latency. Run at least five repetitions after a stated warm-up for warm/changed cases; retain each sample plus median and nearest-rank p95. Record exact revision and dirty state, OS, CPU, RAM, GPU/driver, compiler, CMake, Slang, build configuration, project/source hashes, scene size and whether validation/readback were enabled. Keep cold-start samples separate from warm samples; label offscreen first frame and windowed first presented frame separately using the Player's existing `startup_ms.main_to_first_frame` and `presentation_mode` profile fields. Include a larger generated scene/content case alongside the checked-in 2D/3D examples. Measurements are observations, not flaky CI timing gates.
For the documented Linux reference host and exact two sample scenes, track the existing budgets: measured frame p95 ≤ 4 ms, GPU and readback p95 ≤ 1 ms each, simulation and snapshot p95 ≤ 0.5 ms each, explicit live Vulkan allocations ≤ 20 MiB, and startup from `main()` ≤ 500 ms. Run a 3,000-frame warm resource-lifecycle sample to check for growth. Report any missed budget honestly and investigate; do not claim the limits for other machines or content. Record a separate available Windows baseline, distinguishing software Vulkan from physical GPU. If physical Windows hardware is unavailable, mark that coverage unverified rather than blocking the functional Windows CI result.
The Lua module must pass Linux and Windows native CPU suites; the watched/explicit reload integration must run under an available Vulkan device on both CI platforms; and a Lua-only Release export must validate and render after relocation with source project paths unavailable. Both Linux and Windows CI report exact test outcomes. Update the English Manual with C++/Lua iteration recipes, template selection, source editor setup, cache indicators, autosave/conflict recovery, and diagnostic navigation. Update `PLAN.md`, `docs/IMPLEMENTATION.md` and a dated validation dossier only after checks and measured evidence exist.
## Acceptance matrix
| Area | Required evidence |
| --- | --- |
| Lua | Existing lifecycle/safety/CLI suites pass on Linux and Windows; watched/explicit reload and relocated Lua-only Release export render on available Vulkan implementations. |
| Cache | Second unchanged build reuses the same verified schema/package generation and does not invoke SchemaExporter; header, Lua, recipe/toolchain, shader or runtime output changes invalidate appropriately; corrupt entries never become hits. |
| Rollback | Failed compile/schema/copy and source-race cases leave the previous successful pointer and Inspector schema available but stale; export still rejects stale assets. |
| Diagnostics | Clang, clang-cl, Lua, Unicode path, drive-letter and unparseable-output fixtures pass; real C++ compile failure produces a navigable diagnostic and raw log. |
| Templates/navigation | All four new-project combinations launch or validate, old CLI/scaffold defaults still work, existing files are not overwritten, source-open rejects traversal and opens project code at the requested position. |
| Autosave | Named scene saves after idle, rapid edits coalesce, unnamed scene remains in recovery, disk/revision conflicts never overwrite, Undo and Play snapshot remain stable; GUI and MCP show status. |
| Iteration | Raw repeated cold/warm/changed/Play/Lua/UI samples and environment metadata are committed; claims are limited to measured scenes/hosts; reference budgets and any misses are shown. |
| Documentation | Strict MkDocs build and compiled tutorial tests pass; Manual, `PLAN.md` and validation dossier describe observed behavior and known coverage gaps. |
## Delivery sequence
Deliver safe cache keys and source snapshots first, then diagnostics/navigation, templates and autosave as independently testable slices. Finish with cross-platform Lua/export validation, repeated measurements, documentation and independent review. Each slice uses a failing contract test before implementation and a focused passing suite before its commit. The final P1 status is only marked complete when the acceptance matrix has linked evidence; unavailable physical Windows GPU or benchmark hardware is recorded as a coverage limit, not silently treated as passed.
@@ -0,0 +1,51 @@
# P3 lighting and shadows design
This design implements the lighting and shadow portion of [PLAN P3](../../../PLAN.md#p3-освещение-тени-и-temporal-reconstruction) for desktop 3D games. Temporal reconstruction has a separate design and implementation plan. The P2 Direct, GPU frustum, and GPU occlusion paths must shade the same scene from the same immutable snapshot. Ordered sprites, editor UI, and 2D presentation remain unlit unless a later 2D-lighting project explicitly changes them.
## Existing behavior and target
`Snapshot` currently carries one `light_direction`; `SceneView::build` overwrites it for each `faset.light`, without using the authored color or intensity. `fragmentMain` evaluates one hard-coded directional BRDF and samples one 1024² D32 shadow map. Its shadow matrix covers a fixed world-origin orthographic box. The shadow pass renders all `cast_shadow` meshes through CPU-transformed geometry even when P2 GPU visibility is selected; it is independent of the camera visibility decision. The compiled `gpuShadowMain` is not used by the current pass. These facts make multiple authored lights, large camera movement, and explicit shadow budgets impossible without changing the scene and shader contracts.
P3 supplies one sun plus point and spot lights, camera-fitted cascaded sun shadows, a bounded local shadow atlas, and independent per-shadow-view caster selection. Authored values reach exported Player games and the editor preview through the existing scene schema. A scene with no light component retains the previous default sun appearance. Lighting is accumulated in linear space before the current tone mapping; the legacy directional factor is preserved for the default so existing unlit UI and basic rendering tests remain meaningful. Artist-facing intensity is unitless in this stage; photometric units and IES profiles are outside P3.
## Scene contract and validation
Keep `faset.light` at builtin schema version 1 and add optional fields with defaults: `kind` (`directional`, `point`, `spot`, default `directional`), `enabled` (true), `color` (white), `intensity` (1, nonnegative), `range` (10, positive, for local lights), `inner_angle` (0.35 radians), `outer_angle` (0.70 radians, strictly below π/2), `casts_shadow` (true), and `shadow_priority` (integer 0). Existing fields and component IDs remain valid. Cross-field validation requires `0 <= inner_angle <= outer_angle`; local range, color channels, intensity, transforms, and cone directions must be finite. Invalid authored values return a diagnostic with entity and field rather than nonfinite GPU data. Missing new fields use the schema defaults.
`Snapshot` retains `light_direction` for existing direct-render clients. Add an optional `SunLight`, a vector of `LocalLight`, and an optional explicit `CameraFrustum` containing unjittered view/projection matrices, near/far distances, and projection kind. `SceneView` populates these from the authoring scene. Each light carries the stable entity/component identity, transformed position or normalized direction, color, intensity, range, cone angles, and shadow options. The first enabled directional light by stable ID is the sun; extra directionals produce a visible diagnostic until a later multi-sun design exists. If there is no authored directional light, the renderer synthesizes the legacy sun from `Snapshot::light_direction`. Local lights are ordered by stable ID to prevent reordering from changing atlas allocation or results. Point attenuation tends smoothly to zero at `range`; a spot multiplies it by a smooth inner-to-outer cone factor. The shader handles zero distance and invalid normals without NaN output.
Explicit camera frustum data is required for four cascades. If a low-level caller supplies only the legacy `view_projection`, the renderer uses one bounded legacy-compatible sun shadow view and reports `effective_sun_cascades = 1`; it never silently claims CSM. 2D sprite-only snapshots do not incur shadow work. The future temporal stage may jitter the main raster projection, but the shadow planner consumes the unjittered `CameraFrustum` exclusively.
## Graphics shader ABI and ownership
Preserve material descriptor **set 0** and its four bindings: sampled sun depth image at binding 0 (formerly the single shadow map), shadow sampler at 1, color image at 2, color sampler at 3. Add frame lighting descriptor **set 1** with a `StructuredBuffer` of a versioned, 16-byte-lane lighting header and local-light records plus one sampled 2D local depth atlas. Both the Direct pipeline and P2 GPU graphics pipeline bind this frame set; their fragment stage remains `fragmentMain` in `shaders/baseline.slang`. Move the P2 GPU vertex scene bindings (instance, visible-ID, view buffers) from set 1 to **set 2** in `shaders/gpu_scene.slang`, the Vulkan graphics layout, and the exact reflection validator. P2 cull and HZB compute bindings stay in their current set 0. Baseline graphics push constants remain 96 bytes and P2 graphics push constants remain 112 bytes, under the Vulkan 1.3 guaranteed 128-byte minimum. The shadow vertex entry can continue to read a per-view matrix through the existing push block, so no new shader entry is required for atlas rasterization.
Host and Slang structs have named offsets, strides, static assertions, and reflection checks. Keep the SPIR-V + normalized Faset reflection package as the runtime boundary; `slangc` remains a build dependency, not a Player dependency. Existing shader-reload behavior must preserve working pipelines when an incompatible or invalid bundle is offered. Update the package validator and export checks whenever an entry or layout changes. Lighting buffers are renderer-owned per frame and never expose Vulkan types to authoring, gameplay, or MCP.
## Sun cascades and atlas
Use four practical-split cascades between the camera near plane and `min(camera far, 80 world units)` with λ = 0.5. Bound each receiver frustum slice by a stable square extent derived from its enclosing sphere in sun-light space; snap the XY projection center to its effective texel grid so small camera movement does not shimmer. The sun atlas is one 2048² D32 image with four 1024² tiles, including a two-texel guard inside each tile. Its C++/shader metadata contains each light VP, split end, atlas scale/offset, effective UV clamp, and bias. Select the cascade by unjittered view depth and blend over the final 5% of a split to avoid a hard line. Three-by-three PCF samples clamp to that tile's guarded interior. Atlas rendering clears each tile independently and samples it only after the whole atlas transitions from depth attachment to depth read-only. The split/fitting, atlas, blend, PCF guard, and depth-bias considerations follow Microsoft's [CSM technical article](https://learn.microsoft.com/en-us/windows/win32/dxtecharts/cascaded-shadow-maps) and [shadow depth-map guidance](https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/DxTechArts/common-techniques-to-improve-shadow-depth-maps.md); Faset's actual implementation remains Vulkan-native.
The receiver frustum determines a cascade's XY footprint, but casters upstream of the camera slice still matter. Build shadow-view visibility from world-space caster bounds, never the main camera's P2 visible IDs. Extend the light-space depth interval conservatively over all cast-shadow bounds overlapping the cascade's XY footprint, then test candidates against that resulting view. Do not truncate this interval by the main camera far plane. Shadow geometry uses source LOD 0 initially, independent of the main camera's selected mesh LOD; a measured shadow-LOD policy can follow later.
## Local shadows and budgets
The local atlas is a separate 2048² D32 image with sixteen 512² guarded tiles. A spot light uses one perspective shadow view; a point light uses six 90° face views and receives all six tiles or none. A shadow tile stores its owner stable ID/face and generation. Rank candidate shadowed lights by authored `shadow_priority`, then projected influence, then stable ID; keep allocations stable while their owners remain eligible. Lower-priority lights that do not fit still illuminate the scene without shadowing, with an explicit reason/counter. No partial point-light shadow cubemap is allowed.
At most 128 local lights are submitted to the shader per view; select them deterministically by priority/influence if more exist and report the count of omitted lights. At most 16 local shadow faces and 4096 caster draws per frame are scheduled. Shadow views are atomic for draw budgeting: if all casters for a view do not fit, skip that view and treat its contribution as unshadowed. Sun cascades are considered nearest first; local views follow by priority. The policy exposes requested and effective counts. If the nearest cascade itself exceeds the draw budget, it becomes unshadowed rather than emitting an incomplete shadow. No skipped tile is sampled. Atlas allocation is capped at 32 MiB of D32 image payload for both atlases, excluding Vulkan alignment/driver overhead; actual allocation bytes remain visible in `FrameStats`.
Check D32 sampled/depth-attachment support, 2048² image extent, relevant sampled-image/storage-buffer limits, and allocation results before enabling the corresponding shadow feature. If the 2048² profile is unavailable, try a 1024² profile with four 512² sun tiles and sixteen 256² local tiles. If neither profile is available, keep direct lighting and explicitly report shadows unavailable. A failed optional atlas must not silently disable local illumination or crash a scene that previously rendered. Maintain explicit Vulkan transitions and barriers; the current `RenderGraph` validates ordering but does not synthesize barriers. One `ShadowAtlases` graph pass may loop over tile rendering instances because graph pass names must be unique.
Every scheduled view records why it was redrawn (first use, light/camera/caster change, atlas reassignment, or conservative every-frame update), its caster count, tile, and GPU time. Caching is optional in this stage: reporting an every-frame update is preferable to reusing stale depth. If caching is introduced, an uncertain caster revision forces a redraw. Camera cuts, resolution changes, light deletion, and slot reuse invalidate relevant assignments and histories.
## Forward+ measurement gate and diagnostics
Start with a correct bounded loop over local lights in the fragment shader. Benchmark fixed 1080p scenes with 0, 4, 16, 32, 64, and 128 visible local lights in Release, with shadows separately disabled and enabled. Retain raw per-frame CSV, driver/GPU/commit/configuration, warm-up and three repeated runs, median and p95 of main raster, shadow passes, full GPU command span, and capture/readback CPU time. The current full framebuffer readback is synchronous, so its CPU cost is not attributed to light shading. If 32 or more lights add at least 1.0 ms median to main raster or at least 15% of the light-free GPU frame on the Linux reference GPU, add tiled Forward+; otherwise record why the simple path remains default and keep the measurement harness for later hardware. A conservative, depth-free 16×16 screen-tile list built from projected light volumes is sufficient for the first measured optimization; it does not require a depth prepass before the existing forward pass. No light may disappear on list overflow: the fragment path checks an overflow flag and scans the full submitted-light list for that tile. Measure the sum of list construction and raster, not the raster pass alone, before enabling tiled mode by default.
`FrameStats`, Player diagnostics, and the editor debug overlay expose requested/effective sun cascades, visible and omitted local lights, scheduled/dropped shadow faces and reasons, atlas occupancy and allocated bytes, caster draws, shadow GPU time, and the active lighting path. Counters remain diagnostic-only; they do not require GPU readback for scheduling. Shadow atlas thumbnails may be added to developer diagnostics but are not a prerequisite for functional lighting.
## Acceptance and integration boundaries
CPU tests cover schema defaults/invalid fields, stable extraction from reordered entities, shadow-view math, cascade split/texel snap, offscreen casters, deterministic atlas allocation and face/draw capacity, point-light all-or-none behavior, and fallback capability policy. GPU image tests cover point distance, spot cone, color/intensity, sun cascade boundaries, caster movement, camera translation/cut/resize, point seams and atlas tile isolation, overflow as unshadowed light, and Direct vs P2 frustum/occlusion equivalence. Shader reflection, hot reload, exported Player shader contents, and Vulkan validation are exercised on Linux physical GPU and pinned Linux SwiftShader; Windows native build, GPU-labeled CI tests, and relocated exported Player examples run on pinned Windows SwiftShader. A physical Windows GPU is reported only if actually tested. The manual documents light properties, budgets, expected fallbacks, and script/editor examples. Acceptance records commit, hardware/driver, raw benchmark data, tests, known limitations, and links from `PLAN.md` without claiming universal speedups.
Temporal reconstruction owns previous transforms, velocity targets, jitter, history rejection, TAA, and upscaling. Lighting owns only stable unjittered shadow views and direct-light data. The shared files `renderer.hpp`, `renderer.cpp`, and `baseline.slang` need sequenced integration or an agreed ABI commit before concurrent feature work; temporal code must not repurpose the lighting descriptors or shadow camera metadata.
@@ -0,0 +1,59 @@
# P3 temporal reconstruction design
This design implements the temporal half of [PLAN P3](../../../PLAN.md#p3-освещение-тени-и-temporal-reconstruction) for Linux and Windows desktop games. The outcome is a selectable, observable 1:1 TAA path followed by a lower-internal-resolution temporal upscaler. The existing Direct renderer without temporal processing remains the reference and the default. P3 lighting and shadow work has its own design and can change the scene shader's lighting inputs without changing the temporal pass contract.
Temporal renderer and shader integration starts **after** the lighting plan's Task 2 checkpoint commit, `Share typed multi-light shading across Direct and GPU paths`. That commit establishes material descriptor set 0, frame-lighting set 1, and GPU graphics scene set 2; GPU cull/HZB compute descriptors remain in set 0. Temporal graphics variants consume the same material and lighting sets as `fragmentMain`, add prior-transform reads only from the GPU scene set 2, and leave the 96-byte Direct and 112-byte GPU graphics push constants intact. Temporal resolve/composite use their own checked pipeline layouts. The shared reflection and hot-reload validator must accept the combined ABI, not reconstruct the pre-lighting set 1 layout.
## Boundaries and modes
`RendererConfig` selects `TemporalMode::Off`, `TAA`, or `Upscale`; `Upscale` also specifies an internal render scale in `[0.5, 1)`. TAA uses scale `1`. A runtime setter lets Editor and Player select the same modes without changing scene authoring files. Requested and effective modes are recorded separately: unsupported compute/format/extent capabilities produce an explicit `Off` fallback and a readable reason. Invalid scale is rejected as configuration input, rather than silently clamped. An exported Player contains cooked SPIR-V and reflection; Slang is not needed at runtime. Temporal processing is renderer-side and adds no live-game MCP endpoint.
The output extent remains `Renderer::width()/height()`, the swapchain/capture extent and the pixel coordinate system of UI. At scale below one, opaque and world-space raster targets use an internal extent, with integer rounding made deterministic. `Snapshot::scene_rect` remains in output pixels; it is converted once to the internal viewport. Camera aspect and projection remain those of the output scene rectangle. Shadow map dimensions and light-space matrices never depend on render scale. HZB dimensions, viewport metadata, culling projection, and post-cull depth refer to the actual internal scene target.
## Frame data and history ownership
The caller supplies an **unjittered** `Snapshot::view_projection` and `projection`. Picking, gizmos, and authoring coordinates continue using them. The renderer chooses a deterministic Halton(2,3) subpixel offset, applies it only to the scene raster clip transform, and retains both current and previous jittered view-projection matrices. The offset is measured in internal scene pixels and enters clip space as `clip.xy += 2 * jitter / internalViewportSize * clip.w`; the sign is verified by an image/motion test for the Vulkan viewport convention. Culling and HZB projections must match the jittered geometry they test, with conservative edge handling. `Snapshot::camera_cut` is authoritative; a large discontinuity in camera position/orientation also rejects history when a caller fails to mark a teleport.
`InstanceTracker` already records `previous_model`, world bounds, a stable slot/generation, mesh identity and rendered-frame completion. That state becomes available to both Direct and GPU paths. HZB history and temporal color history have **independent** validity decisions; absence of HZB or a Direct visibility mode cannot invalidate an otherwise valid TAA history. Mesh/LOD replacement, reused slot, an anonymous draw, camera cut and changed view make that instance's velocity invalid. GPU `InstanceRecord` gains a previous model matrix while keeping the existing stable slot/generation lanes. The `metadata.x` low bit remains the HZB eligibility bit and a distinct bit marks temporal previous-transform eligibility; culling tests only the HZB bit. Dense candidate indices remain frame-local addresses, not temporal identities.
Temporal color/depth history is renderer-owned and double-buffered at **output resolution**. Each completed render commits one frame. A pending failed render must not promote history. The current active view may reuse history only if the previous frame has the same `view_id`, output/internal extents, scene rectangle, unjittered projection, temporal mode/scale, and compatible shader generation, and is not a cut. Switching among views safely resets history; retaining multiple cached histories is not required. Resize, shader reload, mode or scale change, and camera discontinuity reset it explicitly. The first valid frame uses current color only. The reset reason and whether a previous history was actually used are visible in `FrameStats` and Editor diagnostics.
## Motion and reconstruction signal
Both opaque mesh paths write the same per-pixel motion contract:
- Direct path: the CPU-transformed vertex carries current clip position, prior clip position computed from `InstanceUpdate::previous_model` and prior jittered view-projection, plus a validity flag. The existing Off vertex format/shader remains a compatible path.
- GPU path: the vertex shader reads current/previous model matrices from the instance record and current/previous jittered view-projections from the view record. It emits current and prior clip coordinates plus validity. Both Main and Post raster use this contract.
- The scene fragment writes display-UV motion (`currentUV - previousUV`), expected depth in the previous projection, and validity/reactive state to a sampled floating-point MRT. A missing prior transform, invalid clip `w`, nonfinite projection, or non-opaque/reactive fragment marks motion invalid. UI never contributes motion. UVs are local to the scene rectangle so internal and output extents map consistently.
The scene image remains the current renderer's display-referred LDR shading for this stage. History uses a higher-precision floating-point color image so repeated accumulation does not quantize to 8 bits; the final composite writes the existing `R8G8B8A8_UNORM` output exactly once. There is no exposure adaptation or HDR claim in this temporal implementation. P3 lighting may later move scene shading to linear HDR behind the same `sceneColor/depth/velocity -> resolvedColor` boundary.
The signal checklist is informed by [AMD's FSR 2 integration guide](https://github.com/GPUOpen-Effects/FidelityFX-FSR2/blob/master/README.md#input-resources): it describes current color, depth, motion and reactive data, and explicit reset on camera cuts. Faset implements its own resolver and does not link FSR 2. Faset's **current-minus-previous local UV** motion sign and jitter-inclusive convention are its own contract; an FSR 2 adapter would have to convert to that API's documented motion and jitter conventions. [NVIDIA's Adaptive TAA paper](https://research.nvidia.com/publication/2018-08_adaptive-temporal-antialiasing) identifies blur and ghosting as temporal failure modes, which motivates the moving-image acceptance tests here; its adaptive ray-tracing algorithm is not part of this design.
The 1:1 TAA resolve samples current color/depth/velocity, dilates motion from the nearest depth in a 3×3 neighborhood at silhouettes, and computes `previousUV = currentUV - motion`. It rejects history when view history or fragment motion is invalid, coordinates leave the previous scene rectangle, or previous expected depth disagrees with sampled history depth beyond a depth-aware tolerance. Surviving history color is clipped to a current-color 3×3 neighborhood and blended with a bounded weight reduced by motion and reactive content. A newly exposed background pixel must use current color immediately; it may accumulate normally on following frames. The resolve writes color and depth to the next output-resolution history image. Sky/clear pixels use current color when reliable reprojection is unavailable.
`Upscale` reuses this validated resolve but samples current color/depth/velocity from a lower internal extent. It reconstructs the current signal at output-pixel positions, keeps output-resolution history, and uses the same rejection/dilation and first-frame spatial fallback. Switching render scale resets history. TAA must pass its moving-image tests before upscaling is enabled or described as complete; this path is a small, measured temporal upscaler, not a promise of Unreal TSR quality.
## Render graph and scene/UI split
The temporal path adds internal `sceneColor`, `sceneDepth`, and `sceneVelocity` targets while retaining `color` as the full-resolution final image used by Readback and Presentation. Existing Off rendering and its one-target shader remain available. The temporal graph orders passes as follows:
1. ShadowMap and optional P2 MainCull.
2. Main scene raster clears scene color/depth/velocity and renders opaque meshes. Optional P2 BuildCurrentHZB and PostCull run from this scene depth, then PostRaster loads all three scene attachments and finishes deferred opaque meshes.
3. World transparency and sprites render against the same internal scene depth before resolve. Pixels they blend into color mark their velocity/reactive value invalid, so stale opaque history cannot leak through moving translucent content. They do not acquire synthetic rigid-mesh motion. The existing ordering and depth behavior are retained.
4. TemporalResolve reads the completed scene image/depth/velocity and previous history, writes the next color/depth history. TemporalComposite maps that resolved scene to the full-size `color` target.
5. A separate output-resolution UI pass renders quads, text, gizmo overlays and Editor chrome on `color`, without jitter or TAA. Readback and Presentation then consume `color` as before.
Only the temporal-enabled branch needs new scene targets and a second color attachment in opaque pipelines. A one-target UI pipeline remains valid. `RenderGraph` validates ordering by names; Vulkan layout transitions and compute→sampled/attachment barriers are recorded explicitly. The renderer's single queue, fence-per-frame model makes history lifetime simple but its synchronous capture/readback cost remains part of full-frame timings. The present 12-slot GPU timestamp pool must grow enough to time every new pass instead of silently omitting the end of the graph.
## Shader package, controls and fallback
Temporal vertex/fragment, resolve and composite entry points have validated reflection, SPIR-V hash, descriptor types and record strides. Adding `previousModel` changes the GPU instance stride; the C++ static assertion, Slang record, reflection validator and GPU tests change together. Baseline Off shaders retain their existing entry points and layout fingerprints. Shader reload replaces a **complete** compatible temporal pipeline set atomically or preserves the working set; successful reload invalidates color history. BuildService copies every temporal `.spv` and `.reflection.json` into Linux/Windows exports and validates a relocated Player without Slang installed.
Editor diagnostics expose mode, scale, effective mode/fallback, internal extent, jitter, history accepted/reset reason, reject statistics when requested, GPU temporal pass time and memory. Player profiling records these same fields per frame. GPU readback for per-pixel diagnostics is optional and never required for the normal resolve. If temporal resources cannot be created on a supported Vulkan 1.3 device, the actual mode is `Off`, the frame still renders by the existing path, and the reason is reported. A failed temporal shader reload keeps the prior pipelines and does not corrupt history or change the visible mode.
## Acceptance
Deterministic headless GPU sequences run for Direct, GPU frustum and GPU occlusion where supported. They cover static thin diagonals/wires at subpixel positions, slow camera pan, a moving rigid object, a doorway disocclusion, an explicit camera cut and unmarked teleport, projection/view/scene-rectangle/resize/mode/scale changes, anonymous and mesh-replaced instances, world transparency, sprites and pixel-exact UI. Tests compare current frames to `Off` and a high-resolution spatial reference, with fixed tolerance and documented image metrics rather than requiring bit-identical output across drivers. They assert zero Vulkan validation errors where the layer is present, no reappearance of old color after cut/reveal, correct actual mode and reset reason, and no UI softening. A clean fallback is exercised on a deliberately unsupported capability fixture.
Run CPU policy tests, reflection/package/tamper tests, Linux validation on physical GPU and software Vulkan, Windows software Vulkan CI and native builds, and relocated 2D/3D Player captures. Record the exact revision, driver/GPU, build mode, internal/output resolutions, temporal and full-frame GPU/CPU costs, memory, image differences, and limitations. These results gate any claim that TAA/upscaling improves image quality or frame time in a particular scene.
+2
View File
@@ -3,7 +3,9 @@
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.
- [P1 gameplay iteration](p1-iteration-2026-09-24/README.md): repeatable Debug C++/Lua workflow, raw samples, toolchain provenance, real compiler navigation and coverage limits.
- [P2 GPU visibility Linux evidence](p2-gpu-visibility-2026-09-23/README.md): Debug/Release GPU acceptance, lavapipe functional checks, relocated Player exports, and explicit platform/performance limits.
- [P2 pinned SwiftShader compatibility](p2-swiftshader-2026-09-23/README.md): the Windows CI regression, shader capability fix, independent review closure, final native CI and relocated Player evidence.
- [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.
+18 -1
View File
@@ -40,7 +40,7 @@ check. For sanitizers, configure with `-DFASET_SANITIZERS=ON`, build the
`faset_lua_tests`, `faset_lua_safety_tests`, and `faset_schema_exporter` targets, then
run `ctest --test-dir <build> --output-on-failure -R '^lua_'`.
## Not verified here
## Not verified in the September 18 run
- Windows compilation or execution of the new module.
- Graphical/window interaction and real-time Lua reload in a running rendered game.
@@ -53,3 +53,20 @@ run `ctest --test-dir <build> --output-on-failure -R '^lua_'`.
The renderer-linked CPU checks used the existing Vulkan loader, repo-pinned Vulkan
headers and cached Slang, with SDL X11/Wayland disabled. No system graphics packages
were installed. This proves linkage and CPU validation, not graphics compatibility.
## September 24 Release follow-up
The [Release execution record](lua-release-2026-09-24/README.md) closes the
Linux offscreen export gap above. At source commit `4331857`, a Release Editor
exported the checked-in Lua-only sample into a disposable Unicode-path project.
The package was relocated, the source project path was hidden, and its Player
validated and rendered 120 frames on an NVIDIA GeForce RTX 2080 Ti with driver
595.84 and Khronos validation enabled; zero validation errors were reported.
The package manifest has `lua_enabled: true`, declared Lua source and its license,
and no project C++ gameplay source. The five focused Release Lua/schema tests
passed. This verifies an offscreen run on Linux; a separate physical Windows GPU
and manual window interaction have not been tested by this record. Windows
SwiftShader execution is configured in CI and needs a passing run before it is
claimed as covered.
![Relocated Lua sample after 120 frames](lua-release-2026-09-24/lua.png)
@@ -0,0 +1,58 @@
# Lua-only Release game: relocated execution
This is a local Linux result for source revision
`433185717e1f324acb6eaf186818589d11697453` on 2026-09-24. The
[machine-readable report](report.json) records the exact source input hashes,
Release package, Editor binary hash, command arguments and elapsed times. The
Editor itself was built with the `linux-release` preset. The exported Player
and package also identify their configuration as Release.
| Check | Observed result |
|---|---|
| Disposable source | `examples/lua` copied to a Unicode-path temporary project outside the engine tree |
| Package | `lua_enabled: true`; 32 hash-checked files, including Lua gameplay source and `Notices/lua/LICENSE.txt`; no project `Gameplay.cpp` or `.hpp` |
| Relocation | Generation copied outside the project/engine; original project path hidden before Player launch |
| CPU validation | Packaged Player `--validate` succeeded from an unrelated empty working directory |
| GPU execution | 120/120 headless 1280×720 frames, 0 validation errors |
| Device | NVIDIA GeForce RTX 2080 Ti; proprietary driver 595.84, Vulkan 1.4.329; selected-device details in the report and [Vulkan probe](vulkaninfo-summary.txt) |
| Image | Four expected scene colors, including cyan player and gold collectible; raw [PPM](lua.ppm) SHA-256 `e2f4c3b84de75dadd2e31d388d0ba51abc1861f1ec76eb7ba51a4a2dcac720a7` |
| Focused Release CTest | 5/5: `lua_contracts`, `lua_safety_contracts`, `lua_cli_contracts`, `lua_player_reload`, `build_schema_publication` |
The [manifest](lua-manifest.json) binds the package file checksums. The
[Player profile](lua-profile.json) contains all 120 per-frame samples and
resource/timing definitions; the [validate](lua-validate.json),
[render](lua-run.json) and [export](lua-export.json) logs retain raw process
results. The [PNG preview](lua.png) is a lossless conversion of the checked
PPM.
The verifier also confirmed a clean Git source tree, rejected undeclared
package files and unexpected C++ script files, classified the selected device
as physical, and found the sample Lua `on_start` log in the relocated run.
To repeat this check from a built checkout, choose a **new, empty** output
directory:
```sh
cmake --preset linux-release
cmake --build --preset linux-release --target faset_editor --parallel 4
python3 tools/verify_playable_exports.py --editor build/linux-release/faset_editor \
--output .cache/lua-release-check --only-lua
cmake --build --preset linux-release --target faset_player faset_schema_exporter \
faset_lua_tests faset_lua_safety_tests faset_build_schema_tests --parallel 4
ctest --test-dir build/linux-release --no-tests=error --output-on-failure \
-R '^(lua_contracts|lua_safety_contracts|lua_cli_contracts|lua_player_reload|build_schema_publication)$'
```
The render used the Player's synthetic fixed timestep and offscreen Vulkan
presentation. It does not establish manual window interaction or physical
Windows GPU compatibility. The separate
[Windows graphics run at `4ac02ee`](https://github.com/emil28092005/Faset_Engine/actions/runs/35937433040)
passed 61/61 CTests and relocated all three Release games under pinned SwiftShader.
The Lua-only package validated and completed 120 offscreen frames with its source
project paths hidden. Its device is a **software** Vulkan implementation; the
Khronos validation layer was not active on that Windows worker. The
[P1 dossier's Windows report](../p1-iteration-2026-09-24/windows-4ac02ee/playable-report.json)
retains the source/package identities and result. This does not establish
physical Windows GPU compatibility.
![Lua game rendered by the relocated Release Player](lua.png)
File diff suppressed because one or more lines are too long
@@ -0,0 +1,220 @@
{
"asset_generations": {},
"build_fingerprint": "b745490f85f05987d849f6bb6f4d06953883f4b8e10c5551fadf164c7eec5857",
"configuration": "Release",
"executable": "faset_player",
"files": [
{
"path": "README.txt",
"sha256": "82bf72bc542643f6c4090e0a5de69b0014ca3fa17c6f9dfb12cc41e44c8b8db6",
"size": 262
},
{
"path": "Notices/Faset-NOTICE.txt",
"sha256": "30c2e7e80f5153b31ab57675e07246aef9165965d64e36022b426a26449ece3f",
"size": 204
},
{
"path": "Notices/dependencies.json",
"sha256": "262e8f63cf21c516829b1fedb0900ac90e76d62aa8f78d9ad02d8f24e0359e74",
"size": 2468
},
{
"path": "Notices/lua/LICENSE.txt",
"sha256": "e42d195c77d3ad7255ee9c874772d3c53998e7f60f9acf453d4630fa161e0bfe",
"size": 1126
},
{
"path": "Notices/stb/LICENSE",
"sha256": "bebfe904b14301657e4e5d655c811d51fd31b97c455b9cc2d8600d6bac6cff63",
"size": 2510
},
{
"path": "Notices/json/LICENSE.MIT",
"sha256": "46a65cffd1ea955132d95a8dd921640714a8d6b537d2e4e482d31145ae95b603",
"size": 1076
},
{
"path": "Notices/box3d/LICENSE",
"sha256": "da5e31a26bf3cfd5ba5c96d6823e480128c81e76c107ef9d3ee5d94789184b90",
"size": 1067
},
{
"path": "Notices/box2d/LICENSE",
"sha256": "68a3e676d7e94093b102d5cba0d4e04af812040d6f230c3db67a6664574e43d2",
"size": 1067
},
{
"path": "Notices/entt/LICENSE",
"sha256": "0785027ce472d7c61f05fba664a1d3ba6639c1593ced351ad9e4bed868765d99",
"size": 1097
},
{
"path": "Notices/sdl3/LICENSE.txt",
"sha256": "97f35b302b361680ec1e891e95d2d52097bb95abff361434916d99dc1305f127",
"size": 884
},
{
"path": "shaders/gpuPostCullMain.reflection.json",
"sha256": "eed140bbe09c7d14935b688c770261ac0e81677c0974aa811c88ed67ea779b95",
"size": 3591
},
{
"path": "shaders/gpuHzbMain.reflection.json",
"sha256": "589818f670881c64f3aba726ee3bc82fe4b9d835a347f99134e0e157dc4a112e",
"size": 2050
},
{
"path": "shaders/gpuCullMain.reflection.json",
"sha256": "996fe5c7e31c6b8f316d8f290b3be0ae7063eb46c500a661e8715e9b822a2598",
"size": 3587
},
{
"path": "shaders/gpuShadowMain.reflection.json",
"sha256": "9a03a2b692d2b0c758f63fb27760352cfef20a068372ebc137f1ca1797dcd3c2",
"size": 2755
},
{
"path": "shaders/gpuVertexMain.reflection.json",
"sha256": "9f43472c267dbb6a2ddc69e252f7f00c5ca46f376d3c7c27165c8d12a175179d",
"size": 3098
},
{
"path": "shaders/gpuPostCullMain.spv",
"sha256": "62cb72e356346e1511c6d640a3cecb4a6e5283795338299b357edae5f7f52be4",
"size": 12460
},
{
"path": "shaders/gpuHzbMain.spv",
"sha256": "08682362ee2120ed19d260e613285071747c3e436bb09bb6238e46f9cedc2308",
"size": 4140
},
{
"path": "shaders/gpuCullMain.spv",
"sha256": "7691360ec55f41d40d43652f506b051cef8b3a3ddcdcc7db61a7d4d97f9e87d8",
"size": 16180
},
{
"path": "shaders/gpuShadowMain.spv",
"sha256": "ffcdb91e70c41ce9452094865546a009b9203140cf8eeb3ad1b4e5868d82b815",
"size": 2404
},
{
"path": "shaders/gpuVertexMain.spv",
"sha256": "4647ef4fe1a68ab623ee9c19dac56631907f825cba0becd30869c5221c2a0385",
"size": 4928
},
{
"path": "shaders/shadowMain.reflection.json",
"sha256": "e3fd0889cd2f569d410f5d7558a4091d9f57f247a1dcf1788462f67525340819",
"size": 2645
},
{
"path": "shaders/fragmentMain.reflection.json",
"sha256": "72ebf8d32452830650416dc4717d89a4324e97ece7028dfb97c66803c8dce71f",
"size": 2649
},
{
"path": "shaders/vertexMain.reflection.json",
"sha256": "c584f3b957ebf194a292451362a98590f85ff3cfbfd8f917b1c1265039381777",
"size": 2639
},
{
"path": "shaders/shadowMain.spv",
"sha256": "7643b4d688492b5ee923b9606f0b0e70343ca05fa67673838206119a6f86d8d8",
"size": 784
},
{
"path": "shaders/fragmentMain.spv",
"sha256": "f5c4f289917ecab5d00d543c053aac1d10e6cca311fd172daf06d315160a3505",
"size": 7436
},
{
"path": "shaders/vertexMain.spv",
"sha256": "1ad2631c35d654f48166321ae43d4165043e1b919b14f1dc61caf97b1ada0898",
"size": 1224
},
{
"path": "faset_player",
"sha256": "99d7b8286a9151ec4eff9a9899e862d987048b031e1e40ca1938646afbde37bd",
"size": 6329192
},
{
"path": "project.faset.json",
"sha256": "e07b12047f8141c3c4cc8f0e7d0905a5c635375e4494755e34191db0859ab86f",
"size": 175
},
{
"path": "Scripts/util/motion.lua",
"sha256": "bad3dcdc1081fd1dec589c12dad8dc175b739d77ea52b957ead9ecb4b7cb88a3",
"size": 150
},
{
"path": "Scripts/player.lua",
"sha256": "1c51810574a3413c46a7cbd0ea2e84a9b802da1206a2b72bb78f6cc951367a11",
"size": 1376
},
{
"path": "Scripts/beacon.lua",
"sha256": "44d932ebedf66cadb9620f2d926177be2f8d43d71d56aa83611de686af026231",
"size": 820
},
{
"path": "scene.fscene",
"sha256": "641ba14a1f9c3966b9ec797814fdfa54f954dc5e0d53ab00c645598edef61baf",
"size": 1681
}
],
"format": "faset.export",
"generation": "aa31bf16-984e-4854-b46f-15655bd6cc67",
"lua_enabled": true,
"lua_fingerprint": "a9261789bb1e0ca38bd40b08b0454165cd51454cba3ad4725dd357812ced6e35",
"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": "143724a468d711cbdf9a9700310d7b0acef003ce1b21422ca5204e0e565fa02c",
"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
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
{
"arguments": [
"/tmp/faset-playable-exports-8wzkrc_u/Faset Café 世界/lua/faset_player",
"--headless",
"--frames",
"120",
"--capture",
"/tmp/faset-playable-exports-8wzkrc_u/Faset Café 世界/lua/verification.ppm",
"--profile",
"/tmp/faset-playable-exports-8wzkrc_u/Faset Café 世界/lua/profile.json"
],
"cwd": "/tmp/faset-playable-exports-8wzkrc_u/empty-working-directory",
"seconds": 0.6288121230027173,
"exit_code": 0,
"timed_out": false,
"stdout": "{\"device\":\"NVIDIA GeForce RTX 2080 Ti\",\"dimension\":2,\"effective_visibility_mode\":\"direct\",\"frames\":120,\"gpu_visibility_active\":false,\"ticks\":120,\"validation_errors\":0,\"visibility_mode\":\"direct\"}\n",
"stderr": "Lua player ready: A/D move, Space jump, E reset\n"
}
@@ -0,0 +1,12 @@
{
"arguments": [
"/tmp/faset-playable-exports-8wzkrc_u/Faset Café 世界/lua/faset_player",
"--validate"
],
"cwd": "/tmp/faset-playable-exports-8wzkrc_u/empty-working-directory",
"seconds": 0.007359832990914583,
"exit_code": 0,
"timed_out": false,
"stdout": "{\"dimension\":2,\"validated\":true}\n",
"stderr": ""
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

File diff suppressed because one or more lines are too long
@@ -0,0 +1,134 @@
{
"format": "faset.playable-export-verification",
"version": 1,
"started_utc": "2026-09-23T23:30:12.719805+00:00",
"platform": "linux",
"engine": "/home/emil/Desktop/.worktrees/Faset_Engine-p1-lua-export",
"editor": "/home/emil/Desktop/.worktrees/Faset_Engine-p1-lua-export/build/linux-release/faset_editor",
"standalone_root": "/tmp/faset-playable-exports-8wzkrc_u",
"engine_revision": "433185717e1f324acb6eaf186818589d11697453",
"engine_dirty": false,
"engine_git_status": [],
"engine_diff_sha256": null,
"editor_sha256": "03ac75942f5f1d29e9d6fedb21ecffc6c74f4299401dbe84f01ab1b4e9c176a0",
"project_workspace": "/tmp/faset-playable-projects-cuhq4iwr",
"vulkaninfo_available": true,
"driver_override": null,
"frames_per_game": 120,
"status": "passed",
"projects": [
{
"name": "lua",
"dimension": 2,
"language": "lua",
"source_inputs": [
{
"path": "README.md",
"sha256": "8392da3c18916400ea628daed094c77c390c553cbdcf822da227bdcd3d86380c"
},
{
"path": "Scenes/main.scene.json",
"sha256": "f1ab54150a58b289c47bcc34566ad7ee3025ad931db81e7f5dce70d9fbcac8ab"
},
{
"path": "Scripts/beacon.lua",
"sha256": "44d932ebedf66cadb9620f2d926177be2f8d43d71d56aa83611de686af026231"
},
{
"path": "Scripts/player.lua",
"sha256": "1c51810574a3413c46a7cbd0ea2e84a9b802da1206a2b72bb78f6cc951367a11"
},
{
"path": "Scripts/util/motion.lua",
"sha256": "bad3dcdc1081fd1dec589c12dad8dc175b739d77ea52b957ead9ecb4b7cb88a3"
},
{
"path": "project.faset.json",
"sha256": "7e4c0781c1837208b4499a9991ffb702143edf1f933ae9c7e3d2543fd2577747"
}
],
"generation": "aa31bf16-984e-4854-b46f-15655bd6cc67",
"configuration": "Release",
"standalone_directory": "/tmp/faset-playable-exports-8wzkrc_u/Faset Café 世界/lua",
"executable": "faset_player",
"package_file_count": 32,
"asset_generations": {},
"device": "NVIDIA GeForce RTX 2080 Ti",
"device_class": "physical",
"driver": {
"apiVersion": "1.4.329",
"driverVersion": "595.84.0.0",
"deviceName": "NVIDIA GeForce RTX 2080 Ti",
"driverID": "DRIVER_ID_NVIDIA_PROPRIETARY",
"driverName": "NVIDIA",
"driverInfo": "595.84"
},
"validation_enabled": true,
"validation_errors": 0,
"completed_frames": 120,
"summary_ms": {
"gpu": {
"max": 0.584992,
"min": 0.551712,
"p50": 0.5608,
"p95": 0.574592,
"samples": 120
},
"render_call": {
"max": 4.928888,
"min": 1.544123,
"p50": 1.919881,
"p95": 2.423702,
"samples": 120
},
"renderer_cpu": {
"max": 4.923196,
"min": 1.540666,
"p50": 1.915753,
"p95": 2.420426,
"samples": 120
},
"renderer_readback_cpu": {
"max": 2.837363,
"min": 0.395756,
"p50": 0.504853,
"p95": 0.641169,
"samples": 120
},
"simulation": {
"max": 0.210056,
"min": 0.084289,
"p50": 0.122261,
"p95": 0.171062,
"samples": 120
},
"snapshot": {
"max": 0.254931,
"min": 0.046518,
"p50": 0.065754,
"p95": 0.087906,
"samples": 120
},
"wall": {
"max": 5.322149,
"min": 1.702973,
"p50": 2.142903,
"p95": 2.634219,
"samples": 120
}
},
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 4,
"sha256": "e2f4c3b84de75dadd2e31d388d0ba51abc1861f1ec76eb7ba51a4a2dcac720a7"
},
"source_project_paths_unavailable": true,
"status": "passed"
}
],
"requested_projects": [
"lua"
],
"finished_utc": "2026-09-23T23:32:18.151669+00:00"
}
@@ -0,0 +1,82 @@
==========
VULKANINFO
==========
Vulkan Instance Version: 1.4.341
Instance Extensions: count = 27
-------------------------------
VK_EXT_acquire_drm_display : extension revision 1
VK_EXT_acquire_xlib_display : extension revision 1
VK_EXT_debug_report : extension revision 10
VK_EXT_debug_utils : extension revision 2
VK_EXT_direct_mode_display : extension revision 1
VK_EXT_display_surface_counter : extension revision 1
VK_EXT_headless_surface : extension revision 1
VK_EXT_layer_settings : extension revision 2
VK_EXT_surface_maintenance1 : extension revision 1
VK_EXT_swapchain_colorspace : extension revision 5
VK_KHR_device_group_creation : extension revision 1
VK_KHR_display : extension revision 23
VK_KHR_external_fence_capabilities : extension revision 1
VK_KHR_external_memory_capabilities : extension revision 1
VK_KHR_external_semaphore_capabilities : extension revision 1
VK_KHR_get_display_properties2 : extension revision 1
VK_KHR_get_physical_device_properties2 : extension revision 2
VK_KHR_get_surface_capabilities2 : extension revision 1
VK_KHR_portability_enumeration : extension revision 1
VK_KHR_surface : extension revision 25
VK_KHR_surface_maintenance1 : extension revision 1
VK_KHR_surface_protected_capabilities : extension revision 1
VK_KHR_wayland_surface : extension revision 6
VK_KHR_xcb_surface : extension revision 6
VK_KHR_xlib_surface : extension revision 6
VK_LUNARG_direct_driver_loading : extension revision 1
VK_NV_display_stereo : extension revision 1
Instance Layers: count = 12
---------------------------
VK_LAYER_INTEL_nullhw INTEL NULL HW 1.1.73 version 1
VK_LAYER_KHRONOS_validation Khronos Validation Layer 1.4.341 version 1
VK_LAYER_MESA_anti_lag Open-source implementation of the VK_AMD_anti_lag extension. 1.4.303 version 1
VK_LAYER_MESA_device_select Linux device selection layer 1.4.303 version 1
VK_LAYER_MESA_overlay Mesa Overlay layer 1.4.303 version 1
VK_LAYER_MESA_screenshot Mesa Screenshot layer 1.4.303 version 1
VK_LAYER_NV_optimus NVIDIA Optimus layer 1.4.329 version 1
VK_LAYER_NV_present NVIDIA Presentation Layer 1.4.329 version 1
VK_LAYER_VALVE_steam_fossilize_32 Steam Pipeline Caching Layer 1.3.207 version 1
VK_LAYER_VALVE_steam_fossilize_64 Steam Pipeline Caching Layer 1.3.207 version 1
VK_LAYER_VALVE_steam_overlay_32 Steam Overlay Layer 1.3.207 version 1
VK_LAYER_VALVE_steam_overlay_64 Steam Overlay Layer 1.3.207 version 1
Devices:
========
GPU0:
apiVersion = 1.4.329
driverVersion = 595.84.0.0
vendorID = 0x10de
deviceID = 0x1e04
deviceType = PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
deviceName = NVIDIA GeForce RTX 2080 Ti
driverID = DRIVER_ID_NVIDIA_PROPRIETARY
driverName = NVIDIA
driverInfo = 595.84
conformanceVersion = 1.4.3.3
deviceUUID = 2c71c7ba-97ac-c78e-2c06-af362631f3fe
driverUUID = 027cbdd1-c478-513e-af69-ebc9eaa7de87
GPU1:
apiVersion = 1.4.335
driverVersion = 26.0.8
vendorID = 0x10005
deviceID = 0x0000
deviceType = PHYSICAL_DEVICE_TYPE_CPU
deviceName = llvmpipe (LLVM 21.1.8, 256 bits)
driverID = DRIVER_ID_MESA_LLVMPIPE
driverName = llvmpipe
driverInfo = Mesa 26.0.8-1ubuntu0.3 (LLVM 21.1.8)
conformanceVersion = 1.3.1.1
deviceUUID = 6d657361-3236-2e30-2e38-2d3175627500
driverUUID = 6c6c766d-7069-7065-5555-494400000000
WARNING: [Loader Message] Code 0 : ICD for selected physical device does not export vkGetPhysicalDeviceDisplayPlanePropertiesKHR!
WARNING: [Loader Message] Code 0 : ICD for selected physical device does not export vkGetPhysicalDeviceDisplayPropertiesKHR!
@@ -0,0 +1,138 @@
# P1 gameplay iteration: measured Linux workflow
The [unaltered version 2 workflow report](report.json) was produced by a clean
`6c2e8fa80036315a695f67f3c53524165fb4b10a` checkout on 2026-09-24.
It used disposable copies of the checked-in `collect-3d` and `examples/lua`
projects. Every sample, process log and Player profile is retained under `raw/`;
[the run log](workflow.log) shows the order and completion. The report identifies
each input revision by SHA-256, and records all command arguments, phase labels,
configuration and dirty-tree state. Its SHA-256 is
`fd463c0cd12eed0a22143aa68bd732386804b0df4ecb2a3d9caa26f59dcfe6e2`.
The original report's `toolchain.slang` is `null` because that version looked
for `slangc` on `PATH`, while CMake used a configured executable. A **separate**
[post-run provenance supplement](provenance.json), generated after commit
`abd8c21` from the surviving disposable [compressed CMake cache](CMakeCache.txt.gz), hashes
the selected CMake, Ninja, C/C++ compiler, Slang, Editor and UI-probe binaries.
It binds to the original report hash and records both script revisions. The
measured report was not rewritten. Subsequent workflow runs include these
identities in their report directly. `4ac02ee` changed only the Windows CI export
fixture location; neither follow-up is presented as the measured code revision.
## Measurement conditions and results
The host ran Linux 7.0, Ryzen 7 1700, 32 GiB RAM, RTX 2080 Ti and NVIDIA 595.84.
The disposable project used Debug, Clang 21.1.8, CMake 4.2.3, Ninja 1.13.2 and
Slang 2026.18. The Player rendered offscreen Vulkan at 1280×720 with validation
active and full-frame readback. The run did not overlap the separate P3 GPU sweep.
Build times still depend on ambient CPU load, filesystem cache and prepared
dependencies. The `cold` row means an empty project `.faset` cache, **not** a cold
OS page cache or a first dependency download.
| Observation | Samples | Median | Nearest-rank p95 | Scope |
| --- | ---: | ---: | ---: | --- |
| Initial configure and native build | 1 | 106.01 s | — | Disposable Debug project |
| Unchanged build | 5 | 11.53 s | 13.12 s | All five reused the verified schema/package generation |
| Changed `Gameplay.cpp` build | 5 | 28.16 s | 30.33 s | One warm-up edit excluded |
| Changed `Gameplay.hpp` build | 5 | 23.89 s | 25.51 s | One warm-up edit excluded |
| Watched Lua edit to reload log | 5 | 0.502 s | 0.504 s | Persistent Player; includes watcher polling |
| First completed offscreen Player frame | 5 | 0.284 s | 0.333 s | From `main()`; excludes OS loader and window presentation |
| Synthetic Editor input to rendered state | 100 | 13.97 ms | 18.03 ms | Ten warm-ups; offscreen retained-UI frame, not input-to-photon |
The intentional C++ compile failure took 10.00 s and the corrected recovery
build 19.93 s. The failed job preserved its prior successful build pointer.
The run also imported the small Blender bundle cold and cached in 49 and 52 ms,
respectively; Editor process startup dominates that tiny asset sample, so those
two values do not establish import-cache speedup. The raw
[failed-build job](raw/build_failure-01-stdout.txt) exposed a navigation defect:
the compiler named the staged source copy, and the original structured
diagnostic omitted `file`. A later fix maps only the verified staged `Scripts`
subtree to project-relative source. A [real failed-build replay](diagnostic-replay.json)
with the corrected Editor returns `Scripts/Gameplay.cpp`, line 73, column 2.
The `build_diagnostics` test also covers project-contained snapshot paths and
Windows drives; `build_schema_publication` verifies that an error after 250
third-party warnings survives the 200-row diagnostic limit. These are separate
from this unmodified measurement.
Follow-up `54d1bae` rehashes both the live source and the staged copy after
native build and before reuse/publication. A fixture changes the staged C++
header during schema export; the build fails and retains the last-good pointer.
The copy is content-verified at those boundaries, not sealed against a same-user
write-and-restore during compiler reads.
The [3,000-frame lifecycle profile](raw/cpp-3000-frame-lifecycle.json) completed
with zero Vulkan validation errors. Maximum explicit Vulkan allocation was
15,787,008 bytes in both frames 101–200 and 2901–3000, a measured growth of zero;
the live texture count remained one. These counters exclude driver-internal
memory and do not prove absence of all resource leaks.
The generated [527-entity scene](raw/generated-512.scene.json) adds 512 seeded
copies to the 15-entity source and ran for 240 offscreen frames with zero Vulkan
validation errors. Its [raw profile](raw/generated-512-profile.json) reports p95
frame wall 149.39 ms, simulation 48.19 ms, scene snapshot 68.12 ms, renderer call
36.51 ms and GPU timestamp 0.911 ms. It exposes a significant CPU scaling cost
in this synthetic content; it is not a typical-game benchmark or a Release budget
failure for the tiny checked-in sample.
## Reference budgets and coverage
The [2026-09-18 Release reference baseline](../linux-release-2026-09-18/README.md)
measured the **exact** `collect-2d` and `collect-3d` packaged scenes on this host,
at 1280×720 with validation/readback. Its 240-frame p95s met the initial tracking
budgets: frame 2.684/2.732 ms versus ≤4; GPU 0.579/0.600 ms versus ≤1; readback
0.633/0.608 ms versus ≤1; simulation 0.258/0.300 ms and snapshot 0.215/0.257 ms
versus ≤0.5 each. Explicit allocations were about 15.02/15.06 MiB versus
≤20 MiB; first-frame startup from `main()` was 265.5/260.8 ms versus ≤500 ms.
That baseline was an evolving earlier working tree. Its values are **historical**
and are not compared with this Debug workflow's timing.
The [fresh clean combined P1/P3 Release reference](final-release/README.md) at
`4a3453e` repeats these exact scenes on the same physical RTX 2080 Ti, now with
120-frame source-hidden relocation checks for C++ 2D/3D and Lua-only, plus
sequential 240-frame C++ 2D/3D profiles. The frame, GPU, readback, simulation,
snapshot and startup tracking budgets all pass. Explicit allocations instead
reach **43.022/43.118 MiB**, above the former ≤20 MiB budget because the new
renderer eagerly retains a 16 MiB sun atlas and a 16 MiB local atlas in both
scenes. The 2D sample casts no sun shadows and neither sample uses local shadow
faces. This is a measured tracking-budget miss, not a failed export or a reason
to redefine the counter silently. The linked dossier retains exact values,
atlas breakdown, raw profiles and source/toolchain provenance.
The [Lua-only Linux Release dossier](../lua-release-2026-09-24/README.md) separately
records a relocated package that validated and rendered 120 frames after hiding
its source project, with 0 validation errors. The final fixture placement was
checked in clean-revision [`4ac02ee` Windows graphics CI](https://github.com/emil28092005/Faset_Engine/actions/runs/35937433040):
61/61 CTests passed on pinned SwiftShader, the real 2D/3D Release export
integration passed, and **all three** relocated Release games (`collect-2d`,
`collect-3d`, Lua-only) validated and rendered 120/120 frames with source paths
unavailable. The [Windows playable report](windows-4ac02ee/playable-report.json)
contains package and source hashes, device identity, result details and capture
checksums; [integration results](windows-4ac02ee/integration-result-2.json),
[integration manifests](windows-4ac02ee/integration-manifest-2.json),
[Vulkan probe](windows-4ac02ee/vulkan-probe.json) and
[toolchain record](windows-4ac02ee/toolchain.json) preserve representative raw
evidence. The parallel [native/manual CI](https://github.com/emil28092005/Faset_Engine/actions/runs/35937433211)
passed on Linux and Windows. This is **software** Vulkan; Windows did not have an
active Khronos validation layer (`validation_enabled: false`), and no physical
Windows GPU/driver performance claim follows. The later [combined P1/P3 Windows
graphics CI](https://github.com/emil28092005/Faset_Engine/actions/runs/35942307579)
at `87230f3` passed 74/74 CTests, including real diagnostic navigation, real
Release export integration and all three relocated games. The physical-GPU
Linux reference above had active validation.
## P1 acceptance record
| Area | Evidence and current limit |
| --- | --- |
| Lua | Linux native and Release [relocated Lua result](../lua-release-2026-09-24/README.md), repeated in the [clean combined three-game Release reference](final-release/README.md); Windows CPU/SwiftShader [combined 74-test suite](https://github.com/emil28092005/Faset_Engine/actions/runs/35942307579) and [relocated Lua-only Release report](windows-4ac02ee/playable-report.json), 120 completed frames. |
| Cache and rollback | [Five verified hits](report.json), `build_cache` and `build_schema_publication` contracts for headers, toolchain, schema/shader corruption and source races; the raw failed/recovered build retains the previous pointer. |
| Diagnostics | `build_diagnostics` fixtures cover Clang, clang-cl, Lua, Unicode and unparseable output. The raw real failure above found a staged-source mapping gap; [the replay](diagnostic-replay.json), parser case, warning-flood case and real BuildService integration assertion cover the correction. |
| Starters/navigation | [Four runnable C++/Lua × 2D/3D starters](../p1-starters-linux-2026-09-24.md) plus launcher/CLI and source-open tests. |
| Autosave | `editor_session_settings`, UI tests and [workspace behavior](../../manual/editor/workspace.md#create-and-save-a-scene): idle coalescing, unnamed Save As, external/revision conflicts, Undo and recovery. |
| Iteration | This Debug report, 179 raw files, 3,000-frame and seeded larger-scene profiles, plus the [clean combined Release reference](final-release/README.md) with three relocated games and exact 2D/3D budgets. The allocation budget miss is recorded; no CI timing gates. |
| Documentation | The English Manual describes build/reload, templates, navigation, autosave and profile interpretation; strict MkDocs and full platform test totals are recorded in the implementation journal. |
The [profiling guide](../../manual/editor/profiling.md#measure-editor-c-and-lua-iteration)
documents how to repeat the run and interpret its deliberately distinct timing
labels. C++ hot reload, state-preserving Lua reload and Player-world MCP are not
part of this P1 contract.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,111 @@
# Final combined P1/P3 Release reference — 2026-09-24
This is the clean-source, physical-GPU reference for the checked-in playable
`collect-2d`, `collect-3d`, and Lua-only games at
`4a3453e2b1868555d833f578d43f48b8bc47d41a`. The real Release Editor
exported each project to a self-contained package. The packages were copied to
Unicode-named paths outside their disposable source projects, and those source
paths were hidden while each Player ran from an unrelated empty working
directory. The 3D export imported and packaged the Blender exit arch. All three
games passed standalone CPU validation and rendered **120/120** offscreen Vulkan
frames on the physical NVIDIA GeForce RTX 2080 Ti with the Khronos validation
layer active and **zero validation errors**. The [verifier report](report.json),
[2D manifest](evidence/collect-2d-manifest.json),
[3D manifest](evidence/collect-3d-manifest.json), and
[Lua manifest](evidence/lua-manifest.json) retain input, package and binary
hashes. The [toolchain supplement](provenance.json) binds the unmodified report
and 240-frame summary to the exact Editor, CMake, Ninja, Clang and Slang
binaries. The verifier recorded a clean checkout at its start, and the second
profiles used the same relocated Player binaries.
| Relocated package | Runtime | Frames | GPU validation | Source paths hidden |
| --- | --- | ---: | --- | --- |
| `collect-2d` | C++ | 120/120 | active, 0 errors | yes |
| `collect-3d` | C++ with Blender asset | 120/120 | active, 0 errors | yes |
| `lua` | Lua-only | 120/120 | active, 0 errors | yes |
The two **same relocated C++ executables** then ran a second, sequential
240-frame profile. For each run, the disposable source-project paths were again
hidden and restored afterward. These exact 2D/3D samples used default Direct
visibility and temporal Off, 1280×720 offscreen Vulkan, synchronous readback,
the active Khronos validation layer and synthetic fixed-step simulation. No
frame was discarded as a warm-up: the table uses nearest-rank p95 over all 240
completed frames, including the first. Both profiles report zero validation
errors. Raw [2D](reference-240/collect-2d-profile-240.json) and
[3D](reference-240/collect-3d-profile-240.json) profiles, exact
[2D invocation](reference-240/collect-2d-run-240.json) and
[3D invocation](reference-240/collect-3d-run-240.json), and the
[machine-readable budget comparison](reference-240/reference-summary.json)
support every value below.
| Exact-scene observation | Initial P1 tracking budget | 2D | 3D |
| --- | ---: | ---: | ---: |
| Frame wall p95 | ≤4 ms | 2.694 ms | 3.036 ms |
| GPU timestamp p95 | ≤1 ms | 0.573 ms | 0.617 ms |
| Readback CPU p95 | ≤1 ms | 0.559 ms | 0.561 ms |
| Simulation p95 | ≤0.5 ms | 0.242 ms | 0.300 ms |
| Scene snapshot p95 | ≤0.5 ms | 0.197 ms | 0.288 ms |
| `main()` to first frame | ≤500 ms | 295.4 ms | 248.3 ms |
| Maximum explicit Vulkan allocation | ≤20 MiB | **43.022 MiB — miss** | **43.118 MiB — miss** |
The memory miss is real, not a profile rounding artifact: 2D used 45,112,032
bytes and 3D used 45,212,272 bytes, while the earlier threshold was 20,971,520
bytes. Each run counted a **16 MiB sun atlas and a 16 MiB local-light atlas**
inside the live Vulkan allocation total. The 2D sprites produced no sun-shadow
raster work; the 3D meshes rendered four sun cascades. Neither sample used a
local shadow face. Both 2048² D32 atlases are currently allocated when the
Renderer is created, even when the scene never uses them. The remaining explicit
allocations were 11.022 MiB (2D) and 11.118 MiB (3D). Lazy local-atlas
allocation is a sensible follow-up, but alone it would still leave the 3D
scene above 20 MiB with its current 2048² sun atlas. We have kept the physical
counter and shadow resolution intact and recorded the miss rather than silently
changing either to satisfy an older threshold. The 20 MiB value is an initial
**tracking budget** for tiny pre-P3 scenes, not a functional export gate; any
revised shadow-memory budget needs its own measured baseline.
The Lua run establishes relocated Release execution, not a 2D/3D latency
budget. The [2026-09-18 Release reference](../../linux-release-2026-09-18/README.md)
used these scenes on the same GPU but an evolving earlier working tree; its
numbers are historical context, not this clean combined revision. This run
measures small scenes on one active Linux desktop and one driver (NVIDIA
595.84). GPU timestamps exclude CPU work, `renderer_readback_cpu` is a
map/copy/unmap duration inside the renderer call, and the explicit allocation
counter excludes driver-internal memory. It does not establish production-game
scaling, whole-process VRAM usage, display-paced FPS, physical Windows GPU
performance or absence of all leaks.
## Reproduce and inspect
From this repository with the dependencies configured:
```sh
cmake --preset linux-release
cmake --build --preset linux-release --target faset_editor --parallel 2
python3 tools/verify_playable_exports.py \
--editor build/linux-release/faset_editor \
--output /tmp/faset-p1-release-new \
--standalone-root /tmp/faset-p1-packages-new \
--include-lua
python3 docs/validation/p1-iteration-2026-09-24/final-release/profile_exact_scenes.py \
--verification /tmp/faset-p1-release-new/report.json \
--output /tmp/faset-p1-release-new/reference-240
```
Use **new, empty** output and standalone directories. The second helper
refuses an incomplete or dirty-tree verifier report, hides the disposable
source projects during each 240-frame run, checks exact settings and validation,
then restores them. The original [verifier log](verifier.log),
[240-frame log](reference-240.log), raw 120-frame profiles for
[2D](evidence/collect-2d-profile.json),
[3D](evidence/collect-3d-profile.json) and
[Lua](evidence/lua-profile.json), PPM captures, and the
[SHA-256 file list](SHA256SUMS.txt) are retained here. The
large disposable native build directories and relocated executable packages
remain under `/tmp`; their input, manifest and executable hashes are in the
reports.
![Relocated 2D sample](collect-2d.png)
![Relocated 3D sample with Blender asset](collect-3d.png)
![Relocated Lua sample](lua.png)
@@ -0,0 +1,35 @@
d5dca233d7ddbb7c32998ec920fda6cf67e6515c965372985c807ccd4ff54075 README.md
d23e8614c133938974d1c1a865100d24175242ab818573cb1416ba82d1e62260 collect-2d.png
f83799304917c2e0250605e83f69cbee4865818078ba3f9aed590c010f2b19b3 collect-3d.png
3ca7066843c2875c86a4917782a07f93358a44ebd220d25a3f2457ae542ec415 evidence/collect-2d-export.json
5f8de3eb000dc7207b256265f0fa20a29efe194dd1347ab4c3bf670cfdcfb2b0 evidence/collect-2d-manifest.json
c45dc33981db0c2038d22cda2be4151de46445a4f368259d6b0002c35e402633 evidence/collect-2d-profile.json
591329c7cb85c9fc0220deec7e76ca24e139037481d949c91882858c76c2621a evidence/collect-2d-run.json
af24b1c312e67b0e0cbe2b5871888db17b9bb47e95eca5028c9e07d72b2f1bea evidence/collect-2d-validate.json
f9d74067d87c1ebf30c6db372a26d93b7285791c8cb0307a24af5adb23bf201a evidence/collect-2d.ppm
a00c55bbda0242e924781b4ca9d73910741506dce4df93dafde8d729389511a8 evidence/collect-3d-export.json
7e09c5ae35c8f998510b83af51ded67bba0bc7483f8cfdb17a785368a8f9b2ca evidence/collect-3d-import.json
484eda90411c48277385a2f679561cfa6304965a7a7be6ec065bfbd9dace1722 evidence/collect-3d-manifest.json
96193de60cf7fa139fc110c99628e065779a378151e816ce947129ed4217f9a0 evidence/collect-3d-profile.json
17e2ea46f9811432018704fa2468850f015b113f3407525bf795c21cf44b4fcb evidence/collect-3d-run.json
f102446ff954cf1e9062726772a868ff1a30783da5ff77e2c3486227e8acc867 evidence/collect-3d-validate.json
2905891c9766b2f1f65d5b4284056cb3839f1faae195f9fea39a56e3ebf44363 evidence/collect-3d.ppm
737db5241ea2caba9393e18dbcf1e48ef6c106ae6f4fdc6ac3bfe0e2fc43325c evidence/lua-export.json
3caecdb756b289c2bd1f61ec4e5a8c5194b35166f34f332a1651a5a846e7f631 evidence/lua-manifest.json
eb985bd647266529ab98d0535150e20e7c54692a2546e0df935bbcfb71cbca66 evidence/lua-profile.json
acce29b23725fba6a54e2e70827b0bd92137408eacaec4f58f5dee37666a8d0c evidence/lua-run.json
f44cbe6db9ac6170c4db7f6f502fe09d62cc5da1e2535fe93cc474f6a28293ca evidence/lua-validate.json
e2f4c3b84de75dadd2e31d388d0ba51abc1861f1ec76eb7ba51a4a2dcac720a7 evidence/lua.ppm
5e395b08f275eadc6066697204abf2287e9a1ea124cba6cf6f5deca3924282aa evidence/vulkaninfo-summary.txt
19c9b2c5a401af52980604fa074f89fe9452cfa728a606ad937c509a1c4164eb lua.png
5d69428a1202c1e144779074c0ab88360c459e2dd99b22c1fcf8929dbeab2618 profile_exact_scenes.py
fa49dae530785e24fe028ee5d0ade949fab61d540b118d8bf96d787267e6f87b provenance.json
65858b1de2334d304ad3a0907180aaf4f79ead3da63762500e50b7298d922bfa reference-240/collect-2d-profile-240.json
0d117fc6571fc497271921e818c97c729aeb58da6613bb2b1f24c815ae171319 reference-240/collect-2d-run-240.json
b0c4c6b4020b1ded792917fff1f36510b76bb30864d89bd4e6e250b5693c5fe8 reference-240/collect-3d-profile-240.json
ebbc376e8788b11b31f1eb7a767142348bcc6a6986e0593e20b44cb0e2d1e65d reference-240/collect-3d-run-240.json
63da692b605621acc1cace00d4a3c9cf35de13abfd4351aea6653a88c870e03f reference-240/playable-report.json
f8297f3cb0578addee56c0b3cb9439b37ae37707b02d433d662b534620f329e9 reference-240/reference-summary.json
d97939ba5cee29ae237568c00714b68ab6849c4c506987cff05392750da5e0ca reference-240.log
63da692b605621acc1cace00d4a3c9cf35de13abfd4351aea6653a88c870e03f report.json
f4a0f3a487a5d7180caa08c8c254d2e39bad5d6cf5c4877d131af3dbd98fa777 verifier.log
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long
@@ -0,0 +1,269 @@
{
"asset_generations": {},
"build_fingerprint": "b2559255996b7990919d5aa15cbfa53c225a0ee299e6a43ffbf04cd8bbb606db",
"configuration": "Release",
"executable": "faset_player",
"files": [
{
"path": "README.txt",
"sha256": "82bf72bc542643f6c4090e0a5de69b0014ca3fa17c6f9dfb12cc41e44c8b8db6",
"size": 262
},
{
"path": "Notices/Faset-NOTICE.txt",
"sha256": "30c2e7e80f5153b31ab57675e07246aef9165965d64e36022b426a26449ece3f",
"size": 204
},
{
"path": "Notices/dependencies.json",
"sha256": "a54639ad2d9ad13c2e5f1ec90d6775f5dfc493656789b78c7e44debe2a378fdc",
"size": 2206
},
{
"path": "Notices/stb/LICENSE",
"sha256": "bebfe904b14301657e4e5d655c811d51fd31b97c455b9cc2d8600d6bac6cff63",
"size": 2510
},
{
"path": "Notices/json/LICENSE.MIT",
"sha256": "46a65cffd1ea955132d95a8dd921640714a8d6b537d2e4e482d31145ae95b603",
"size": 1076
},
{
"path": "Notices/box3d/LICENSE",
"sha256": "da5e31a26bf3cfd5ba5c96d6823e480128c81e76c107ef9d3ee5d94789184b90",
"size": 1067
},
{
"path": "Notices/box2d/LICENSE",
"sha256": "68a3e676d7e94093b102d5cba0d4e04af812040d6f230c3db67a6664574e43d2",
"size": 1067
},
{
"path": "Notices/entt/LICENSE",
"sha256": "0785027ce472d7c61f05fba664a1d3ba6639c1593ced351ad9e4bed868765d99",
"size": 1097
},
{
"path": "Notices/sdl3/LICENSE.txt",
"sha256": "97f35b302b361680ec1e891e95d2d52097bb95abff361434916d99dc1305f127",
"size": 884
},
{
"path": "shaders/gpuTemporalVertexMain.reflection.json",
"sha256": "7a8efe2645e7ce465ca44d19f8cfb2eb6c767abd1c3ce12ed69a149a50be8a8c",
"size": 3308
},
{
"path": "shaders/gpuTemporalVertexMain.spv",
"sha256": "dfe7309aafbd41974668b295e179354965db440e4156b9f12d6a67db16b357f9",
"size": 5996
},
{
"path": "shaders/temporalFragmentMain.reflection.json",
"sha256": "e36452227788f3703d7342b6d9a7a0efef74ce4da3aa96bb176a26e169ec3628",
"size": 3874
},
{
"path": "shaders/temporalFragmentMain.spv",
"sha256": "03adc6673276979b3ea46becf3a36940d3061af77cb8c965c7471feff8dbf89a",
"size": 30976
},
{
"path": "shaders/temporalVertexMain.reflection.json",
"sha256": "6f7812ce88b14bca62a6d2ad82a8657e2ee48c252558f34677c6a2bd980a7edb",
"size": 3935
},
{
"path": "shaders/temporalVertexMain.spv",
"sha256": "6cc3535c952a33ee46fd417fbec7fcad8f24357508b2119256b3077be3d60955",
"size": 1784
},
{
"path": "shaders/temporalCompositeFragmentMain.reflection.json",
"sha256": "44765a41fc362c9a97b1a19834f285e2e7facb24d8715e3d8559b8e9633a7a0e",
"size": 959
},
{
"path": "shaders/temporalCompositeFragmentMain.spv",
"sha256": "9e21c8534c0e70a58ae018fb2d143bf9cfb70112e71b67585f525eef0aecad1b",
"size": 768
},
{
"path": "shaders/temporalCompositeVertexMain.reflection.json",
"sha256": "a3a2ea858bd038423896043b0703704f707addceb5bd56721cd897f6bd092065",
"size": 956
},
{
"path": "shaders/temporalCompositeVertexMain.spv",
"sha256": "6b19f65b64f40d0a249660fdfba9dd5d95831ffab7314a307bf1f5cf39c6a248",
"size": 340
},
{
"path": "shaders/temporalResolveMain.reflection.json",
"sha256": "9407ee1522f32df231d996ea691224863c023c1ca42cc02ccd7e16de1d873082",
"size": 3179
},
{
"path": "shaders/temporalResolveMain.spv",
"sha256": "4c5fc0da7a4c424e5426694c2c2d9f3cd17d1a64066712f5defe882ad8776662",
"size": 22192
},
{
"path": "shaders/gpuPostCullMain.reflection.json",
"sha256": "19ce128388c7f88e0c517fb58a25e46f1382e35ae5f376b10eebe155a2811c4c",
"size": 3591
},
{
"path": "shaders/gpuHzbMain.reflection.json",
"sha256": "589818f670881c64f3aba726ee3bc82fe4b9d835a347f99134e0e157dc4a112e",
"size": 2050
},
{
"path": "shaders/gpuCullMain.reflection.json",
"sha256": "8986a45a8d29743d16f0caece9181b18c85d6490d93b4a56cdb4f53052890f3f",
"size": 3587
},
{
"path": "shaders/gpuShadowMain.reflection.json",
"sha256": "96b3620b163dadf214933cfba6600b1b6a835c4d98b75b1f83e6ccd3dedb2425",
"size": 2755
},
{
"path": "shaders/gpuVertexMain.reflection.json",
"sha256": "01a6cc1bbf3c11c558560d6f8557fd4582d392906ed20a8c729673d0ebf640ed",
"size": 3098
},
{
"path": "shaders/gpuPostCullMain.spv",
"sha256": "1f8edd3be1f99f9433494a6f7be68f1317d6e7099e19354a7b8130d5d932224d",
"size": 12548
},
{
"path": "shaders/gpuHzbMain.spv",
"sha256": "08682362ee2120ed19d260e613285071747c3e436bb09bb6238e46f9cedc2308",
"size": 4140
},
{
"path": "shaders/gpuCullMain.spv",
"sha256": "e5ea6589ddb3613b59f8e58420be95a534cf4149816bc65bae26b1395c638955",
"size": 16288
},
{
"path": "shaders/gpuShadowMain.spv",
"sha256": "f56e0ae3e9b31cb1e3f24e379c81e11c853f8a7102648aaaebc92a17c7e8e53c",
"size": 2492
},
{
"path": "shaders/gpuVertexMain.spv",
"sha256": "32421d4a91f3a88d5210fee7af34b48bcad83072f14e2ccb9ef2ff983e45f78f",
"size": 5016
},
{
"path": "shaders/shadowMain.reflection.json",
"sha256": "d39e2f83c797589296eac9dce139b9ad836a895d6685bc40f79dd2609d687fee",
"size": 3597
},
{
"path": "shaders/fragmentMain.reflection.json",
"sha256": "2b07af2aae7578bdb6c2aa22658538821608b1dd5aeddd7b06e42b2e78cedf12",
"size": 3596
},
{
"path": "shaders/vertexMain.reflection.json",
"sha256": "b3bdff863d53a0b385e06a36701a83a6ecafe9a93e97281a732cce8ff862c0db",
"size": 3591
},
{
"path": "shaders/lightTileMain.reflection.json",
"sha256": "bf6a9256aaa6d6edacfa1e1c265d7c8ce2c1e4ea4036e0bea078c22fd4b536ca",
"size": 1978
},
{
"path": "shaders/lightTileMain.spv",
"sha256": "92850edcc5ee5b586a90ce84ee69a98075a6fff3c13db4a57528d9431afe8b68",
"size": 6800
},
{
"path": "shaders/shadowMain.spv",
"sha256": "7643b4d688492b5ee923b9606f0b0e70343ca05fa67673838206119a6f86d8d8",
"size": 784
},
{
"path": "shaders/fragmentMain.spv",
"sha256": "fea4dd48664a510bad12b5e8582f2db798d4dbea62ce0de35dc2acfe32372733",
"size": 28960
},
{
"path": "shaders/vertexMain.spv",
"sha256": "1ad2631c35d654f48166321ae43d4165043e1b919b14f1dc61caf97b1ada0898",
"size": 1224
},
{
"path": "faset_player",
"sha256": "5936d8a1ceae9250c69f1b8ebccbadebc40b13e812bebeb9752da3d14d202a4a",
"size": 6154704
},
{
"path": "scene.fscene",
"sha256": "0d7b221abc1cae5e9f1d04357c684ac6993f5878d634b4b950eec66dd8e55b2a",
"size": 6214
}
],
"format": "faset.export",
"generation": "ca306aa8-a094-42bc-833f-a24a05cab73d",
"lua_enabled": false,
"lua_fingerprint": "",
"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_atlases": {
"caster_draw_limit": 4096,
"fallback_resolution": 1024,
"local_face_limit": 16,
"preferred_resolution": 2048,
"sampled_d32_required": true,
"sun_cascade_limit": 4
},
"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-p1-final-packages-4a3453e/Faset Café 世界/collect-2d/faset_player",
"--headless",
"--frames",
"120",
"--capture",
"/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-2d/verification.ppm",
"--profile",
"/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-2d/profile.json"
],
"cwd": "/tmp/faset-p1-final-packages-4a3453e/empty-working-directory",
"seconds": 0.6589637870201841,
"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,\"effective_temporal_mode\":\"off\",\"effective_visibility_mode\":\"direct\",\"frames\":120,\"gpu_visibility_active\":false,\"temporal_mode\":\"off\",\"ticks\":120,\"validation_errors\":0,\"visibility_mode\":\"direct\"}\n",
"stderr": ""
}
@@ -0,0 +1,12 @@
{
"arguments": [
"/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-2d/faset_player",
"--validate"
],
"cwd": "/tmp/faset-p1-final-packages-4a3453e/empty-working-directory",
"seconds": 0.008382081025047228,
"exit_code": 0,
"timed_out": false,
"stdout": "{\"dimension\":2,\"validated\":true}\n",
"stderr": ""
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,296 @@
{
"asset_generations": {
"5832763b-3ed0-44d6-9088-0b524f196a91": "4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2"
},
"build_fingerprint": "bd02f95edc719dfab9193177d2839502f3322cd85b8fc8f6dcb4f9a2560362b1",
"configuration": "Release",
"executable": "faset_player",
"files": [
{
"path": "README.txt",
"sha256": "82bf72bc542643f6c4090e0a5de69b0014ca3fa17c6f9dfb12cc41e44c8b8db6",
"size": 262
},
{
"path": "Notices/Faset-NOTICE.txt",
"sha256": "30c2e7e80f5153b31ab57675e07246aef9165965d64e36022b426a26449ece3f",
"size": 204
},
{
"path": "Notices/dependencies.json",
"sha256": "a54639ad2d9ad13c2e5f1ec90d6775f5dfc493656789b78c7e44debe2a378fdc",
"size": 2206
},
{
"path": "Notices/stb/LICENSE",
"sha256": "bebfe904b14301657e4e5d655c811d51fd31b97c455b9cc2d8600d6bac6cff63",
"size": 2510
},
{
"path": "Notices/json/LICENSE.MIT",
"sha256": "46a65cffd1ea955132d95a8dd921640714a8d6b537d2e4e482d31145ae95b603",
"size": 1076
},
{
"path": "Notices/box3d/LICENSE",
"sha256": "da5e31a26bf3cfd5ba5c96d6823e480128c81e76c107ef9d3ee5d94789184b90",
"size": 1067
},
{
"path": "Notices/box2d/LICENSE",
"sha256": "68a3e676d7e94093b102d5cba0d4e04af812040d6f230c3db67a6664574e43d2",
"size": 1067
},
{
"path": "Notices/entt/LICENSE",
"sha256": "0785027ce472d7c61f05fba664a1d3ba6639c1593ced351ad9e4bed868765d99",
"size": 1097
},
{
"path": "Notices/sdl3/LICENSE.txt",
"sha256": "97f35b302b361680ec1e891e95d2d52097bb95abff361434916d99dc1305f127",
"size": 884
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/current.json",
"sha256": "b0732959808ce61ef5a3223fba2d9ad8c17a3ea7873d4a81c00aadee6c51032e",
"size": 134
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2/manifest.json",
"sha256": "ee3f68794c6dc44ab492b1ba4640aa74e4de79a5ec10aed8dcdd45a03afc508e",
"size": 4532
},
{
"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/meshes/mesh-0961e2a3f92977836a3aa80cab09277e-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2/meshes/mesh-fc069a07a87e0f831fd92efdbc4efa1f-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "shaders/gpuTemporalVertexMain.reflection.json",
"sha256": "7a8efe2645e7ce465ca44d19f8cfb2eb6c767abd1c3ce12ed69a149a50be8a8c",
"size": 3308
},
{
"path": "shaders/gpuTemporalVertexMain.spv",
"sha256": "dfe7309aafbd41974668b295e179354965db440e4156b9f12d6a67db16b357f9",
"size": 5996
},
{
"path": "shaders/temporalFragmentMain.reflection.json",
"sha256": "e36452227788f3703d7342b6d9a7a0efef74ce4da3aa96bb176a26e169ec3628",
"size": 3874
},
{
"path": "shaders/temporalFragmentMain.spv",
"sha256": "03adc6673276979b3ea46becf3a36940d3061af77cb8c965c7471feff8dbf89a",
"size": 30976
},
{
"path": "shaders/temporalVertexMain.reflection.json",
"sha256": "6f7812ce88b14bca62a6d2ad82a8657e2ee48c252558f34677c6a2bd980a7edb",
"size": 3935
},
{
"path": "shaders/temporalVertexMain.spv",
"sha256": "6cc3535c952a33ee46fd417fbec7fcad8f24357508b2119256b3077be3d60955",
"size": 1784
},
{
"path": "shaders/temporalCompositeFragmentMain.reflection.json",
"sha256": "44765a41fc362c9a97b1a19834f285e2e7facb24d8715e3d8559b8e9633a7a0e",
"size": 959
},
{
"path": "shaders/temporalCompositeFragmentMain.spv",
"sha256": "9e21c8534c0e70a58ae018fb2d143bf9cfb70112e71b67585f525eef0aecad1b",
"size": 768
},
{
"path": "shaders/temporalCompositeVertexMain.reflection.json",
"sha256": "a3a2ea858bd038423896043b0703704f707addceb5bd56721cd897f6bd092065",
"size": 956
},
{
"path": "shaders/temporalCompositeVertexMain.spv",
"sha256": "6b19f65b64f40d0a249660fdfba9dd5d95831ffab7314a307bf1f5cf39c6a248",
"size": 340
},
{
"path": "shaders/temporalResolveMain.reflection.json",
"sha256": "9407ee1522f32df231d996ea691224863c023c1ca42cc02ccd7e16de1d873082",
"size": 3179
},
{
"path": "shaders/temporalResolveMain.spv",
"sha256": "4c5fc0da7a4c424e5426694c2c2d9f3cd17d1a64066712f5defe882ad8776662",
"size": 22192
},
{
"path": "shaders/gpuPostCullMain.reflection.json",
"sha256": "19ce128388c7f88e0c517fb58a25e46f1382e35ae5f376b10eebe155a2811c4c",
"size": 3591
},
{
"path": "shaders/gpuHzbMain.reflection.json",
"sha256": "589818f670881c64f3aba726ee3bc82fe4b9d835a347f99134e0e157dc4a112e",
"size": 2050
},
{
"path": "shaders/gpuCullMain.reflection.json",
"sha256": "8986a45a8d29743d16f0caece9181b18c85d6490d93b4a56cdb4f53052890f3f",
"size": 3587
},
{
"path": "shaders/gpuShadowMain.reflection.json",
"sha256": "96b3620b163dadf214933cfba6600b1b6a835c4d98b75b1f83e6ccd3dedb2425",
"size": 2755
},
{
"path": "shaders/gpuVertexMain.reflection.json",
"sha256": "01a6cc1bbf3c11c558560d6f8557fd4582d392906ed20a8c729673d0ebf640ed",
"size": 3098
},
{
"path": "shaders/gpuPostCullMain.spv",
"sha256": "1f8edd3be1f99f9433494a6f7be68f1317d6e7099e19354a7b8130d5d932224d",
"size": 12548
},
{
"path": "shaders/gpuHzbMain.spv",
"sha256": "08682362ee2120ed19d260e613285071747c3e436bb09bb6238e46f9cedc2308",
"size": 4140
},
{
"path": "shaders/gpuCullMain.spv",
"sha256": "e5ea6589ddb3613b59f8e58420be95a534cf4149816bc65bae26b1395c638955",
"size": 16288
},
{
"path": "shaders/gpuShadowMain.spv",
"sha256": "f56e0ae3e9b31cb1e3f24e379c81e11c853f8a7102648aaaebc92a17c7e8e53c",
"size": 2492
},
{
"path": "shaders/gpuVertexMain.spv",
"sha256": "32421d4a91f3a88d5210fee7af34b48bcad83072f14e2ccb9ef2ff983e45f78f",
"size": 5016
},
{
"path": "shaders/shadowMain.reflection.json",
"sha256": "d39e2f83c797589296eac9dce139b9ad836a895d6685bc40f79dd2609d687fee",
"size": 3597
},
{
"path": "shaders/fragmentMain.reflection.json",
"sha256": "2b07af2aae7578bdb6c2aa22658538821608b1dd5aeddd7b06e42b2e78cedf12",
"size": 3596
},
{
"path": "shaders/vertexMain.reflection.json",
"sha256": "b3bdff863d53a0b385e06a36701a83a6ecafe9a93e97281a732cce8ff862c0db",
"size": 3591
},
{
"path": "shaders/lightTileMain.reflection.json",
"sha256": "bf6a9256aaa6d6edacfa1e1c265d7c8ce2c1e4ea4036e0bea078c22fd4b536ca",
"size": 1978
},
{
"path": "shaders/lightTileMain.spv",
"sha256": "92850edcc5ee5b586a90ce84ee69a98075a6fff3c13db4a57528d9431afe8b68",
"size": 6800
},
{
"path": "shaders/shadowMain.spv",
"sha256": "7643b4d688492b5ee923b9606f0b0e70343ca05fa67673838206119a6f86d8d8",
"size": 784
},
{
"path": "shaders/fragmentMain.spv",
"sha256": "fea4dd48664a510bad12b5e8582f2db798d4dbea62ce0de35dc2acfe32372733",
"size": 28960
},
{
"path": "shaders/vertexMain.spv",
"sha256": "1ad2631c35d654f48166321ae43d4165043e1b919b14f1dc61caf97b1ada0898",
"size": 1224
},
{
"path": "faset_player",
"sha256": "52fbe0067fc2be3807e60ffa5a77e819838dc50a4b723af51f16379b4a487e7c",
"size": 6163816
},
{
"path": "scene.fscene",
"sha256": "21c364140669ec82c6886102667b255df5ccdf0ec77e0ca64060f84dd7b86632",
"size": 8639
}
],
"format": "faset.export",
"generation": "004c6e18-969b-4ef5-95bb-5d7742f96564",
"lua_enabled": false,
"lua_fingerprint": "",
"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_atlases": {
"caster_draw_limit": 4096,
"fallback_resolution": 1024,
"local_face_limit": 16,
"preferred_resolution": 2048,
"sampled_d32_required": true,
"sun_cascade_limit": 4
},
"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-p1-final-packages-4a3453e/Faset Café 世界/collect-3d/faset_player",
"--headless",
"--frames",
"120",
"--capture",
"/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-3d/verification.ppm",
"--profile",
"/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-3d/profile.json"
],
"cwd": "/tmp/faset-p1-final-packages-4a3453e/empty-working-directory",
"seconds": 0.6575766110036056,
"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,\"effective_temporal_mode\":\"off\",\"effective_visibility_mode\":\"direct\",\"frames\":120,\"gpu_visibility_active\":false,\"temporal_mode\":\"off\",\"ticks\":120,\"validation_errors\":0,\"visibility_mode\":\"direct\"}\n",
"stderr": "warning: material material-bb63b094febbf90bb7049c9bcf7587bc has features beyond the initial base-color/PBR renderer\n"
}
@@ -0,0 +1,12 @@
{
"arguments": [
"/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-3d/faset_player",
"--validate"
],
"cwd": "/tmp/faset-p1-final-packages-4a3453e/empty-working-directory",
"seconds": 0.00796160800382495,
"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"
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,294 @@
{
"asset_generations": {},
"build_fingerprint": "0bfae1f2850e06540c91a793a50673f53e9768434c02b5625dbe4839e27f23f0",
"configuration": "Release",
"executable": "faset_player",
"files": [
{
"path": "README.txt",
"sha256": "82bf72bc542643f6c4090e0a5de69b0014ca3fa17c6f9dfb12cc41e44c8b8db6",
"size": 262
},
{
"path": "Notices/Faset-NOTICE.txt",
"sha256": "30c2e7e80f5153b31ab57675e07246aef9165965d64e36022b426a26449ece3f",
"size": 204
},
{
"path": "Notices/dependencies.json",
"sha256": "262e8f63cf21c516829b1fedb0900ac90e76d62aa8f78d9ad02d8f24e0359e74",
"size": 2468
},
{
"path": "Notices/lua/LICENSE.txt",
"sha256": "e42d195c77d3ad7255ee9c874772d3c53998e7f60f9acf453d4630fa161e0bfe",
"size": 1126
},
{
"path": "Notices/stb/LICENSE",
"sha256": "bebfe904b14301657e4e5d655c811d51fd31b97c455b9cc2d8600d6bac6cff63",
"size": 2510
},
{
"path": "Notices/json/LICENSE.MIT",
"sha256": "46a65cffd1ea955132d95a8dd921640714a8d6b537d2e4e482d31145ae95b603",
"size": 1076
},
{
"path": "Notices/box3d/LICENSE",
"sha256": "da5e31a26bf3cfd5ba5c96d6823e480128c81e76c107ef9d3ee5d94789184b90",
"size": 1067
},
{
"path": "Notices/box2d/LICENSE",
"sha256": "68a3e676d7e94093b102d5cba0d4e04af812040d6f230c3db67a6664574e43d2",
"size": 1067
},
{
"path": "Notices/entt/LICENSE",
"sha256": "0785027ce472d7c61f05fba664a1d3ba6639c1593ced351ad9e4bed868765d99",
"size": 1097
},
{
"path": "Notices/sdl3/LICENSE.txt",
"sha256": "97f35b302b361680ec1e891e95d2d52097bb95abff361434916d99dc1305f127",
"size": 884
},
{
"path": "shaders/gpuTemporalVertexMain.reflection.json",
"sha256": "7a8efe2645e7ce465ca44d19f8cfb2eb6c767abd1c3ce12ed69a149a50be8a8c",
"size": 3308
},
{
"path": "shaders/gpuTemporalVertexMain.spv",
"sha256": "dfe7309aafbd41974668b295e179354965db440e4156b9f12d6a67db16b357f9",
"size": 5996
},
{
"path": "shaders/temporalFragmentMain.reflection.json",
"sha256": "e36452227788f3703d7342b6d9a7a0efef74ce4da3aa96bb176a26e169ec3628",
"size": 3874
},
{
"path": "shaders/temporalFragmentMain.spv",
"sha256": "03adc6673276979b3ea46becf3a36940d3061af77cb8c965c7471feff8dbf89a",
"size": 30976
},
{
"path": "shaders/temporalVertexMain.reflection.json",
"sha256": "6f7812ce88b14bca62a6d2ad82a8657e2ee48c252558f34677c6a2bd980a7edb",
"size": 3935
},
{
"path": "shaders/temporalVertexMain.spv",
"sha256": "6cc3535c952a33ee46fd417fbec7fcad8f24357508b2119256b3077be3d60955",
"size": 1784
},
{
"path": "shaders/temporalCompositeFragmentMain.reflection.json",
"sha256": "44765a41fc362c9a97b1a19834f285e2e7facb24d8715e3d8559b8e9633a7a0e",
"size": 959
},
{
"path": "shaders/temporalCompositeFragmentMain.spv",
"sha256": "9e21c8534c0e70a58ae018fb2d143bf9cfb70112e71b67585f525eef0aecad1b",
"size": 768
},
{
"path": "shaders/temporalCompositeVertexMain.reflection.json",
"sha256": "a3a2ea858bd038423896043b0703704f707addceb5bd56721cd897f6bd092065",
"size": 956
},
{
"path": "shaders/temporalCompositeVertexMain.spv",
"sha256": "6b19f65b64f40d0a249660fdfba9dd5d95831ffab7314a307bf1f5cf39c6a248",
"size": 340
},
{
"path": "shaders/temporalResolveMain.reflection.json",
"sha256": "9407ee1522f32df231d996ea691224863c023c1ca42cc02ccd7e16de1d873082",
"size": 3179
},
{
"path": "shaders/temporalResolveMain.spv",
"sha256": "4c5fc0da7a4c424e5426694c2c2d9f3cd17d1a64066712f5defe882ad8776662",
"size": 22192
},
{
"path": "shaders/gpuPostCullMain.reflection.json",
"sha256": "19ce128388c7f88e0c517fb58a25e46f1382e35ae5f376b10eebe155a2811c4c",
"size": 3591
},
{
"path": "shaders/gpuHzbMain.reflection.json",
"sha256": "589818f670881c64f3aba726ee3bc82fe4b9d835a347f99134e0e157dc4a112e",
"size": 2050
},
{
"path": "shaders/gpuCullMain.reflection.json",
"sha256": "8986a45a8d29743d16f0caece9181b18c85d6490d93b4a56cdb4f53052890f3f",
"size": 3587
},
{
"path": "shaders/gpuShadowMain.reflection.json",
"sha256": "96b3620b163dadf214933cfba6600b1b6a835c4d98b75b1f83e6ccd3dedb2425",
"size": 2755
},
{
"path": "shaders/gpuVertexMain.reflection.json",
"sha256": "01a6cc1bbf3c11c558560d6f8557fd4582d392906ed20a8c729673d0ebf640ed",
"size": 3098
},
{
"path": "shaders/gpuPostCullMain.spv",
"sha256": "1f8edd3be1f99f9433494a6f7be68f1317d6e7099e19354a7b8130d5d932224d",
"size": 12548
},
{
"path": "shaders/gpuHzbMain.spv",
"sha256": "08682362ee2120ed19d260e613285071747c3e436bb09bb6238e46f9cedc2308",
"size": 4140
},
{
"path": "shaders/gpuCullMain.spv",
"sha256": "e5ea6589ddb3613b59f8e58420be95a534cf4149816bc65bae26b1395c638955",
"size": 16288
},
{
"path": "shaders/gpuShadowMain.spv",
"sha256": "f56e0ae3e9b31cb1e3f24e379c81e11c853f8a7102648aaaebc92a17c7e8e53c",
"size": 2492
},
{
"path": "shaders/gpuVertexMain.spv",
"sha256": "32421d4a91f3a88d5210fee7af34b48bcad83072f14e2ccb9ef2ff983e45f78f",
"size": 5016
},
{
"path": "shaders/shadowMain.reflection.json",
"sha256": "d39e2f83c797589296eac9dce139b9ad836a895d6685bc40f79dd2609d687fee",
"size": 3597
},
{
"path": "shaders/fragmentMain.reflection.json",
"sha256": "2b07af2aae7578bdb6c2aa22658538821608b1dd5aeddd7b06e42b2e78cedf12",
"size": 3596
},
{
"path": "shaders/vertexMain.reflection.json",
"sha256": "b3bdff863d53a0b385e06a36701a83a6ecafe9a93e97281a732cce8ff862c0db",
"size": 3591
},
{
"path": "shaders/lightTileMain.reflection.json",
"sha256": "bf6a9256aaa6d6edacfa1e1c265d7c8ce2c1e4ea4036e0bea078c22fd4b536ca",
"size": 1978
},
{
"path": "shaders/lightTileMain.spv",
"sha256": "92850edcc5ee5b586a90ce84ee69a98075a6fff3c13db4a57528d9431afe8b68",
"size": 6800
},
{
"path": "shaders/shadowMain.spv",
"sha256": "7643b4d688492b5ee923b9606f0b0e70343ca05fa67673838206119a6f86d8d8",
"size": 784
},
{
"path": "shaders/fragmentMain.spv",
"sha256": "fea4dd48664a510bad12b5e8582f2db798d4dbea62ce0de35dc2acfe32372733",
"size": 28960
},
{
"path": "shaders/vertexMain.spv",
"sha256": "1ad2631c35d654f48166321ae43d4165043e1b919b14f1dc61caf97b1ada0898",
"size": 1224
},
{
"path": "faset_player",
"sha256": "6df88e53e4fff9376341fd3d049b4e047a268d0bf344184d903b6c17ff20bd0e",
"size": 6509384
},
{
"path": "project.faset.json",
"sha256": "e07b12047f8141c3c4cc8f0e7d0905a5c635375e4494755e34191db0859ab86f",
"size": 175
},
{
"path": "Scripts/util/motion.lua",
"sha256": "bad3dcdc1081fd1dec589c12dad8dc175b739d77ea52b957ead9ecb4b7cb88a3",
"size": 150
},
{
"path": "Scripts/player.lua",
"sha256": "1c51810574a3413c46a7cbd0ea2e84a9b802da1206a2b72bb78f6cc951367a11",
"size": 1376
},
{
"path": "Scripts/beacon.lua",
"sha256": "44d932ebedf66cadb9620f2d926177be2f8d43d71d56aa83611de686af026231",
"size": 820
},
{
"path": "scene.fscene",
"sha256": "641ba14a1f9c3966b9ec797814fdfa54f954dc5e0d53ab00c645598edef61baf",
"size": 1681
}
],
"format": "faset.export",
"generation": "92846e8b-d199-4679-8f6a-fb0686acba75",
"lua_enabled": true,
"lua_fingerprint": "a9261789bb1e0ca38bd40b08b0454165cd51454cba3ad4725dd357812ced6e35",
"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_atlases": {
"caster_draw_limit": 4096,
"fallback_resolution": 1024,
"local_face_limit": 16,
"preferred_resolution": 2048,
"sampled_d32_required": true,
"sun_cascade_limit": 4
},
"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": "143724a468d711cbdf9a9700310d7b0acef003ce1b21422ca5204e0e565fa02c",
"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
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
{
"arguments": [
"/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/lua/faset_player",
"--headless",
"--frames",
"120",
"--capture",
"/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/lua/verification.ppm",
"--profile",
"/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/lua/profile.json"
],
"cwd": "/tmp/faset-p1-final-packages-4a3453e/empty-working-directory",
"seconds": 0.6108254339778796,
"exit_code": 0,
"timed_out": false,
"stdout": "{\"device\":\"NVIDIA GeForce RTX 2080 Ti\",\"dimension\":2,\"effective_temporal_mode\":\"off\",\"effective_visibility_mode\":\"direct\",\"frames\":120,\"gpu_visibility_active\":false,\"temporal_mode\":\"off\",\"ticks\":120,\"validation_errors\":0,\"visibility_mode\":\"direct\"}\n",
"stderr": "Lua player ready: A/D move, Space jump, E reset\n"
}
@@ -0,0 +1,12 @@
{
"arguments": [
"/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/lua/faset_player",
"--validate"
],
"cwd": "/tmp/faset-p1-final-packages-4a3453e/empty-working-directory",
"seconds": 0.009439726010896266,
"exit_code": 0,
"timed_out": false,
"stdout": "{\"dimension\":2,\"validated\":true}\n",
"stderr": ""
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,82 @@
==========
VULKANINFO
==========
Vulkan Instance Version: 1.4.341
Instance Extensions: count = 27
-------------------------------
VK_EXT_acquire_drm_display : extension revision 1
VK_EXT_acquire_xlib_display : extension revision 1
VK_EXT_debug_report : extension revision 10
VK_EXT_debug_utils : extension revision 2
VK_EXT_direct_mode_display : extension revision 1
VK_EXT_display_surface_counter : extension revision 1
VK_EXT_headless_surface : extension revision 1
VK_EXT_layer_settings : extension revision 2
VK_EXT_surface_maintenance1 : extension revision 1
VK_EXT_swapchain_colorspace : extension revision 5
VK_KHR_device_group_creation : extension revision 1
VK_KHR_display : extension revision 23
VK_KHR_external_fence_capabilities : extension revision 1
VK_KHR_external_memory_capabilities : extension revision 1
VK_KHR_external_semaphore_capabilities : extension revision 1
VK_KHR_get_display_properties2 : extension revision 1
VK_KHR_get_physical_device_properties2 : extension revision 2
VK_KHR_get_surface_capabilities2 : extension revision 1
VK_KHR_portability_enumeration : extension revision 1
VK_KHR_surface : extension revision 25
VK_KHR_surface_maintenance1 : extension revision 1
VK_KHR_surface_protected_capabilities : extension revision 1
VK_KHR_wayland_surface : extension revision 6
VK_KHR_xcb_surface : extension revision 6
VK_KHR_xlib_surface : extension revision 6
VK_LUNARG_direct_driver_loading : extension revision 1
VK_NV_display_stereo : extension revision 1
Instance Layers: count = 12
---------------------------
VK_LAYER_INTEL_nullhw INTEL NULL HW 1.1.73 version 1
VK_LAYER_KHRONOS_validation Khronos Validation Layer 1.4.341 version 1
VK_LAYER_MESA_anti_lag Open-source implementation of the VK_AMD_anti_lag extension. 1.4.303 version 1
VK_LAYER_MESA_device_select Linux device selection layer 1.4.303 version 1
VK_LAYER_MESA_overlay Mesa Overlay layer 1.4.303 version 1
VK_LAYER_MESA_screenshot Mesa Screenshot layer 1.4.303 version 1
VK_LAYER_NV_optimus NVIDIA Optimus layer 1.4.329 version 1
VK_LAYER_NV_present NVIDIA Presentation Layer 1.4.329 version 1
VK_LAYER_VALVE_steam_fossilize_32 Steam Pipeline Caching Layer 1.3.207 version 1
VK_LAYER_VALVE_steam_fossilize_64 Steam Pipeline Caching Layer 1.3.207 version 1
VK_LAYER_VALVE_steam_overlay_32 Steam Overlay Layer 1.3.207 version 1
VK_LAYER_VALVE_steam_overlay_64 Steam Overlay Layer 1.3.207 version 1
Devices:
========
GPU0:
apiVersion = 1.4.329
driverVersion = 595.84.0.0
vendorID = 0x10de
deviceID = 0x1e04
deviceType = PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
deviceName = NVIDIA GeForce RTX 2080 Ti
driverID = DRIVER_ID_NVIDIA_PROPRIETARY
driverName = NVIDIA
driverInfo = 595.84
conformanceVersion = 1.4.3.3
deviceUUID = 2c71c7ba-97ac-c78e-2c06-af362631f3fe
driverUUID = 027cbdd1-c478-513e-af69-ebc9eaa7de87
GPU1:
apiVersion = 1.4.335
driverVersion = 26.0.8
vendorID = 0x10005
deviceID = 0x0000
deviceType = PHYSICAL_DEVICE_TYPE_CPU
deviceName = llvmpipe (LLVM 21.1.8, 256 bits)
driverID = DRIVER_ID_MESA_LLVMPIPE
driverName = llvmpipe
driverInfo = Mesa 26.0.8-1ubuntu0.3 (LLVM 21.1.8)
conformanceVersion = 1.3.1.1
deviceUUID = 6d657361-3236-2e30-2e38-2d3175627500
driverUUID = 6c6c766d-7069-7065-5555-494400000000
WARNING: [Loader Message] Code 0 : ICD for selected physical device does not export vkGetPhysicalDeviceDisplayPlanePropertiesKHR!
WARNING: [Loader Message] Code 0 : ICD for selected physical device does not export vkGetPhysicalDeviceDisplayPropertiesKHR!
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

@@ -0,0 +1,167 @@
#!/usr/bin/env python3
"""Profile the two relocated Release samples retained by verify_playable_exports.py."""
import argparse
import hashlib
import json
from pathlib import Path
import platform
import shutil
import subprocess
import time
def digest(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def read(path: Path) -> dict:
return json.loads(path.read_text(encoding="utf-8"))
def write(path: Path, value: object) -> None:
path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def require(condition: bool, message: str) -> None:
if not condition:
raise RuntimeError(message)
def run_source_hidden(source: dict, command: list[str], cwd: Path) -> subprocess.CompletedProcess:
projects = Path(source["project_workspace"]) / "Faset Café 世界"
disabled = projects.parent / "projects-offline-reference"
require(projects.is_dir() and not disabled.exists(),
"Disposable source projects must be available to hide")
projects.rename(disabled)
try:
require(not projects.exists(), "Source project paths remain available")
return subprocess.run(command, cwd=cwd, capture_output=True, text=True,
encoding="utf-8", errors="replace", timeout=300)
finally:
if disabled.exists() and not projects.exists():
disabled.rename(projects)
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--verification", type=Path, required=True,
help="Passed report.json from tools/verify_playable_exports.py")
parser.add_argument("--output", type=Path, required=True,
help="New/empty directory for raw 240-frame evidence")
args = parser.parse_args()
verification = args.verification.resolve()
output = args.output.resolve()
source = read(verification)
require(source.get("status") == "passed" and not source.get("engine_dirty"),
"A clean, passed export verification is required")
require(source.get("frames_per_game") == 120, "Expected a 120-frame verifier")
require(set(source.get("requested_projects", [])) ==
{"collect-2d", "collect-3d", "lua"} and
{item.get("name") for item in source.get("projects", [])} ==
{"collect-2d", "collect-3d", "lua"} and
all(item.get("status") == "passed" and
item.get("completed_frames") == 120 and
item.get("source_project_paths_unavailable") is True
for item in source["projects"]),
"Expected three source-hidden 120-frame Release games")
require(not output.exists() or not any(output.iterdir()),
"Output directory must be new or empty")
output.mkdir(parents=True, exist_ok=True)
shutil.copy2(verification, output / "playable-report.json")
cwd = Path(source["standalone_root"]) / "empty-working-directory"
require(cwd.is_dir(), "Verifier's unrelated working directory is missing")
limits = {"wall": 4.0, "gpu": 1.0, "renderer_readback_cpu": 1.0,
"simulation": 0.5, "snapshot": 0.5}
report = {"format": "faset.p1-release-reference", "version": 1,
"source_revision": source["engine_revision"],
"editor_sha256": source["editor_sha256"],
"verification_sha256": digest(verification),
"host": {"platform": platform.platform(), "machine": platform.machine(),
"processor": platform.processor()},
"method": "sequential relocated Release Player, default direct/temporal-off, "
"offscreen 1280x720, 240 completed frames including first; "
"nearest-rank p95, validation/readback enabled",
"projects": []}
for item in source["projects"]:
if item["name"] not in ("collect-2d", "collect-3d"):
continue
require(item.get("status") == "passed" and item.get("configuration") == "Release" and
item.get("source_project_paths_unavailable") is True,
f"Incomplete relocated export: {item['name']}")
name = item["name"]
package = Path(item["standalone_directory"])
player = package / item["executable"]
require(player.is_file() and not player.is_symlink(), f"Missing Player: {player}")
profile_path = output / f"{name}-profile-240.json"
command = [str(player), "--headless", "--frames", "240", "--profile",
str(profile_path)]
started = time.monotonic()
process = run_source_hidden(source, command, cwd)
invocation = {"arguments": command, "cwd": str(cwd),
"source_project_paths_unavailable": True,
"seconds": time.monotonic() - started,
"exit_code": process.returncode,
"stdout": process.stdout, "stderr": process.stderr}
write(output / f"{name}-run-240.json", invocation)
require(process.returncode == 0 and profile_path.is_file(),
f"240-frame Player failed; inspect {name}-run-240.json")
profile = read(profile_path)
require(profile.get("format") == "faset.player-profile" and
profile.get("completed_frames") == 240 and
len(profile.get("samples", [])) == 240 and
profile.get("dimension") == item["dimension"] and
profile.get("presentation_mode") == "offscreen" and
profile.get("width") == 1280 and profile.get("height") == 720 and
profile.get("visibility_mode") == "direct" and
profile.get("effective_visibility_mode") == "direct" and
profile.get("temporal_mode") == "off" and
profile.get("effective_temporal_mode") == "off" and
profile.get("percentile_method") ==
"nearest_rank_all_completed_frames_no_warmup_exclusion" and
profile.get("validation_enabled") is True and
profile.get("validation_errors") == 0,
f"Invalid reference profile: {profile_path}")
p95 = {key: profile["summary_ms"][key]["p95"] for key in limits}
allocated = max(sample["gpu_allocated_bytes"] for sample in profile["samples"])
sun_atlas = max(sample["sun_shadow_atlas_bytes"] for sample in profile["samples"])
local_atlas = max(sample["local_shadow_atlas_bytes"] for sample in profile["samples"])
startup = profile["startup_ms"]["main_to_first_frame"]
checks = {key: {"observed_ms": value, "limit_ms": limits[key],
"met": value is not None and value <= limits[key]}
for key, value in p95.items()}
checks["explicit_allocation"] = {"observed_bytes": allocated,
"limit_bytes": 20 * 1024 * 1024,
"met": allocated <= 20 * 1024 * 1024}
checks["startup"] = {"observed_ms": startup, "limit_ms": 500.0,
"met": startup <= 500.0}
result = {"name": name, "dimension": item["dimension"],
"device": profile["device"], "player_sha256": digest(player),
"manifest_sha256": digest(package / "manifest.json"),
"profile_sha256": digest(profile_path),
"requested_frames": profile["requested_frames"],
"completed_frames": profile["completed_frames"],
"summary_ms": profile["summary_ms"], "startup_ms": profile["startup_ms"],
"effective_lighting_path": profile.get("effective_lighting_path"),
"gpu_allocated_bytes_max": allocated,
"sun_shadow_atlas_bytes_max": sun_atlas,
"local_shadow_atlas_bytes_max": local_atlas,
"gpu_allocated_excluding_shadow_atlases_bytes_max": max(
sample["gpu_allocated_bytes"] -
sample["sun_shadow_atlas_bytes"] -
sample["local_shadow_atlas_bytes"]
for sample in profile["samples"]),
"effective_sun_cascades_max": max(
sample["effective_sun_cascades"] for sample in profile["samples"]),
"local_shadow_faces_max": max(
sample["local_shadow_faces"] for sample in profile["samples"]),
"budget_checks": checks}
report["projects"].append(result)
write(output / "reference-summary.json", report)
require({item["name"] for item in report["projects"]} == {"collect-2d", "collect-3d"},
"Both exact sample scenes are required")
print(output / "reference-summary.json")
if __name__ == "__main__":
main()
@@ -0,0 +1,111 @@
{
"format": "faset.p1-release-toolchain-provenance",
"version": 1,
"source_revision": "4a3453e2b1868555d833f578d43f48b8bc47d41a",
"source_clean_at_verifier_start": true,
"build_preset": "linux-release",
"configuration": "Release",
"host": {
"platform": "Linux-7.0.0-31-generic-x86_64-with-glibc2.43",
"machine": "x86_64",
"processor": "",
"logical_cpus": 16,
"cpu_model": "AMD Ryzen 7 1700 Eight-Core Processor",
"mem_total": "32784216 kB"
},
"report_sha256": "63da692b605621acc1cace00d4a3c9cf35de13abfd4351aea6653a88c870e03f",
"reference_summary_sha256": "f8297f3cb0578addee56c0b3cb9439b37ae37707b02d433d662b534620f329e9",
"editor_sha256": "73702e9f0fe015ef932db694400a0501048be2d602ab521ecc48c51767275d23",
"verifier_tool_sha256": "21485df78b81c22bce1f6dfc810997f3ac0505cacd8bc647786ce9d90a7c67e1",
"profile_helper_sha256": "5d69428a1202c1e144779074c0ab88360c459e2dd99b22c1fcf8929dbeab2618",
"selected_tools": {
"cmake": {
"selected": "/usr/bin/cmake",
"resolved": "/usr/bin/cmake",
"sha256": "6e1dccda39845415d68eabb934c598998949c99ec4668625d571aee1827b05c7",
"version_exit_code": 0,
"version_output": "cmake version 4.2.3\n\nCMake suite maintained and supported by Kitware (kitware.com/cmake).",
"version_flag": "--version"
},
"ninja": {
"selected": "/usr/bin/ninja",
"resolved": "/usr/bin/ninja",
"sha256": "91e9548850cda2799facfdaa7abe33f9e978832f85e98212255708a7bbe437f2",
"version_exit_code": 0,
"version_output": "1.13.2",
"version_flag": "--version"
},
"c_compiler": {
"selected": "clang",
"resolved": "/usr/lib/llvm-21/bin/clang",
"sha256": "412bbe8c60571a1eb06f48fde89635033621caeb01a9b4ee76d46711bae8e932",
"version_exit_code": 0,
"version_output": "Ubuntu clang version 21.1.8 (6ubuntu1)\nTarget: x86_64-pc-linux-gnu\nThread model: posix\nInstalledDir: /usr/lib/llvm-21/bin",
"version_flag": "--version"
},
"cxx_compiler": {
"selected": "clang++",
"resolved": "/usr/lib/llvm-21/bin/clang",
"sha256": "412bbe8c60571a1eb06f48fde89635033621caeb01a9b4ee76d46711bae8e932",
"version_exit_code": 0,
"version_output": "Ubuntu clang version 21.1.8 (6ubuntu1)\nTarget: x86_64-pc-linux-gnu\nThread model: posix\nInstalledDir: /usr/lib/llvm-21/bin",
"version_flag": "--version"
},
"slangc": {
"selected": "/home/emil/Desktop/.worktrees/Faset_Engine-p1-p3-integration/.cache/slang/bin/slangc",
"resolved": "/home/emil/Desktop/Faset_Engine/.cache/slang/bin/slangc",
"sha256": "ff47ad16a3110e43f96a45e90720e6f894371d27f5e47715b298159e395b75e8",
"version_exit_code": 0,
"version_output": "2026.18",
"version_flag": "-version"
}
},
"gpu": [
{
"project": "collect-2d",
"device": "NVIDIA GeForce RTX 2080 Ti",
"device_class": "physical",
"driver": {
"apiVersion": "1.4.329",
"driverVersion": "595.84.0.0",
"deviceName": "NVIDIA GeForce RTX 2080 Ti",
"driverID": "DRIVER_ID_NVIDIA_PROPRIETARY",
"driverName": "NVIDIA",
"driverInfo": "595.84"
},
"validation_enabled": true,
"validation_errors": 0
},
{
"project": "collect-3d",
"device": "NVIDIA GeForce RTX 2080 Ti",
"device_class": "physical",
"driver": {
"apiVersion": "1.4.329",
"driverVersion": "595.84.0.0",
"deviceName": "NVIDIA GeForce RTX 2080 Ti",
"driverID": "DRIVER_ID_NVIDIA_PROPRIETARY",
"driverName": "NVIDIA",
"driverInfo": "595.84"
},
"validation_enabled": true,
"validation_errors": 0
},
{
"project": "lua",
"device": "NVIDIA GeForce RTX 2080 Ti",
"device_class": "physical",
"driver": {
"apiVersion": "1.4.329",
"driverVersion": "595.84.0.0",
"deviceName": "NVIDIA GeForce RTX 2080 Ti",
"driverID": "DRIVER_ID_NVIDIA_PROPRIETARY",
"driverName": "NVIDIA",
"driverInfo": "595.84"
},
"validation_enabled": true,
"validation_errors": 0
}
],
"supplement_created_utc": "2026-09-24T02:15:34.021841+00:00"
}
@@ -0,0 +1 @@
/tmp/faset-p1-final-release-4a3453e/reference-240/reference-summary.json
@@ -0,0 +1,16 @@
{
"arguments": [
"/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-2d/faset_player",
"--headless",
"--frames",
"240",
"--profile",
"/tmp/faset-p1-final-release-4a3453e/reference-240/collect-2d-profile-240.json"
],
"cwd": "/tmp/faset-p1-final-packages-4a3453e/empty-working-directory",
"source_project_paths_unavailable": true,
"seconds": 0.9077925750170834,
"exit_code": 0,
"stdout": "New round: collect the three gold cubes, then reach the green exit. E resets.\n{\"device\":\"NVIDIA GeForce RTX 2080 Ti\",\"dimension\":2,\"effective_temporal_mode\":\"off\",\"effective_visibility_mode\":\"direct\",\"frames\":240,\"gpu_visibility_active\":false,\"temporal_mode\":\"off\",\"ticks\":240,\"validation_errors\":0,\"visibility_mode\":\"direct\"}\n",
"stderr": ""
}
@@ -0,0 +1,16 @@
{
"arguments": [
"/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-3d/faset_player",
"--headless",
"--frames",
"240",
"--profile",
"/tmp/faset-p1-final-release-4a3453e/reference-240/collect-3d-profile-240.json"
],
"cwd": "/tmp/faset-p1-final-packages-4a3453e/empty-working-directory",
"source_project_paths_unavailable": true,
"seconds": 0.969158330000937,
"exit_code": 0,
"stdout": "New round: collect the three gold cubes, then reach the green exit. E resets.\n{\"device\":\"NVIDIA GeForce RTX 2080 Ti\",\"dimension\":3,\"effective_temporal_mode\":\"off\",\"effective_visibility_mode\":\"direct\",\"frames\":240,\"gpu_visibility_active\":false,\"temporal_mode\":\"off\",\"ticks\":240,\"validation_errors\":0,\"visibility_mode\":\"direct\"}\n",
"stderr": "warning: material material-bb63b094febbf90bb7049c9bcf7587bc has features beyond the initial base-color/PBR renderer\n"
}
@@ -0,0 +1,577 @@
{
"format": "faset.playable-export-verification",
"version": 1,
"started_utc": "2026-09-24T02:01:24.205054+00:00",
"platform": "linux",
"engine": "/home/emil/Desktop/.worktrees/Faset_Engine-p1-p3-integration",
"editor": "/home/emil/Desktop/.worktrees/Faset_Engine-p1-p3-integration/build/linux-release/faset_editor",
"standalone_root": "/tmp/faset-p1-final-packages-4a3453e",
"engine_revision": "4a3453e2b1868555d833f578d43f48b8bc47d41a",
"engine_dirty": false,
"engine_git_status": [],
"engine_diff_sha256": null,
"editor_sha256": "73702e9f0fe015ef932db694400a0501048be2d602ab521ecc48c51767275d23",
"project_workspace": "/tmp/faset-p1-final-release-4a3453e",
"vulkaninfo_available": true,
"driver_override": null,
"frames_per_game": 120,
"status": "passed",
"projects": [
{
"name": "collect-2d",
"dimension": 2,
"language": "cpp",
"source_inputs": [
{
"path": ".gitignore",
"sha256": "53aa4d7124d4c8d93b8d7c4cb470b6c82b844e019a660d6243ffeadc50602de1"
},
{
"path": "README.md",
"sha256": "8cf5a2d6de849eb090b9ea6d5e27841f551bd06947e5054f0d2f59639a90edd0"
},
{
"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": "ca306aa8-a094-42bc-833f-a24a05cab73d",
"configuration": "Release",
"standalone_directory": "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-2d",
"executable": "faset_player",
"package_file_count": 41,
"asset_generations": {},
"device": "NVIDIA GeForce RTX 2080 Ti",
"device_class": "physical",
"driver": {
"apiVersion": "1.4.329",
"driverVersion": "595.84.0.0",
"deviceName": "NVIDIA GeForce RTX 2080 Ti",
"driverID": "DRIVER_ID_NVIDIA_PROPRIETARY",
"driverName": "NVIDIA",
"driverInfo": "595.84"
},
"validation_enabled": true,
"validation_errors": 0,
"completed_frames": 120,
"summary_ms": {
"gpu": {
"max": 0.581984,
"min": 0.555552,
"p50": 0.5592,
"p95": 0.568896,
"samples": 120
},
"render_call": {
"max": 6.021328,
"min": 1.627681,
"p50": 1.892219,
"p95": 2.165605,
"samples": 120
},
"renderer_cpu": {
"max": 6.013313,
"min": 1.621609,
"p50": 1.883223,
"p95": 2.15763,
"samples": 120
},
"renderer_readback_cpu": {
"max": 2.645991,
"min": 0.386439,
"p50": 0.478583,
"p95": 0.582889,
"samples": 120
},
"simulation": {
"max": 0.332657,
"min": 0.135175,
"p50": 0.166364,
"p95": 0.244932,
"samples": 120
},
"snapshot": {
"max": 0.250653,
"min": 0.130637,
"p50": 0.153339,
"p95": 0.221498,
"samples": 120
},
"wall": {
"max": 6.632972,
"min": 1.913981,
"p50": 2.233333,
"p95": 2.59779,
"samples": 120
}
},
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 7,
"sha256": "f9d74067d87c1ebf30c6db372a26d93b7285791c8cb0307a24af5adb23bf201a"
},
"source_project_paths_unavailable": true,
"status": "passed"
},
{
"name": "collect-3d",
"dimension": 3,
"language": "cpp",
"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": "/tmp/faset-p1-final-release-4a3453e/Faset Café 世界/collect-3d/Assets/exit-arch/payload/ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3.glb",
"schema_version": 1,
"settings": {},
"source": "/tmp/faset-p1-final-release-4a3453e/Faset Café 世界/collect-3d/Assets/exit-arch/manifest.json",
"source_sha256": "ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3",
"textures": []
},
"previous_generation": "",
"removed_output_ids": []
},
"generation": "004c6e18-969b-4ef5-95bb-5d7742f96564",
"configuration": "Release",
"standalone_directory": "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-3d",
"executable": "faset_player",
"package_file_count": 46,
"asset_generations": {
"5832763b-3ed0-44d6-9088-0b524f196a91": "4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2"
},
"device": "NVIDIA GeForce RTX 2080 Ti",
"device_class": "physical",
"driver": {
"apiVersion": "1.4.329",
"driverVersion": "595.84.0.0",
"deviceName": "NVIDIA GeForce RTX 2080 Ti",
"driverID": "DRIVER_ID_NVIDIA_PROPRIETARY",
"driverName": "NVIDIA",
"driverInfo": "595.84"
},
"validation_enabled": true,
"validation_errors": 0,
"completed_frames": 120,
"summary_ms": {
"gpu": {
"max": 0.62432,
"min": 0.592032,
"p50": 0.593792,
"p95": 0.61008,
"samples": 120
},
"render_call": {
"max": 5.917472,
"min": 1.873163,
"p50": 2.209838,
"p95": 2.552535,
"samples": 120
},
"renderer_cpu": {
"max": 5.909758,
"min": 1.867162,
"p50": 2.201673,
"p95": 2.53905,
"samples": 120
},
"renderer_readback_cpu": {
"max": 2.530744,
"min": 0.388893,
"p50": 0.499071,
"p95": 0.636931,
"samples": 120
},
"simulation": {
"max": 0.608728,
"min": 0.172335,
"p50": 0.203934,
"p95": 0.284217,
"samples": 120
},
"snapshot": {
"max": 0.714438,
"min": 0.170371,
"p50": 0.191832,
"p95": 0.280138,
"samples": 120
},
"wall": {
"max": 6.969116,
"min": 2.24785,
"p50": 2.627506,
"p95": 3.081914,
"samples": 120
}
},
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 65,
"sha256": "2905891c9766b2f1f65d5b4284056cb3839f1faae195f9fea39a56e3ebf44363"
},
"source_project_paths_unavailable": true,
"status": "passed"
},
{
"name": "lua",
"dimension": 2,
"language": "lua",
"source_inputs": [
{
"path": "README.md",
"sha256": "8392da3c18916400ea628daed094c77c390c553cbdcf822da227bdcd3d86380c"
},
{
"path": "Scenes/main.scene.json",
"sha256": "f1ab54150a58b289c47bcc34566ad7ee3025ad931db81e7f5dce70d9fbcac8ab"
},
{
"path": "Scripts/beacon.lua",
"sha256": "44d932ebedf66cadb9620f2d926177be2f8d43d71d56aa83611de686af026231"
},
{
"path": "Scripts/player.lua",
"sha256": "1c51810574a3413c46a7cbd0ea2e84a9b802da1206a2b72bb78f6cc951367a11"
},
{
"path": "Scripts/util/motion.lua",
"sha256": "bad3dcdc1081fd1dec589c12dad8dc175b739d77ea52b957ead9ecb4b7cb88a3"
},
{
"path": "project.faset.json",
"sha256": "7e4c0781c1837208b4499a9991ffb702143edf1f933ae9c7e3d2543fd2577747"
}
],
"generation": "92846e8b-d199-4679-8f6a-fb0686acba75",
"configuration": "Release",
"standalone_directory": "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/lua",
"executable": "faset_player",
"package_file_count": 46,
"asset_generations": {},
"device": "NVIDIA GeForce RTX 2080 Ti",
"device_class": "physical",
"driver": {
"apiVersion": "1.4.329",
"driverVersion": "595.84.0.0",
"deviceName": "NVIDIA GeForce RTX 2080 Ti",
"driverID": "DRIVER_ID_NVIDIA_PROPRIETARY",
"driverName": "NVIDIA",
"driverInfo": "595.84"
},
"validation_enabled": true,
"validation_errors": 0,
"completed_frames": 120,
"summary_ms": {
"gpu": {
"max": 0.589568,
"min": 0.551712,
"p50": 0.552416,
"p95": 0.570016,
"samples": 120
},
"render_call": {
"max": 6.280958,
"min": 1.607562,
"p50": 1.83424,
"p95": 2.180794,
"samples": 120
},
"renderer_cpu": {
"max": 6.273714,
"min": 1.602192,
"p50": 1.828289,
"p95": 2.169262,
"samples": 120
},
"renderer_readback_cpu": {
"max": 3.40345,
"min": 0.412939,
"p50": 0.503159,
"p95": 0.622655,
"samples": 120
},
"simulation": {
"max": 0.231888,
"min": 0.080883,
"p50": 0.103485,
"p95": 0.166965,
"samples": 120
},
"snapshot": {
"max": 0.136677,
"min": 0.046938,
"p50": 0.056226,
"p95": 0.083728,
"samples": 120
},
"wall": {
"max": 6.601142,
"min": 1.775219,
"p50": 2.019429,
"p95": 2.450732,
"samples": 120
}
},
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 4,
"sha256": "e2f4c3b84de75dadd2e31d388d0ba51abc1861f1ec76eb7ba51a4a2dcac720a7"
},
"source_project_paths_unavailable": true,
"status": "passed"
}
],
"requested_projects": [
"collect-2d",
"collect-3d",
"lua"
],
"finished_utc": "2026-09-24T02:07:41.244688+00:00"
}
@@ -0,0 +1,237 @@
{
"format": "faset.p1-release-reference",
"version": 1,
"source_revision": "4a3453e2b1868555d833f578d43f48b8bc47d41a",
"editor_sha256": "73702e9f0fe015ef932db694400a0501048be2d602ab521ecc48c51767275d23",
"verification_sha256": "63da692b605621acc1cace00d4a3c9cf35de13abfd4351aea6653a88c870e03f",
"host": {
"platform": "Linux-7.0.0-31-generic-x86_64-with-glibc2.43",
"machine": "x86_64",
"processor": ""
},
"method": "sequential relocated Release Player, default direct/temporal-off, offscreen 1280x720, 240 completed frames including first; nearest-rank p95, validation/readback enabled",
"projects": [
{
"name": "collect-2d",
"dimension": 2,
"device": "NVIDIA GeForce RTX 2080 Ti",
"player_sha256": "5936d8a1ceae9250c69f1b8ebccbadebc40b13e812bebeb9752da3d14d202a4a",
"manifest_sha256": "5f8de3eb000dc7207b256265f0fa20a29efe194dd1347ab4c3bf670cfdcfb2b0",
"profile_sha256": "65858b1de2334d304ad3a0907180aaf4f79ead3da63762500e50b7298d922bfa",
"requested_frames": 240,
"completed_frames": 240,
"summary_ms": {
"gpu": {
"max": 0.585248,
"min": 0.552064,
"p50": 0.557184,
"p95": 0.573056,
"samples": 240
},
"render_call": {
"max": 6.04885,
"min": 1.470684,
"p50": 1.795998,
"p95": 2.305238,
"samples": 240
},
"renderer_cpu": {
"max": 6.043249,
"min": 1.466045,
"p50": 1.79204,
"p95": 2.300209,
"samples": 240
},
"renderer_readback_cpu": {
"max": 2.620153,
"min": 0.398713,
"p50": 0.462312,
"p95": 0.559205,
"samples": 240
},
"simulation": {
"max": 0.357284,
"min": 0.130406,
"p50": 0.168848,
"p95": 0.242057,
"samples": 240
},
"snapshot": {
"max": 0.338358,
"min": 0.124996,
"p50": 0.157076,
"p95": 0.197363,
"samples": 240
},
"wall": {
"max": 6.47329,
"min": 1.746054,
"p50": 2.137342,
"p95": 2.693952,
"samples": 240
}
},
"startup_ms": {
"main_to_first_frame": 295.436243,
"renderer_initialization": 284.655357,
"scene_read": 0.410555,
"world_initialization": 0.64129
},
"effective_lighting_path": "forward",
"gpu_allocated_bytes_max": 45112032,
"sun_shadow_atlas_bytes_max": 16777216,
"local_shadow_atlas_bytes_max": 16777216,
"gpu_allocated_excluding_shadow_atlases_bytes_max": 11557600,
"effective_sun_cascades_max": 0,
"local_shadow_faces_max": 0,
"budget_checks": {
"wall": {
"observed_ms": 2.693952,
"limit_ms": 4.0,
"met": true
},
"gpu": {
"observed_ms": 0.573056,
"limit_ms": 1.0,
"met": true
},
"renderer_readback_cpu": {
"observed_ms": 0.559205,
"limit_ms": 1.0,
"met": true
},
"simulation": {
"observed_ms": 0.242057,
"limit_ms": 0.5,
"met": true
},
"snapshot": {
"observed_ms": 0.197363,
"limit_ms": 0.5,
"met": true
},
"explicit_allocation": {
"observed_bytes": 45112032,
"limit_bytes": 20971520,
"met": false
},
"startup": {
"observed_ms": 295.436243,
"limit_ms": 500.0,
"met": true
}
}
},
{
"name": "collect-3d",
"dimension": 3,
"device": "NVIDIA GeForce RTX 2080 Ti",
"player_sha256": "52fbe0067fc2be3807e60ffa5a77e819838dc50a4b723af51f16379b4a487e7c",
"manifest_sha256": "484eda90411c48277385a2f679561cfa6304965a7a7be6ec065bfbd9dace1722",
"profile_sha256": "b0c4c6b4020b1ded792917fff1f36510b76bb30864d89bd4e6e250b5693c5fe8",
"requested_frames": 240,
"completed_frames": 240,
"summary_ms": {
"gpu": {
"max": 0.628288,
"min": 0.590144,
"p50": 0.592224,
"p95": 0.616512,
"samples": 240
},
"render_call": {
"max": 5.902553,
"min": 1.958014,
"p50": 2.192415,
"p95": 2.551243,
"samples": 240
},
"renderer_cpu": {
"max": 5.89525,
"min": 1.949527,
"p50": 2.184501,
"p95": 2.543007,
"samples": 240
},
"renderer_readback_cpu": {
"max": 2.581069,
"min": 0.350231,
"p50": 0.454097,
"p95": 0.560908,
"samples": 240
},
"simulation": {
"max": 0.432225,
"min": 0.170772,
"p50": 0.191732,
"p95": 0.299776,
"samples": 240
},
"snapshot": {
"max": 0.745236,
"min": 0.171273,
"p50": 0.188084,
"p95": 0.287632,
"samples": 240
},
"wall": {
"max": 7.001186,
"min": 2.330556,
"p50": 2.605595,
"p95": 3.036327,
"samples": 240
}
},
"startup_ms": {
"main_to_first_frame": 248.272928,
"renderer_initialization": 237.578356,
"scene_read": 0.506575,
"world_initialization": 0.62037
},
"effective_lighting_path": "forward",
"gpu_allocated_bytes_max": 45212272,
"sun_shadow_atlas_bytes_max": 16777216,
"local_shadow_atlas_bytes_max": 16777216,
"gpu_allocated_excluding_shadow_atlases_bytes_max": 11657840,
"effective_sun_cascades_max": 4,
"local_shadow_faces_max": 0,
"budget_checks": {
"wall": {
"observed_ms": 3.036327,
"limit_ms": 4.0,
"met": true
},
"gpu": {
"observed_ms": 0.616512,
"limit_ms": 1.0,
"met": true
},
"renderer_readback_cpu": {
"observed_ms": 0.560908,
"limit_ms": 1.0,
"met": true
},
"simulation": {
"observed_ms": 0.299776,
"limit_ms": 0.5,
"met": true
},
"snapshot": {
"observed_ms": 0.287632,
"limit_ms": 0.5,
"met": true
},
"explicit_allocation": {
"observed_bytes": 45212272,
"limit_bytes": 20971520,
"met": false
},
"startup": {
"observed_ms": 248.272928,
"limit_ms": 500.0,
"met": true
}
}
}
]
}
@@ -0,0 +1,577 @@
{
"format": "faset.playable-export-verification",
"version": 1,
"started_utc": "2026-09-24T02:01:24.205054+00:00",
"platform": "linux",
"engine": "/home/emil/Desktop/.worktrees/Faset_Engine-p1-p3-integration",
"editor": "/home/emil/Desktop/.worktrees/Faset_Engine-p1-p3-integration/build/linux-release/faset_editor",
"standalone_root": "/tmp/faset-p1-final-packages-4a3453e",
"engine_revision": "4a3453e2b1868555d833f578d43f48b8bc47d41a",
"engine_dirty": false,
"engine_git_status": [],
"engine_diff_sha256": null,
"editor_sha256": "73702e9f0fe015ef932db694400a0501048be2d602ab521ecc48c51767275d23",
"project_workspace": "/tmp/faset-p1-final-release-4a3453e",
"vulkaninfo_available": true,
"driver_override": null,
"frames_per_game": 120,
"status": "passed",
"projects": [
{
"name": "collect-2d",
"dimension": 2,
"language": "cpp",
"source_inputs": [
{
"path": ".gitignore",
"sha256": "53aa4d7124d4c8d93b8d7c4cb470b6c82b844e019a660d6243ffeadc50602de1"
},
{
"path": "README.md",
"sha256": "8cf5a2d6de849eb090b9ea6d5e27841f551bd06947e5054f0d2f59639a90edd0"
},
{
"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": "ca306aa8-a094-42bc-833f-a24a05cab73d",
"configuration": "Release",
"standalone_directory": "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-2d",
"executable": "faset_player",
"package_file_count": 41,
"asset_generations": {},
"device": "NVIDIA GeForce RTX 2080 Ti",
"device_class": "physical",
"driver": {
"apiVersion": "1.4.329",
"driverVersion": "595.84.0.0",
"deviceName": "NVIDIA GeForce RTX 2080 Ti",
"driverID": "DRIVER_ID_NVIDIA_PROPRIETARY",
"driverName": "NVIDIA",
"driverInfo": "595.84"
},
"validation_enabled": true,
"validation_errors": 0,
"completed_frames": 120,
"summary_ms": {
"gpu": {
"max": 0.581984,
"min": 0.555552,
"p50": 0.5592,
"p95": 0.568896,
"samples": 120
},
"render_call": {
"max": 6.021328,
"min": 1.627681,
"p50": 1.892219,
"p95": 2.165605,
"samples": 120
},
"renderer_cpu": {
"max": 6.013313,
"min": 1.621609,
"p50": 1.883223,
"p95": 2.15763,
"samples": 120
},
"renderer_readback_cpu": {
"max": 2.645991,
"min": 0.386439,
"p50": 0.478583,
"p95": 0.582889,
"samples": 120
},
"simulation": {
"max": 0.332657,
"min": 0.135175,
"p50": 0.166364,
"p95": 0.244932,
"samples": 120
},
"snapshot": {
"max": 0.250653,
"min": 0.130637,
"p50": 0.153339,
"p95": 0.221498,
"samples": 120
},
"wall": {
"max": 6.632972,
"min": 1.913981,
"p50": 2.233333,
"p95": 2.59779,
"samples": 120
}
},
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 7,
"sha256": "f9d74067d87c1ebf30c6db372a26d93b7285791c8cb0307a24af5adb23bf201a"
},
"source_project_paths_unavailable": true,
"status": "passed"
},
{
"name": "collect-3d",
"dimension": 3,
"language": "cpp",
"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": "/tmp/faset-p1-final-release-4a3453e/Faset Café 世界/collect-3d/Assets/exit-arch/payload/ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3.glb",
"schema_version": 1,
"settings": {},
"source": "/tmp/faset-p1-final-release-4a3453e/Faset Café 世界/collect-3d/Assets/exit-arch/manifest.json",
"source_sha256": "ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3",
"textures": []
},
"previous_generation": "",
"removed_output_ids": []
},
"generation": "004c6e18-969b-4ef5-95bb-5d7742f96564",
"configuration": "Release",
"standalone_directory": "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-3d",
"executable": "faset_player",
"package_file_count": 46,
"asset_generations": {
"5832763b-3ed0-44d6-9088-0b524f196a91": "4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2"
},
"device": "NVIDIA GeForce RTX 2080 Ti",
"device_class": "physical",
"driver": {
"apiVersion": "1.4.329",
"driverVersion": "595.84.0.0",
"deviceName": "NVIDIA GeForce RTX 2080 Ti",
"driverID": "DRIVER_ID_NVIDIA_PROPRIETARY",
"driverName": "NVIDIA",
"driverInfo": "595.84"
},
"validation_enabled": true,
"validation_errors": 0,
"completed_frames": 120,
"summary_ms": {
"gpu": {
"max": 0.62432,
"min": 0.592032,
"p50": 0.593792,
"p95": 0.61008,
"samples": 120
},
"render_call": {
"max": 5.917472,
"min": 1.873163,
"p50": 2.209838,
"p95": 2.552535,
"samples": 120
},
"renderer_cpu": {
"max": 5.909758,
"min": 1.867162,
"p50": 2.201673,
"p95": 2.53905,
"samples": 120
},
"renderer_readback_cpu": {
"max": 2.530744,
"min": 0.388893,
"p50": 0.499071,
"p95": 0.636931,
"samples": 120
},
"simulation": {
"max": 0.608728,
"min": 0.172335,
"p50": 0.203934,
"p95": 0.284217,
"samples": 120
},
"snapshot": {
"max": 0.714438,
"min": 0.170371,
"p50": 0.191832,
"p95": 0.280138,
"samples": 120
},
"wall": {
"max": 6.969116,
"min": 2.24785,
"p50": 2.627506,
"p95": 3.081914,
"samples": 120
}
},
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 65,
"sha256": "2905891c9766b2f1f65d5b4284056cb3839f1faae195f9fea39a56e3ebf44363"
},
"source_project_paths_unavailable": true,
"status": "passed"
},
{
"name": "lua",
"dimension": 2,
"language": "lua",
"source_inputs": [
{
"path": "README.md",
"sha256": "8392da3c18916400ea628daed094c77c390c553cbdcf822da227bdcd3d86380c"
},
{
"path": "Scenes/main.scene.json",
"sha256": "f1ab54150a58b289c47bcc34566ad7ee3025ad931db81e7f5dce70d9fbcac8ab"
},
{
"path": "Scripts/beacon.lua",
"sha256": "44d932ebedf66cadb9620f2d926177be2f8d43d71d56aa83611de686af026231"
},
{
"path": "Scripts/player.lua",
"sha256": "1c51810574a3413c46a7cbd0ea2e84a9b802da1206a2b72bb78f6cc951367a11"
},
{
"path": "Scripts/util/motion.lua",
"sha256": "bad3dcdc1081fd1dec589c12dad8dc175b739d77ea52b957ead9ecb4b7cb88a3"
},
{
"path": "project.faset.json",
"sha256": "7e4c0781c1837208b4499a9991ffb702143edf1f933ae9c7e3d2543fd2577747"
}
],
"generation": "92846e8b-d199-4679-8f6a-fb0686acba75",
"configuration": "Release",
"standalone_directory": "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/lua",
"executable": "faset_player",
"package_file_count": 46,
"asset_generations": {},
"device": "NVIDIA GeForce RTX 2080 Ti",
"device_class": "physical",
"driver": {
"apiVersion": "1.4.329",
"driverVersion": "595.84.0.0",
"deviceName": "NVIDIA GeForce RTX 2080 Ti",
"driverID": "DRIVER_ID_NVIDIA_PROPRIETARY",
"driverName": "NVIDIA",
"driverInfo": "595.84"
},
"validation_enabled": true,
"validation_errors": 0,
"completed_frames": 120,
"summary_ms": {
"gpu": {
"max": 0.589568,
"min": 0.551712,
"p50": 0.552416,
"p95": 0.570016,
"samples": 120
},
"render_call": {
"max": 6.280958,
"min": 1.607562,
"p50": 1.83424,
"p95": 2.180794,
"samples": 120
},
"renderer_cpu": {
"max": 6.273714,
"min": 1.602192,
"p50": 1.828289,
"p95": 2.169262,
"samples": 120
},
"renderer_readback_cpu": {
"max": 3.40345,
"min": 0.412939,
"p50": 0.503159,
"p95": 0.622655,
"samples": 120
},
"simulation": {
"max": 0.231888,
"min": 0.080883,
"p50": 0.103485,
"p95": 0.166965,
"samples": 120
},
"snapshot": {
"max": 0.136677,
"min": 0.046938,
"p50": 0.056226,
"p95": 0.083728,
"samples": 120
},
"wall": {
"max": 6.601142,
"min": 1.775219,
"p50": 2.019429,
"p95": 2.450732,
"samples": 120
}
},
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 4,
"sha256": "e2f4c3b84de75dadd2e31d388d0ba51abc1861f1ec76eb7ba51a4a2dcac720a7"
},
"source_project_paths_unavailable": true,
"status": "passed"
}
],
"requested_projects": [
"collect-2d",
"collect-3d",
"lua"
],
"finished_utc": "2026-09-24T02:07:41.244688+00:00"
}
@@ -0,0 +1,11 @@
collect-2d-export: /home/emil/Desktop/.worktrees/Faset_Engine-p1-p3-integration/build/linux-release/faset_editor --engine /home/emil/Desktop/.worktrees/Faset_Engine-p1-p3-integration --project "/tmp/faset-p1-final-release-4a3453e/Faset Café 世界/collect-2d" --command "{\"name\": \"faset_export\", \"arguments\": {\"document\": \"example-collect-2d-scene\", \"output\": \"Exports\"}}" --wait
collect-3d-import: /home/emil/Desktop/.worktrees/Faset_Engine-p1-p3-integration/build/linux-release/faset_editor --engine /home/emil/Desktop/.worktrees/Faset_Engine-p1-p3-integration --project "/tmp/faset-p1-final-release-4a3453e/Faset Café 世界/collect-3d" --command "{\"name\": \"faset_import\", \"arguments\": {\"path\": \"Assets/exit-arch/manifest.json\"}}" --wait
collect-3d-export: /home/emil/Desktop/.worktrees/Faset_Engine-p1-p3-integration/build/linux-release/faset_editor --engine /home/emil/Desktop/.worktrees/Faset_Engine-p1-p3-integration --project "/tmp/faset-p1-final-release-4a3453e/Faset Café 世界/collect-3d" --command "{\"name\": \"faset_export\", \"arguments\": {\"document\": \"example-collect-3d-scene\", \"output\": \"Exports\"}}" --wait
lua-export: /home/emil/Desktop/.worktrees/Faset_Engine-p1-p3-integration/build/linux-release/faset_editor --engine /home/emil/Desktop/.worktrees/Faset_Engine-p1-p3-integration --project "/tmp/faset-p1-final-release-4a3453e/Faset Café 世界/lua" --command "{\"name\": \"faset_export\", \"arguments\": {\"document\": \"example-lua-scene\", \"output\": \"Exports\"}}" --wait
collect-2d-validate: "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-2d/faset_player" --validate
collect-2d-run: "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-2d/faset_player" --headless --frames 120 --capture "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-2d/verification.ppm" --profile "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-2d/profile.json"
collect-3d-validate: "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-3d/faset_player" --validate
collect-3d-run: "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-3d/faset_player" --headless --frames 120 --capture "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-3d/verification.ppm" --profile "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/collect-3d/profile.json"
lua-validate: "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/lua/faset_player" --validate
lua-run: "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/lua/faset_player" --headless --frames 120 --capture "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/lua/verification.ppm" --profile "/tmp/faset-p1-final-packages-4a3453e/Faset Café 世界/lua/profile.json"
3 relocated Release game(s) passed: /tmp/faset-p1-final-release-4a3453e/report.json
@@ -0,0 +1,53 @@
{
"format": "faset.workflow-provenance-supplement",
"version": 1,
"captured_at_utc": "2026-09-24T00:17:12.501486+00:00",
"measurement_revision": "6c2e8fa80036315a695f67f3c53524165fb4b10a",
"collector_revision": "abd8c21273ac886f6afe30e728a27a65b882f11f",
"report_sha256": "fd463c0cd12eed0a22143aa68bd732386804b0df4ecb2a3d9caa26f59dcfe6e2",
"measurement_script_sha256": "dc13c1b253b8093ac4a528b7194a1e54e0cb00fdfe0881021e6740de40717ee3",
"collector_script_sha256": "8fd8098c11e01ef6e0b2c0681f26d62b1dedf0e25840222642ec5d77df248868",
"archived_cmake_cache": "CMakeCache.txt.gz",
"binary_provenance": {
"cmake_cache_sha256": "d82a2b2ed39430f9ebba82b87b182a1a8822e2419a69778eb4dadcb074064c73",
"binaries": {
"cmake": {
"path": "/usr/bin/cmake",
"sha256": "6e1dccda39845415d68eabb934c598998949c99ec4668625d571aee1827b05c7",
"version": "cmake version 4.2.3"
},
"ninja": {
"path": "/usr/bin/ninja",
"sha256": "91e9548850cda2799facfdaa7abe33f9e978832f85e98212255708a7bbe437f2",
"version": "1.13.2"
},
"c": {
"path": "/usr/lib/llvm-21/bin/clang",
"sha256": "412bbe8c60571a1eb06f48fde89635033621caeb01a9b4ee76d46711bae8e932",
"version": "Ubuntu clang version 21.1.8 (6ubuntu1)"
},
"cxx": {
"path": "/usr/lib/llvm-21/bin/clang",
"sha256": "412bbe8c60571a1eb06f48fde89635033621caeb01a9b4ee76d46711bae8e932",
"version": "Ubuntu clang version 21.1.8 (6ubuntu1)"
},
"slang": {
"path": "/home/emil/Desktop/Faset_Engine/.cache/slang/bin/slangc",
"sha256": "ff47ad16a3110e43f96a45e90720e6f894371d27f5e47715b298159e395b75e8",
"version": "2026.18"
},
"editor": {
"path": "/home/emil/Desktop/.worktrees/Faset_Engine-p1-p3/build/linux-debug/faset_editor",
"sha256": "e0cd1a3e7dec54e6960723b1a9068602a17e84e14e490289ea6de6ebf3964657",
"version": null
},
"ui_latency_binary": {
"path": "/home/emil/Desktop/.worktrees/Faset_Engine-p1-p3/build/linux-debug/faset_editor_ui_latency",
"sha256": "c1b7f68c0e68d5c38d458330d22cbaa7dd6ed2d2962df520fd99cf7eb71ea1ed",
"version": null
}
}
},
"note": "Captured after the completed run from its surviving disposable CMake cache and unchanged measured binaries; report.json is the unmodified original run output.",
"cmake_cache_archive_sha256": "da52ab111b3b1a14a73f322e4ad7594ac55e72512074ec92c1889f03ae98a38b"
}
@@ -0,0 +1,2 @@
Command exited with non-zero status 1
{"peak_rss_kib":303308}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,17 @@
{
"seconds": 9.99945926599321,
"returncode": 1,
"build_configuration": "Debug",
"source_hash": "9cf76ceab03f32fc0baf1eebf6db150fe386fe685ee3a9137271a806f97bdff9",
"command": [
"/home/emil/Desktop/.worktrees/Faset_Engine-p1-p3/build/linux-debug/faset_editor",
"--project",
"/tmp/faset-p1-workflows-20260924-3/project",
"--command",
"{\"name\": \"faset_build\", \"arguments\": {}}",
"--wait"
],
"stdout": "raw/build_failure-01-stdout.txt",
"stderr": "raw/build_failure-01-stderr.txt",
"peak_rss_kib": 303308
}
@@ -0,0 +1 @@
{"peak_rss_kib":351984}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,17 @@
{
"seconds": 19.928895885997918,
"returncode": 0,
"build_configuration": "Debug",
"source_hash": "801fb7ba8f67c774ab1d7ec6350c8f099bf37aa6cd34afc952f7e81954df971e",
"command": [
"/home/emil/Desktop/.worktrees/Faset_Engine-p1-p3/build/linux-debug/faset_editor",
"--project",
"/tmp/faset-p1-workflows-20260924-3/project",
"--command",
"{\"name\": \"faset_build\", \"arguments\": {}}",
"--wait"
],
"stdout": "raw/build_recovery-01-stdout.txt",
"stderr": "raw/build_recovery-01-stderr.txt",
"peak_rss_kib": 351984
}
@@ -0,0 +1 @@
{"peak_rss_kib":9248}
@@ -0,0 +1,210 @@
{
"error": "",
"id": "import-ae79d218-16e3-4275-a83b-1c3ac2aa99f3",
"kind": "import",
"progress": 1.0,
"request": {
"allow_removed_outputs": false,
"path": "Assets/exit-arch/manifest.json",
"settings": null
},
"result": {
"asset_id": "5832763b-3ed0-44d6-9088-0b524f196a91",
"cache_hit": true,
"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": "/tmp/faset-p1-workflows-20260924-3/project/Assets/exit-arch/payload/ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3.glb",
"schema_version": 1,
"settings": {},
"source": "/tmp/faset-p1-workflows-20260924-3/project/Assets/exit-arch/manifest.json",
"source_sha256": "ee6c748a212905fc9c465cbe95c033e94b80be643624f4f240ad5353761205b3",
"textures": []
},
"previous_generation": "4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2",
"removed_output_ids": []
},
"stage": "complete",
"state": "succeeded"
}
@@ -0,0 +1,17 @@
{
"seconds": 0.05242176400497556,
"returncode": 0,
"build_configuration": "Debug",
"source_hash": "5d419786a6861d7f14ed4eca54634694d8cd43be5dbbd60a2c6790db665c3a6a",
"command": [
"/home/emil/Desktop/.worktrees/Faset_Engine-p1-p3/build/linux-debug/faset_editor",
"--project",
"/tmp/faset-p1-workflows-20260924-3/project",
"--command",
"{\"name\": \"faset_import\", \"arguments\": {\"path\": \"Assets/exit-arch/manifest.json\"}}",
"--wait"
],
"stdout": "raw/cached_asset_import-01-stdout.txt",
"stderr": "raw/cached_asset_import-01-stderr.txt",
"peak_rss_kib": 9248
}
@@ -0,0 +1 @@
{"peak_rss_kib":353052}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,17 @@
{
"seconds": 29.83193460397888,
"returncode": 0,
"build_configuration": "Debug",
"source_hash": "f94e26cbed75fee4b6f565adb64f525eb702221116ecb4fb3034b08bb2f5647a",
"command": [
"/home/emil/Desktop/.worktrees/Faset_Engine-p1-p3/build/linux-debug/faset_editor",
"--project",
"/tmp/faset-p1-workflows-20260924-3/project",
"--command",
"{\"name\": \"faset_build\", \"arguments\": {}}",
"--wait"
],
"stdout": "raw/changed_cpp_build-01-stdout.txt",
"stderr": "raw/changed_cpp_build-01-stderr.txt",
"peak_rss_kib": 353052
}

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