npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@forgeax/engine-devkit

v0.3.11

Published

ForgeaX external game project CLI and build orchestration.

Readme

@forgeax/engine-devkit

CLI producer lifetime

The published forgeax CLI preserves operation-owned imports. Help and live control queries do not activate Vite, Playwright or build cookers. Source-only project tool discovery starts Vite only when a declared tool contract needs module loading. Browser execution and build operations retain their existing owners; Pack authoring activates one gateway per client and preserves request-id deduplication. The package ships the CLI's dynamic chunks under dist together with the public entry, so SDK/archive consumers follow the same path. Generated Node program tables load the shipped internal pack-program-host.mjs artifact directly; restoring their module closure does not import the DevKit facade or activate Vite/Playwright.

Create behavior assets

forgeax asset plugin create --path assets/movement/movement.pack.ts --json
forgeax asset plugin create --path assets/physics.pack.json --module @forgeax/engine/physics/rapier3d --json

A .pack.ts path creates a native Cordis implementation and its asset definition in one file unless --module selects an existing implementation. A .pack.json requires --module. Use --input for structured configuration, --export for a named runtime export, and --dry-run to inspect the returned source without writing. Creation never overwrites a source. Set the returned GUID with forgeax project root set only when the asset is a project root; feature assets can instead be referenced by another Pack.

Organize Packs by feature. Small behaviors belong in same-file named exports; complex implementations may use ordinary TypeScript modules. Neither plugin.ts nor .plugin.ts is a special entrypoint.

DevKit is the Node-only external-project seam for ForgeaX. It derives project commands, authoring-preview contributions, and one AI-facing RHI-debug path from the existing project authorities. It owns orchestration and file access; Engine owns rendering, RHI events, replay backends, and preview execution.

[!IMPORTANT] Keep one artifact reference across the RHI-debug flow. rhi.summary and rhi.inspect never rediscover a tape, infer a file pair, or parse error text.

The generated development-server owner invalidates compiled module payloads after server close, including the retired environment of a Vite restart. Serving plugins release their own caches through Vite lifecycle hooks; this cleanup does not change project, run, or browser ownership.

Independent backend startup

forgeax backend start --root /path/to/workspace --json
forgeax backend start --root /path/to/workspace --host-pack /path/to/view/host.pack.json --json
forgeax backend status --root /path/to/workspace --json
forgeax backend stop --root /path/to/workspace --json

start provides one resident BackendHost with Engine workspace tools and the selected host PluginAsset. --host-pack selects one installed package's Pack without copying it into the game or changing the game's forge.json. Omit it to use the project's declared host root. A running backend rejects a different explicit host Pack; stop it before changing the selection. The selected path is reported by backend status. It does not launch a browser, create a project session, initialize graphics or execute game Entries. A directory without forge.json can host the Engine APIs with no project plugins. The returned status exposes no transport credentials. Stop detaches its authenticated connections and releases their native sockets without waiting for a browser close handshake; independent runs keep their own engine run stop lifecycle.

The CLI waits up to 120 seconds for the backend to become ready by default. package.json#forgeax.startupTimeoutMs overrides that wait for a project; set it to null to wait until startup completes or the backend process exits.

Native host plugins inject devkitBackend, engineWorkspace and toolApi. devkitBackend exposes the exact Host, workspace root and optional hostPackageRoot. A presentation plugin may provide an optional devkitWorkspaceFrontend { assembly, module } through its Fiber; it is sampled when a caller explicitly opens a workspace. Connection projections use Host's existing bindProjection and DevKitHostBinding.frontendAssembly with an explicit immutable frontendModule, and their lifetime belongs to the connection or owning plugin. Do not update the parent Loader during plugin disposal. Generated serving files remain outside project hot updates. DevKit resolves Vite's normalized file IDs before checking the serving directory, including on Windows; sibling directories and external frontend source changes still reject immutable frontend bindings. callDevKitBackend(root, operation, args) calls one active ToolApi provider over the authenticated local transport; absent or ambiguous capabilities fail.

For example, an installed View host Pack contributes separate view start, view status and view stop commands. view start requires this backend and returns a UI URL; the user opens a browser and explicitly chooses a project or observation target. Its optional Shell mounts before rendering, so a renderer failure does not remove the interface. The embedding devKitWorkspacePlugin contract remains available for applications that already own a Host.

A healthy browser refresh retains the project, session, target, Vite server and accepted Catalog. The workspace reports starting while the old browser's resource previews and presented Play retire through their existing owners. The replacement becomes running only after actual cleanup and its authenticated ready handshake; browserGeneration advances once per accepted browser realm. Commands already sent to the old realm fail and are never replayed. New commands and cancellation use the exact authenticated browser connection, so an old connection cannot execute a replacement's writes.

A known terminal failure remains terminal. Presentation clients may request a guarded replacement through Engine's project.open({ root, expectedTargetId, expectedTargetState: 'lost' }) contract. The session projects its existing failure; no asset query or parallel health monitor decides whether replacement is allowed. Cleanup failure retains the original structured error and presented Play owner instead of admitting a new browser or reporting successful native cleanup. Picking uses the existing target.pick request/result path, including its authentication and exact target/preview-owner checks. The provider neither interprets clicks nor forwards a dedicated selection or pick-error topic; presentation clients decide how query results affect their selection.

Navigation

CLI and catalog

ToolCatalogEntry derives its identity and evidence fields from ToolDescriptor. The catalog requires a normalized command path and keeps schemas as serialized strings; descriptor parser objects remain with the runtime contribution.

The normal project commands remain available through the forgeax CLI:

forgeax project new [directory] --template empty|game-3d
forgeax project init
forgeax project check
forgeax project skill install
forgeax project skill verify
forgeax project test
forgeax dev start
forgeax project build
forgeax project capture --backend auto --require-ui --output artifacts/capture/game-ui.png --json
forgeax project preview
forgeax project package [--format web-zip|single-html] [--output release/game-web.zip]
forgeax help --tree --json
forgeax debug rhi summary --artifact .forgeax-debug/{runId}/frame.rhitape --json
forgeax debug rhi inspect --artifact .forgeax-debug/{runId}/frame.rhitape --work-index {workIndex} --json
forgeax debug rhi read --artifact .forgeax-debug/{runId}/frame.rhitape --reads '[{"resourceId":"buffer:7","workIndex":{workIndex}}]' --json
forgeax debug rhi timing --artifact .forgeax-debug/{runId}/frame.rhitape --json

