Checkpoint 3: deliver playable samples and complete native authoring workflows

This commit is contained in:
Emil
2026-09-18 04:29:41 +03:00
parent fad5eb4e55
commit d834cfad67
92 changed files with 8335 additions and 353 deletions
+120
View File
@@ -0,0 +1,120 @@
# Assets and Blender
Keep source assets under your project's `Assets` directory. Faset imports PNG/JPEG,
static glTF/GLB, and manifests published by the optional Blender helper. The import
service runs in the Editor; exported games load cooked data and need no Blender.
## Import a model
1. Copy a `.glb`, or a `.gltf` with its relative buffers and images, into `Assets`.
2. In the **Assets** panel select the source and use **Import**. Watch the job state
and diagnostics. Import is asynchronous; the previous working generation stays
available until a complete replacement is ready.
3. Place the imported scene in the viewport. The Faset object stores the AssetId;
its transform, gameplay components, and physics remain authoring data.
4. Add a rigid body explicitly if needed. Import does not infer gameplay collisions
from the visual mesh.
The same operation works through the command line:
```sh
build/linux-debug/faset_editor --project MyGame --command \
'{"name":"faset_import","arguments":{"path":"Assets/door.glb"}}' --wait
```
With MCP, call `faset_import`, then query `faset_job` using the returned job ID.
`faset_job_cancel` requests cancellation. Cancelling an import does not Undo an
authoring edit. See [MCP and CLI](mcp.md) for transport setup and errors.
## Import an image
PNG and JPEG become image assets. Drag an imported image into a scene to create a
sprite. `pixels_per_unit` is a positive import setting, defaulting to 100; it controls
the sprite's natural size. Color, transform, and layer remain scene properties.
```json
{"name":"faset_import","arguments":{
"path":"Assets/player.png","settings":{"pixels_per_unit":32}
}}
```
Omitting `settings` reuses the saved recipe. Providing an object replaces the recipe;
it is not a partial merge. Malformed or oversized images produce diagnostics and
retain the last successful generation.
## Use the optional Blender helper
The helper works in unmodified Blender. Blender 4.5.3 LTS is the version used for
the observed round-trip test. Ordinary GLB import remains available without it.
1. Zip the repository's `tools/blender_addon` directory with that directory at the
ZIP's top level. Install the ZIP through Blender's add-on preferences and enable
**Faset GLB Export**.
2. Build a static scene using ordinary meshes and glTF-compatible PBR materials.
3. Choose **File → Export → Faset GLB Bundle** and place `manifest.json` in a directory
inside the Faset project's `Assets` directory.
4. **Save the `.blend` after the first export.** The helper assigns `faset_id` custom
properties to objects, meshes and materials, and `faset_asset_id` to the scene.
5. Import the exported `manifest.json` in Faset. Keep the manifest and its
`payload/<sha256>.glb` files together.
The helper publishes the manifest only after the payload is complete. It exports
the active Blender scene, not just the current selection. Gameplay and physics
components are added in Faset and are never written by the Blender helper.
## Rename, duplicate, and reimport
Renaming an object with a saved `faset_id` preserves its source identity. Re-export
and reimport update the shared asset; scene placements using that AssetId load the
new geometry while keeping their own transforms and components.
Duplicating an object can also duplicate its custom ID. The exporter rejects
ambiguous IDs. Select the new copy and run **Faset: New IDs for Selected**. Shared
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
IDs uses structural matching, which cannot guarantee identity after rename or
restructuring.
## Coordinates and supported content
The static profile uses right-handed glTF coordinates: metres, Y up. The Blender
helper enables glTF's Y-up conversion; do not add a second manual axis conversion.
Transforms and hierarchy are retained. Physics currently supports explicit boxes
on root-level objects. A visual imported mesh is not a mesh collider.
The renderer uses static triangle meshes, UV0, base-color factor/texture, metallic
and roughness factors, and directional light/shadows. Import can retain additional
glTF material metadata, but normal/occlusion/emissive/metallic-roughness texture maps,
glTF alpha-mode selection, unlit selection and per-material face-culling behavior
are not all implemented in the baseline renderer. Diagnostics identify unsupported
material features. Procedural Blender node networks need baking or simplification.
Skinning, animation playback, morph targets and compressed geometry are outside this
profile. Imported material records use the versioned `faset.material` format inside
the asset manifest; a standalone material editor is later work.
## What to commit and how to rebuild caches
Commit your source files, Blender bundle manifests/payloads, and
`<source>.faset-import.json` sidecars. Sidecars preserve AssetId and import settings.
Also commit `<source>.faset-overrides.json` if used. Commit `.blend` sources when they
are part of your project. Do not commit `.faset/cache`.
After clearing the cache or cloning a project, import the same sources again. Their
sidecars restore identities and recipes. Rebuild C++ to restore gameplay metadata.
Deleting the **entire** `.faset` directory also removes recovery journals and local
editor state; preserve unsaved work before doing that. An export fails if a referenced
asset has not yet been rebuilt.
The cache key includes source/dependency hashes, settings, importer recipe, pinned
importer dependencies and the desktop target profile. Linux and Windows share this
portable content profile; runtime executable builds remain platform-specific.
For an end-to-end example, open `examples/projects/collect-3d`. Its exit arch includes
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.
+99
View File
@@ -0,0 +1,99 @@
# Build, Play, and export
Faset compiles C++ gameplay into a separate native Player. Changing C++ requires a
build and restart. Changes to an authoring scene can be tested without saving it:
**Play** captures the current resolved scene, builds gameplay, and opens its own
Player process. **Stop** leaves the authoring document unchanged.
## The iteration loop
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
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.
Play captures the authoring document when requested. Editing the document while its
build runs does not silently change that snapshot. Runtime movement, spawned objects,
and gameplay progress do not write back into authoring or its Undo history.
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.
## Export a standalone game
Use **Export** in the Editor for the current scene. Resolve template conflicts and
import all referenced assets first. Export validates the scene, builds C++ and
metadata, cooks resources, copies runtime dependencies/notices, and verifies the
package before publishing it.
Through MCP or the command interface, the operation is:
```json
{"name":"faset_export","arguments":{
"document":"the-open-document-id","output":"Exports/MyGame"
}}
```
The result is a job ID. Poll `faset_job` until `succeeded`, `failed` or `cancelled`.
The successful job's `result` identifies the package. Output paths are relative to
the project; the Editor rejects paths escaping it.
Development builds use **Debug**. Exports default to **Release** and use a separate
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
`Exports/MyGame/generations/<generation>`. `current.json` points to the active
generation. 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.
## Run the package
Open the generation directory and launch `faset_player` on Linux or
`faset_player.exe` on Windows. The package selects its cooked start scene and shader
directory. It does not need the Faset source checkout, Editor, MCP, Blender, CMake,
SchemaExporter or Slang compiler. Vulkan drivers still create native GPU pipelines
from the packaged SPIR-V.
The package includes its resource hashes, build profile and third-party notices.
Keep these files when redistributing it. Faset's own repository license remains an
explicit project-owner decision; third-party notices do not assign an engine license.
## Platform requirements
- **Linux x86-64:** a desktop session and Vulkan loader/driver exposing the baseline
Vulkan 1.3 features. Build on a distribution compatible with the target machines'
C/C++ runtime; the export is not an all-distribution static executable.
- **Windows x86-64:** a supported Vulkan driver and the compatible Microsoft Visual
C++ runtime. Release packages use the dynamic release CRT; install the matching
x64 Visual C++ Redistributable on the target machine. Debug development builds also
require development runtime libraries and are not the distribution package.
Build and test Linux packages on Linux, Windows packages on Windows. Cross-compilation
is not part of this MVP. CI uses software Vulkan on Windows for deterministic image
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.
**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
substitute for the correct source identity.
**Shader build error:** fix the Slang diagnostic and build again. Failed compilation
retains the last successful shader generation; it is not reported as a successful
new build. A pipeline reload also checks resource layouts before replacing a working
pipeline.
**No Vulkan device / unsupported feature:** use a compatible driver/device. The
renderer reports the required capability rather than silently selecting a reduced
graphics profile. Headless authoring and schema export do not require a GPU; playing
and capturing images do.
+76
View File
@@ -0,0 +1,76 @@
# Profiling and measurements
Use a Release export to measure the shipping Player. Record the exact scene, hardware,
driver, build configuration and resolution with the result. Small test scenes do not
establish performance for a large game.
## Capture a bounded Player profile
From a standalone generation directory:
```sh
./faset_player --headless --frames 240 --profile profile.json
```
`--headless` here means **offscreen Vulkan rendering**. A GPU/driver is still required.
The Editor's headless authoring mode is a separate feature. Omit this flag to measure
the windowed path. `--profile` requires an explicit `--frames` between 1 and 100000,
which bounds the stored samples.
The JSON contains raw completed-frame samples and nearest-rank p50/p95 summaries.
No warm-up frames are silently removed. It records the presentation mode, device,
resolution, validation activation, fixed ticks and timestep. A bounded run advances
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
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
when timestamps are unsupported.
Resource counters report live renderer allocations and texture count. GPU allocation
bytes include Vulkan allocation alignment and exclude driver-internal memory; they
are not a whole-process VRAM meter. The fallback white texture is included.
Use `--debug-physics` or press **F3** to show current physics box colliders. Debug
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
From the engine repository:
```sh
python3 tools/measure_workflows.py \
--editor build/linux-debug/faset_editor \
--project examples/projects/collect-3d \
--output .cache/my-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.
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.
`tools/verify_playable_exports.py` separately verifies the two sample games in
relocated Release packages and records their Player profiles. Its assertions test
correct execution, not a frame-time threshold.
## Current performance scope
The MVP renderer is intentionally conservative: direct draws, CPU culling, one
graphics queue and synchronous capture/readback. Use the measurements to find the
next bottleneck before introducing parallel jobs or GPU-driven rendering. Neither
an offscreen capture benchmark nor a tiny demo is a promise of a production frame
budget. Observed measurements and follow-up targets belong in the implementation
acceptance report with their source revision and method.
+128
View File
@@ -0,0 +1,128 @@
# Scene templates and local overrides
A template is an ordinary `.scene.json` scene used as the source of an instance in
another scene. Each instance stores its source path and local differences. Editing
a source field updates instances that have not overridden that field. Templates can
contain other scene instances, forming a nested composition.
## Make a reusable object
1. Create an object and its children, then select the root object in the Scene tree.
2. Open **Scene** and set the template path, for example
`Assets/Templates/Door.scene.json`.
3. Choose **Save selection as template**.
4. Select a containing scene, open **Scene**, enter the same source path, and choose
**Instance scene at this path**. Alternatively, select the saved scene in Assets
and choose **Instance Scene**.
The save operation copies the selected **resolved subtree** to a new source file.
It does not replace the original objects with an instance or apply changes to an
existing source. Nested content inside that copied subtree is flattened into ordinary
objects. The selected root becomes parentless and retains its local transform, so
check its placement if it was previously under a transformed parent. References to
objects outside the copied subtree need deliberate handling; they are not collected
automatically.
Use a new path: Save As will not overwrite another existing scene file. Creating this
source file is separate from the containing scene's Undo history. Remove the original
objects only if you intend to replace them with your new instance.
## Recognize an instance
The Scene tree shows a **[T]** group named after the source scene. Nested instances
have their own indented groups. Select the group to see **Scene instance** in the
Inspector, including its source path and **Open source**.
Select an inherited object inside the group to edit its fields. Its Inspector shows
the source filename. Each exposed field is marked **Source** when inherited or
**Override** when the containing scene supplies a local value.
To change one door's position or behavior setting, edit that field in the instance.
This records a local override; it does not modify the source file. **Revert** removes
that field's local override so it follows the source again. Undo can restore the
previous override.
## Edit the source
Choose **Open source** from an instance or inherited-object Inspector. Edit the
opened source scene, then use the viewport's **Back** button to return to the previous
document. Back switches documents; it is not an Undo operation.
The current editor session resolves instances against an open source document's
in-memory state, including unsaved edits. Save each changed source scene to make those
edits available after restarting or to another session. Saving only the containing
scene does not save its source documents.
Renaming, adding/removing source components, and deeper nested structural changes are
done in the appropriate source document. Inherited object names and component
structure are not editable as arbitrary local overrides. There is no **Apply all
instance changes to source** action in this version: open the source and make the
intended shared edit explicitly.
## Make structural changes to one instance
The top-level instance Inspector provides these operations:
- **Add local object** creates an object owned by this instance. An inherited object's
**Add local child** creates one parented to that object. Local additions can be
renamed and have components added or removed without changing the source.
- **Delete** on an inherited object suppresses it in this instance. The source stays
intact. Select the instance group and choose its **Restore suppressed object …**
button to remove that suppression.
- **Remove instance** removes the entire top-level instance record from the containing
scene. It does not delete the source file. Undo restores the record and its local
differences.
- Edit the top-level instance's **source path** to point to another scene. Existing
local differences are retained; review Conflicts because the new source may not
contain their targets.
These edits are authoring transactions and support Undo. Removing a local addition
from the visible instance also uses suppression, so the instance Inspector can restore
it.
Drag an inherited object's tree row onto another object **within the same instance
path** to reparent it locally. The editor keeps its world pose when representable.
Dragging across instance boundaries is rejected; use **Add local child** or edit the
source hierarchy instead. A nested instance group disables top-level structural
controls and directs you to **Open source**.
## Resolve conflicts without losing overrides
An override targets source object, component, and field IDs rather than the label
shown in the tree. If the source component is removed, for example, the local override
cannot be applied. The Editor keeps that record and lists it in **Conflicts** instead
of silently discarding it.
For a reproducible example:
1. Add a component to the source and save it.
2. In the containing scene, override one of that component's fields.
3. Open the source and remove that component.
4. Return with **Back**. The containing scene reports the unresolved override.
Use the conflict's **Open source** button to inspect the change. If the source deletion
was accidental, undo it in the source document; restoring the original identity lets
the override resolve again. Recreating a same-named component can give it a new ID and
does not automatically reconnect the old record.
If the local value is no longer needed, **Discard override** removes that record from
the containing scene. This action supports Undo. A missing suppressed object can also
show **Discard suppression**. These discard buttons cover supported top-level records;
for nested-source conflicts, open the source that owns the change. Other conflict
kinds are displayed for diagnosis and are not automatically repaired by renaming.
Resolve conflicts before Play or export. A missing source, source cycle, or invalid
address is not a successful partial game build. See [Build, Play, and export](export.md)
for validation and job diagnostics.
## Scope and persistence
Save the containing scene to persist its instance paths, overrides, suppressions,
local additions, and reparents. Save source scenes separately. Unsaved committed
changes have the same [recovery behavior](workspace.md#recover-unsaved-work) as other
authoring edits.
Template resolution produces the flattened scene used for preview and Player startup.
The running game's components are independent of this authoring composition: gameplay
writes do not become template overrides, and MCP does not inspect or change the
Player's live entities.
+174
View File
@@ -0,0 +1,174 @@
# Editor workspace
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.
## Open or create a project
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.
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`.
A recent-project entry fills the directory field; confirm to open it.
The directory browser has **Up**, **Home**, and folder rows. Choose the directory
before confirming the launcher. **Tab** moves focus, **Ctrl+Enter** confirms the
current launcher/browser action, and **Escape** cancels the browser or launcher.
In an open Editor, **File → Open / create another project** returns to the launcher.
If there are unsaved scenes, queued/running jobs, or a Player, a dialog offers
**Cancel** or **Switch project**. Cancel and save first if you want ordinary scene
files updated. Switching stops that session's jobs and Player; unsaved authoring
changes remain in recovery journals. Project switching is disabled when the Editor
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.
## Create and save a scene
Use **File → New 2D scene** or **New 3D scene**. The Scene panel offers **+ Object**,
**Cube**, and **Sprite**. Select an object in the tree or viewport to inspect it.
Choose **Save** or press **Ctrl+S**. A new scene opens the File menu's path field;
enter a project-relative path such as `Scenes/Room.scene.json`, then choose
**Save scene as**. Subsequent saves update that file. The title's `*` marks unsaved
changes. **Open project-relative scene** opens another scene by its project-relative
path; the Assets panel also has **Open Scene**.
Save As refuses to overwrite a different existing file. If the scene file changed
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.
## Navigate and transform
- **Right drag:** orbit the 3D view; pan in a 2D scene.
- **Middle drag:** pan. **Mouse wheel:** zoom.
- **F** or **Frame:** frame the selected object.
- **W / E / R:** choose Move / Rotate / Scale. The viewport also has named buttons.
- Drag a gizmo axis to preview a transform; release to commit one Undo operation.
**Escape** cancels the drag.
- Hold **Ctrl** while dragging to snap: movement uses 0.25-unit increments,
rotation uses 15-degree increments, and scale uses 0.25 increments.
- **Delete** removes a local object, or suppresses an inherited template object.
**Edit → Duplicate selection** duplicates an ordinary local subtree.
Move uses world axes, including for parented objects. Rotate and Scale operate in
local space. The Inspector stores rotation in **radians**. Dragging one Scene-tree
object onto another reparents it while preserving its world pose when representable;
drop into the tree's empty area to return it to the root. Template boundaries impose
additional rules described in [Scene templates](templates.md). Physics bodies must
remain roots for the current runtime adapters.
These shortcuts apply when a text or numeric editor is not consuming the key.
The viewport camera is an editing camera; navigating it does not rewrite a scene
camera component.
## Edit components and undo
The Inspector uses the registered component schema to show numbers, vectors,
checkboxes, enum choices, and text. Use **+ Add Component** to attach a registered
type; the component header's **x** removes a locally owned component.
Type into a field and press **Enter**, or move focus to commit. Numeric fields also
support dragging. A committed field edit or completed drag is one scene Undo action;
preview changes are not separate history entries. **Escape** cancels an unfinished
field edit. **Tab / Shift+Tab** move between controls. Invalid numbers keep their
error state until corrected or cancelled.
Use **Undo / Redo** or **Ctrl+Z / Ctrl+Shift+Z** after finishing the active field.
While editing text, its own editing history can consume Undo. Scene history belongs
to the active document: returning from a source template and undoing in its containing
scene does not undo the source document's edits.
GUI actions and MCP use the same authoring command service. If another action changes
the document while a field or gizmo drag is in progress, the old revision is rejected.
Read the fresh value and retry; the stale edit does not silently replace the newer one.
## Refresh C++ metadata
**Build C++ !** and the status message indicate stale gameplay metadata, for example
after changing gameplay sources or after a failed build. Choose **Build C++**, then
check **Jobs** and **Console**. A successful build and schema export refresh the
Inspector. A failed build retains the previous metadata and reports the failure.
A missing schema or unsupported component version appears as read-only raw fields
with **Copy raw fields**. The Editor preserves that data. Restore the matching module
or provide a migration and rebuild before expecting normal field editing or Play.
See [Build, Play, and export](export.md) for the C++ iteration loop.
## 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.
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.
**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
saved settings before you retry.
## Configure scene simulation
Choose **Simulation** in the toolbar. These settings belong to the **current scene**:
fixed tick rate in Hz, maximum catch-up ticks, physics substeps, and gravity XYZ.
Defaults are 60 Hz, 4 catch-up ticks, 4 substeps, and `(0, -9.81, 0)` gravity in metres
per second squared. Each committed change is one Undo action and is saved with the
scene.
The next **Play** snapshot uses the settings. Editing them does not reconfigure an
already running Player. The 2D adapter uses gravity X/Y and ignores Z. See
[Physics and grounded movement](../scripting/physics.md) before changing the tick rate
or substeps.
## Panels and commands
**Assets** lists project files and imported resources. **Console** shows recent
messages and diagnostics. **Jobs** shows build/import/export progress and cancellation.
**Conflicts** lists unresolved template records. Drag the bottom tabs to reorder them;
drag panel dividers to resize the Scene, Inspector, and bottom areas. These choices
are stored per project in `.faset/editor-layout.json`.
The current layout supports these panel sizes and bottom-tab ordering. It does not
provide floating panels or multiple Editor windows. Theme and base layout JSON live
in the engine's `assets/ui/dark.json` and `assets/ui/editor-layout.json`. The Editor
checks their contents every 500 ms and applies valid changes without restarting.
Malformed changes retain the last working appearance and report an error in Console;
fix the files and the next successful check applies them. Focus and unfinished field
text are preserved.
Layout reload updates properties of existing widgets; retain their IDs, kinds, and
parents. It is not a way to add arbitrary controls or move them to new parents.
Theme-only edits preserve resized panel widths. Editing the base layout can replace
properties explicitly present there, including panel sizes; per-project docking
preferences are stored separately.
**Commands** or **Ctrl+P** opens the command palette. Filter by command name, select a
command, enter its JSON arguments, and choose **Run selected command**. Results appear
in Console. For example, `faset_schema_status` takes `{}`. This is the same editor
command surface described in [MCP and command line](mcp.md).
## Recover unsaved work
Committed authoring changes are written to `.faset/recovery/`. On startup,
**Unsaved authoring recovery** offers **Restore &lt;scene name&gt;** for dirty journals.
Restore brings the recovered scene into memory; inspect it and **Save** when ready.
It recovers scene data, not the full previous session's Undo history or Player state.
**Continue without restoring** closes the prompt without deleting its journals.
Later edits or saves of the same document can replace its recovery record, so restore
or copy a journal before continuing if you still need that draft. Uncommitted text
or gizmo previews are not recovery checkpoints.
If the scene file changed or disappeared since the journal was written, restoration
reports a disk conflict. Keep copies of the journal and current file, then reconcile
them explicitly; recovery does not automatically overwrite external changes.