Publish Forma Engine 0.3.0 source with documentation and CI
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# Architecture
|
||||
|
||||
The editor and MCP mutate one serializable project through `ProjectStore`. The runtime loads a copy; stopping playback restores the editor scene. Project content and behaviours remain in project files.
|
||||
|
||||
| Module | Responsibility |
|
||||
| --- | --- |
|
||||
| `engine/schema.ts` | Project format, validation, IDs, transforms and references |
|
||||
| `engine/store.ts` | Atomic transactions, revisions, undo/redo and request receipts |
|
||||
| `engine/geometry.ts` | Procedural geometry and compound models |
|
||||
| `engine/runtime.ts` | Babylon rendering, model loading, animation, physics, input and worker bridge |
|
||||
| `engine/character.ts` | Fixed-step Rapier character movement and contacts |
|
||||
| `engine/script-worker.js` | Project behaviour lifecycle and command output |
|
||||
| `engine/templates.ts` | Blank project construction |
|
||||
| `engine/archive.ts`, `engine/build-kit.ts` | Portable projects, web output and application build kits |
|
||||
| `editor/` | React editor, inspector, file mode and IndexedDB draft storage |
|
||||
| `server/index.ts` | Local HTTP service, project persistence, events and runtime bridge |
|
||||
| `server/mcp.ts`, `server/stdio.ts` | MCP tools, resources, prompts and stdio adapter |
|
||||
| `server/builds.ts` | Build queue, immutable snapshots, logs and artifacts |
|
||||
| `native/` | Optional application wrappers and packaging tools |
|
||||
| `scripts/build-local.mjs` | Reproducible editor/player bundles and build-kit resources |
|
||||
|
||||
## Document and runtime
|
||||
|
||||
Transforms are local to an entity's parent. Reparenting preserves local coordinates unless a transform is supplied. Duplicate and prefab operations remap internal parent, camera target and typed entity-property references; external references remain unchanged.
|
||||
|
||||
Transactions apply atomically. A stale `expectedRevision` fails instead of overwriting intervening edits. Repeating a transaction with the same `requestId` can reuse the recorded result. History and receipts are held in memory, while the project document is persisted.
|
||||
|
||||
Runtime loading, playback, stopping and spawning are serialized. Physics advances at a fixed 1/60-second step. A worker receives copied state and returns commands; it does not own the editor document. A behaviour error disables that behaviour. An unresponsive worker is terminated after its watchdog timeout. This protects responsiveness, not against malicious project code.
|
||||
|
||||
## Local service
|
||||
|
||||
The Node server listens on loopback and checks Host and Origin. MCP requests require a bearer token. Files are written atomically, imported paths are constrained to the project directory, and static resources come from the engine's `public` directory. The service is intended for one user and one server process per project folder.
|
||||
|
||||
The browser performs rendering, simulation and scripting. Runtime MCP tools need an open editor tab; the first connected tab handles runtime requests. File-only mode can edit projects without the Node service, but local MCP and application build jobs then are unavailable.
|
||||
|
||||
## Distribution
|
||||
|
||||
`npm run build` generates browser bundles from source and copies the native build-kit templates. These generated directories are not tracked. A standalone web export contains the runtime, project and resources, and needs neither the React editor nor MCP.
|
||||
|
||||
Native outputs wrap the web runtime in Electron or Android WebView. Native toolchains and user projects are separate from the engine repository. No hosted-site deployment configuration or hosted account is required.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Blender and model import
|
||||
|
||||
1. Check the model's scale, armature, deformations and animation names.
|
||||
2. Export **glTF 2.0 → GLB** so geometry and textures travel in one file.
|
||||
3. Include skinning and the desired animation actions/NLA tracks in the export settings.
|
||||
4. Use **Импорт GLB** in Forma or drag the file into the editor. Import errors appear in the console.
|
||||
5. Preview imported animation clips from the animation component. Choose clip names in your project scripts.
|
||||
6. Add colliders and rigid-body or character components as needed. A model with feet at `y = 0` often needs an upward collider offset.
|
||||
7. Attach a behaviour to add movement or other interactions. A model alone does not provide game logic.
|
||||
|
||||
Forma imports glTF PBR materials, model textures, skeletons and animation clips. Blender procedural materials and geometry-node workflows do not transfer directly; bake or apply them before export.
|
||||
|
||||
Self-contained `.gltf` files with data URIs are supported. External `.bin` and texture file sets are not assembled by the editor; GLB is the simplest portable path. Draco, Meshopt and KTX2-compressed assets are rejected by the current importer to avoid external decoder requirements.
|
||||
|
||||
Blender MCP is optional and is not installed by this repository. Forma's `arena` and `character` generators create editable hierarchies; `extrude`, `lathe` and `mesh_create` provide geometry tools without Blender. The compound character generator does not create a rig, and no neural text-to-3D model is bundled.
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
# Web and application builds
|
||||
|
||||
Forma packages a project as a standalone web build, Linux x64 AppImage, Windows x64 portable EXE or Android APK. The output contains the project's scenes, behaviours, models and runtime. The player does not need the editor or MCP service.
|
||||
|
||||
Application builds wrap the web runtime. Desktop uses Electron and Chromium; Android uses a Java WebView shell. Project JavaScript is not compiled ahead of time into native machine code. Android requires Android 8+ and a compatible, updated system WebView.
|
||||
|
||||
## From the editor
|
||||
|
||||
1. Start the local engine and open **Сборка игры**.
|
||||
2. Choose a target, name, stable application ID and version.
|
||||
3. Set desktop window/fullscreen options or Android orientation.
|
||||
4. Start the build and inspect its log. The job uses an immutable snapshot of the selected project revision.
|
||||
5. Download a successful artifact. The build panel shows its size, SHA-256 and source revision.
|
||||
|
||||
The queue allows one active build and up to three waiting jobs. Jobs can be cancelled. Build results and logs remain in the project folder; an interrupted build is marked failed when the server restarts.
|
||||
|
||||
Without the local service, the editor can download a **build kit** containing the current web project, platform templates, configuration and lockfile. A build kit is source material for a local build, not an already compiled executable.
|
||||
|
||||
## Web
|
||||
|
||||
Choose the web target and extract its ZIP. Serve the extracted directory over HTTP:
|
||||
|
||||
```bash
|
||||
python3 -m http.server 8080
|
||||
```
|
||||
|
||||
Open `http://localhost:8080/`. Static HTTPS hosting also works. Opening `index.html` through `file://` is not supported. For testing on a phone, serve only the exported project to your local network; the editor's MCP service remains bound to loopback.
|
||||
|
||||
## Desktop toolchain
|
||||
|
||||
Requires Node.js 22.13+. Install the optional tools separately from the editor:
|
||||
|
||||
```bash
|
||||
npm run setup:desktop
|
||||
npm run build:doctor
|
||||
```
|
||||
|
||||
The first build downloads the required Electron runtime and packaging tools. Linux AppImage requires a Linux host. The current Windows configuration can be packaged from Linux; Windows output is unsigned.
|
||||
|
||||
`ELECTRON_BUILDER_COMPRESSION_LEVEL` can override the default compression level of 3. Toolchains live outside project source and are not required for web export.
|
||||
|
||||
## Android toolchain on Linux
|
||||
|
||||
Install a full **JDK 17**, including `javac`, plus `curl` and `unzip`. Set `JAVA_HOME`, then run:
|
||||
|
||||
```bash
|
||||
npm run setup:android
|
||||
npm run build:doctor
|
||||
```
|
||||
|
||||
The setup script downloads Gradle 8.13, Android SDK platform 36 and Build Tools 35.0.0. Android SDK license acceptance and dependency downloads are part of local setup. The Gradle archive is checked against its published SHA-256.
|
||||
|
||||
An existing SDK can be selected with `ANDROID_HOME` or `ANDROID_SDK_ROOT`. `FORMA_GRADLE` selects an existing Gradle executable by absolute path. The automated Android toolchain setup currently targets Linux.
|
||||
|
||||
Debug APKs use the local Android debug signing key. Release APKs require your signing configuration in the server process environment:
|
||||
|
||||
| Variable | Value |
|
||||
| --- | --- |
|
||||
| `FORMA_KEYSTORE` | Absolute keystore path |
|
||||
| `FORMA_KEY_ALIAS` | Signing-key alias |
|
||||
| `FORMA_KEYSTORE_PASSWORD` | Keystore password |
|
||||
| `FORMA_KEY_PASSWORD` | Key password |
|
||||
|
||||
Keep the key outside the repository and retain a backup for application updates. Signing passwords are not entered through the editor or MCP. The builder verifies generated APK signatures. A release build without a configured key fails instead of substituting a debug package. AAB output is not implemented.
|
||||
|
||||
## Command line
|
||||
|
||||
After `npm run build`, pass a saved project folder, JSON or `.forma` file:
|
||||
|
||||
```bash
|
||||
npm run game:build -- --project ./projects/MyProject --target linux --app-id games.studio.myproject --version 1.0.0 --mode release
|
||||
npm run game:build -- --project ./projects/MyProject --target windows --app-id games.studio.myproject --mode release
|
||||
npm run game:build -- --project ./projects/MyProject --target android --app-id games.studio.myproject --mode debug --version-code 1
|
||||
```
|
||||
|
||||
Additional CLI options include `--name` and `--fullscreen`. A downloaded build kit provides a JSON configuration for window dimensions and orientation. The standalone builder accepts:
|
||||
|
||||
```bash
|
||||
node native/build.mjs --config ./my-build.json --game ./my-web-project --out ./my-output
|
||||
```
|
||||
|
||||
The output directory must be new or empty. Existing application packages are not overwritten.
|
||||
|
||||
## MCP build tools
|
||||
|
||||
| Tool | Purpose |
|
||||
| --- | --- |
|
||||
| `build_targets` | Inspect toolchain and signing readiness |
|
||||
| `build_start` | Queue a build from the current project revision |
|
||||
| `build_status` | Read status, logs, artifacts and hashes |
|
||||
| `build_list` | List recent jobs |
|
||||
| `build_cancel` | Cancel a waiting or active job |
|
||||
|
||||
Read the project and available targets before starting a build. Poll its status until it finishes. Successful packaging confirms that an artifact was produced; launch and performance checks on the target device are separate.
|
||||
|
||||
## Runtime boundaries
|
||||
|
||||
Desktop keeps Node integration unavailable to project scripts, with sandboxing and context isolation enabled. Packaged resources are served through the local `forma` protocol; external navigation and permissions are blocked. AppImage needs a working Chromium sandbox, and the wrapper rejects `--no-sandbox`.
|
||||
|
||||
Android uses WebViewAssetLoader for packaged resources, with no Internet permission, general file access or JavaScript-to-Java bridge. Both wrappers are intended for offline projects. Network features, storefront integration and platform services require additional implementation.
|
||||
|
||||
The automated test suite covers build configuration, resource access, kit generation and job handling. Native package creation requires the separate platform toolchains. Hardware performance and application launch behaviour must be checked on the intended devices.
|
||||
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"transactions": "1–1000 commands apply atomically. Read project_read first. Mutation expectedRevision must match current revision. requestId deduplicates successful retried transactions. On REVISION_CONFLICT reread project before editing.",
|
||||
"coordinates": "Y up, meters, radians. Entity transforms local to parent. Reparent preserves local coordinates unless you provide transform. Kinematic move uses world displacement; other move uses local.",
|
||||
"commands": {
|
||||
"project.rename": "{name}",
|
||||
"project.settings": "{background:\"#dedbd2\",ambient:0.85,shadows:true,renderScale:1}",
|
||||
"scene.create": "{id?,name}",
|
||||
"scene.activate": "{id}",
|
||||
"scene.rename": "{sceneId,name}",
|
||||
"node.create": "{name,id?,position?,parentId?,components?,sceneId?} OR {entity:{id,name,parentId:null,enabled:true,transform:{position:[0,0,0],rotation:[0,0,0],scale:[1,1,1]},components:{}}}",
|
||||
"node.patch": "{id,patch:{name?,enabled?,transform?,components?},sceneId?}; deep merge, arrays replace, id/parentId immutable here",
|
||||
"node.reparent": "{id,parentId:null|string,transform?,sceneId?}",
|
||||
"node.delete": "{id,sceneId?}; subtree",
|
||||
"node.duplicate": "{id,sceneId?}; subtree and internal references",
|
||||
"component.set": "{id,type,value,sceneId?}; replaces component",
|
||||
"component.remove": "{id,type,sceneId?}",
|
||||
"asset.upsert": "{asset:{id,name,kind:\"model\"|\"geometry\"|\"prefab\",uri?,geometry?,entities?,metadata?}}",
|
||||
"asset.delete": "{id}; fails if referenced",
|
||||
"script.upsert": "{script:{id,name,source,fields:{speed:{type:\"number\",default:5,label:\"Speed\",min:0,max:30}}}}",
|
||||
"prefab.create": "{id,name?,sceneId?}",
|
||||
"prefab.instantiate": "{assetId,position?,sceneId?}"
|
||||
},
|
||||
"components": {
|
||||
"mesh": {
|
||||
"type": "box | sphere | cylinder | icosphere | torus | model | geometry | custom",
|
||||
"size": [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"assetId": "for model/geometry types",
|
||||
"geometry": "{positions,indices,normals?,uvs?} for custom type"
|
||||
},
|
||||
"material": {
|
||||
"color": "#91a697",
|
||||
"roughness": 0.8,
|
||||
"metallic": 0,
|
||||
"emissive": 0,
|
||||
"override": false
|
||||
},
|
||||
"collider": {
|
||||
"shape": "box | ball | capsule",
|
||||
"size": [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"radius": 0.32,
|
||||
"height": 1.8,
|
||||
"offset": [
|
||||
0,
|
||||
0.9,
|
||||
0
|
||||
],
|
||||
"sensor": false,
|
||||
"enabled": true
|
||||
},
|
||||
"rigidbody": {
|
||||
"type": "fixed | dynamic | kinematic",
|
||||
"mass": 1,
|
||||
"restitution": 0.1
|
||||
},
|
||||
"camera": {
|
||||
"mode": "follow | firstPerson",
|
||||
"targetId": "subject",
|
||||
"offset": [
|
||||
0,
|
||||
13,
|
||||
-10
|
||||
],
|
||||
"fov": 0.72,
|
||||
"yaw": 0,
|
||||
"pitch": 0
|
||||
},
|
||||
"character": {
|
||||
"gravity": 24,
|
||||
"autostep": 0.25,
|
||||
"requires": "kinematic rigidbody + capsule collider"
|
||||
},
|
||||
"sign": {
|
||||
"text": "Text in the scene",
|
||||
"color": "#d7f34b",
|
||||
"width": 5
|
||||
},
|
||||
"light": {
|
||||
"color": "#fff1da",
|
||||
"intensity": 2
|
||||
},
|
||||
"animator": {
|
||||
"idle": "Idle",
|
||||
"run": "Run",
|
||||
"attack": "Attack",
|
||||
"death": "Death",
|
||||
"speed": 1
|
||||
},
|
||||
"script": {
|
||||
"scriptId": "script_rotate",
|
||||
"params": {
|
||||
"speed": 1
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"customValue": 1,
|
||||
"label": "Application-defined properties"
|
||||
}
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
# MCP setup
|
||||
|
||||
Forma uses the MCP TypeScript SDK with Streamable HTTP and a stdio adapter. Both access the same project as the local browser editor.
|
||||
|
||||
## Start the engine
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run build
|
||||
npm start -- --project ./projects/MyProject
|
||||
```
|
||||
|
||||
Open `http://127.0.0.1:4318/`. The server creates a private `.mcp-token` file inside the project folder. The token is a credential and is excluded from Git.
|
||||
|
||||
The HTTP endpoint is `http://127.0.0.1:4318/mcp`. Clients send `Authorization: Bearer <token>`. This stateless endpoint accepts MCP POST requests; GET and DELETE return 405.
|
||||
|
||||
## Local stdio client
|
||||
|
||||
Configure a compatible client using absolute paths:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"forma": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"/absolute/path/forma-engine/server/stdio.mjs",
|
||||
"--project",
|
||||
"/absolute/path/forma-engine/projects/MyProject"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The adapter connects to an already running local HTTP service. It resolves the TypeScript loader relative to the engine installation, so the client's working directory can differ. Standard output contains only protocol messages.
|
||||
|
||||
For a non-default server port, add `--url` and `http://127.0.0.1:4320/mcp` to the adapter arguments. Each adapter must use the folder belonging to that server's project.
|
||||
|
||||
## Agent workflow
|
||||
|
||||
1. Call `project_read` and read `forma://reference/commands` and `forma://reference/scripts`.
|
||||
2. Apply related edits together with `commands_apply`, using the current `expectedRevision`.
|
||||
3. On a revision conflict, reread the project. When retrying the same request, retain its `requestId`.
|
||||
4. Inspect the scene and diagnostics. Use `runtime_play`, input, snapshot and capture tools to check the running project.
|
||||
5. Save the project and request a web or application build.
|
||||
|
||||
`runtime_capture` returns a real PNG from the connected viewport. Runtime tools require an open local editor tab. `EDITOR_DISCONNECTED` means no tab is connected; `EDITOR_TIMEOUT` means the editor did not answer within 15 seconds. Keep one editor tab open for automation.
|
||||
|
||||
`model_generate` and `mesh_create` support procedural geometry without Blender. Imported files may be supplied as base64 or by a path within the project folder. Game logic is authored in project scripts; the engine does not ship a complete game.
|
||||
|
||||
The server exposes its current schemas through MCP discovery. Static reference files in `docs/` document commands and script APIs.
|
||||
|
||||
## Remote clients
|
||||
|
||||
A cloud client cannot directly reach `127.0.0.1` on your computer. A compatible secure connection or authenticated HTTPS gateway is needed. Forma does not include OAuth, a hosted MCP service or an automatic tunnel installer. Account-specific connector setup is separate from installing this engine.
|
||||
|
||||
The local editor service is designed for loopback use. Remote access needs a deployment designed for that environment; changing the bind address alone does not provide one.
|
||||
+1164
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "JavaScript expression returning {start(api), update(api,dt)}. No imports or TypeScript. Worker watchdog 1500ms. Imported project scripts are TRUSTED code; Worker is responsiveness isolation, not a security sandbox.",
|
||||
"api": {
|
||||
"state": "Persistent per-instance mutable data during one play run",
|
||||
"params": "Field defaults + component.script.params",
|
||||
"input": "{x,z,attack,pointer,aim,jump,dash,sprint,jumpPressed,dashPressed,resetPressed,yaw,pitch}; yaw=0 faces -Z in first person",
|
||||
"get": "api.get(id?) -> clone of entity or null",
|
||||
"entities": "api.entities() -> clones of entity states",
|
||||
"position": "api.position(id?) -> local coordinates",
|
||||
"move": "api.move([dx,dy,dz]); real collisions for kinematic body",
|
||||
"physics": "api.physics(id?) -> {grounded,velocity:{x,y,z},contacts:[{entityId,normal:[x,y,z]}]}",
|
||||
"velocity": "api.velocity({x?,y?,z?,gravityScale?}); persistent m/s, requires character component; gravity runs at 60 Hz",
|
||||
"teleport": "api.teleport([x,y,z],yaw?); clears velocity and contacts for character respawn",
|
||||
"emit": "api.emit(name,data?); delivers a presentation event to runtime callbacks",
|
||||
"rotate": "api.rotate(yRadians)",
|
||||
"patch": "api.patch(id,patch); updates state/transform/enabled. Structural mesh/collider edits take effect next Play.",
|
||||
"animate": "api.animate(clipOrState,loop=true); use false for a one-shot animation",
|
||||
"effect": "api.effect(\"swing\"|\"hit\",id?)",
|
||||
"spawn": "api.spawn(prefabAssetId,position); behaviors start on spawned instances",
|
||||
"destroy": "api.destroy(id?); disables entity+collider",
|
||||
"scene": "api.scene(sceneId); starts another scene",
|
||||
"log": "api.log(text)"
|
||||
},
|
||||
"example": "({ update(api, dt) { const n = api.get(); api.rotate(n.transform.rotation[1] + api.params.speed * dt); } })"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# Third-party dependencies
|
||||
|
||||
Forma builds on these packages. Exact versions and transitive dependencies are recorded in the root and `native/` lockfiles.
|
||||
|
||||
| Package | License |
|
||||
| --- | --- |
|
||||
| Babylon.js | Apache-2.0 |
|
||||
| Rapier JavaScript | Apache-2.0 |
|
||||
| MCP TypeScript SDK | MIT |
|
||||
| React | MIT |
|
||||
| fflate | MIT |
|
||||
| Lucide | ISC |
|
||||
| Zod | MIT |
|
||||
| esbuild | MIT |
|
||||
| TypeScript | Apache-2.0 |
|
||||
| tsx | MIT |
|
||||
|
||||
Full license texts are provided by the installed packages. Preserve applicable third-party notices when distributing bundled output. esbuild retains legal comments in generated bundles.
|
||||
|
||||
Optional application builds use Electron 44.3.0, electron-builder 26.15.3, AndroidX WebKit 1.14.0, Android Gradle Plugin 8.13.2 and Gradle 8.13. Electron and electron-builder use MIT licenses; AndroidX, Android Gradle Plugin and Gradle use Apache-2.0. Electron includes Chromium and its third-party notices in distributed applications.
|
||||
|
||||
Android SDK tools are installed separately under their applicable terms and are not included in this repository. A JDK must also be installed separately. These dependency notices do not designate a license for Forma's own source code.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Engine-only validation — 2026-09-09
|
||||
|
||||
Verified for this source package, including a clean installation after extracting the release source archive on Linux with Node.js 22.22.1 and npm 9.2.0:
|
||||
|
||||
- `npm ci --no-audit --no-fund`: successful clean dependency install.
|
||||
- `npm run typecheck`: successful.
|
||||
- `npm run build`: editor, standalone player and native build templates generated successfully.
|
||||
- `npm test`: 39 passing tests, no failures.
|
||||
|
||||
Coverage includes empty-project creation, atomic/revision-checked transactions, geometry, project archives, a generated skeletal GLB fixture loaded by Babylon, worker lifecycle and watchdog recovery, Rapier collision/character movement, real MCP HTTP and stdio requests, and build orchestration.
|
||||
|
||||
Games, game assets, project-specific players/HUDs and hosted-service metadata were removed. Browser bundles are generated locally and excluded from source control. New projects have no bundled assets, scripts or entities.
|
||||
|
||||
The local editor was opened in a desktop browser. Visual checks covered the initial empty scene, creating cube/sphere/cylinder primitives, editing transforms, restoring the scene after a reload, and focusing/zooming the viewport. A real editor screenshot is included at [screenshots/editor.png](screenshots/editor.png). The temporary preview project is excluded from Git.
|
||||
|
||||
GitHub Actions repeats the clean install, build, type check and test suite on Node.js 22. The build runs before integration tests because they read the generated editor and player bundles.
|
||||
|
||||
Native application binaries were not rebuilt for this engine-only package; the tests cover build packaging and orchestration, not real installation on every target device.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
Reference in New Issue
Block a user