Capability Matrix

Measured on: 2026-09-05 against main and the production deployment. Tracking issue: #9720. Readiness review: #9714.

This is the accurate statement of which capability works today through which entry point. It replaces the README's former claim that "every capability" is exposed and controllable via MCP. Every row below carries one status per entry point; a status that is not proven names the issue that tracks the gap, and a status of excluded says why in the Notes column.

The four entry points:

ColumnMeaning
Human/UIA person using the editor's panels, inspectors and dialogs.
In-app AIThe built-in chat assistant and the game-creation pipeline, which call manifest commands through web/src/lib/chat/executor.ts. The model is offered every manifest command with a :write scope plus the query category (getChatTools()): 290 of the 374.
ScriptingA user-authored game script calling the forge.* API (web/src/lib/scripting/forgeTypes.ts), which dispatches only the names in scriptAllowlist.ts.
External MCPAn MCP client (Claude Code, Claude Desktop, any MCP host) attached to the editor through mcp-server/ and its relay. The bridge is an allowlist (web/src/lib/mcp/bridgeAllowlist.ts) that permits 316 of the 374 command names and withholds the scripting, generation, export, publishing, security and economy categories and the ai:generate / project:manage scopes by design.

Legend

StatusDefinition
provenExercised end to end against a running build, with the evidence named in Notes: a Playwright spec in the CI @engine-smoke gate (web/playwright.engine.config.ts), or a by-hand check on production on the date in the header. Nothing else earns this word. On a category row it means Notes names the spec and the commands it drives; the commands in that row the spec does not drive are implemented-unverified (#9714), and Notes says so.
implemented-unverifiedThe code path exists and unit or parity tests pin it, but there is no end-to-end evidence against the shipped artifact. The launch-readiness review that owns closing this gap is #9714.
partialPart of the row works through this entry point and part does not. Notes says which part; the issue names the gap.
unavailableCannot be used through this entry point today, whatever the code says — a missing key, a missing gate, a missing engine arm. The issue names the fix.
excludedDeliberately not offered through this entry point. Notes says why. This is a design decision, not a gap.

Facts this matrix reflects (verified 2026-09-05)

Generation capabilities

One row per entry in PROVIDER_CAPABILITIES (web/src/lib/config/providers.ts). Provider per capability is DIRECT_CAPABILITY_PROVIDER; the platform key each provider needs is PLATFORM_KEY_ENV. Every External MCP cell in this table is excluded: the bridge allowlist denies the generation category and the ai:generate scope, so no generation command is reachable over the bridge even in a local build.

CapabilityHuman/UIIn-app AIScriptingExternal MCPNotes
generation:chatimplemented-unverified(#9714)implemented-unverified(#9714)excludedexcludedAnthropic. Served by the Vercel AI Gateway (OIDC) or ANTHROPIC_API_KEY, not a PLATFORM_* key, so #9117 does not apply. Chat panel; ai-chat.spec.ts and chat-commands.spec.ts exist but are outside the CI engine gate. Scripts have no chat function by design — forge.ai only wraps asset generation. An external MCP client is its own model; the bridge exposes no chat call.
generation:embeddingexcludedunavailable(#9117)excludedexcludedOpenAI. No user-facing entry point: embeddings are search infrastructure. PLATFORM_OPENAI_KEY is unset and OpenAI is not a BYOK provider. Not a manifest command, so nothing to bridge.
generation:imageunavailable(#9818)unavailable(#9818)excludedexcludedOpenAI is DIRECT_CAPABILITY_PROVIDER.image, and image is a live capability of the provider registry — the direct backend, OpenRouter and the Vercel AI Gateway all declare it (GATEWAY_CAPABILITIES; web/scripts/verify-platform-generation.ts grades the gateway on it). But no generate route or chat handler resolves image, so there is no product entry point: generate_skybox posts to /api/generate/skybox, which is Meshy under the texture capability (see that row), and apply_style_transfer calls no provider — it returns a suggestion to regenerate with generate_texture / generate_sprite. The OpenAI key is consumed by generation:sprite (DALL-E 3), not here. #9734 confirmed there is no product code path; the capability is kept in the registry (removing it would drop the verified gateway path) and the missing product entry point is owned by #9818 (FR-2). forge.ai has no image function. Bridge: ai:generate scope denied.
generation:model3dunavailable(#9117)unavailable(#9117)unavailable(#9117)excludedMeshy (generate_3d_model, generate_3d_from_image, generate_character). PLATFORM_MESHY_KEY is unset. BYOK: a user's own Meshy key works through the dialog, chat and forge.ai.generateModel (implemented-unverified). A stored Hyper3D key does nothing — /api/generate/model resolves DB_PROVIDER.model3d (Meshy) only. Bridge: generation category denied.
generation:textureunavailable(#9117)unavailable(#9117)unavailable(#9117)excludedMeshy (generate_texture, generate_pbr_maps, and generate_skybox via /api/generate/skybox). PLATFORM_MESHY_KEY is unset. BYOK Meshy key works through the dialog, chat and forge.ai.generateTexture (implemented-unverified). Bridge: generation category denied.
generation:sfxunavailable(#9117)unavailable(#9117)unavailable(#9117)excludedElevenLabs (generate_sfx). PLATFORM_ELEVENLABS_KEY is unset. BYOK ElevenLabs key works through the dialog, chat and forge.ai.generateSound (implemented-unverified). Bridge: generation category denied.
generation:voiceunavailable(#9117)unavailable(#9117)unavailable(#9117)excludedElevenLabs (generate_voice). PLATFORM_ELEVENLABS_KEY is unset. BYOK ElevenLabs key works through the dialog, chat and forge.ai.generateVoice (implemented-unverified). Bridge: generation category denied.
generation:musicunavailable(#9117)unavailable(#9117)unavailable(#9117)excludedElevenLabs (generate_music, forge.ai.generateMusic). Moved off Suno to ElevenLabs in #9522 — one ElevenLabs key also covers sfx and voice. PLATFORM_ELEVENLABS_KEY is unset. BYOK ElevenLabs key works through the dialog, chat and forge.ai.generateMusic (implemented-unverified). Bridge: generation category denied.
generation:spriteunavailable(#9117)unavailable(#9117)excludedexcluded/api/generate/sprite (generate_sprite, generate_sprite_sheet, generate_tileset, generate_pixel_art) resolves Replicate SDXL for the pixel-art style and OpenAI DALL-E 3 for every other style. PLATFORM_REPLICATE_KEY and PLATFORM_OPENAI_KEY are unset and neither is a BYOK provider. forge.ai has no sprite function. Bridge: generation category denied.
generation:bg_removalunavailable(#9117)unavailable(#9117)excludedexcludedremove.bg is DIRECT_CAPABILITY_PROVIDER.bg_removal. The code path now exists (#9734): /api/generate/sprite resolves the remove.bg key with resolveByokOrPlatformKey(userId, 'removebg')PLATFORM_REMOVEBG_KEY (web/src/app/api/generate/sprite/route.ts), and SpriteClient.generateSprite chains removeBackground() on the synchronous DALL-E path when removeBackground: true (web/src/lib/generate/spriteClient.ts, generateWithDalle). So it is now key-gated exactly like the other platform capabilities, not code-missing: PLATFORM_REMOVEBG_KEY is unset in production and remove.bg is not a BYOK provider, so it stays unavailable (#9117). The SDXL path returns a pending prediction id with no inline URL to post to remove.bg and is not wired here — residual owner #9818. forge.ai has no background-removal function. Bridge: ai:generate scope denied.

Command categories

One row per category value in mcp-server/manifest/commands.json. The count is public/internal commands in that category. "Chat-exposed" means getChatTools() offers the command to the model (a :write scope, or the query category). An External MCP cell is excluded exactly when BRIDGE_DENIED_CATEGORIES in bridgeAllowlist.ts names the category; every other External MCP cell is unavailable (#9722) because the bridge cannot attach to production.

CategoryHuman/UIIn-app AIScriptingExternal MCPNotes
commands:animationimplemented-unverified(#9714)implemented-unverified(#9714)implemented-unverified(#9714)unavailable(#9722)20/0. Timeline panel and animation inspector; animation-workflow.spec.ts is outside the CI engine gate. forge.animation dispatches the eight *_animation / set_clip_speed names, all armed.
commands:assetimplemented-unverified(#9714)implemented-unverified(#9714)implemented-unverified(#9714)unavailable(#9722)5/0. Asset browser panel. forge.asset.loadImage / loadModel.
commands:audioimplemented-unverified(#9735)implemented-unverified(#9714)partial(#9284)unavailable(#9722)38/0. Audio mixer panel, AudioInspector and AdaptiveMusicInspector; audio-mixer.spec.ts is outside the CI engine gate. Human/UI: the AdaptiveMusicInspector's Configure Stems button registers the default adaptive track via audioManager.setAdaptiveMusic, and the intensity slider then forwards to audioManager.setMusicIntensity('default', clamped) on the same path as the set_music_intensity chat handler (value clamped 0–1, NaN refused), syncing adaptiveMusicIntensity in the store; moving the slider before a track is registered surfaces a toast rather than silently doing nothing (#9735). Scripting: forge.audio play/stop/pause/volume/bus and the layering calls work (the layering calls are answered JS-side by audioManager); forge.audio.setMusicIntensity and forge.audio.loadStems dispatch the phantom set_music_intensity / set_music_stems (there is no setMusicStems function).
commands:cameraimplemented-unverified(#9714)implemented-unverified(#9714)excludedunavailable(#9722)4/0. Editor viewport camera (focus, frame, reset). Not a runtime concern; runtime camera control is forge.camera, covered under commands:game_cameras.
commands:compoundexcludedimplemented-unverified(#9714)excludedunavailable(#9722)9/0. Multi-step tools (create_scene_from_description, setup_game_from_description, ...) that exist for the model; a person composes the same result from the individual panels. pipeline-live-engine.spec.ts (CI @engine-smoke) proves the generated-game pipeline builds through the real engine, but drives the game-creation flow rather than a chat tool call.
commands:cutsceneimplemented-unverified(#9714)implemented-unverified(#9714)excludedunavailable(#9722)5/0. Timeline panel. Cutscene authoring is editor-time; scripts trigger dialogue and scene loads instead.
commands:dialogueimplemented-unverified(#9714)implemented-unverified(#9714)implemented-unverified(#9714)unavailable(#9722)8/0. Dialogue editor panel; dialogue-editor.spec.ts is outside the CI engine gate. forge.dialogue runs at play time.
commands:docsimplemented-unverified(#9714)excludedexcludedunavailable(#9722)0/3. In-editor docs browser (F1). docs:read commands are not offered to the chat model (getChatTools() exposes :write scopes and query only); scripts have no documentation API. The public reference at docs.spawnforge.ai/mcp returns 500 (#9718; fix in PR #9730, open).
commands:economyimplemented-unverified(#9714)implemented-unverified(#9714)excludedexcluded1/0. Economy panel. No forge.* economy namespace exists. Bridge: bridgeAllowlist.ts denies economy ("touches the token economy").
commands:editorimplemented-unverified(#9714)implemented-unverified(#9714)excludedunavailable(#9722)7/0. Gizmo mode, snapping, coordinate mode, selection. keyboard-shortcuts.spec.ts carries six @engine-ui tests that run in the per-PR engine gate; they cover the gizmo hotkeys and no-selection safety, which this row does not count as end-to-end evidence for the seven commands. viewport-picking.spec.ts is outside the gate. Editor-time only.
commands:environmentimplemented-unverified(#9714)implemented-unverified(#9714)excludedunavailable(#9722)5/0. Scene settings panel (skybox, ambient, fog); scene-settings.spec.ts is outside the CI engine gate. Editor-time only.
commands:exportimplemented-unverified(#9714)implemented-unverified(#9714)excludedexcluded6/0. Export dialog. engine-smoke.spec.ts (CI @engine-smoke) opens the dialog and asserts the Export Mode options render, then closes it — no export is produced, so none of the six commands is exercised end to end. Export is a person's decision; scripts run inside the exported game. Bridge: bridgeAllowlist.ts denies export ("moves the project out of the tab").
commands:game_camerasimplemented-unverified(#9714)implemented-unverified(#9714)partial(#9284)unavailable(#9722)4/0. GameCameraInspector and SmartCameraPanel. forge.camera.setMode, setTarget, shake and setProperty work through worker messages useScriptRunner answers JS-side; forge.camera.follow, stopFollow, setPosition and lookAt dispatch the phantom camera_follow / camera_stop_follow / camera_set_position / camera_look_at and are silent.
commands:game_componentsimplemented-unverified(#9714)implemented-unverified(#9714)excludedunavailable(#9722)5/0. Inspector drag-and-drop behaviours; game-components.spec.ts is outside the CI engine gate. Components are configured at edit time; scripts read their effect through forge.physics / forge.input.
commands:generationpartial(#9117, #9818)partial(#9117, #9818)unavailable(#9117)excluded5/19. Idea generation (generate_game_ideas, start_from_idea, ...) runs on the chat backend and is implemented-unverified; every asset generator is unavailable per the generation rows above (music moved off Suno to ElevenLabs in #9522 and now follows #9117 like sfx/voice). forge.ai wraps only asset generation, so scripting inherits #9117 (BYOK Meshy / ElevenLabs keys excepted). Bridge: bridgeAllowlist.ts denies generation ("spends generation tokens").
commands:historyimplemented-unverified(#9714)implemented-unverified(#9714)excludedunavailable(#9722)2/0. Undo / redo. Editor-time only.
commands:lightingimplemented-unverified(#9714)implemented-unverified(#9714)excludedunavailable(#9722)2/0. LightInspector. No forge.* lighting namespace; scripts change visuals through forge.setColor / setEmissive.
commands:localizationimplemented-unverified(#9714)implemented-unverified(#9714)implemented-unverified(#9714)unavailable(#9722)3/1. Localization strings in the inspector. forge.i18n is now built by scriptWorker.ts (scriptWorker.ts:1147): t/setLocale/getLocale/getAvailableLocales resolve synchronously against the locale bundles shipped in the worker init message from the editor store's localization slice. Unit-pinned by scriptWorker.test.ts ("forge.i18n.t returns the translation for the active (preview) locale", "…falls back to the default text…", "…getLocale / setLocale round-trips…"); no end-to-end evidence, so implemented-unverified (#9733#9714). Over a local bridge translate_scene (ai:generate) is withheld by the allowlist.
commands:materialsimplemented-unverified(#9714)implemented-unverified(#9714)implemented-unverified(#9714)unavailable(#9722)11/0. Inspector and material library; update-material.spec.ts and material-library.spec.ts are outside the CI engine gate. forge.setColor / setEmissive dispatch update_material.
commands:meshimplemented-unverified(#9714)implemented-unverified(#9714)excludedunavailable(#9722)11/0. CSG and procedural mesh tools (extrude, lathe, array, combine). Editor-time geometry authoring.
commands:modelingimplemented-unverified(#9714)implemented-unverified(#9714)excludedunavailable(#9722)6/0. EditModeInspector (vertex / edge / face). Editor-time only.
commands:particlesimplemented-unverified(#9714)implemented-unverified(#9714)implemented-unverified(#9714)unavailable(#9722)8/0. ParticleInspector. forge.particles dispatches set_particle_preset, toggle_particle, burst_particle, all armed. GPU rendering is WebGPU-only (bevy_hanabi).
commands:performanceimplemented-unverified(#9714)implemented-unverified(#9714)excludedunavailable(#9722)0/7. PerformanceProfiler and LodInspector; performance-budget.spec.ts is outside the CI engine gate. Internal commands, but :write scoped so the model is offered them. Editor-time only.
commands:physics2dimplemented-unverified(#9714)implemented-unverified(#9714)partial(#9284)unavailable(#9722)10/0. Physics2dInspector. 2d-workflows.spec.ts has one @engine-ui test in the per-PR engine gate (the project-type selector exists); the rest of the file is outside it. forge.physics2d force / impulse / gravity are armed; forge.physics2d.setVelocity and setAngularVelocity dispatch the phantom set_velocity2d / set_angular_velocity2d.
commands:prefabpartial(#9811)partial(#9811)excludedunavailable(#9722)6/3. Flat save / instantiate and read-only saved-link metadata exist; templates-prefabs.spec.ts is outside the CI engine gate. Linked scene placement, nesting and propagation are unavailable: their UI controls are disabled, compatibility commands are internal and return errors, and both model tool sets withhold them. Data-only override resolution and graph validation do not complete scene.FR-1.OP-01/02/04. Scripts spawn primitives with forge.spawn, not prefabs.
commands:publishingimplemented-unverified(#9714)excludedexcludedexcluded0/8. PublishDialog; publish-flow.spec.ts and play-published.spec.ts are outside the CI engine gate. Publishing to a shareable URL is a person's decision, so the commands are internal and not offered to the model; scripts run inside the published game. Bridge: bridgeAllowlist.ts denies publishing ("makes the project visible to other people") and the project:manage scope.
commands:queryexcludedimplemented-unverified(#9714)excludedunavailable(#9722)16/0. Read-only get_* / list_* for agents; the UI reads store state directly and scripts use forge.getTransform / forge.scene.
commands:renderingimplemented-unverified(#9714)implemented-unverified(#9714)excludedunavailable(#9722)2/2. Quality presets and post-processing in scene settings; theme-effects.spec.ts is outside the CI engine gate. Editor-time only.
commands:runtimeprovenimplemented-unverified(#9714)partial(#9284)unavailable(#9722)12/0. Play / pause / stop, 3D physics forces, input bindings. engine-smoke.spec.ts (CI @engine-smoke) enters and leaves play mode against the built engine — that proves play and stop; the physics-force and input-binding commands in this row are implemented-unverified (#9714). Scripting: forge.physics.applyForce / applyImpulse and forge.stop are armed; forge.physics.setVelocity and forge.input.vibrate dispatch the phantom set_velocity / vibrate. forge.time, forge.state and forge.game are answered JS-side; forge.leaderboard.submit/getTop are now built (scriptWorker.ts:1132) and route through the leaderboard async channel (channels/leaderboardChannel.ts:41) backed by /api/play/[userId]/[slug]/leaderboard — the editor's test-play worker has no published-game identity so they reject with a clear reason there (unit-pinned by leaderboardChannel.test.ts and useScriptRunner.test.ts), succeeding only once a published play host runs the worker (#9733).
commands:sceneprovenimplemented-unverified(#9714)implemented-unverified(#9714)unavailable(#9722)29/1. Hierarchy, inspector, spawn / delete / rename / reparent. engine-smoke.spec.ts (CI @engine-smoke) spawns and inspects against the built engine — that proves spawn and inspect; delete, rename, reparent and the rest of the row are implemented-unverified (#9714). chat-commands.spec.ts covers the chat path but is outside that gate. forge.spawn / destroy / setPosition / setVisibility dispatch armed names.
commands:scriptingimplemented-unverified(#9714)implemented-unverified(#9714)excludedexcluded15/0. Script editor and script explorer panels; script-editor.spec.ts and visual-scripting.spec.ts are outside the CI engine gate. A script does not manage the script library. Sandbox is defence in depth, not a security boundary (#8700). Bridge: bridgeAllowlist.ts denies scripting ("authors code that the script worker compiles with Function() — see SEC-2").
commands:securityexcludedexcludedexcludedexcluded0/2. get_security_status / validate_project_security are server-side checks run by the API and the MCP server, not a panel, a chat tool or a script API. Bridge: bridgeAllowlist.ts denies security ("changes the project security posture").
commands:shadersimplemented-unverified(#9714)implemented-unverified(#9714)excludedunavailable(#9722)0/10. Shader node editor panel. Internal commands, but :write scoped so the model is offered them. Editor-time only.
commands:skeleton2dpartial(#9732)implemented-unverified(#9714)partial(#9284)unavailable(#9722)13/0. SkeletonInspector covers bones, skins, IK and mesh attachments — the mesh editor authors vertices and per-vertex bone weights and adds client-side validation the add_skeleton2d_mesh_attachment command and chat path do not enforce (unknown-bone references and zero-total-weight vertices are rejected, not normalized — the command/chat path only checks vertex/weight count parity), so a panel now exists where before there was only command / chat (the engine side shipped under #6364, closed). Real-runtime deformation proof through the manual and AI paths is the residual gate (#10005). forge.skeleton2d bone / skin / play calls are armed; forge.skeleton2d.stopAnimation and setIkTarget dispatch the phantom stop_skeletal_animation2d / set_ik_target2d.
commands:spriteimplemented-unverified(#9714)implemented-unverified(#9714)unavailable(#9284)unavailable(#9722)8/0. SpriteInspector and Camera2dInspector; 2d-workflows.spec.ts has one @engine-ui test in the per-PR engine gate and the rest is outside it. forge.sprite exposes only playAnimation / stopAnimation / setAnimSpeed / setAnimParam / getCurrentFrame; the four dispatching calls push the phantom sprite-animation names and no allowlisted script command belongs to this category, so a script has no working sprite call.
commands:sprite_animationimplemented-unverified(#9714)implemented-unverified(#9714)unavailable(#9284)unavailable(#9722)6/0. SpriteAnimationInspector; the chat handler drives the state machine JS-side. All four sprite-animation names a script can dispatch (play_sprite_animation, stop_sprite_animation, set_sprite_anim_speed, set_sprite_anim_param) are phantoms with no engine arm.
commands:templatesimplemented-unverified(#9714)implemented-unverified(#9714)excludedunavailable(#9722)3/0. TemplateGallery; template-flow.spec.ts is outside the CI engine gate. Editor-time only.
commands:terrainimplemented-unverified(#9714)implemented-unverified(#9714)excludedunavailable(#9722)4/0. TerrainInspector; terrain-procedural.spec.ts is outside the CI engine gate. Editor-time only.
commands:tilemapimplemented-unverified(#9714)implemented-unverified(#9714)implemented-unverified(#9714)unavailable(#9722)10/1. Tileset panel and TilemapInspector. forge.tilemap dispatches paint_tile / erase_tile / fill_tiles / set_tile_collision_shape, the engine's own names (the former set_tile / clear_tiles / resize_tilemap phantoms were removed). Collision shapes are stored metadata only; they do not affect play physics (#9814). Shape script methods run during editor test-play and are unavailable in standalone HTML/ZIP scripts.
commands:uiimplemented-unverified(#9714)implemented-unverified(#9714)implemented-unverified(#9714)unavailable(#9722)0/15. UI builder panel. Internal commands, but :write scoped so the model is offered them. forge.ui shows and hides screens through worker messages at play time.
commands:world_buildingimplemented-unverified(#9714)implemented-unverified(#9714)excludedunavailable(#9722)2/1. World builder panel. Editor-time only. Over a local bridge build_world (ai:generate) is withheld by the allowlist.

How this file is checked

web/src/lib/config/__tests__/capabilityMatrix.test.ts reads this file and runs in the web unit-test gate, which .github/workflows/ci.yml fires for an edit to this file alone (scripts/__tests__/ci-gate-path-filters.test.sh pins that). It fails when:

The same suite also pins README.md's command counts — the manifest total, category and public counts, the getChatTools() count the in-app AI is offered, and the bridgeAllowedCommands() count the MCP bridge executes — against the code that produces them, and sweeps the file for any other three-digit count on a line that mentions commands. The README's "350" rotted because that number was prose too.

The walk fails closed: an unreadable file, zero rows, or a header missing one of the four columns is a failure, never a pass. To add a row, copy an existing one and change every cell you can stand behind. To promote a cell to proven, name the CI-run spec or the production check in Notes.