# Pack authoring identity and recovery
forgeax asset resolve --package-id {packageId} --source-key {sourceKey} --require identity --json
forgeax asset resolve {assetGuid} --require ready --json

[!IMPORTANT] forgeax dev start is a source-development server and forgeax project preview is a verified static dist/ server. Their --json startup envelopes expose mode, serves, and the current rhi.capture capability. Neither command silently invents a live capture attachment.

Static preview prepares gzip level-1 representations of BIN files from the verified dist manifest before listening. It rechecks those original bytes while preparing, leaves dist files and SHA-256 unchanged, and uses the same Node HTTP artifact owner as dev. Vite retains its static HEAD, Range, and conditional-request handling and its existing text compression. The server owns these prepared representations until close; an external HTTPS deployment controls its own HTTP compression policy. For matched BIN URLs, HEAD, Range, and conditional requests require acceptable identity bytes; an explicit rejection of identity returns 406 before delegation.

Persistent live control

Run commands from a game project root, or pass --root. The live owner keeps one actual game running between CLI calls. Use help dev for discovery.

The detached owner keeps its control endpoint in .forgeax/dev-session.json; its project Vite child uses an OS-assigned loopback port rather than the default strict 5173. The ready result and dev status expose the actual project URL, so another process may already own 5173 without changing the owner PID, control endpoint, or revision contract.

When the App bridge is connected, dev status.execution forwards its last sampled Host-owned ExecutionReport, including Engine health, frame credits and Render Worker completion. It samples the existing report without scheduling an eval in the Engine Worker, so stalled frame progress remains directly diagnosable.

forgeax dev start --headless false --json
forgeax dev find --name Player --json
forgeax dev focus --name Player --distance 6 --json
forgeax dev camera set --position '[8,6,8]' --target '[0,1,0]' --json
forgeax dev camera set --lens '{"projection":"perspective","fov":1.0472,"near":0.1,"far":1000}' --json
forgeax dev capture --json
forgeax dev camera release --json
forgeax dev stop --json

Observation commands default to the current runtime. Every successful observation returns its revision; pass --revision to require that exact runtime on the next command. Reload or World replacement invalidates old revisions and entity refs. find returns refs usable with focus --ref when names are ambiguous. focus --name uses an exact name and never chooses the first duplicate. find --limit defaults to 20 (maximum 100).

dev eval --revision <revision> --code 'return simulation.execution.report()' requires a revision because arbitrary code can contain old entity handles. Canceling its wait does not stop already-running code; reload/stop destroys its execution environment. Camera control is temporary: set/focus acquires an observation camera, get reports control, and release returns control to the game. These operations do not save scene edits.

dev capture returns PNG and JSON report references with absolute paths and digests. Default artifacts live in the user's data directory and survive stop or temporary-project deletion. --output selects an explicit PNG path; relative paths resolve against the project. --checkpoint waits for an exact game-provided ready marker; it is not an image label and is unnecessary for ordinary screenshots. The frame number confirms a submission before capture, not the exact frame represented by the PNG.

dev camera set accepts --lens and --exposure as JSON objects. These are temporary observation values; camera release restores the authored game camera. The same values can be passed through --input from a script when a single request needs both fields.

Bounded asset discovery

The unified asset list always returns a bounded page envelope. The default --limit is 100 (maximum 256); add --type and an opaque numeric --cursor to continue a filtered query:

forgeax asset list --type mesh --limit 20 --root ./game --json
forgeax asset list --type mesh --limit 20 --cursor 20 --root ./game --json

The envelope contains items and page for imported assets, or assets, sources, and page for Pack authoring sources. Pack uses one cursor for both projections; page.total is the longer of the filtered output and source collections, so a source-only pack.ts project remains fully discoverable. page.nextCursor is omitted when both projections are complete.

When a project has a generated dist/pack-index.json, its current Pack rows are included as materialized outputs. Their GUID, packageId, sourceKey, kind, source path, readiness, and producer publication are therefore visible to the same list, inspect, and verify commands; a malformed index fails closed instead of silently hiding outputs. Inspecting a published output GUID resolves that output identity, while inspecting a Pack source path still returns source metadata.

asset verify returns one asset-verification-v1 report. It separates the author source (meta.json, pack.json, or pack.ts) from output status and producer state; unproduced and unknown are explicit results. The report is bounded to 256 assets and includes its scanned source scope, so a large result cannot silently masquerade as an empty catalog.

The default --backend auto prefers hardware and allows software fallback; status reports the requested policy, actual backend, and fallback reason. Explicit hardware or software must match the selected adapter. Stop an existing owner before changing its browser options. Headed live windows use the native display density; fixed-size capture keeps its declared resolution.

Use --headless true for unattended operation and --workers '{}' for automatic Engine + Render + eligible Kernel Workers. Each worker accepts auto, true (require), or false (disable), for example --workers '{"kernels":false}'. The status exposes actual workers decisions. An omitted --workers uses the project default: automatic Engine, Render and eligible Kernel Workers.

Projects that combine animation with skeletal motion select the existing App policy with forgeax dev start --animation '{"cadence":"fixed"}'. The public startLiveDev(root, { animation: { cadence: 'fixed' } }) option follows the same path for detached and embedded owners. DevKit forwards it to the one App-owned animation plugin in both main and Worker placements; authored engine plugins consume that owner rather than installing another animationPlugin. The default remains Update. Production builds select the same policy with forgeax project build --animation '{"cadence":"fixed"}' or buildCommand({ animation: { cadence: 'fixed' } }). Build identity includes this choice; changing it requires a new runtime, preserving the old run.

dev reload rereads project inputs; Live checks author file metadata in the background and before/after operations and reloads; it does not depend on filesystem event delivery. Changes reject observations until the new runtime is ready. Unreadable inputs report failure and are retried; edits during reload trigger another reload. revision identifies a runtime, not an atomic disk snapshot or content digest. The scan reads author source bodies only to discover literal documentation dependencies; unrelated prose remains metadata-only. The interval adapts to scan cost (at least one second), while explicit operations check immediately. Checks include path, inode, size, mtime, ctime and mode, and follow author symlinks. Installed dependencies, .git, .forgeax, .forgeax-harness, .worktrees, dist, artifacts, and coverage are excluded. Use explicit reload after changing installed dependencies or Engine builds. The contract assumes ordinary local filesystem metadata; it does not guarantee network-filesystem consistency or atomic multi-file writes. The owner PID and endpoint remain stable; internal project/browser/bridge instances are recreated. dev status reports stopped when no owner is registered, and unreachable when an existing owner cannot be contacted; it never silently replaces that owner.

