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
+56
View File
@@ -85,3 +85,59 @@ triangle glTF meshes/UV0, basic PBR/directional shadows and a conservative seria
Vulkan renderer. Advanced rendering and broader content profiles remain later work.
The generated UI reference determines visual direction only; architecture, behavior
and acceptance criteria remain authoritative.
## Checkpoint 3 — playable projects and complete authoring workflows
Implemented and exercised on Linux:
- Two playable C++ projects with real Box2D/Box3D input routes, pickups, a physical
gate, an exit condition and reset. The 3D project includes a reproducible original
Blender arch, `.blend` source, stable-ID bundle and import instructions.
- Native project launcher, retained folder browser, real recent projects, keyboard
navigation and safe project switching. GUI/MCP sessions retain one fixed project.
- Nested template UI, source navigation, local additions/suppression/reparent,
per-field origin/Revert and conflict preservation. Parented gizmos have tested
transforms, cancellation and one committed Undo operation.
- Explicit Project settings with file-content revisions and atomic save; per-scene
Simulation remains a separate authoring transaction. Live theme/layout reload
validates candidates and keeps working state on malformed edits.
- Standalone PNG/JPEG assets; versioned material records and explicit portable
cache profile/toolchain identity. Clearing cache preserves identity and overrides
when original sources and sidecars are reimported.
- Normalized Slang reflection, shader artifact hashes and renderer ABI compatibility
validation. Real compile failure, changed binding/matrix layout and invalid SPIR-V
keep a working pipeline; compatible pixel-changing reload succeeds.
- Physics debug box outlines, bounded raw Player profiles, observed validation
activation, CPU/GPU/readback durations and allocation counters.
- MCP broken-pipe/EOF handling and fresh GUI capture after presentation back-pressure.
A real GUI + stdio regression performs 12 PNG captures, a conflicting edit and Undo.
- English manual guides for workspace, templates, Blender, export and profiling,
alongside compiled C++ tutorials. Generated UI references remain visual guidance.
Acceptance evidence:
- The integrated Linux test suite passes **29/29 tests**, including ten GPU tests:
native CPU, Vulkan, UI, real MCP transport, plugins, schema, physics and playable
input routes. Strict MkDocs also passes.
- `tools/verify_playable_exports.py` exported the exact 2D/3D samples in Release,
imported the Blender arch, checked package hashes and relocated each package outside
its project. With the source-project paths unavailable, both passed validation and
120 offscreen frames on RTX 2080 Ti with the Khronos layer active and zero errors.
- Renderer/shader regressions also passed the pinned Linux SwiftShader driver. This
is additional software-driver coverage, separate from Windows execution.
- `tools/measure_workflows.py` recorded a fresh sample Debug build and incremental
iteration, including stale-schema transitions. On the development host, the
initial build took 93.06 s, unchanged build 5.59 s, changed gameplay build 11.18 s,
and the subsequent one-frame Player process 0.37 s. The first/cached small Blender
arch imports each took about 0.047 s including Editor startup. Ambient builds were
running; these are observations, not release budgets.
- Profiling identified uncached readback memory as a concrete bottleneck. In a small
paired five-frame diagnostic, preferring compatible HOST_CACHED memory reduced
median readback from 27.87 to 0.47 ms and renderer-call wall time from 30.81 to
2.92 ms. GPU work was about 0.58 ms. A longer final baseline is still required.
This remains an implementation checkpoint. Windows full graphics/export CI is still
building its pinned software driver. A review also identified Windows Unicode path
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.
+71
View File
@@ -0,0 +1,71 @@
# Toolchain and execution profiles
Faset uses C++20 without compiler extensions. Dependencies are pinned by commit and
archive SHA-256 in `dependencies.lock.json`; Slang is pinned to **2026.18** by
`tools/fetch_slang.py`. Changing these inputs is a deliberate SDK change. Editor
plugins additionally require the exact generated build fingerprint, including
compiler, platform, CRT, configuration, dependencies and SDK source identity.
## Observed compiler profiles
- **Linux development:** Ubuntu 26.04, x86-64, Clang **21.1.8**, CMake **4.2.3**,
Ninja, glibc **2.43**. Native Editor, GPU tests, gameplay and exports execute here.
- **Linux CI:** Ubuntu 24.04, x86-64, Clang **18.1.3**. The headless CPU suite and
manual run on this profile. It is not evidence for desktop rendering on that runner.
- **Windows CI:** Windows Server 2025 runner, x86-64, clang-cl **20.1.8**,
MSVC toolset **14.51.36231**, Windows SDK **10.0.26100.0**. Headless tests have
passed. Full Editor, software Vulkan and Release-package acceptance are tracked
separately in the implementation log until that job completes.
These are recorded validation profiles, not a claim that every intermediate Clang
release or every supported Windows desktop has been tested. The presets deliberately
use the compiler available on PATH so local installations remain practical. CI
configuration output records the actual detected versions; runner image upgrades
must be reviewed against this baseline. CMake **3.25** is the declared minimum, not
the exact version used in every recorded run.
MSVC-family builds use the dynamic CRT: `/MDd` in Debug, `/MD` otherwise. Physics,
Player, Editor and native plugins use compatible settings. Development and export
have separate CMake directories, avoiding accidental Debug/Release mixing.
## Graphics profiles
The baseline requires Vulkan 1.3 with dynamic rendering and synchronization2, a
graphics queue, the renderer's color/depth format usages and limits, and presentation
support for a native window. The backend checks these requirements before creating
resources. It does not require hardware ray tracing, mesh shaders or descriptor
indexing. Timestamp availability is queried; unavailable GPU timings are reported
as unavailable rather than zero.
- **Physical Linux GPU:** NVIDIA GeForce RTX 2080 Ti, driver **595.84**, reported
Vulkan **1.4.329**. Tests request `VK_LAYER_KHRONOS_validation` and report whether
the layer actually activated. This machine runs both Wayland window and offscreen
scenarios.
- **Software Vulkan:** official SwiftShader and Vulkan Loader sources are pinned
with archive hashes in `tools/ci/prepare_windows_vulkan.py`. This profile checks
API execution and output pixels on Windows CI. It is CPU rendering and is not a
physical GPU performance benchmark. The bootstrap does **not** install validation
layers, so its zero error counter must not be described as validation-layer proof.
The measured physical GPU is one verified device, not an exhaustive compatibility
matrix. Additional AMD/Intel GPUs, Windows desktop drivers and display configurations
need their own execution records.
## Offline preparation
Before disconnecting, install the native toolchain and platform development packages,
fetch Slang, and run:
```sh
python3 tools/fetch_dependencies.py
python3 tools/fetch_dependencies.py --verify-only
```
Preserve `.cache/downloads`, `.cache/slang`, compilers, CMake/Ninja, Python and system
SDK/libraries. A fresh source checkout can then configure and build without fetching
third-party sources. Runtime exports require only their documented target runtime
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.
+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.
+6
View File
@@ -36,6 +36,9 @@ Create a project and open the native Editor:
build/linux-debug/faset_editor --project "$PWD/MyGame" --new MyGame --dimension 3
```
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.
Use **Build C++** after changing `MyGame/Scripts/Gameplay.cpp`, then **Play**.
The Player runs separately. Stop it before changing and rebuilding C++ gameplay.
See [MCP and CLI](../editor/mcp.md) for headless authoring and automation.
@@ -73,3 +76,6 @@ ctest --preset windows-debug
Windows acceptance is tracked separately from Linux; a successful Linux build does
not verify a Windows build.
The repository's `docs/TOOLCHAINS.md` records the exact compiler, SDK and GPU profiles
used in observed validation, separately from the minimum tool requirements above.
+2 -1
View File
@@ -7,7 +7,8 @@ and how those functions interact with scenes, physics, and the editor.
!!! warning "Development status"
MVP implementation is in progress. A planned feature is not a working feature.
Individual guides state their prerequisites and validation status. The current
Editor, gameplay tutorials and Linux export can be built and tested. Final Windows graphics/export acceptance and complete sample games are still in progress.
Editor, gameplay tutorials, two playable sample games and Linux export can be built
and tested. Final Windows graphics/export acceptance is still in progress.
Start with [how C++ gameplay works](scripting/index.md), then read
[frame and physics updates](scripting/lifecycle.md). See
+21 -1
View File
@@ -38,7 +38,7 @@ These are values, not borrowed component pointers. Changing a returned copy has
- `void teleport(EntityHandle, const Transform&)` changes the pose discontinuously, wakes the body, and resets interpolation/contact-query history. It does not zero velocity.
- `bool grounded(EntityHandle) const` tests support using recent native contact normals.
These methods require a valid handle and a physics body. Ordinary `setTransform` is rejected for physical objects, including static and kinematic bodies. See [physics](physics.md) for dimensions, tolerances, and collider limits.
All methods require a valid handle. Velocity, impulse, and grounded queries also require a physics body; `teleport` accepts physical and non-physical objects. Ordinary `setTransform` is rejected for physical objects, including static and kinematic bodies. See [physics](physics.md) for dimensions, tolerances, and collider limits.
## Input and collision events
@@ -68,3 +68,23 @@ Immediate validation errors throw. Deferred failures are recorded in diagnostics
`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.
`snapshot()` returns a value snapshot for rendering; `snapshotJson()` provides its JSON representation. `diagnostics()` returns a read-only vector of runtime messages. These are native C++ APIs for the Player and tests, **not MCP endpoints**.
## Inspect a running Player locally
The Player has local development controls in addition to gameplay input: **P** toggles
pause, **N** steps one fixed tick while paused, and **Escape** closes the Player.
**F3** toggles physics-box outlines; `--debug-physics` enables them from startup.
Static bodies are green, kinematic bodies orange, and dynamic bodies cyan.
These outlines use current physics poses and collider half-extents multiplied by the
absolute transform scale. They can differ slightly from an interpolated visible mesh.
The initial adapters support root-only boxes: four outline edges in 2D, twelve in 3D.
This view does not show contact normals, broad-phase cells, or arbitrary mesh colliders.
To record measurements, run the Player with `--profile measurements.json --frames 240`.
A profile requires an explicit count from 1 to 100,000. As with all bounded Player
runs, simulation uses the configured fixed delta each frame; `--headless` renders
through offscreen Vulkan. The report contains measured durations rather than treating
that simulation delta as frame time. See [Player profiling](../editor/profiling.md)
for startup, CPU/GPU timing, percentiles, and their limits. These flags do not add MCP
to the Player.
+8
View File
@@ -48,3 +48,11 @@ ctest --test-dir build/linux-debug -R '^tutorial_' --output-on-failure
On Windows use the chosen Windows build directory. The four tests compile separate gameplay libraries and execute their actual callbacks. They require neither a graphics window nor the Editor. Running a tutorial through `faset_player` additionally checks rendering and platform integration and requires the documented Vulkan setup.
Schema declarations are tested for stable map-key/FieldId matching and defaults. They remain ordinary C++ source; the engine does not scan arbitrary C++ classes to create this API automatically.
## Play complete small projects
`examples/projects/collect-2d` and `examples/projects/collect-3d` contain complete projects with a manifest, scene, and a separate `Scripts` module. Open either folder with the Editor's `--project` option, then use Play. Each project's README also provides a direct Player build command.
Move the blue block with A/D in 2D or WASD in 3D, jump with Space, collect three gold cubes, and reach the green exit after its red gate opens. E resets the round. One pickup is on a raised platform. A visible gold marker and the Player log confirm completion; these initial examples use geometric progress displays instead of a text HUD.
The `playable_2d` and `playable_3d` CTests drive the actual modules through input, including the jump, objective, reset, and fresh session. The 3D module also explicitly registers a separately packaged `example.beacon` component from its local `Scripts/Extensions/Beacon.hpp`. Both projects use built-in geometry and need no imported assets to start.