Checkpoint 4: harden native paths, authoring workflows and Player lifecycle

This commit is contained in:
Emil
2026-09-18 05:14:50 +03:00
parent 5ac4db438d
commit 45bc352d46
88 changed files with 12606 additions and 504 deletions
+3 -1
View File
@@ -57,6 +57,8 @@ Scene compiler переводит авторские данные в runtime и
Retained UI получает состояние и уведомления от AuthoringService; состояние виджетов не заменяет состояние документа. Стили/layout отделены от команд; ключевые действия доступны из интерфейса и через editor API. Импорт, компиляция и cook не блокируют event loop. Необходимые текстовый ввод, focus, clipboard, DPI и платформенные поведения проверяются на обеих ОС как часть UI, а не считаются автоматически решёнными SDL3.
**UI-референс не является источником истины.** Сгенерированные изображения задают визуальное направление: тёмные поверхности, плотность, типографику и иерархию. Состав функций, состояние документов и поведение определяют эти контракты, PLAN и проверенные пользовательские сценарии. Референс можно менять под рабочий функционал.
## 6. Runtime ECS, поведение и игровой цикл — принято
EnTT используется для компонентов и запросов за типизированным API Faset. Простое поведение и массовая система обращаются к одному состоянию. Собственная ECS не разрабатывается на первом этапе; выбор EnTT не обещает большей скорости или дешёвой замены backend. Системы объявляют фазу, чтение/запись компонентов и внешних ресурсов, зависимости и диагностическое имя.
@@ -99,7 +101,7 @@ Development и release Player содержат runtime, выбранные иг
Первый путь доставки — самостоятельный каталог/архив игры и отдельно symbols. Windows и Linux собираются и проверяются собственными native workers. Clang/clang-cl не устраняют необходимость платформенных SDK/runtime; кросс-экспорт из Linux в Windows не считается готовой возможностью. Полностью побайтово воспроизводимый executable и стабильный ABI любых компиляторов не обещаются наличием build manifest.
Критерии [плана](../PLAN.md) включают цикл сцена → Inspector → MCP authoring edit → Undo → save → build → отдельный Player, повторный импорт GLB с сохранением overrides, lifecycle/handle tests и запуск на обеих ОС без Editor/Blender/MCP/SDK. Эти проверки предстоит реализовать и выполнить.
Критерии [плана](../PLAN.md) включают цикл сцена → Inspector → MCP authoring edit → Undo → save → build → отдельный Player, повторный импорт GLB с сохранением overrides, lifecycle/handle tests и запуск на обеих ОС без Editor/Blender/MCP/SDK. Выполненные проверки и незакрытые условия перечислены в [журнале реализации](IMPLEMENTATION.md); наличие теста само по себе не означает прохождения на обеих ОС.
## История решений
+65
View File
@@ -141,3 +141,68 @@ building its pinned software driver. A review also identified Windows Unicode pa
boundaries that must be corrected before cross-platform acceptance. Clean offline
build verification, final performance baselines and the final acceptance record
remain open; no MVP tag has been created.
### Checks after checkpoint 3
- Public commit: `d834cfad67cd81d8c4998b90c16791361ca8c0f8`.
- Linux and Windows headless CI plus strict manual passed in GitHub run
[35295534027](https://github.com/emil28092005/Faset_Engine/actions/runs/35295534027).
- A clean committed source snapshot built all native targets with external networking
disabled; all 19 CPU tests passed. The recorded build/test phase took 174.03 s.
[Offline evidence](validation/offline-linux-2026-09-18.json) distinguishes prepared
system tools from source/dependency inputs. GPU/window checks remain separate.
- The first full Windows graphics job compiled the whole Editor successfully, then
failed creating a Vulkan instance with its CI software driver. Its CPU SceneView
test also reported a texture-loading error. These are acceptance failures under
investigation, not verified Windows graphics/export support.
## Checkpoint 4 — portability and acceptance hardening
Implemented after concrete regression findings:
- UTF-8 project, resource, plugin, CLI and subprocess boundaries. Windows native paths
stay wide internally; Editor/Player/SchemaExporter normalize `wmain` arguments.
File reads, hashes and atomic writes use extended native paths. All Faset Windows
executables declare long-path support; CI enables the documented developer policy.
- Normal Stop requests Player shutdown before bounded termination fallback. Real
`onDestroy` execution was observed, and a dedicated Player fixture verifies that
callback diagnostics also reach logs during shutdown. Custom component versions
are checked against the linked gameplay schema, including data-only components.
- POSIX subprocess cleanup keeps the exited leader's PID reserved while terminating
its owned process group, then reaps it. A regression verifies that a descendant
ignoring SIGTERM cannot survive normal leader exit or cancellation, without losing
the leader's stdout tail or signaling a reused process-group ID.
- Transparent sprites use stable layer/depth ordering without depth writes, avoiding
invisible sprites hiding objects behind them. GPU regressions cover alpha layering,
shader/capture paths longer than 300 characters, and window capture after resize.
- Editor/launcher DPI scaling, logical-unit layout persistence and safe cancellation
of unfinished drags on scale changes. Retained UI tests cover 1×/2× text rasterization,
clipping, hit testing and text-input rectangles.
- Manual import-removal review with exact IDs/names and explicit acceptance. Both the
reviewed candidate and active generation are checked before publishing, so stale
confirmation cannot apply a different import. The GUI regression exercises rejection,
fresh review and successful acceptance through actual widget events.
- Extended template workflow now saves through UI controls and opens a new Session,
comparing IDs, nested origins, local additions and resolved values.
- Blender verification now updates two instances in one live Editor/SceneView, preserves
placement, tint, physics and opaque gameplay fields, and retains both visible meshes
after a removal conflict. [Blender evidence](validation/blender-live-linux-2026-09-18.json).
Observed Linux integration: **32 passed, 1 skipped, 0 failed** in the 33-test suite.
The skip is native Wayland programmatic restore, which the compositor declined;
the same lifecycle scenario passed under XWayland. ASan/UBSan passed **17/17** tests.
Strict MkDocs passed. [Recovery evidence](validation/editor-recovery-linux-2026-09-18.json)
records actual Editor termination and failed save/import/build/Player scenarios.
[Release baseline](validation/linux-release-2026-09-18/README.md) records the two
relocated Unicode-path packages and 240-frame profiles with their scope and hashes.
Windows execution of these fixes remains a separate gate. The previous fast CPU run
isolated a 264-character cooked path failure; the new native-path fixes must pass on
Windows before that issue is considered closed there. Native OS IME composition and
moving between physical monitors still have less coverage than deterministic widget
and platform-boundary tests. No MVP tag has been created.
The final build-service review also reproduced a publication defect: an invalid
custom-field default can pass the build stage's shallow schema check before the
Editor rejects it. Full schema validation before publishing `last_build.json` is the
next bounded correction; checkpoint 4 does not claim this gate is already complete.
+14
View File
@@ -28,6 +28,14 @@ MSVC-family builds use the dynamic CRT: `/MDd` in Debug, `/MD` otherwise. Physic
Player, Editor and native plugins use compatible settings. Development and export
have separate CMake directories, avoiding accidental Debug/Release mixing.
Windows developer machines use the **Enable Win32 long paths** policy
(`LongPathsEnabled=1`). Faset executables carry `longPathAware` manifests. Engine
stream/hash/atomic-file boundaries also use wide extended paths, while serialized
project paths remain ordinary UTF-8. This does not change the independent path
support of CMake, Ninja, the compiler or Slang. CI records/enables the same host
profile; the build guide explains setup. A temporary path longer than 260 characters
exposed the original SceneView failure and now has dedicated regression coverage.
## Graphics profiles
The baseline requires Vulkan 1.3 with dynamic rendering and synchronization2, a
@@ -69,3 +77,9 @@ and graphics driver; they do not need this development toolchain.
An offline build is recorded only after a clean build directory is configured and
built with network access disabled. A warm incremental build or archive checksum
verification alone is not that acceptance check.
Commit `d834cfad67cd81d8c4998b90c16791361ca8c0f8` passed this check on Linux: full
native build and 19 CPU tests in a new user/network namespace with no external
connectivity, using a `git archive` checkout and only prefetched inputs. See
[`validation/offline-linux-2026-09-18.json`](validation/offline-linux-2026-09-18.json).
Reproduce using `python3 tools/verify_offline_build.py --output .cache/offline-check`.
+15 -3
View File
@@ -74,9 +74,17 @@ mesh datablocks remain shared. Repair deliberately duplicated material IDs in
Blender's Custom Properties. Save the `.blend` again.
Removing an exported output produces an import conflict. Faset keeps the old
generation active. Inspect the diagnostics and update affected references before
explicitly importing with `allow_removed_outputs: true`. This accepts removal;
it does not automatically remap references. A normal GLB without persistent custom
generation active. In **Jobs**, choose **Review removals** to open **Conflicts**.
Review the removed stable IDs and their last-good names; **Copy removed IDs** helps
locate references. Update affected references, then use **Accept reviewed removal**.
If source data or the active generation has changed since review, the acceptance
fails safely; use **Reimport / review again** and inspect the new result.
The same explicit API action uses `allow_removed_outputs: true`, with
`expected_generation` and `expected_active_generation` taken from the reviewed
job's `result.generation` and `result.previous_generation`. Use the same source and
settings. This accepts removal; it does not automatically remap references or delete
scene components. A normal GLB without persistent custom
IDs uses structural matching, which cannot guarantee identity after rename or
restructuring.
@@ -118,3 +126,7 @@ For an end-to-end example, open `examples/projects/collect-3d`. Its exit arch in
a `.blend`, reproducible Blender script and a published bundle. The repository's
`tools/verify_blender_roundtrip.py` runs the actual Blender helper and Editor imports
to check rename, geometry changes, removal conflicts and failure preservation.
Pass `--ui-probe build/linux-debug/faset_blender_editor_probe` to also test two
instances in one live editor session: both receive new geometry while preserving
placement, tint, opaque gameplay fields and the authoring revision. This additional
probe requires Vulkan and produces before/after captures.
+6 -3
View File
@@ -80,9 +80,12 @@ and package execution tests; that is separate from physical GPU-driver testing.
## Troubleshooting
**Unknown component or schema version:** enable/register the missing runtime module
and rebuild. The Editor preserves its data as opaque authoring fields, but export
requires a matching runtime implementation.
**Unknown component:** enable/register the missing runtime module and rebuild. A
data-only custom component still needs a schema; it does not need callbacks.
**Schema version mismatch:** match the linked gameplay schema to the document or
apply an explicit data migration, then rebuild. The Editor preserves future or
missing component data as opaque fields; export requires a matching schema.
**Missing asset:** import the original source or Blender manifest again. Preserve its
sidecar so the AssetId remains stable. A cache copied from another project is not a
+3 -2
View File
@@ -23,8 +23,9 @@ resolution, validation activation, fixed ticks and timestep. A bounded run advan
one synthetic fixed timestep per frame; it does not reproduce a real-time input
session. Keep that distinction when comparing runs.
Startup starts at `main()` and ends at the first completed frame. OS process loading
before `main()` is excluded. Frame wall times exclude writing the final profile and
Startup starts at the Player application entry after platform argument normalization
and ends at the first completed frame. OS process loading and Windows `wmain` UTF-8
argument conversion are excluded. Frame wall times exclude writing the final profile and
capture files. Simulation and scene-snapshot times are separate from the renderer
call. Renderer CPU wall duration includes GPU waits and readback; it is **not CPU
utilization**. GPU timestamps measure the submitted graphics work and can be null
+5
View File
@@ -1,5 +1,10 @@
# Editor workspace
The Editor follows the window's display scale. Text is rasterized at that scale;
panel dimensions are saved in logical units so the layout remains usable when
moving between displays. A scale change preserves focused text and cancels an
unfinished numeric, divider or gizmo drag without committing an edit.
The Editor edits saved scene data and previews it in a Vulkan viewport. **Play**
opens a separate Player process. Gameplay movement and spawned objects stay in that
process; stopping it leaves the authoring scene unchanged.
+15
View File
@@ -67,6 +67,21 @@ Use an x64 Visual Studio Developer shell with the Windows SDK, MSVC runtime libr
LLVM `clang-cl`, Ninja, CMake, and the Vulkan SDK available. Then use the
`windows-debug` or `windows-release` presets.
Enable **Win32 long paths** on the Windows development machine before starting the
build shell. Faset's executable manifest declares long-path support, and its direct
file IO uses wide extended paths; external CMake/Ninja/compiler tools also need a
compatible host policy for deeply nested build/cache directories. An administrator
can enable the policy once in PowerShell:
```powershell
New-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' `
-Name LongPathsEnabled -Value 1 -PropertyType DWORD -Force
```
Open a new build shell afterwards; Windows may require a restart for existing
processes. See Microsoft's [long-path requirements](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation).
Windows CI enables and records this developer profile explicitly.
```powershell
py tools/fetch_slang.py
cmake --preset windows-debug
+8 -1
View File
@@ -4,7 +4,7 @@ Include `<faset/runtime/Runtime.hpp>` and use namespace `faset::runtime`. This i
## Register behavior
`void Runtime::registerBehavior(std::string componentType, Behavior behavior)` registers callbacks before scene loading. An empty or duplicate type, registration during a callback, and registration after entities have loaded are rejected.
`void Runtime::registerBehavior(std::string componentType, Behavior behavior)` registers callbacks before scene loading. An empty or duplicate type and registration while entities are present or a callback is running are rejected. An empty or cleared world can register additional types.
`Behavior::Callback` is `std::function<void(Runtime&, EntityHandle, double)>`. Assign it to any of `onStart`, `fixedUpdate`, `update`, `lateUpdate`, and `onDestroy`. Unassigned members do nothing. `Behavior::onCollision` instead accepts `(Runtime&, EntityHandle, const CollisionEvent&)`.
@@ -63,6 +63,13 @@ Immediate validation errors throw. Deferred failures are recorded in diagnostics
`Runtime(RuntimeConfig = {})` constructs the controller. `load(const nlohmann::json&)` validates and prepares a scene, creates its entities, then calls initial callbacks. Invalid scene data leaves the preceding world intact. `clear()` destroys the current entities and invalidates their session handles.
Direct callers can include `<faset/runtime/schema.hpp>` and call
`validate_scene_schemas(scene, gameplaySchema)` before `load`. The Player does this
automatically: each custom TypeId/version must match its linked schema. Generic
`Runtime` accepts opaque custom data with positive versions, including data-only
components; built-ins use version 1. This helper verifies identity/version, without
automatic migrations or authoring-style custom-field constraint validation.
`FrameStats advance(double elapsedSeconds, InputState = {})` advances fixed ticks, frame callbacks, interpolation, and late callbacks. `singleStep(InputState = {})` advances one fixed tick. `setPaused(bool)` clears accumulated time and resets presentation history; `paused()` reports this local state. Do not call load/clear/advance recursively from a callback.
`RuntimeConfig` defaults to `fixedDelta = 1.0 / 60.0`, `maxCatchUpTicks = 4`, `physicsSubsteps = 4`, and `gravity = {0, -9.81f, 0}`. `FrameStats` reports fixed ticks performed, dropped time, interpolation fraction, and total tick count.
+5 -2
View File
@@ -42,7 +42,7 @@ Read the callback from top to bottom:
4. `transform` gives a pose copy. Multiplying metres per second by seconds gives a displacement in metres.
5. `setTransform` publishes the changed non-physical pose.
The `[](...) { ... }` expression is a C++ lambda: a function stored in `Behavior::update`. Empty brackets mean it captures no local variables. `registerBehavior` takes ownership of the callback object. Register before calling `load`; registration after a world has loaded is rejected.
The `[](...) { ... }` expression is a C++ lambda: a function stored in `Behavior::update`. Empty brackets mean it captures no local variables. `registerBehavior` takes ownership of the callback object. Register before calling `load`; registration while entities exist or a callback is running is rejected.
The `schema()` function describes editable configuration. It does not create a runtime object. `tutorial.move_x` is the stable `TypeId`; `speed` is a stable `FieldId` within that type. Keep these IDs when changing a display label. Changing a field's meaning or units needs an explicit data migration, not just a new label.
@@ -56,7 +56,10 @@ 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 your project's schema is exported and loaded by the Editor, the type can be described through the same authoring schema used by the Inspector. The direct Player command above is useful before building an Editor workflow around your component.
After **Build C++** 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
+7
View File
@@ -23,6 +23,13 @@ A gameplay directory contains `Gameplay.hpp` and `Gameplay.cpp`. It provides two
The Player calls registration before loading a scene. The separate SchemaExporter calls `schema()` without creating a game world or running gameplay callbacks. The Editor reads the resulting declaration; it does not load the gameplay binary into the Editor process.
Every custom component in a played scene must have a matching TypeId and exact
positive version in the linked gameplay `schema()`. The Player checks this before
loading the world, including `--validate`. A data-only custom component still needs
a schema, even when it registers no callbacks. Built-in TypeIds are reserved. The
Editor preserves missing or future component data for recovery, but the Player
rejects it until the corresponding module/schema is available or the data is migrated.
A scene object receives a behavior by containing a component whose `type` matches the string passed to `registerBehavior`. Registering a behavior does not attach it to every object. A component can contain data without having any callbacks.
## Data, poses, and state
+7 -1
View File
@@ -10,6 +10,12 @@ Each ordinary callback receives `(Runtime& game, EntityHandle self, double delta
`onDestroy` runs while that object's handle and allowed component data are still valid. Clean up subscriptions or external C++ state there. After removal, `valid(oldHandle)` returns false. Removing a behavior component also runs that component's `onDestroy`. Clearing or replacing a scene runs destruction callbacks; a new scene uses a new session identity.
Normal Player shutdown and the Editor's **Stop** request also run destruction callbacks.
Stop gives the Player a bounded grace period to finish. An unresponsive native callback
can force the Editor to terminate the process; no application can guarantee cleanup
callbacks after forced termination or a crash. Such fallback is reported in Console.
Exceptions caught from callbacks, including shutdown callbacks, appear in Player logs.
Registering a callback does not make captured pointers safe. A lambda that stores a reference to a stack variable in `registerGameplay` will outlive that variable. The [timed-despawn example](examples.md#spawn-and-destroy-on-safe-boundaries) uses shared ownership for captured state and removes each object's entry on destruction.
## One fixed tick
@@ -58,4 +64,4 @@ The Player reads this optional object from the scene document. The settings are
}
```
This is an excerpt, not a complete scene. The tutorial scenes contain complete examples. `fixed_delta` is seconds, gravity is metres per second squared, and substeps are solver subdivisions inside one fixed tick. These do not create additional gameplay callbacks. Invalid configuration is rejected before the Player starts. Editing these JSON settings is implemented; an Editor settings panel should only be relied on where the current UI exposes it.
This is an excerpt, not a complete scene. The tutorial scenes contain complete examples. `fixed_delta` is seconds, gravity is metres per second squared, and substeps are solver subdivisions inside one fixed tick. These do not create additional gameplay callbacks. Invalid configuration is rejected before the Player starts. The Editor's **Simulation** dialog edits these scene settings through normal Undo transactions; **Project settings** edits the project's name, dimension and start scene separately.
+14
View File
@@ -0,0 +1,14 @@
# Validation evidence
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.
- [Linux offline build](offline-linux-2026-09-18.json): a fresh build from committed `d834cfad67cd81d8c4998b90c16791361ca8c0f8`, prefetched dependency inputs, and a network namespace without external connectivity. CPU tests ran; graphics execution is separate.
- [Linux Release export and rendering](linux-release-2026-09-18/README.md): two relocated playable games, Unicode paths, imported Blender geometry, raw frame profiles and exact package hashes. This measurement used an evolving working tree around `5ac4db4`, not a clean checkout of the current commit.
- [Live Blender reimport](blender-live-linux-2026-09-18.json): actual Blender 4.5.3 exports and two live Editor instances, preserving authoring/gameplay/physics while refreshing geometry.
- [Editor recovery](editor-recovery-linux-2026-09-18.json): killed Editor, explicit journal recovery, failed save/import/build and failed Player startup with last-good preservation.
- [Native window boundaries](native-window-linux-2026-09-18.json): XWayland lifecycle, isolated Unicode clipboard, SDL text-input rectangles and the explicit native Wayland restore limitation.
- [Linux native window and input boundaries](native-window-linux-2026-09-18.json): XWayland resize/minimize/restore and SDL text-input-area checks; isolated Xvfb Unicode clipboard roundtrip with restoration. Native Wayland restore remains unconfirmed; real OS IME composition was not tested.
The Linux records do not establish Windows correctness, physical Windows GPU performance, native Wayland restore support, or editor responsiveness under large workloads. Windows CI artifacts record their own commit and software Vulkan results. A skipped compositor operation is not a completed acceptance check.
Performance budgets below are proposals for P1 regression tracking, not completed release requirements. Repeat measurements on a controlled reference host and add representative content before treating them as engine-wide targets.
@@ -0,0 +1,23 @@
{
"blender": "Blender 4.5.3 LTS",
"asset_id": "54c039e4-c19f-4365-a9f8-a38416ec6e3f",
"initial_generation": "499f254ce440ecb86118f2f6803f21013e763d35cfdba1c4356be7a457eec718",
"renamed_generation": "963e996b59ba11c2f9dd9eba51b89e7ae3f3dce6f177e4adaf5682479cb204a8",
"stable_output_ids": true,
"removed_output_conflict": true,
"failed_import_keeps_generation": true,
"authoring_preserved": true,
"live_editor": {
"conflict_keeps_visible_generation": true,
"instances": 2,
"live_geometry_updated": true,
"opaque_gameplay_preserved": true,
"physics_fields_preserved": true,
"placement_and_tint_preserved": true,
"status": "passed",
"validation_enabled": true,
"validation_errors": 0
},
"source_context": "Shared Linux checkpoint 4 working tree around 5ac4db4; not a clean-commit result",
"reproduce": "python3 tools/verify_blender_roundtrip.py --blender PATH --editor build/linux-debug/faset_editor --ui-probe build/linux-debug/faset_blender_editor_probe --output NEW_DIRECTORY"
}
@@ -0,0 +1,33 @@
{
"format": "faset.editor-recovery-verification",
"version": 1,
"status": "passed",
"checks": [
"acknowledged unsaved edit survives killed Editor and explicit recovery",
"failed save preserves live authoring and last saved file",
"failed Player process is reported without changing authoring data",
"Undo repairs the scene, then Play and Stop leave authoring unchanged",
"failed C++ build keeps prior schema and reports stale status",
"fixed C++ rebuild restores current schema status",
"failed import preserves authoring"
],
"logs": [
"build completed",
"Gameplay schema loaded",
"Play started in a separate Player process",
"Player failed: unsupported component version\n",
"Player exited with code 1",
"build completed",
"Gameplay schema loaded",
"Play started in a separate Player process",
"New round: collect the three gold cubes, then reach the green exit. E resets.\n{\"device\":\"NVIDIA GeForce RTX 2080 Ti\",\"dimension\":2,\"frames\":382,\"ticks\":103,\"validation_errors\":0}\n",
"Play stopped; authoring scene unchanged",
"build failed: Process exited with code 1; see job log; previous gameplay metadata remains available and is marked stale",
"build completed",
"Gameplay schema loaded",
"Asset import failed: Cannot read: /home/emil/Desktop/Faset_Engine/.cache/recovery-checkpoint3-v3/project/Assets/missing.glb\n"
],
"engine_revision": "d834cfad67cd81d8c4998b90c16791361ca8c0f8",
"execution_context": "Linux development desktop; Editor and source from the clean offline checkout; disposable sample project",
"scope": "Crash recovery and last-good preservation. Graceful OnDestroy is verified separately by the later Player shutdown regression."
}
@@ -0,0 +1,33 @@
# Linux Release export baseline — 2026-09-18
Both checked-in playable projects passed real Editor export, standalone CPU validation and 120 offscreen frames after relocation. Their disposable source-project paths were hidden during launch; no `--scene` or `--assets` override was supplied. Copied project and executable paths contain `Faset Café 世界`. The 3D export includes the Blender exit-arch asset.
This is a measurement of the shared working tree around `5ac4db438dae5ee28e5619e62cf8d6647beecd52` plus in-progress checkpoint 4 changes. It is **not an exact clean-commit result**. The two Release generations are `48ec8546-3750-463f-9c4c-f76c3b933256` (2D) and `17281e17-9f4f-44f0-8821-6e0071e9a45b` (3D). [Evidence](evidence.json) retains project input hashes and build fingerprints; [2D](collect-2d-manifest.json) and [3D](collect-3d-manifest.json) manifests retain the exact executable, cooked data and shader hashes.
The same relocated executables then recorded 240 frames each, sequentially, without physics debug drawing or player input. Hardware: Ryzen 7 1700, 32 GiB RAM, RTX 2080 Ti 11 GiB, NVIDIA 595.84; Ubuntu 26.04, kernel 7.0.0-31. Build: Clang 21.1.8, Release `-O3 -DNDEBUG`, Vulkan 1.3, 1280×720, actual Khronos validation enabled. Both runs had zero validation errors. The desktop remained active; this was not an isolated benchmark machine.
Raw [2D](collect-2d-profile-240.json) and [3D](collect-3d-profile-240.json) profiles include every completed frame, including the first, with nearest-rank percentiles. Times below are p50 / p95 milliseconds:
- 2D: total measured frame 2.234 / 2.684; renderer call 1.865 / 2.221; GPU 0.565 / 0.579; readback 0.510 / 0.633; simulation 0.201 / 0.258; snapshot 0.170 / 0.215.
- 3D: total measured frame 2.299 / 2.732; renderer call 1.884 / 2.180; GPU 0.588 / 0.600; readback 0.469 / 0.608; simulation 0.213 / 0.300; snapshot 0.181 / 0.257.
Live Vulkan allocations stayed at 15,750,288 bytes (2D) and 15,787,008 bytes (3D), about 15.02 and 15.06 MiB. Counts were 11 / 31 draws, 66 / 576 packed vertices per frame, and one fallback texture. A packed mesh can be drawn again for its shadow pass. These memory totals include aligned explicit allocations and exclude driver internals. First-frame startup from `main()` was 265.5 / 260.8 ms; OS loader time is excluded.
`render_call` includes GPU waits and synchronous readback; it is not CPU utilization. GPU timestamps cover submitted rendering. Frame timings exclude profile bookkeeping and final file writes. Synthetic 1/60-second simulation ticks run as fast as the offscreen loop allows. The resulting numbers are not display FPS or a real-time gameplay pacing test. Small sample scenes, one GPU, 240 frames and a static camera cannot establish scalability, a memory-leak guarantee, or broad performance claims. The imported 3D material also emits the recorded warning about unsupported material features in its manifest.
Proposed P1 budgets for these exact scenes on this reference-class Linux host, at the same resolution and validation/readback settings:
- Measured frame p95 ≤ 4 ms; GPU p95 ≤ 1 ms; readback p95 ≤ 1 ms.
- Simulation and snapshot p95 ≤ 0.5 ms each.
- Explicit live Vulkan memory ≤ 20 MiB, with no growth after warmup over a future 3,000-frame resource-lifecycle run.
- Startup from `main()` ≤ 500 ms for these tiny packages.
These initial thresholds leave headroom above the observed values. They are **proposed**, not enforced acceptance criteria. P1 should retain cold-start results separately, run repeated sessions, measure editor input latency and larger content, and collect independent Windows hardware baselines before adopting release gates.
Reproduce export validation from the repository with `python tools/verify_playable_exports.py --editor build/linux-debug/faset_editor --output <new-empty-directory>`. Run each retained standalone Player with `--headless --frames 240 --profile <output.json>` to repeat the longer measurement. A new run builds current sources and produces new immutable generations; use the stored hashes to distinguish it from this record.
Captured 120-frame outputs:
![Relocated 2D sample](collect-2d.png)
![Relocated 3D sample with imported Blender arch](collect-3d.png)
@@ -0,0 +1,143 @@
{
"asset_generations": {},
"build_fingerprint": "5b1996b0fe44eeaa01ee1fd581f9f063f8c84147128e896541d27207dbab9aaa",
"configuration": "Release",
"executable": "faset_player",
"files": [
{
"path": "scene.fscene",
"sha256": "811242b85ede88c6d7920eeda5b7f6ae189db4d9e17d568cdfd230f27ce629b6",
"size": 6496
},
{
"path": "faset_player",
"sha256": "ef12daa3a1e71ef9e9d933cfa0347249de86b5e8da3536e709a3899a731e4333",
"size": 5809704
},
{
"path": "shaders/vertexMain.spv",
"sha256": "1ad2631c35d654f48166321ae43d4165043e1b919b14f1dc61caf97b1ada0898",
"size": 1224
},
{
"path": "shaders/fragmentMain.spv",
"sha256": "f5c4f289917ecab5d00d543c053aac1d10e6cca311fd172daf06d315160a3505",
"size": 7436
},
{
"path": "shaders/shadowMain.spv",
"sha256": "7643b4d688492b5ee923b9606f0b0e70343ca05fa67673838206119a6f86d8d8",
"size": 784
},
{
"path": "shaders/vertexMain.reflection.json",
"sha256": "c584f3b957ebf194a292451362a98590f85ff3cfbfd8f917b1c1265039381777",
"size": 2639
},
{
"path": "shaders/fragmentMain.reflection.json",
"sha256": "72ebf8d32452830650416dc4717d89a4324e97ece7028dfb97c66803c8dce71f",
"size": 2649
},
{
"path": "shaders/shadowMain.reflection.json",
"sha256": "e3fd0889cd2f569d410f5d7558a4091d9f57f247a1dcf1788462f67525340819",
"size": 2645
},
{
"path": "Notices/sdl3/LICENSE.txt",
"sha256": "97f35b302b361680ec1e891e95d2d52097bb95abff361434916d99dc1305f127",
"size": 884
},
{
"path": "Notices/entt/LICENSE",
"sha256": "0785027ce472d7c61f05fba664a1d3ba6639c1593ced351ad9e4bed868765d99",
"size": 1097
},
{
"path": "Notices/box2d/LICENSE",
"sha256": "68a3e676d7e94093b102d5cba0d4e04af812040d6f230c3db67a6664574e43d2",
"size": 1067
},
{
"path": "Notices/box3d/LICENSE",
"sha256": "da5e31a26bf3cfd5ba5c96d6823e480128c81e76c107ef9d3ee5d94789184b90",
"size": 1067
},
{
"path": "Notices/json/LICENSE.MIT",
"sha256": "46a65cffd1ea955132d95a8dd921640714a8d6b537d2e4e482d31145ae95b603",
"size": 1076
},
{
"path": "Notices/stb/LICENSE",
"sha256": "bebfe904b14301657e4e5d655c811d51fd31b97c455b9cc2d8600d6bac6cff63",
"size": 2510
},
{
"path": "Notices/dependencies.json",
"sha256": "a54639ad2d9ad13c2e5f1ec90d6775f5dfc493656789b78c7e44debe2a378fdc",
"size": 2206
},
{
"path": "Notices/Faset-NOTICE.txt",
"sha256": "30c2e7e80f5153b31ab57675e07246aef9165965d64e36022b426a26449ece3f",
"size": 204
},
{
"path": "README.txt",
"sha256": "4dc2f0271c69fe39017ff96aa2dfa00b625bec1401d924dc396193ce71c09f3e",
"size": 225
}
],
"format": "faset.export",
"generation": "48ec8546-3750-463f-9c4c-f76c3b933256",
"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": "05ab2c3e5b152ab25867976c47635961f440f41d8cd0408732eb2d8d5b7800a8",
"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
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -0,0 +1,170 @@
{
"asset_generations": {
"5832763b-3ed0-44d6-9088-0b524f196a91": "4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2"
},
"build_fingerprint": "5a516f5c4bded8f8e3f027b3dd2812a34b7f40b37e281c21ce87d7586fff88e4",
"configuration": "Release",
"executable": "faset_player",
"files": [
{
"path": "scene.fscene",
"sha256": "a7ac2dcd93dc9dce2d8ce860aa5dd838af2604dd7b40d5b50581d1a72bd5bf23",
"size": 9218
},
{
"path": "faset_player",
"sha256": "a68c39af919d5c04db4985f36e3da4573f9a4c13862bc599cd85724ac6da22dc",
"size": 5814720
},
{
"path": "shaders/vertexMain.spv",
"sha256": "1ad2631c35d654f48166321ae43d4165043e1b919b14f1dc61caf97b1ada0898",
"size": 1224
},
{
"path": "shaders/fragmentMain.spv",
"sha256": "f5c4f289917ecab5d00d543c053aac1d10e6cca311fd172daf06d315160a3505",
"size": 7436
},
{
"path": "shaders/shadowMain.spv",
"sha256": "7643b4d688492b5ee923b9606f0b0e70343ca05fa67673838206119a6f86d8d8",
"size": 784
},
{
"path": "shaders/vertexMain.reflection.json",
"sha256": "c584f3b957ebf194a292451362a98590f85ff3cfbfd8f917b1c1265039381777",
"size": 2639
},
{
"path": "shaders/fragmentMain.reflection.json",
"sha256": "72ebf8d32452830650416dc4717d89a4324e97ece7028dfb97c66803c8dce71f",
"size": 2649
},
{
"path": "shaders/shadowMain.reflection.json",
"sha256": "e3fd0889cd2f569d410f5d7558a4091d9f57f247a1dcf1788462f67525340819",
"size": 2645
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2/meshes/mesh-fc069a07a87e0f831fd92efdbc4efa1f-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2/meshes/mesh-0961e2a3f92977836a3aa80cab09277e-0.fmesh",
"sha256": "98b38aca7d2786c58a2265ba4d1f2255123f03d31d947e239232cf55bfdc2c97",
"size": 928
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2/meshes/mesh-93b5b3e52cb6bb120fefb80e2d8e222b-0.fmesh",
"sha256": "3875c16ea628d4126373ebfe07816621fbad3961596a2fcd112f1eec0e378071",
"size": 928
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/generations/4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2/manifest.json",
"sha256": "ee3f68794c6dc44ab492b1ba4640aa74e4de79a5ec10aed8dcdd45a03afc508e",
"size": 4532
},
{
"path": "assets/5832763b-3ed0-44d6-9088-0b524f196a91/current.json",
"sha256": "b0732959808ce61ef5a3223fba2d9ad8c17a3ea7873d4a81c00aadee6c51032e",
"size": 134
},
{
"path": "Notices/sdl3/LICENSE.txt",
"sha256": "97f35b302b361680ec1e891e95d2d52097bb95abff361434916d99dc1305f127",
"size": 884
},
{
"path": "Notices/entt/LICENSE",
"sha256": "0785027ce472d7c61f05fba664a1d3ba6639c1593ced351ad9e4bed868765d99",
"size": 1097
},
{
"path": "Notices/box2d/LICENSE",
"sha256": "68a3e676d7e94093b102d5cba0d4e04af812040d6f230c3db67a6664574e43d2",
"size": 1067
},
{
"path": "Notices/box3d/LICENSE",
"sha256": "da5e31a26bf3cfd5ba5c96d6823e480128c81e76c107ef9d3ee5d94789184b90",
"size": 1067
},
{
"path": "Notices/json/LICENSE.MIT",
"sha256": "46a65cffd1ea955132d95a8dd921640714a8d6b537d2e4e482d31145ae95b603",
"size": 1076
},
{
"path": "Notices/stb/LICENSE",
"sha256": "bebfe904b14301657e4e5d655c811d51fd31b97c455b9cc2d8600d6bac6cff63",
"size": 2510
},
{
"path": "Notices/dependencies.json",
"sha256": "a54639ad2d9ad13c2e5f1ec90d6775f5dfc493656789b78c7e44debe2a378fdc",
"size": 2206
},
{
"path": "Notices/Faset-NOTICE.txt",
"sha256": "30c2e7e80f5153b31ab57675e07246aef9165965d64e36022b426a26449ece3f",
"size": 204
},
{
"path": "README.txt",
"sha256": "4dc2f0271c69fe39017ff96aa2dfa00b625bec1401d924dc396193ce71c09f3e",
"size": 225
}
],
"format": "faset.export",
"generation": "17281e17-9f4f-44f0-8821-6e0071e9a45b",
"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": "6510f994ee01af5823614911fcdeff0fd433846d937ff1a65d5bf5fb08e45e8f",
"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
}
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,312 @@
{
"format": "faset.validation-evidence",
"version": 1,
"status": "passed",
"scope": "Linux relocated Release game export and offscreen small-scene measurement",
"source_checkout": {
"reference_commit": "5ac4db438dae5ee28e5619e62cf8d6647beecd52",
"clean_commit_match": false,
"description": "Shared working tree around 5ac4db4 plus in-progress checkpoint 4 work; builds were not made from a frozen clean commit. Exact exported binary/shader hashes are retained in each manifest."
},
"export_started_utc": "2026-09-18T01:46:06.267995+00:00",
"export_finished_utc": "2026-09-18T01:49:50.149934+00:00",
"host": {
"os": "Ubuntu 26.04 LTS",
"kernel": "7.0.0-31-generic x86_64",
"cpu": "AMD Ryzen 7 1700 Eight-Core Processor (8 cores,16 threads)",
"system_memory_bytes": 33571037184,
"gpu": "NVIDIA GeForce RTX 2080 Ti",
"gpu_memory_mib": 11264,
"nvidia_driver": "595.84"
},
"configuration": {
"build": "Release",
"compiler": "Clang 21.1.8",
"cxx_flags": "-O3 -DNDEBUG",
"cmake": "4.2.3",
"api": "Vulkan 1.3",
"resolution": [
1280,
720
],
"presentation": "offscreen",
"validation_enabled": true,
"physics_debug": false,
"frames_per_final_profile": 240,
"fixed_delta_seconds": 0.016666666666666666,
"simulation": "synthetic fixed timestep, no player input",
"sampling": "nearest rank, all completed frames, cold first frame retained"
},
"verification": {
"tool": "tools/verify_playable_exports.py",
"project_copies_and_relocated_binaries_include": "Faset Café 世界",
"steps": [
"Copy exact checked-in sample project inputs",
"Real Editor import of 3D Blender arch bundle",
"Real Editor Release export",
"Verify every package file size and SHA256",
"Copy generation outside engine/source trees",
"Hide disposable source-project paths",
"Launch Player from unrelated empty cwd without scene/assets arguments",
"CPU --validate then 120-frame offscreen capture and profile",
"Additional 240-frame no-debug profile of each same relocated executable"
],
"validation_errors": 0
},
"projects": [
{
"name": "collect-2d",
"dimension": 2,
"generation": "48ec8546-3750-463f-9c4c-f76c3b933256",
"build_fingerprint": "5b1996b0fe44eeaa01ee1fd581f9f063f8c84147128e896541d27207dbab9aaa",
"scene_hash": "05ab2c3e5b152ab25867976c47635961f440f41d8cd0408732eb2d8d5b7800a8",
"asset_generations": {},
"source_inputs": [
{
"path": ".gitignore",
"sha256": "53aa4d7124d4c8d93b8d7c4cb470b6c82b844e019a660d6243ffeadc50602de1"
},
{
"path": "README.md",
"sha256": "d6c35206b10ee28efd5cc02da77a3ce9b6e1f8ce085793150bbf2f23bd925d65"
},
{
"path": "Scenes/main.scene.json",
"sha256": "be48a6d21385e717b6c8ebf9a3b4322eebbeff6bb067428e089ab5214be79854"
},
{
"path": "Scripts/Gameplay.cpp",
"sha256": "6b581b24da613e1915819de0a0f0ea820dd3333efa62c4b479a9ea7ffdbc6bcc"
},
{
"path": "Scripts/Gameplay.hpp",
"sha256": "4121587182ed4f74901b1c0757c53daadbf309af1d6bb5dd87fef142130ccbbe"
},
{
"path": "project.faset.json",
"sha256": "36e2acd32210f5489372a2331346a3294ead2395f0713e3e5fd2da4e83263689"
}
],
"package_file_count": 17,
"source_project_paths_unavailable": true,
"standalone_validation": "passed",
"standalone_render_frames": 120,
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 7,
"sha256": "f9d74067d87c1ebf30c6db372a26d93b7285791c8cb0307a24af5adb23bf201a"
},
"final_profile": "collect-2d-profile-240.json",
"profile_recorded_at_utc": "2026-09-18T01:51:24.599491+00:00",
"gpu_allocated_bytes": {
"min": 15750288,
"max": 15750288
},
"draw_calls": [
11
],
"vertices": [
66
],
"texture_count": [
1
],
"summary_ms": {
"gpu": {
"max": 0.58912,
"min": 0.563616,
"p50": 0.564832,
"p95": 0.57856,
"samples": 240
},
"render_call": {
"max": 5.705444,
"min": 1.581003,
"p50": 1.865269,
"p95": 2.22086,
"samples": 240
},
"renderer_cpu": {
"max": 5.701056,
"min": 1.578639,
"p50": 1.861723,
"p95": 2.216031,
"samples": 240
},
"renderer_readback_cpu": {
"max": 2.763424,
"min": 0.432917,
"p50": 0.509912,
"p95": 0.632894,
"samples": 240
},
"simulation": {
"max": 0.513789,
"min": 0.144743,
"p50": 0.200708,
"p95": 0.258247,
"samples": 240
},
"snapshot": {
"max": 0.25444,
"min": 0.127029,
"p50": 0.170341,
"p95": 0.214946,
"samples": 240
},
"wall": {
"max": 6.089358,
"min": 1.879436,
"p50": 2.233955,
"p95": 2.684014,
"samples": 240
}
}
},
{
"name": "collect-3d",
"dimension": 3,
"generation": "17281e17-9f4f-44f0-8821-6e0071e9a45b",
"build_fingerprint": "5a516f5c4bded8f8e3f027b3dd2812a34b7f40b37e281c21ce87d7586fff88e4",
"scene_hash": "6510f994ee01af5823614911fcdeff0fd433846d937ff1a65d5bf5fb08e45e8f",
"asset_generations": {
"5832763b-3ed0-44d6-9088-0b524f196a91": "4a5bfcc1437a7b4d65c388da2beace0308cceda8e16503d08ce676260f21d5c2"
},
"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"
}
],
"package_file_count": 22,
"source_project_paths_unavailable": true,
"standalone_validation": "passed",
"standalone_render_frames": 120,
"capture": {
"width": 1280,
"height": 720,
"sampled_colors": 42,
"sha256": "914060401de93d08b33bf09a1e210859e491a0060c331e5e2044f517356f4980"
},
"final_profile": "collect-3d-profile-240.json",
"profile_recorded_at_utc": "2026-09-18T01:51:25.501500+00:00",
"gpu_allocated_bytes": {
"min": 15787008,
"max": 15787008
},
"draw_calls": [
31
],
"vertices": [
576
],
"texture_count": [
1
],
"summary_ms": {
"gpu": {
"max": 0.624064,
"min": 0.58432,
"p50": 0.58752,
"p95": 0.600448,
"samples": 240
},
"render_call": {
"max": 4.807059,
"min": 1.644403,
"p50": 1.884165,
"p95": 2.179593,
"samples": 240
},
"renderer_cpu": {
"max": 4.803463,
"min": 1.626759,
"p50": 1.880197,
"p95": 2.174082,
"samples": 240
},
"renderer_readback_cpu": {
"max": 2.729419,
"min": 0.398812,
"p50": 0.468945,
"p95": 0.608337,
"samples": 240
},
"simulation": {
"max": 0.441703,
"min": 0.185701,
"p50": 0.213223,
"p95": 0.300146,
"samples": 240
},
"snapshot": {
"max": 0.740007,
"min": 0.162066,
"p50": 0.181302,
"p95": 0.257285,
"samples": 240
},
"wall": {
"max": 5.902556,
"min": 2.040269,
"p50": 2.298847,
"p95": 2.732225,
"samples": 240
}
}
}
],
"evidence_sha256": {
"collect-2d-profile-240.json": "0141cb2ad6d31394b542d575dce3409fef82ff2e77fe91eb4a7183811471e36f",
"collect-3d-profile-240.json": "bedf3a68df03c1af42bdb4d3b416d930c6e92fa6e99033c63ea9b17ae977c8c6",
"collect-2d-manifest.json": "83a0c8a58dae07376c0db58ca58b115d9ecedb245bcba88bdcd5429796ba5be9",
"collect-3d-manifest.json": "6374a6af5618a6c613b09978b2bf42300b1565808ded21bf9c2b82444027f252",
"collect-2d.png": "c380583f2fd8bc09c5b0e1c6e755c35c3d64b5385ed66177b04d1bbdb532b236",
"collect-3d.png": "98941a2004c3345da336294fa0e1b9e363922fc581c7a8612652ecbb0d764c07"
}
}
@@ -0,0 +1,50 @@
{
"format": "faset.native-window-validation",
"version": 1,
"date": "2026-09-18",
"platform": "Linux, KDE Wayland session and isolated Xvfb",
"source_checkout": "Working tree around5ac4db4 plus checkpoint4 changes; not an exact clean-commit result",
"fixture": "tests/render_window_tests.cpp",
"cases": [
{
"surface": "XWayland in the active desktop",
"result": "passed",
"scope": [
"resize convergence",
"fresh readback while minimized",
"native minimize/restore events",
"live SDL display-scale getter",
"native SDL text input start/stop",
"drawable-pixel input-area conversion checked through SDL_GetTextInputArea"
],
"clipboard": "Skipped because the existing clipboard advertised non-text payload; no replacement attempted.",
"log": "driver=x11 stage=created\nstage=resized width=360 height=300 resize_events=2\nclipboard=skipped_to_preserve_non_text_payload\ntext_input_area=coordinate_conversion_passed\nstage=minimized fresh_frames=12 minimize_events=1 focus_lost_events=1\nstage=restored restored_events=1 activation_required=0 frames=56 validation_enabled=1 validation_errors=0\n"
},
{
"surface": "Private Xvfb server, SDL X11 driver",
"result": "clipboard and input-area checks passed; lifecycle skipped",
"scope": [
"Unicode clipboard roundtrip through Renderer/SDL",
"saved plain-text clipboard restored without logging contents",
"native SDL text-input activation and rectangle conversion"
],
"limit": "No window manager; minimize request unconfirmed, so this is not minimize/restore acceptance.",
"log": "driver=x11 stage=created\nstage=resized width=360 height=300 resize_events=2\nclipboard=unicode_roundtrip_passed\ntext_input_area=coordinate_conversion_passed\nSKIP: window system declined the minimize request\n"
},
{
"surface": "Native Wayland in the active desktop",
"result": "partial; restore acceptance unresolved",
"scope": [
"resize convergence",
"12 fresh readbacks while SDL reported minimized"
],
"limit": "Compositor did not confirm restore/activation. SDL Wayland may synthesize its minimized flag because xdg-shell has no minimized-state acknowledgement; physical minimization is not independently established."
}
],
"limits": [
"No real OS IME composition session was performed.",
"Synthetic text events in other tests do not substitute for real IME acceptance.",
"Windows native clipboard/window behavior requires its own run.",
"No clipboard contents were retained in logs or artifacts."
]
}
@@ -0,0 +1,12 @@
{
"format": "faset.offline-build",
"version": 1,
"revision": "d834cfad67cd81d8c4998b90c16791361ca8c0f8",
"recorded_at_utc": "2026-09-18T01:33:07.301477+00:00",
"seconds": 174.03467636999994,
"returncode": 0,
"network": "fresh user/network namespace; external connection must fail",
"source": "git archive of committed HEAD; no existing build directory",
"inputs": "prefetched archives + Slang; system compiler, SDK and development libraries",
"tests": "full native build, CPU CTests; GPU/window execution verified separately"
}