Static deployment is an Engine-owned build product. A publishing host supplies the project root, public URL base, and dedicated output directory; DevKit owns the generated host, asset cooking, shader compilation, Pack index, and forgeax-dist.json closure.

Generated asset host

DevKit derives one producer composition for dev and build. The generated host uses pluginPack with the project's asset roots and producer registrations; serve mode fixes each catalog with watch: false. DevKit compiles and validates a candidate session before replacing the server and reloading the page. Failed candidates leave the running session intact; old lazy module requests refuse changed source bytes. runtimeBinding identifies the asset scope, while plugin sessionGeneration identifies the whole executable session.

Bootstrap scans retain metadata and captured source bytes; a bounded pool acquires each source or instance only while it builds its plugin definitions. Blocked cooked reads remain deferred to ordinary production. See Pack worker ownership for source evidence and lease cleanup.

| Fact | DevKit rule | |:--|:--| | producerReadiness | Keep producer capability explicit; a missing importer or cooker is a structured failure. | | sourceKey | Preserve producer semantic output identity; never replace it with source position or a cache path. | | Catalog | Read the projection and its lifecycle/diagnostics; do not write Pack, Meta, or DDC from the generated host. | | LKG | Treat lastKnownGood as read-only evidence while repairing the producer, then retry the same GUID. |

The authority map and executable audit are schemas/asset-authority.schema.json and check-asset-authority-audit.mjs. A failed command should expose its structured code, expected, hint, and detail so the named producer can be repaired without guessing from messages.

Plugin definitions are Pack outputs with kind: 'plugin', a module/export locator, and serializable configuration. forge.json.roots selects one asset per realm: Node host, browser frontend, World engine, and isolated build. DevKit compiles literal program tables and plugin-program-inventory.json; it never discovers runtime modules by scanning installed packages.

Custom Vite hosts use executionWorkerEntries() once per host alongside pluginRuntimeProjection() and their pluginProgramsBuild(...) contributions, all from @forgeax/engine/devkit/plugin-build. The Vite root owns Engine module identity and program imports; each contribution's projectRoot owns its content sources and inventory. A host may serve several content projects without adding an Engine umbrella dependency to every content package.

A standalone host with a reloadable Vite config file prepares fresh plugin instances before retiring its accepted executable session when an authored source changes. A failed candidate keeps that session and its frozen bytes. Inline configurations retain their frozen session; their caller owns replacement with a freshly prepared configuration. Generated DevKit hosts use the project session owner above rather than a second automatic restart path. Failed candidate preparation closes its native Vite server before returning the original failure, while the accepted server and its frozen executable bytes remain available.

executionWorkerEntries() keeps emitted Workers in that shared graph and disables DOM module preloading for build output. Lazy Worker imports remain native imports and work without document; custom hosts need no separate preload override.

Emitted JavaScript uses a module lexer for literal import/re-export edges; large generated shader payloads do not require a full TypeScript syntax tree. No-substitution template imports retain their decoded literal semantics. Program relocation and Engine dependency isolation inspect the same edges, while authoring-source validation remains a separate TypeScript operation.

A build root is a native Plugin installed in an isolated Node process. It registers ctx.nativeCookers and ctx.importers through effects. Original producer functions remain in that process; typed IPC carries requests and bytes. Import @forgeax/engine/devkit/plugin-build for the build Context type augmentation. Duplicate keys, blocked bootstrap content reads, startup failures and cleanup residuals reject preparation.

Native external program identity hashes every package file and its declared runtime dependency and peer graph. Its ordered byte traversal uses synchronous Node filesystem reads during preparation, avoiding one asynchronous filesystem round trip per file. No persistent identity cache or file exclusion replaces fresh byte verification; nested resources and type-only ESM directories remain part of the owning package identity.

Source-only plugin discovery evaluates at most four Pack declarations at once, each through its own single-task Worker and the captured source snapshot. A batch settles every started task before validating and committing outputs in original source order, so completion order cannot change duplicate GUID or failure ownership. Caller-retained definitions and parameterized instances remain serial. This changes neither tool-extension discovery nor source fences, runtime readiness or startup bounds.

import type {} from '@forgeax/engine/devkit/plugin-build';
import type { Plugin } from '@forgeax/engine/plugin';
import { volumeCooker } from 'project-volume-tools';
export default {
  inject: ['nativeCookers'],
  apply(ctx) { ctx.effect(() => ctx.nativeCookers!.register(volumeCooker)); },
} satisfies Plugin;

[!IMPORTANT] DevKit also closes the Engine builtin-mesh dependency boundary. During a standalone build it adds a generated Pack descriptor only for builtin mesh GUIDs absent from the project; the Geometry decoder turns those descriptors into validated primitive meshes. Project-authored descriptors remain the single declaration, so the generated Pack never creates a GUID collision.

forgeax project build --root ./games/my-game \
  --base /games/my-game/ \
  --out-dir ./website-staging/games/my-game \
  --json

--out-dir resolves relative to the game project root unless it is absolute. The selected directory is a derived build root and is emptied before writing. forgeax project preview continues to verify and serve the default dist/ directory.

Runtime Pack capability

preparePackProgramSource() and prepareRuntimePackProgram() are build-only preparation APIs from @forgeax/engine/devkit/plugin-build. Custom hosts use this focused entry to prepare JS or TS program sources before admitting their runtime projection. The DevKit root owns backend and CLI orchestration.

Generated main, frontend and Engine Worker providers expose their own runtimePacks Cordis service. Game code injects it and calls its producer to admit content, generate parameter instances, save snapshots or restore them. Frontend's five-action reader consumes that provider's combined Catalog and fetcher. The frontend and Engine producers remain independent. producer.inspect().imports is the discoverable module identity contract; DevKit combines the default roster in src/build/pack-program-imports.ts with Engine capabilities discovered in the actual build graph. A delivered module does not imply that its Cordis services are installed.

Plugin delivery retains pure tool contracts and lowers their executor references into the same program table as plugin exports. Plugin and tool exports from *.pack.ts share the existing runtime projection; the authoring expression is removed once. Each selected plugin has a target-filtered contract, including an explicit empty contract when it declares no tools. Native registerAssetTools owns activation and lazy execution; program-table construction evaluates neither plugin implementations nor tool executors.

The generated bootstrap exposes exact public Engine module locators, including production facade chunks, without eagerly importing them. Retained-program offline evidence requires the executing realm's Engine modules to be already loaded or otherwise available offline; the binding table alone does not provide their bytes. It does not load new Pack implementations or install their plugins. In development, the selected native module files are explicitly admitted to Vite's file serving boundary, including installed packages outside the project. Their module URLs use file URL pathnames so Windows drive letters and encoded path characters retain the same native file identity. Vite session queries stay in the URL query rather than becoming part of the native filename. This does not grant access to their unreferenced sibling files or directories.

| Environment | New native JS module delivery | |:--|:--| | Main/frontend over HTTP(S) | Prepare the application Service Worker on first explicit program load; publish native ESM through CacheStorage. | | Engine Worker | First new-program load requests bounded preparation from its page over a DevKit-owned BroadcastChannel for that App. Existing native graphs need no request. Worker publishes its own modules. | | file://, missing browser capability or failed preparation | Ordinary assets and delivered programs retain their existing routes. New native runtime programs report a structured loading failure. |

The delivery worker only intercepts its program URL namespace. An existing application Service Worker requires Host integration; it is never silently replaced. Program data URLs are not a fallback for failed browser delivery. DevKit-owned live and capture browsers permit Service Workers in their fresh contexts, so HTTP projects use this same native delivery route. Closing the browser releases its context and derived caches; launch profiles do not change the project's module delivery capabilities. The private delivery channel leaves the game's borrowed MessagePort queue untouched. Its name travels through existing bootstrap data; each World owns its pending request, rejects stale replies, and releases its listener on disposal. Missing channel support or preparation failure affects new-program loading, not static startup. Saved source snapshots remain the persistence authority; CacheStorage contains reconstructible module files. Execution after a content-channel disconnect does not prove that the entire application can cold-start offline. Single-HTML export keeps its existing zero-external-request contract.

Workspace publishes the same project-level engine.runtime-pack.* tools through its existing Host ToolApi, authorization hook and command/result transport. prepare invokes the Node program producer (including real TS conversion); the remaining operations execute on the actual project App even before its first preview frame. engine.run.eval remains unavailable to frontend callers. See App's runtime Pack contract for target identity, connection-owned native installations and cancellation evidence. Snapshots return portable source data; the caller chooses a persistence medium. Independent resource previews read the project App's combined Catalog through its existing package transport. Preview disposal preserves the parent producer, and its Registry can observe newly generated versions without owning production.

Borrowed Host project closure waits for the old page's Workspace-scope cleanup response before unregistering its result service. A surviving old page retains its game App and Pack definitions, while its closed Workspace refuses new content operations. Known cleanup failures remain failures of project closure. A lost connection retires with explicit cleanup: 'unconfirmed' evidence on the old handle and tool result, retaining the existing exact lost-target recovery path. An execution failure while the browser remains connected still uses the cleanup command; failed World readiness does not suppress native disposal.

Production Worker selection

DevKit forwards FORGEAX_EXECUTION_WORKERS to App's composable worker policy. An empty object enables all supported workers; individual fields override it. Omitting the variable selects auto for Engine, Render and Kernel Workers, including ordinary projects created from either template. Use {"engine":false} for local execution through the same bootstrap.

FORGEAX_EXECUTION_WORKERS='{}' \
  forgeax project build --root ./games/my-game --json

FORGEAX_EXECUTION_WORKERS='{"kernels":false}' \
  forgeax project package --root ./games/my-game \
  --format web-zip --output release/my-game-web.zip --json

FORGEAX_EXECUTION_WORKERS='{"render":false}' \
  forgeax project package --root ./games/my-game \
  --format single-html --output release/my-game-offline.html --json

Source gameplay plugins can inject world, assets and executionBootstrapHost. Generated page and Worker entries share an SSR generation identity derived from the existing plugin source-input fence, including authored WGSL. sourceHead is the project:<id> namespace, not a Git HEAD. sourceTree hashes sorted relative paths and captured content revisions. lockSha256 hashes the fenced root package manifest and available pnpm/Bun/npm locks; buildSha256 hashes those inputs plus roots, command, base, bootstrap kind and session generation. These are generation input bindings, not a final bundle digest or certification of the entire installed Engine source/runtime. Normal compilation retains the same fence and rejects changed or newly added inputs before publication. Independent rendering places renderer in the child realm; a source plugin that requires it must be migrated to the bootstrap's renderer configuration/feature boundary, or explicitly co-located with render: false. DOM UI remains Host-owned. Select browser UI through roots.frontend, World logic through roots.engine, and resident Node services through roots.host; definitions themselves carry no realm. Browser and Engine contexts receive their own gameHost and asset reader. Browser plugins receive App controls, the live canvas and uiRoot; Engine plugins receive World/assets and explicit scene-owner bindings. UI loads its UiAsset without creating another World. Resource preview and bound workspaces retain their explicit assembly. Ordinary generated games also receive an instance-local gameHost.port in both frontend and Engine plugins. DevKit supplies the Engine end through App's existing execution.bootstrapPort; each realm borrows its own end of the same channel. Use addEventListener('message', listener) and port.start(), and remove the listener through ctx.effect(). The game owns message schemas, request/state handling and reconnection after a World rebuild. DevKit closes the frontend end; App closes the Engine end on disposal. DevKit also closes its original Engine handle if startup fails before App accepts it. Plugins must not close borrowed ports. The channel works with local execution and Engine Workers, in development and shipped builds. Resource/editor hosts may omit the port.

The DevKit development and dist-preview servers provide COOP/COEP isolation and Cross-Origin-Resource-Policy: cross-origin so another isolated Engine document can embed their output across ports. An embedding owner that needs isolation inside its iframe also grants allow="cross-origin-isolated". Production hosting must supply the corresponding headers; without isolation, auto preserves Render Worker and executes numeric kernels inline.

The generated host writes the internal .forgeax/generated/execution-bootstrap.ts; authors do not edit or maintain that file or its emitted name. Production builds emit the App Worker runtimes and bootstrap in one shared module graph, preserving component identity and module state when a Worker loads project plugins. Dynamic imports omit the DOM-only module preloader. Development serving excludes the umbrella and its Engine dependencies from Vite prebundling, deriving that roster from the installed umbrella manifest so native Worker imports and project imports retain the same component tokens and Scene state. The App package dependency closure supplies native scan entries, allowing Vite to discover nested third-party dependencies before target activation instead of later reloading the live page. Independent runs may snapshot dependencies outside the project. The snapshot copies the complete resolved published payload, including dirty inputs, before admitting a run; two closure scans and per-file metadata checks reject concurrent input changes. Small files use bounded 64 KiB buffers and larger payloads stream without changing content versions, readonly permissions, dependency links or cancellation cleanup. Serving admits the resolved App output directory for its Worker constructor URLs while preserving the surrounding filesystem boundary. The bootstrap entry is dist/assets/execution-bootstrap.js, and the generated index.html and its main module reference the same closure. web-zip includes that module and all of its discovered dependencies in the verified archive. single-html embeds the same generated page, module, Worker, WASM, Pack, shader, media, and font closure in the candidate HTML. Follow the normal forgeax project preview path for a built directory, upload a Web ZIP to an HTTPS static host, or open the exact single-HTML candidate through the release browser profile.

forgeax project new accepts an absent or empty target only when that target is outside the unpacked SDK root. An SDK-owned target fails before template copying with project-target-inside-sdk; use a sibling directory or an absolute path outside the SDK.

forgeax project new requires an explicit template: choose game-3d for every 3D game, including custom 3D genres, and choose empty otherwise. Omitting --template fails with sdk-template-required.

[!IMPORTANT] game-3d is a runnable, contentful third-person reference, not an empty 3D scene. It copies authored scene content, visible objects and static collision examples, character/material/animation packs, and UI into the new project; those choices affect the initial picture, movement space, and asset closure. Read the generated project's README.md before pruning anything, then keep, adjust, or replace the starter content for the game's goal and update scene, runtime, and pack references together when removing it.

forgeax project package rebuilds with relative URLs, verifies the complete forgeax-dist.json closure, and emits a deterministic Web ZIP plus an adjacent SHA-256 file. The archive contains the bundled Engine JavaScript/WASM runtime and game assets at its root for HTTPS static or HTML-game hosting; it also attaches the project documents README.md and docs/feedback.md unchanged at those same ZIP paths. Both documents are required, so an incomplete game archive fails before the build starts. It does not contain other author source, node_modules, or a local development server. Use forgeax project preview for local HTTP acceptance. Opening the archived index.html through file:// is unsupported.

| Web ZIP path | Owner | Purpose | |:--|:--|:--| | README.md | Game project | Human-facing game overview and run instructions | | docs/feedback.md | Game project | Development issues and Engine/SDK feedback carried with the release | | index.html, forgeax-dist.json, and declared dist artifacts | DevKit build | Browser runtime and cooked game closure |

single-html remains a separate self-contained-file format; the document attachment contract applies to the Web ZIP only.

forgeax project package --format single-html --output release/game-offline.html emits a self-contained single-HTML candidate and adjacent SHA-256. DevKit derives it from the same verified dist manifest, embeds the generated module/worker/WASM/resource closure, and reports the candidate path, digest, dist-manifest digest, and embedded asset count. The candidate is the acceptance object: open that exact file through file:// in the release browser profile and require zero remote requests, failed requests, console/page errors, and resource misses before promotion. This format does not make arbitrary dist index.html files or network-dependent services offline. The second bundle resolves emitted local chunks. Preload dependency rewrites follow the imported binding; same-named nested functions, parameters, and block bindings retain their arguments. Residual optional bare imports and computed imports use the packaged-resource lookup: an unavailable request rejects and appears in the witness, including a Node-only service requested from the browser.

discoverRhiDebugOperations() returns the same descriptors used by help and schema output. The operation manifest is the single discovery and recovery surface for RHI-debug.

Pack authoring operations

The default Tool Catalog projects the Pack-owned descriptors asset.list, asset.inspect, asset.resolve, asset.verify, asset-source.create, asset-source.clone, asset-source.import, asset-source.create-instance, asset-source.apply-values, asset-source.rebuild, and asset-source.cold-cook. forgeax asset resolve is the CLI convenience adapter for the same asset.resolve executor; it accepts a GUID, a packageId + sourceKey pair, or a source locator. --require identity proves only deterministic derivation, present proves current topology, and ready requires current producer evidence and loadable artifacts. Query operations do not build or write; mutations use the shared request-id and source-revision CAS contract owned by @forgeax/engine-pack.

Browser compositor capture

[!IMPORTANT] forgeax project capture is a development visual-evidence path. It uses the host browser adapter when available and can explicitly use a software lane on machines with no physical GPU or display. It is not a player or release-acceptance gate.

The command starts the source-development host on an ephemeral loopback port, creates an Xvfb display when $DISPLAY is absent on Linux, launches a real Chromium browser, waits for an engine frame signal and non-flat canvas pixels, then takes a Playwright page screenshot. A page screenshot composites the WebGPU canvas with normal DOM and open ShadowRoot UI; canvas.toDataURL() cannot provide that proof. --wait-ms is an additional settle interval after the first non-flat canvas frame, not a guess for CPU startup time.

forgeax project capture --backend software --require-ui \
  --output artifacts/capture/game-ui.png \
  --width 1280 --height 720 --wait-ms 4000 --json

The adjacent game-ui.json is the schema-v2 run manifest. Its run-level fields record the browser, viewport, X display, lavapipe ICD discovery, and browser errors; its ordered captures[] rows record each PNG digest, checkpoint, actual GPUAdapterInfo, canvas/UI witnesses, and canvas-only sampled luma range. backend auto (the default) tries the normal browser adapter and uses the configured software lane when it cannot obtain hardware. backend hardware requires a non-software adapter; backend software requires the software lane. The report records the requested policy, observed backend, and fallback reason. The old --software spelling remains an alias for backend software.

The witness screenshot temporarily hides every non-canvas element, so a visible HUD cannot disguise a black 3D frame; the written PNG remains the complete page compositor output. --require-ui requires at least one mounted child under the generated host's #game-ui root; unrelated Engine or browser ShadowRoots are only diagnostics and cannot satisfy the gate. A uniform black frame also fails even when canvas and adapter structure exist. Browser discovery includes the installed Playwright Chromium; --browser explicitly selects another executable. Resource preview and replay reuse this discovery across Linux, macOS, and Windows, including FORGEAX_BROWSER_EXECUTABLE; an explicit FORGEAX_CHROME_CHANNEL selects the Playwright channel instead.

Project capture fixes the viewport and screen size, DPR 1, sRGB colour profile, light colour scheme, en-US locale, UTC timezone, and waits for document.fonts.ready. An unsized headed development session uses the native window and display density. Projects must ship the same Web font files on every machine; a system-font fallback is not a colour or layout parity contract.

For local/remote pixel or colour comparison, add --deterministic. The command navigates with ?forgeaxCapture=1. The Engine App publishes document.documentElement.dataset.forgeaxFrameSubmitted (the monotonic frame id) after the Renderer crosses its real queue-submit boundary and dispatches forgeax:frame-submitted on the canvas. DevKit waits for that engine signal and a non-flat canvas crop before consuming the game's document.documentElement.dataset.forgeaxCaptureReady checkpoint; it never guesses startup time. The game remains the time authority: in capture mode it must pin its random seed, viewport-independent state, local web fonts, and logical time/frame, render that state, then publish the ready marker. A wall-clock delay is useful only as extra settle and is not parity evidence.

forgeax project capture --backend software --require-ui --deterministic \
  --width 1280 --height 720 --output artifacts/capture/parity.png --json

Persistent playthrough capture

capture is the one-shot adapter over the same browser owner. For one game boot followed by input, assertions, and multiple compositor captures, compose the persistent session through createBrowserCapture(root):

import { createBrowserCapture } from '@forgeax/engine/devkit';
const browser = createBrowserCapture(process.cwd());
async function playthrough() {
  const session = await browser.open({
    backend: 'auto',
    deterministic: true,
    requireUi: true,
    outputDir: 'artifacts/playthrough/boss-flow',
  });
  try {
    const { page } = session;
    await page.getByRole('button', { name: 'Start' }).click();
    const spawn = await session.capture('spawn');
    await page.keyboard.press('KeyW');
    const arena = await session.capture('arena');
    await page.getByRole('button', { name: 'Cast' }).click();
    const bossHit = await session.capture('boss-hit');
    return { report: session.reportPath, captures: [spawn, arena, bossHit] };
  } finally {
    await session.close();
    await browser.close();
  }
}
console.log(JSON.stringify(await playthrough()));
node tests/boss-playthrough.mjs

The Engine App frame signal and the game checkpoint are separate composable authorities: session.capture('boss-hit') waits for the engine frame signal, then the exact document.documentElement.dataset.forgeaxCaptureReady value and non-flat pixels. Playwright remains the input/assertion owner through the unwrapped session.page. DevKit owns Xvfb/Chrome/Vite lifecycle, compositor stabilization, PNG validation, numbering, digests, and one run.json. A live Page or session belongs to the ordinary Node or Bun module that created it; the unified CLI does not provide a script execution command.

| Browser backend | Owner | What it proves | |:--|:--|:--| | Mesa lavapipe | Dawn/Node smoke and explicit GPUTexture readback | Offscreen render pixels without a display; no HTML UI | | Chrome hardware adapter | forgeax project capture --backend hardware | Browser compositor pixels on the selected adapter plus HTML/Shadow DOM | | Chrome SwiftShader under Xvfb | forgeax project capture --backend software or a Node or Bun script using the SDK command client browser session | Browser WebGPU canvas plus HTML/Shadow DOM in ordered viewport PNGs |

[!CAUTION] Software pixels are iteration evidence. They do not prove physical-GPU performance, vendor-driver behavior, HDR-display output, or release visual acceptance.

Engine source binding

Built-package games normally resolve @forgeax/engine from the registry. A source-development game can bind the same dependency name to a checked-out Engine workspace without editing its manifest:

forgeax project engine status --json
forgeax project engine use-local ../forgeax-engine --json
forgeax project engine check --json
forgeax project engine unlink --json

The binding is stored only in .forgeax/engine-binding.json; file absence is the single normal registry/SDK state. The project manifest remains the dependency authority. forgeax project engine check fails closed for a pnpm workspace dependency that npm cannot consume, for an unbuilt local workspace, or for a missing SDK package. Status derives its workspace digest from the actual built entry bytes and reports their newest modification time. engine unlink removes the sole override and returns to the normal registry/SDK resolution path.

A packaged desktop DevKit can also host an external project with no Engine install. Its physical node_modules/@forgeax distribution directory supplies the existing Vite resolver fallback, using each package's declared exports. Project-installed packages keep priority; an explicit source binding continues to select that source workspace. This fallback does not install packages or write a project binding.

Physical realm consumers use createRealmDispatch. Each descriptor is routed to one owner for its declared realm (the ToolRealm contract). A missing owner returns tool-capability-unavailable with the realm in detail; it never falls through to another realm.

Startup diagnostics

Generated hosts keep their normal startup deadlines unless the project sets package.json#forgeax.startupTimeoutMs. A positive integer overrides the App, Worker, Host, root-plugin, and CLI backend-startup deadlines. Set it to null to disable those deadlines; explicit cancellation and disposal remain available. The game can also read this value through GameHost.startupTimeoutMs for its own loading barriers. Omission preserves each Engine surface's normal behavior, and per-frame deadlines are unchanged.

The generated game host renders structured startup failures recursively instead of coercing thrown objects to [object Object]. It preserves name, message, code, expected, hint, reason, and bounded nested cause/detail/webgpuError/wgpuError/error fields. Generic objects use bounded JSON serialization and circular objects receive an explicit diagnostic. Traversal admits up to 64 distinct objects, preserving deep plugin/asset cause chains and reporting a diagnostic node limit when exhausted. Startup input capture also accepts events targeting window; only DOM Nodes are tested for overlay ownership.

When the resulting evidence mentions WebGPU, adapter absence, or no usable backend, the host also states that ForgeaX supports browser WebGPU and a wgpu/WebGL2 fallback. This note is intentionally not a fallback decision: the structured cause remains the authority. A publishing Agent must not infer unsupported hardware, swallow the entry error, or inject a replacement Canvas game merely to remove an uncaught exception.

Generated game startup lifecycle

[!NOTE] The generated HTML owns this small lifecycle; game code does not call a loading API. Workspace, resource, and tool pages omit the normal game overlay, while entry/App failures still use the fatal diagnostic surface.

| Phase | Host-owned contract | Cleanup boundary | |:--|:--|:--| | HTML → module | Static title, status, CSS spinner, and noscript text are present before the entry module is imported. | No App, GPU, WASM, or asset request is required to paint the first loading surface. | | Prepare | prepare() runs before app.start() and changes the status to Preparing scene…. Input is sampled through a closed gate and cleared. | Foreground-only slow-load accounting may show a wait/reload hint after 15 seconds; it never fails the game. | | Enter candidate | A matching submitted/completed frame identity, current canvas owner, World/session identity, device generation, presentation: 'ready', and successful receipt Result are required. A submit or ACK alone is insufficient. | A 150 ms fade keeps hit-testing over the canvas; reduced motion removes the transition. | | Entered | The fade completes before gameplay input and pointer-lock policy reopen. | Startup frame, visibility, input-capture, and timer listeners are removed. | | Failed | Entry errors, app.onError, resource errors, and rejected receipts render structured fatal diagnostics with Reload; arbitrary nested detail remains inspectable. | Loading/fade work and gameplay input are cleared; the fatal Reload listener is retained for the terminal page. | | Hidden/destroyed | Background tabs do not spend the slow-load budget or invent a completed frame. | pagehide, entered, and failed paths cancel pending timers and startup listeners. |

Startup observes nonbubbling App receipts through the stable page capture path. Render Worker recovery can replace the transferred canvas; receipts from a retired or unrelated canvas cannot enter the game or reuse an old submission. Replacement during the fade restores preparation and waits for the new canvas before enabling input.

RHI-debug operations

For difficult or unexplained rendering failures, start with forgeax-engine-rhi-debug. Start the live owner with forgeax dev start --rhi-capture true, reproduce the failing state, then run forgeax debug rhi capture --json. The CLI routes capture to that live owner, invokes its recorder, and persists one .rhitape; retain the returned path and digest. Capture requires a ready recorder-enabled App and does not start one automatically. Stop an existing owner before changing its capture options.

The lower-level createCliRhiDebugOperationContext() is offline-only and returns capture-unavailable for capture; it is not the unified CLI's live capture route. File paths are sufficient for both commands. Optional --digest sha256:... verifies the expected artifact bytes; a mismatch returns artifact-digest-mismatch before a replay device is created. rhi.summary returns summary.works[] with work/event/pass coordinates, pipeline entry points and attachments. summary.unseededResources identifies retained resources whose initial bytes are absent; inspect their producing work before using replay pixels as evidence. Full shader and binding details remain in rhi.inspect --fields '["pipeline","bindings"]'.

rhi.summary and rhi.inspect use this offline context to consume the persisted artifact and create a fresh replay backend.

sequenceDiagram
  participant AI as "AI or CLI"
  participant H as "Host"
  participant D as "DevKit"
  participant R as "Fresh replay backend"
  AI->>H: "rhiCapture.captureFrame through the live host"
  H-->>AI: "one ArtifactRef"
  AI->>D: "rhi.summary + ArtifactRef"
  D-->>AI: "FrameModel and workIndex"
  AI->>R: "rhi.inspect + ArtifactRef + workIndex"
  R-->>AI: "WorkInspection or structured failure"

| Operation | Input | Output | Owner | |:--|:--|:--|:--| | rhi.capture | Optional abort signal | One ArtifactRef plus capture bytes | Host and recorder attachment | | rhi.summary | One ArtifactRef | Strict v7 decode and compact work/initial-content summary | Protocol decoder and frame model | | rhi.inspect | One ArtifactRef, one workIndex, optional fields | Fresh-backend WorkInspection | Replay session and readback matrix | | rhi.read | One ArtifactRef, 1-64 reads by resource or work binding | Per-read bytes digest, records, image stats and optional PNG | Replay session batch readback and image decode | | rhi.timing | One ArtifactRef | Replay GPU nanoseconds per render/compute pass and frame total | Replay session timestamp-query replay |

ArtifactRef

ArtifactRef is the handoff SSOT:

| Field | Type | Meaning | |:--|:--|:--| | kind | 'rhi-tape' | The only RHI-debug artifact kind. | | digest | string | Digest of the canonical v7 tape payload. | | source | string | Host or operation that produced the artifact. | | path | string | Optional Node-side path owned by the host. | | bytes | Uint8Array | Capture payload supplied at the host boundary. |

Consumers switch on RhiDebugError.code, then narrow .detail. .expected and .hint provide the next action without message parsing. Unknown operation names, fields, artifact kinds, and tape versions fail at the boundary.

Authoring preview

Preview defaults to four explicit Engine-owned domain descriptors: material.preview, mesh.preview, vfx.preview, and texture.preview. Each follows the same command declaration and terminal path and binds its report and evidence to the requested subject and snapshot.

flowchart LR
  catalog["Digest-bound catalog"] --> descriptor["Domain descriptor"]
  descriptor --> owner["Engine realm owner"]
  owner --> terminal["One terminal result"]
  terminal --> evidence["PNG, RHI tape, and profile refs"]

The generic asset.preview and preview.run shapes are not discoverable. A private legacy proof helper may remain for migration tests, but it cannot appear in the default catalog or act as a second domain owner. Browser and Dawn consumers must verify subject pixels and structured evidence; DOM liveness and RhiNull readiness are not visual proof.

Project authority

forge.json.roots, package manifests/locks, and authored Pack/Meta/code are persistent authority. package.json#forgeax.startupTimeoutMs is the project startup policy: positive milliseconds set a shared startup deadline, null disables it, and omission retains Engine defaults. Program tables, Catalog and runtime assembly are derived. Definitions use deferred reference consumption; loading a plugin asset does not install it.

Node plugin delivery exports portable artifacts through the existing program entries' asynchronous exportSource() for the selected root's reference closure, including referenced but unmounted siblings and their tool executors. Pack co-location does not assign a target; other targets require their own producer projection before complete capture. All selected exports share one complete module graph. The provider carries pure tool contracts plus live Host bindings; archives preserve only artifacts and contracts. createNodePackProgramImports rebinds recorded imports in a fresh Host. Generic npm imports retain a project package authority, dependency route and selected entry, preserving distinct installed instances even when their bytes match. Content identities include runtime peers; Node builtins and Cordis use their actual identities. Declared npm dependencies retain their installed package authority even when their names also identify Node builtins, such as the punycode package. Missing dependencies or changed bytes cannot satisfy the archived import contract.

Browser static plugins use the existing Vite module graph in both the page and Worker; their normal installation does not require Service Worker support. A Pack may also contain backend definitions without putting their implementations in that browser graph. The player boundary admits the browser-safe Import entry and rejects Node imports (including Vite browser stubs) and compiler packages.

Build and development delivery expose lazy program export without evaluating plugins or tool executors. It archives the player's final JavaScript graph, preserving shared project modules and replacing Engine facade locators with exact Host capability requirements. Every selection retains the complete graph. Archive JSON sits beside the emitted program-table chunk; it is not embedded in its own executable graph. The provider chunk hash includes all archived targets and the Engine identity, so either kind of version change also changes the archive URL. An unrecorded image, CSS, public-file, WASM or independent Worker dependency makes export fail explicitly. Ordinary static installation remains available.

Development initializes Vite's optimizer before freezing the final transformed closure of all browser selections, including literal lazy imports. Source-input validation completes before the page can consume this accepted graph. HTTP delivery and export then read the same frozen bytes through failed source candidates. Every frozen module carries the current Host's generated-root identity in its native URL; a replacement Host rejects old-session requests, including archive requests. JSON and third-party virtual modules retain their original Vite producer IDs during conversion; the final response supplies session-bound import URLs. HTTP, transform queries and warmup consume the same frozen bytes, released when the Host closes. This transport identity stays outside the portable program schema. Vite query semantics such as ?raw, ?worker and optimizer versions are preserved. Resource evidence comes from the actual Vite 8 producers; pure raw text and ordinary strings are not rejected based on their filename. Sharing an undeclared transitive Engine dependency makes export fail rather than duplicate that dependency on restore. Asset-only startup retains its on-demand Pack preparation.

The source fence includes declared local plugin, tool-contract and executor closures, including Pack references to published dist JavaScript. Discovery freezes declared files; the actual compiler and contract loader resolve literal imports, self-package exports and package imports before consuming each closure. New paths extend the same input snapshot; changed recorded bytes reject the session. Unrelated dist output remains disposable and is not broadly scanned.

The provider's programHost reuses the delivered native URLs only when the complete archived graph and actual Host binding URLs match. Changing the selected entry or export preserves module identity; changing bytes, graph membership or bindings uses new-module delivery. The known selection entries let unrelated new programs bypass archive I/O. A possibly matching graph with an unavailable archive fails explicitly, preserving component identity instead of silently duplicating it. Engine capability bindings are lazy locators, not an instruction to import every capability at startup. Main, Worker and frontend assembly share the same provider host with their runtime Pack producer.

Browser tests capture a complete mixed-target Pack and restore its JSON snapshot into a fresh World in both page and DedicatedWorker realms, with Service Workers blocked. They preserve project and Engine component identity, native plugin/tool state and the unexecuted backend attachment in build and development modes. Development tests also cover a broken candidate before the first tool execution, and old-page lazy imports across successful Host replacement. Build tests then delete the author checkout and Node producer output, retain only a copied dist and saved Pack, and restore in a new Chromium process at a new origin, in both the page and a fresh DedicatedWorker with Service Workers blocked. Existing component identities survive; plugin/tool evaluation stays lazy and gameplay counters start fresh. New-program tests separately start a fresh browser with empty CacheStorage, prepare only the Host's Service Worker transport, then disconnect the network before the first restore. Saved JS and plugin/tool programs rebuild the empty derived module cache and execute. The consumer must first receive its own Engine and App modules; this proves offline asset restoration after consumer startup, not offline startup of an entire website. Static native modules and their archive remain the consumer Host's delivery responsibility. DedicatedWorker tests also verify first-use delivery preparation after Worker startup. Separate build/dev by JS/TS tests use actual createApp with the null RHI: generate Mesh, Scene and PluginAsset together, instantiate the Scene, execute an inline tool, replace parameters and dispose through native Fibers. They restore and regenerate from saved JSON in a fresh browser after consumer capabilities and SW transport are ready, with empty program caches and the network disabled. JS preparation stays in the browser; TS conversion uses the real DevKit producer before admission. These are structural App checks.

The separate pnpm --dir packages/devkit test:runtime-browser graphics gate uses actual App EngineWorker execution with browser WebGPU, in build/dev by JS/TS. It starts from an empty delivered Catalog, then submits new generator/plugin source after startup. Native PluginAsset installation adds an ECS Update system and an inline tool; withdrawal preserves the installed Fiber, and disposal stops its system while unrelated gameplay continues in the same World. Failed candidates leave the snapshot, Catalog, program table and current Scene intact. The gate requires 60 additional ready, completed GPU receipts after generated content enters the Scene, checks mesh width changes in compositor screenshots, and closes the owned Worker through App disposal. Build JS/TS cases also close Chromium, create a fresh browser and EngineWorker with empty program caches, prepare only consumer Engine capabilities, the empty Catalog and Host SW, then disconnect before the first saved-Pack restore. Restored plugins execute and new parameter values render for another 60 completed frames without a compiler or producer request. This proves offline restoration after consumer startup; the test retains the original static server and does not establish whole-site offline startup. It does not replace View, SDK delivery or the complete Engine smoke gates.

On a Linux host with software WebGPU, run under a task-owned display:

FORGEAX_BROWSER_EXECUTABLE=/opt/google/chrome-beta/chrome \
FORGEAX_RUNTIME_PACK_EVIDENCE=/tmp/runtime-pack-worker-evidence \
xvfb-run -a pnpm --dir packages/devkit test:runtime-browser

The optional executable override otherwise uses Playwright's Chromium. This gate explicitly selects SwiftShader: results establish browser/GPU behavior, not hardware performance. Evidence includes snapshots, parameter-state images, frame and plugin counters, publication checks and disposal results.

| Action | Command / source | |:--|:--| | Create a plugin asset | forgeax asset plugin create | | Inspect definitions and source evidence | forgeax asset plugin inspect | | Select/remove a realm root | forgeax project root set | | Migrate a schema 2.0 project | forgeax project migrate | | Import a portable author closure | forgeax asset source import | | Clone source with independent identities | forgeax asset clone |

Use help <path> --json for the authoritative request schema. For example, forgeax project root set --realm engine --guid <guid> selects a root; --guid null removes it. Source transfer uses a new destination directory beneath assets, preserves source-relative layout, checks exact pnpm dependency-lock evidence, compiles the candidate and fences source/target revisions before commit. Cloning rewrites known GUID/namespace references and project WGSL module identities together with their material/import references. The candidate must retain its source closure inside the new clone directory; computed references that escape it fail before publication. Generated outputs are rebuilt in the target. Sources requiring cooked reads during closure discovery report an explicit unsupported transfer instead of copying an incomplete closure.

Changes to project roots, code, configuration, dependencies, or asset sources compile a candidate before replacing the running session. A standalone Vite Host using pluginProgramsBuild restarts through Vite when its frozen executable inputs change, captures a new program/publication pair, and rejects URLs from the retired session. A new page or Worker receives one sessionGeneration; the accepted asset catalog stays frozen until replacement. Failed compilation preserves the old runtime; failure after replacement requires a new session and does not recover game progress. When the source fence detects a save during candidate compilation, the Host rejects that snapshot and prepares once from current source. This bounded retry handles a coalesced final save without another