{
  "agentResources": {
    "fullCorpus": "/docs/llms-full.txt",
    "index": "/docs/llms.txt",
    "manifest": "/docs/manifest.json"
  },
  "description": "Build, script, share, inspect, and automate live Warp projects.",
  "documents": [
    {
      "audience": "Everyone",
      "featured": true,
      "group": "start",
      "headings": [
        {
          "id": "choose-how-to-open-warp",
          "level": 2,
          "title": "Choose how to open Warp"
        },
        {
          "id": "sign-in-and-open-a-project",
          "level": 2,
          "title": "Sign in and open a project"
        },
        {
          "id": "make-your-first-live-change",
          "level": 2,
          "title": "Make your first live change"
        },
        {
          "id": "connect-a-player",
          "level": 2,
          "title": "Connect a player"
        },
        {
          "id": "work-safely-in-a-workspace",
          "level": 2,
          "title": "Work safely in a workspace"
        },
        {
          "id": "add-gameplay-logic",
          "level": 2,
          "title": "Add gameplay logic"
        },
        {
          "id": "inspect-a-running-game",
          "level": 2,
          "title": "Inspect a running game"
        },
        {
          "id": "use-an-ai-agent",
          "level": 2,
          "title": "Use an AI agent"
        },
        {
          "id": "if-something-goes-wrong",
          "level": 2,
          "title": "If something goes wrong"
        },
        {
          "id": "next-steps",
          "level": 2,
          "title": "Next steps"
        }
      ],
      "html": "<p>Warp turns a game project into shared live state. You edit a scene or gameplay property once, and every connected editor and player receives the same narrow change without rebuilding the game or reopening the level.</p>\n<p>This guide takes you through that loop. You do not need to understand OpenUSD, ECS internals, or the content pipeline first.</p>\n<h2 id=\"choose-how-to-open-warp\">Choose how to open Warp<a class=\"heading-anchor\" href=\"#choose-how-to-open-warp\" aria-label=\"Link to Choose how to open Warp\">#</a></h2>\n<p>The fastest path is the <strong>Web Editor</strong> at <a href=\"/editor\">warp.billrey.net/editor</a>. It runs in a current Safari or Chrome browser and needs no installation.</p>\n<p>For a native Mac workspace, download <strong>Warp Editor</strong> from the <a href=\"/about/#download\">About page</a>. The Mac editor and web editor operate on the same hosted projects and use the same project, workspace, scene, and layer destinations.</p>\n<p>To run the engine without editing, open the browser player or install a native Warp Player. Players receive the same cooked scene and live patches as the editor viewport.</p>\n<h2 id=\"sign-in-and-open-a-project\">Sign in and open a project<a class=\"heading-anchor\" href=\"#sign-in-and-open-a-project\" aria-label=\"Link to Sign in and open a project\">#</a></h2>\n<ol>\n<li>Sign in with your Warp account.</li>\n<li>Choose a project from the project picker.</li>\n<li>Choose a workspace. <code>main</code> is an ordinary workspace and follows the same switching rules as every other workspace.</li>\n<li>Choose a scene if the project contains more than one.</li>\n</ol>\n<p>Warp keeps the current viewport alive while switching. Content-addressed assets that are identical in the next destination remain resident; only missing or changed payloads are fetched.</p>\n<blockquote><p>A project is the durable game package. A workspace is an isolated line of changes inside that project. A scene is the authored world you open. A layer is the current OpenUSD authoring target within that scene.</p></blockquote>\n<h2 id=\"make-your-first-live-change\">Make your first live change<a class=\"heading-anchor\" href=\"#make-your-first-live-change\" aria-label=\"Link to Make your first live change\">#</a></h2>\n<p>Select an object in the hierarchy or viewport. The Inspector shows authored components for that object.</p>\n<p>Try one of these safe edits:</p>\n<ul>\n<li>Move, rotate, or scale an object with the transform tools.</li>\n<li>Change the World background color.</li>\n<li>Toggle an object enabled or disabled.</li>\n<li>Change a light's color or intensity.</li>\n</ul>\n<p>The editor applies the value optimistically, sends a scoped authored edit, and then reconciles with the authoritative revision. Connected players receive a compact runtime patch for the affected entities.</p>\n<p>There is no manual save step for hosted authoring. Before Warp switches to another destination, it waits for pending edits to be acknowledged so a fast switch cannot discard the latest change.</p>\n<h2 id=\"connect-a-player\">Connect a player<a class=\"heading-anchor\" href=\"#connect-a-player\" aria-label=\"Link to Connect a player\">#</a></h2>\n<p>Use <strong>Share</strong> to open the current destination in a player or copy a link. A canonical project link can identify:</p>\n<ul>\n<li>the project;</li>\n<li>workspace;</li>\n<li>scene;</li>\n<li>selected object;</li>\n<li>review;</li>\n<li>captured runtime state.</li>\n</ul>\n<p>Keep the editor and player open side by side. Editing a supported component should update the running player in place. Asset changes stream in the background and use stable asset IDs plus content hashes to avoid unnecessary downloads.</p>\n<h2 id=\"work-safely-in-a-workspace\">Work safely in a workspace<a class=\"heading-anchor\" href=\"#work-safely-in-a-workspace\" aria-label=\"Link to Work safely in a workspace\">#</a></h2>\n<p>Create or select a non-main workspace when you want an isolated experiment. Other people and AI agents can work in their own workspaces without replacing your accepted project state.</p>\n<p>Switching workspaces does not mean cloning the whole project. Warp composes the selected workspace, retains matching content, rejects stale responses from the destination you left, and replaces the visible state when the new authoritative revision arrives.</p>\n<h2 id=\"add-gameplay-logic\">Add gameplay logic<a class=\"heading-anchor\" href=\"#add-gameplay-logic\" aria-label=\"Link to Add gameplay logic\">#</a></h2>\n<p>Warp uses Lua as a small orchestration layer over its native ECS runtime. Create a script in the editor and start with a behavior:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">return furry.behavior(&quot;scripts.RotatePickup&quot;, {\n  writes = { &quot;RuntimeTransform&quot; }\n}, function(world, entity, dt, script)\n  world:spin(entity, { axis = { 0, 1, 0 }, degrees_per_second = 90 })\nend)</code></pre></div>\n<p>Continuous motion runs in native C++, not as a large per-frame loop in Lua. Read <a href=\"/docs/lua-scripting\">Lua gameplay scripting</a> for components, native queries, physics, animation, audio, state, timers, streaming, and lifecycle APIs.</p>\n<h2 id=\"inspect-a-running-game\">Inspect a running game<a class=\"heading-anchor\" href=\"#inspect-a-running-game\" aria-label=\"Link to Inspect a running game\">#</a></h2>\n<p>The Runtime Debugger can discover a connected player for the current project, scene, and workspace. From there you can:</p>\n<ul>\n<li>query typed ECS and Lua state;</li>\n<li>pause, resume, or step exact simulation ticks;</li>\n<li>record a bounded recent history;</li>\n<li>capture a State Capsule;</li>\n<li>share a link that recreates the captured state.</li>\n</ul>\n<p>This is useful for bugs that are difficult to describe. Instead of sending a screenshot and a list of reproduction steps, send the exact state link. See <a href=\"/docs/runtime-introspection\">Runtime introspection</a>.</p>\n<h2 id=\"use-an-ai-agent\">Use an AI agent<a class=\"heading-anchor\" href=\"#use-an-ai-agent\" aria-label=\"Link to Use an AI agent\">#</a></h2>\n<p>Choose <strong>Open in Codex</strong> or <strong>Open in Claude Code</strong> from an editor. Warp installs the official host plugin, completes OAuth, and gives the agent authenticated project and selection context through its remote tool surface. Claude, Claude Desktop, and Cowork use the native Warp Connector. Agent mutations begin in a separate workspace and remain reviewable.</p>\n<p>Agents should query context, author structured changes, validate them, and return the resulting revision and diagnostics. Read <a href=\"/docs/ai-and-automation\">AI and automation</a> for the complete workflow.</p>\n<h2 id=\"if-something-goes-wrong\">If something goes wrong<a class=\"heading-anchor\" href=\"#if-something-goes-wrong\" aria-label=\"Link to If something goes wrong\">#</a></h2>\n<p>Warp clients use shared, actionable connection states. They preserve the visible scene during transient network failures and retry without pretending a rejected edit succeeded.</p>\n<p>When reporting a reproducible runtime problem, include:</p>\n<ol>\n<li>a project/workspace/scene link;</li>\n<li>the selected object path;</li>\n<li>a State Capsule link when the problem depends on runtime state;</li>\n<li>the first actionable error message shown by Warp.</li>\n</ol>\n<p>That information is enough for a person, test runner, or agent to reopen the same context.</p>\n<h2 id=\"next-steps\">Next steps<a class=\"heading-anchor\" href=\"#next-steps\" aria-label=\"Link to Next steps\">#</a></h2>\n<ul>\n<li><a href=\"/docs/editor-and-players\">Editors and players</a></li>\n<li><a href=\"/docs/lua-scripting\">Lua gameplay scripting</a></li>\n<li><a href=\"/docs/ai-and-automation\">AI and automation</a></li>\n<li><a href=\"/docs/deep-links\">Deep links</a></li>\n<li><a href=\"/docs/runtime-introspection\">Runtime introspection</a></li>\n</ul>",
      "icon": "rocket",
      "order": 0,
      "searchText": "Getting started Warp turns a game project into shared live state. You edit a scene or gameplay property once, and every connected editor and player receives the same narrow change without rebuilding the game or reopening the level. This guide takes you through that loop. You do not need to understand OpenUSD, ECS internals, or the content pipeline first. Choose how to open Warp The fastest path is the Web Editor at warp.billrey.net/editor. It runs in a current Safari or Chrome browser and needs no installation. For a native Mac workspace, download Warp Editor from the About page. The Mac editor and web editor operate on the same hosted projects and use the same project, workspace, scene, and layer destinations. To run the engine without editing, open the browser player or install a native Warp Player. Players receive the same cooked scene and live patches as the editor viewport. Sign in and open a project 1. Sign in with your Warp account. 2. Choose a project from the project picker. 3. Choose a workspace. main is an ordinary workspace and follows the same switching rules as every other workspace. 4. Choose a scene if the project contains more than one. Warp keeps the current viewport alive while switching. Content-addressed assets that are identical in the next destination remain resident; only missing or changed payloads are fetched. A project is the durable game package. A workspace is an isolated line of changes inside that project. A scene is the authored world you open. A layer is the current OpenUSD authoring target within that scene. Make your first live change Select an object in the hierarchy or viewport. The Inspector shows authored components for that object. Try one of these safe edits: - Move, rotate, or scale an object with the transform tools. - Change the World background color. - Toggle an object enabled or disabled. - Change a light's color or intensity. The editor applies the value optimistically, sends a scoped authored edit, and then reconciles with the authoritative revision. Connected players receive a compact runtime patch for the affected entities. There is no manual save step for hosted authoring. Before Warp switches to another destination, it waits for pending edits to be acknowledged so a fast switch cannot discard the latest change. Connect a player Use Share to open the current destination in a player or copy a link. A canonical project link can identify: - the project; - workspace; - scene; - selected object; - review; - captured runtime state. Keep the editor and player open side by side. Editing a supported component should update the running player in place. Asset changes stream in the background and use stable asset IDs plus content hashes to avoid unnecessary downloads. Work safely in a workspace Create or select a non-main workspace when you want an isolated experiment. Other people and AI agents can work in their own workspaces without replacing your accepted project state. Switching workspaces does not mean cloning the whole project. Warp composes the selected workspace, retains matching content, rejects stale responses from the destination you left, and replaces the visible state when the new authoritative revision arrives. Add gameplay logic Warp uses Lua as a small orchestration layer over its native ECS runtime. Create a script in the editor and start with a behavior: return furry.behavior(\"scripts.RotatePickup\", { writes = { \"RuntimeTransform\" } }, function(world, entity, dt, script) world:spin(entity, { axis = { 0, 1, 0 }, degrees per second = 90 }) end) Continuous motion runs in native C++, not as a large per-frame loop in Lua. Read Lua gameplay scripting for components, native queries, physics, animation, audio, state, timers, streaming, and lifecycle APIs. Inspect a running game The Runtime Debugger can discover a connected player for the current project, scene, and workspace. From there you can: - query typed ECS and Lua state; - pause, resume, or step exact simulation ticks; - record a bounded recent history; - capture a State Capsule; - share a link that recreates the captured state. This is useful for bugs that are difficult to describe. Instead of sending a screenshot and a list of reproduction steps, send the exact state link. See Runtime introspection. Use an AI agent Choose Open in Codex or Open in Claude Code from an editor. Warp installs the official host plugin, completes OAuth, and gives the agent authenticated project and selection context through its remote tool surface. Claude, Claude Desktop, and Cowork use the native Warp Connector. Agent mutations begin in a separate workspace and remain reviewable. Agents should query context, author structured changes, validate them, and return the resulting revision and diagnostics. Read AI and automation for the complete workflow. If something goes wrong Warp clients use shared, actionable connection states. They preserve the visible scene during transient network failures and retry without pretending a rejected edit succeeded. When reporting a reproducible runtime problem, include: 1. a project/workspace/scene link; 2. the selected object path; 3. a State Capsule link when the problem depends on runtime state; 4. the first actionable error message shown by Warp. That information is enough for a person, test runner, or agent to reopen the same context. Next steps - Editors and players - Lua gameplay scripting - AI and automation - Deep links - Runtime introspection",
      "slug": "getting-started",
      "source": "docs/GETTING_STARTED.md",
      "sourceHash": "8ffbbf76025de903c71d79195e62ea09c8403e5c8d614a9cb7adba4be6a658e8",
      "summary": "Open a project, choose a workspace and scene, make a live edit, and see it in a connected player.",
      "tags": [
        "quickstart",
        "install",
        "project",
        "workspace",
        "scene"
      ],
      "title": "Getting started"
    },
    {
      "audience": "Creators",
      "featured": true,
      "group": "start",
      "headings": [
        {
          "id": "editor-surfaces",
          "level": 2,
          "title": "Editor surfaces"
        },
        {
          "id": "projects-workspaces-scenes-and-layers",
          "level": 2,
          "title": "Projects, workspaces, scenes, and layers"
        },
        {
          "id": "fast-switching",
          "level": 2,
          "title": "Fast switching"
        },
        {
          "id": "live-editing",
          "level": 2,
          "title": "Live editing"
        },
        {
          "id": "selection-and-framing",
          "level": 2,
          "title": "Selection and framing"
        },
        {
          "id": "play-mode-and-connected-players",
          "level": 2,
          "title": "Play mode and connected players"
        },
        {
          "id": "sharing-and-comments",
          "level": 2,
          "title": "Sharing and comments"
        },
        {
          "id": "runtime-debugging",
          "level": 2,
          "title": "Runtime debugging"
        },
        {
          "id": "recovery-principles",
          "level": 2,
          "title": "Recovery principles"
        }
      ],
      "html": "<p>Warp deliberately separates authoring from runtime execution while keeping them connected by the same live project state. Editors author OpenUSD-backed changes. Players consume cooked ECS data and narrow live patches. This gives creators rich source data without forcing shipping runtimes to carry an authoring SDK.</p>\n<h2 id=\"editor-surfaces\">Editor surfaces<a class=\"heading-anchor\" href=\"#editor-surfaces\" aria-label=\"Link to Editor surfaces\">#</a></h2>\n<p>Warp provides a web editor and a native Mac editor. Their presentation follows the platform, but their destination, switching, revision, validation, and live-edit behavior is shared.</p>\n<p>Both editors provide:</p>\n<ul>\n<li>a hierarchy for scene entities;</li>\n<li>an Inspector for components and authored properties;</li>\n<li>a project asset browser;</li>\n<li>project, workspace, scene, and layer selection;</li>\n<li>viewport selection and transform tools;</li>\n<li>play and camera controls;</li>\n<li>comments, review links, and sharing;</li>\n<li>AI-agent entry points;</li>\n<li>runtime debugging controls.</li>\n</ul>\n<p>Full-screen mode keeps the 3D viewport edge to edge and moves secondary information into compact contextual controls and optional floating panels.</p>\n<h2 id=\"projects-workspaces-scenes-and-layers\">Projects, workspaces, scenes, and layers<a class=\"heading-anchor\" href=\"#projects-workspaces-scenes-and-layers\" aria-label=\"Link to Projects, workspaces, scenes, and layers\">#</a></h2>\n<p>These four scopes answer different questions:</p>\n<div class=\"table-scroll\"><table><thead><tr>\n<th>Scope</th>\n<th>What it represents</th>\n<th>Example</th>\n</tr></thead><tbody>\n<tr>\n<td>Project</td>\n<td>The durable game package, assets, settings, and workspace catalog.</td>\n<td><code>MyPlatformGame</code></td>\n</tr>\n<tr>\n<td>Workspace</td>\n<td>An isolated branch of authored project state.</td>\n<td><code>main</code> or <code>lighting-review</code></td>\n</tr>\n<tr>\n<td>Scene</td>\n<td>The OpenUSD world currently open in the editor and player.</td>\n<td><code>Scenes/Main.usda</code></td>\n</tr>\n<tr>\n<td>Layer</td>\n<td>The active OpenUSD edit target within the scene. The root layer is still a real layer.</td>\n<td><code>Scenes/Layers/Gameplay.usda</code></td>\n</tr>\n</tbody></table></div>\n<p><code>main</code> is not a special client mode. Omitting its workspace key from a public URL is only a compact-link optimization; internally it is normalized and routed like every other workspace.</p>\n<p>Every mutation carries a complete destination: project, workspace, scene, layer, and client identity. If a switch starts while edits are pending, the shared switch coordinator waits for their acknowledgements before accepting the new destination.</p>\n<h2 id=\"fast-switching\">Fast switching<a class=\"heading-anchor\" href=\"#fast-switching\" aria-label=\"Link to Fast switching\">#</a></h2>\n<p>Project and workspace switching use the same retained-view fast path:</p>\n<ol>\n<li>Keep the viewport and renderer alive.</li>\n<li>Keep immutable payloads whose stable key and content hash still match.</li>\n<li>Begin loading the next authoritative stage without clearing useful presentation.</li>\n<li>Reject late responses from the previous switch generation.</li>\n<li>Reconcile hierarchy, Inspector, viewport, and players against one accepted destination.</li>\n</ol>\n<p>The result should feel like replacing live data, not restarting an application. A small, non-blocking status indicator may appear while changed content catches up.</p>\n<h2 id=\"live-editing\">Live editing<a class=\"heading-anchor\" href=\"#live-editing\" aria-label=\"Link to Live editing\">#</a></h2>\n<p>An editor change is first represented as a structured authored operation. The authoring service validates the operation against the current destination and revision, applies it to the correct OpenUSD layer, and exports only the runtime records affected by that edit.</p>\n<p>Connected runtimes use the same cooked component format for initial load and live patches. Large payload bytes travel through content URLs or blob endpoints; control messages carry stable IDs, hashes, and small component data.</p>\n<p>For rapid transforms, Warp may stream previews more frequently than it authors durable changes. Sequence and revision guards prevent an older preview or patch from replacing a newer state. Falling behind must trigger resynchronization rather than permanently stalling a player.</p>\n<h2 id=\"selection-and-framing\">Selection and framing<a class=\"heading-anchor\" href=\"#selection-and-framing\" aria-label=\"Link to Selection and framing\">#</a></h2>\n<p>Viewport picking uses visible geometry rather than treating an object's entire bounding box as an opaque selection blocker. This permits selecting through empty space inside a large or concave bound.</p>\n<p>Frame Selected and Frame All animate the camera to a computed target. Their animation must remain render-loop work; asset decode, network parsing, and stage reconciliation run away from the presentation thread wherever the platform allows it.</p>\n<h2 id=\"play-mode-and-connected-players\">Play mode and connected players<a class=\"heading-anchor\" href=\"#play-mode-and-connected-players\" aria-label=\"Link to Play mode and connected players\">#</a></h2>\n<p>The editor viewport can show authored state while a player runs simulation. The browser, Mac, iPad, Windows, and Linux runtime clients share the same project destination and cooked live-update model.</p>\n<p>Players should open either:</p>\n<ul>\n<li>the last valid remote project; or</li>\n<li>an empty project picker when no valid remote destination exists.</li>\n</ul>\n<p>They should never invent a fallback local project. Project and workspace pickers must show one canonical current selection and allow switching back to <code>main</code> normally.</p>\n<h2 id=\"sharing-and-comments\">Sharing and comments<a class=\"heading-anchor\" href=\"#sharing-and-comments\" aria-label=\"Link to Sharing and comments\">#</a></h2>\n<p>Use Share to copy a canonical URL or open a player. Object-aware links add the selected prim path. Review and runtime-state links add their own stable identifiers.</p>\n<p>Comments are anchored to scene context. When a comment is open, its viewport badge and hover preview are suppressed so the same information is not presented twice. Replies and comments can notify project members by email and include a deep link back to the context.</p>\n<h2 id=\"runtime-debugging\">Runtime debugging<a class=\"heading-anchor\" href=\"#runtime-debugging\" aria-label=\"Link to Runtime debugging\">#</a></h2>\n<p>Open Runtime Debugger to inspect the currently connected player. Read operations require project viewer access. Mutating controls such as pause, step, recording mode, and restore require editor access.</p>\n<p>Normal gameplay leaves observation off. Use observe for on-demand inspection and record for a short, bounded exact-tick history. Capturing a state returns a shareable project-scoped capsule link.</p>\n<h2 id=\"recovery-principles\">Recovery principles<a class=\"heading-anchor\" href=\"#recovery-principles\" aria-label=\"Link to Recovery principles\">#</a></h2>\n<p>Warp prioritizes fast optimistic interaction, but never at the expense of silently accepting the wrong state.</p>\n<ul>\n<li>A failed durable edit restores the authoritative value and explains what can be done next.</li>\n<li>A stale response cannot update a newly selected destination.</li>\n<li>A reconnecting player supersedes its old logical runtime instance.</li>\n<li>A missing payload is fetched by stable ID and hash without evicting unrelated resident content.</li>\n<li>An incomplete destination is repaired from the accepted shared scope before a mutation is sent.</li>\n<li>Transient errors preserve the current viewport and offer retry.</li>\n</ul>\n<p>For the stable client error contract, see <a href=\"/docs/client-connection-states\">Client connection states</a>.</p>",
      "icon": "viewport",
      "order": 1,
      "searchText": "Editors and players Warp deliberately separates authoring from runtime execution while keeping them connected by the same live project state. Editors author OpenUSD-backed changes. Players consume cooked ECS data and narrow live patches. This gives creators rich source data without forcing shipping runtimes to carry an authoring SDK. Editor surfaces Warp provides a web editor and a native Mac editor. Their presentation follows the platform, but their destination, switching, revision, validation, and live-edit behavior is shared. Both editors provide: - a hierarchy for scene entities; - an Inspector for components and authored properties; - a project asset browser; - project, workspace, scene, and layer selection; - viewport selection and transform tools; - play and camera controls; - comments, review links, and sharing; - AI-agent entry points; - runtime debugging controls. Full-screen mode keeps the 3D viewport edge to edge and moves secondary information into compact contextual controls and optional floating panels. Projects, workspaces, scenes, and layers These four scopes answer different questions: Scope What it represents Example --- --- --- Project The durable game package, assets, settings, and workspace catalog. MyPlatformGame Workspace An isolated branch of authored project state. main or lighting-review Scene The OpenUSD world currently open in the editor and player. Scenes/Main.usda Layer The active OpenUSD edit target within the scene. The root layer is still a real layer. Scenes/Layers/Gameplay.usda main is not a special client mode. Omitting its workspace key from a public URL is only a compact-link optimization; internally it is normalized and routed like every other workspace. Every mutation carries a complete destination: project, workspace, scene, layer, and client identity. If a switch starts while edits are pending, the shared switch coordinator waits for their acknowledgements before accepting the new destination. Fast switching Project and workspace switching use the same retained-view fast path: 1. Keep the viewport and renderer alive. 2. Keep immutable payloads whose stable key and content hash still match. 3. Begin loading the next authoritative stage without clearing useful presentation. 4. Reject late responses from the previous switch generation. 5. Reconcile hierarchy, Inspector, viewport, and players against one accepted destination. The result should feel like replacing live data, not restarting an application. A small, non-blocking status indicator may appear while changed content catches up. Live editing An editor change is first represented as a structured authored operation. The authoring service validates the operation against the current destination and revision, applies it to the correct OpenUSD layer, and exports only the runtime records affected by that edit. Connected runtimes use the same cooked component format for initial load and live patches. Large payload bytes travel through content URLs or blob endpoints; control messages carry stable IDs, hashes, and small component data. For rapid transforms, Warp may stream previews more frequently than it authors durable changes. Sequence and revision guards prevent an older preview or patch from replacing a newer state. Falling behind must trigger resynchronization rather than permanently stalling a player. Selection and framing Viewport picking uses visible geometry rather than treating an object's entire bounding box as an opaque selection blocker. This permits selecting through empty space inside a large or concave bound. Frame Selected and Frame All animate the camera to a computed target. Their animation must remain render-loop work; asset decode, network parsing, and stage reconciliation run away from the presentation thread wherever the platform allows it. Play mode and connected players The editor viewport can show authored state while a player runs simulation. The browser, Mac, iPad, Windows, and Linux runtime clients share the same project destination and cooked live-update model. Players should open either: - the last valid remote project; or - an empty project picker when no valid remote destination exists. They should never invent a fallback local project. Project and workspace pickers must show one canonical current selection and allow switching back to main normally. Sharing and comments Use Share to copy a canonical URL or open a player. Object-aware links add the selected prim path. Review and runtime-state links add their own stable identifiers. Comments are anchored to scene context. When a comment is open, its viewport badge and hover preview are suppressed so the same information is not presented twice. Replies and comments can notify project members by email and include a deep link back to the context. Runtime debugging Open Runtime Debugger to inspect the currently connected player. Read operations require project viewer access. Mutating controls such as pause, step, recording mode, and restore require editor access. Normal gameplay leaves observation off. Use observe for on-demand inspection and record for a short, bounded exact-tick history. Capturing a state returns a shareable project-scoped capsule link. Recovery principles Warp prioritizes fast optimistic interaction, but never at the expense of silently accepting the wrong state. - A failed durable edit restores the authoritative value and explains what can be done next. - A stale response cannot update a newly selected destination. - A reconnecting player supersedes its old logical runtime instance. - A missing payload is fetched by stable ID and hash without evicting unrelated resident content. - An incomplete destination is repaired from the accepted shared scope before a mutation is sent. - Transient errors preserve the current viewport and offer retry. For the stable client error contract, see Client connection states.",
      "slug": "editor-and-players",
      "source": "docs/EDITOR_AND_PLAYERS.md",
      "sourceHash": "e6b311a6b541ff8d6961ad868dc6ce2a2ccaf13b5848f0c63575ef0406050ee8",
      "summary": "A practical guide to projects, workspaces, scenes, layers, live editing, play mode, sharing, and recovery.",
      "tags": [
        "editor",
        "player",
        "workspace",
        "layers",
        "live updates"
      ],
      "title": "Editors and players"
    },
    {
      "audience": "Game developers",
      "featured": true,
      "group": "create",
      "headings": [
        {
          "id": "minimal-behavior",
          "level": 2,
          "title": "Minimal Behavior"
        },
        {
          "id": "manual-module-shape",
          "level": 2,
          "title": "Manual Module Shape"
        },
        {
          "id": "behavior-helper",
          "level": 2,
          "title": "Behavior Helper"
        },
        {
          "id": "scene-wide-system-helper",
          "level": 2,
          "title": "Scene-wide system helper"
        },
        {
          "id": "script-properties",
          "level": 2,
          "title": "Script Properties"
        },
        {
          "id": "components",
          "level": 2,
          "title": "Components"
        },
        {
          "id": "world-api",
          "level": 2,
          "title": "World API"
        },
        {
          "id": "hierarchy-groups-and-enable-state",
          "level": 3,
          "title": "Hierarchy, groups, and enable state"
        },
        {
          "id": "events",
          "level": 3,
          "title": "Events"
        },
        {
          "id": "game-ui",
          "level": 3,
          "title": "Game UI"
        },
        {
          "id": "entity-lifecycle",
          "level": 3,
          "title": "Entity lifecycle"
        },
        {
          "id": "state-and-timers",
          "level": 3,
          "title": "State and timers"
        },
        {
          "id": "local-world-and-persistent-transforms",
          "level": 3,
          "title": "Local, world, and persistent transforms"
        },
        {
          "id": "animation-audio-effects-and-streaming",
          "level": 3,
          "title": "Animation, audio, effects, and streaming"
        },
        {
          "id": "demo-library",
          "level": 2,
          "title": "Demo library"
        },
        {
          "id": "examples",
          "level": 2,
          "title": "Examples"
        },
        {
          "id": "3d-character-controller",
          "level": 3,
          "title": "3D Character Controller"
        },
        {
          "id": "touch-and-tilt-controller",
          "level": 3,
          "title": "Touch And Tilt Controller"
        },
        {
          "id": "one-shot-sound",
          "level": 3,
          "title": "One-Shot Sound"
        },
        {
          "id": "named-animation",
          "level": 3,
          "title": "Named Animation"
        },
        {
          "id": "legacy-animation-range",
          "level": 3,
          "title": "Legacy Animation Range"
        },
        {
          "id": "editing-scripts",
          "level": 2,
          "title": "Editing Scripts"
        },
        {
          "id": "in-the-editors",
          "level": 3,
          "title": "In The Editors"
        },
        {
          "id": "vs-code",
          "level": 3,
          "title": "VS Code"
        },
        {
          "id": "http-endpoints",
          "level": 3,
          "title": "HTTP Endpoints"
        },
        {
          "id": "cli-agent-editing",
          "level": 3,
          "title": "CLI / Agent Editing"
        },
        {
          "id": "runtime-behavior",
          "level": 2,
          "title": "Runtime Behavior"
        },
        {
          "id": "guardrails",
          "level": 2,
          "title": "Guardrails"
        }
      ],
      "html": "<p>Furry scripts are small sandboxed Lua system modules. They run inside the runtime ECS, not inside the authoring server, and they are meant for gameplay logic that can be hot-reloaded from the editor, VS Code, or CLI/agent tools.</p>\n<p>The API is ECS-first. A module declares its component access, queries native component storage, and queues structural changes that are committed after the current update. Lua orchestrates decisions; native C++ systems own dense iteration, physics, animation, audio, rendering, streaming, and continuous motion.</p>\n<p>Scripts are not native plugins. They do not get raw EnTT handles, C++ pointers, filesystem access, OS access, package loading, renderer internals, or direct USD access.</p>\n<h2 id=\"minimal-behavior\">Minimal Behavior<a class=\"heading-anchor\" href=\"#minimal-behavior\" aria-label=\"Link to Minimal Behavior\">#</a></h2>\n<p>Most scripts should use <code>furry.behavior</code>:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">return furry.behavior(&quot;scripts.PlayerMovement&quot;, {\n  properties = {\n    speed = { type = &quot;number&quot;, default = 3.0 }\n  }\n}, function(world, entity, dt, script)\n  world:move_by_input(entity, script.speed or 3.0, dt)\nend)</code></pre></div>\n<p>The module name is the stable script address. For v1 remote editing, modules are flat:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>text</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-text\">scripts.PlayerMovement -&gt; Scripts/PlayerMovement.lua</code></pre></div>\n<p>Use alphanumeric and underscore stems. Do not use slashes, traversal, nested modules, or arbitrary filesystem paths.</p>\n<h2 id=\"manual-module-shape\">Manual Module Shape<a class=\"heading-anchor\" href=\"#manual-module-shape\" aria-label=\"Link to Manual Module Shape\">#</a></h2>\n<p><code>furry.behavior</code> returns this table shape for you, but a script may return it directly:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">return {\n  schema = {\n    reads = { &quot;Script&quot;, &quot;RuntimeTransform&quot; },\n    writes = { &quot;RuntimeTransform&quot; }\n  },\n  update = function(world, dt)\n    for entity, script, transform in world:view(&quot;Script&quot;, &quot;RuntimeTransform&quot;) do\n      if script.module == &quot;scripts.LiveSpeed&quot; then\n        transform.translation_x = transform.translation_x + (script.speed or 0.0) * dt\n      end\n    end\n  end\n}</code></pre></div>\n<p><code>schema.reads</code> declares components the script inspects. <code>schema.writes</code> declares components the script mutates. Reads and writes not declared in the schema fail with diagnostics. This explicit access model keeps dependencies visible and allows native query paths to evolve without turning Lua modules into opaque object scripts. Manual modules must declare <code>Script</code> when they query it. <code>furry.behavior</code> adds its own engine-owned <code>Script</code> read automatically because the wrapper uses that component to select matching entities.</p>\n<h2 id=\"behavior-helper\">Behavior Helper<a class=\"heading-anchor\" href=\"#behavior-helper\" aria-label=\"Link to Behavior Helper\">#</a></h2>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">furry.behavior(module, options, update_fn)</code></pre></div>\n<p><code>options</code> may contain:</p>\n<ul>\n<li><code>properties</code>: editor-visible script properties.</li>\n<li><code>reads</code>: replace the user-visible default reads. The wrapper always adds its engine-owned <code>Script</code> read if it is not already present.</li>\n<li><code>writes</code>: override default writes.</li>\n</ul>\n<p>Defaults:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">reads = { &quot;Script&quot;, &quot;RuntimeTransform&quot; }\nwrites = { &quot;RuntimeTransform&quot; }</code></pre></div>\n<p><code>update_fn(world, entity, dt, script)</code> is called for each entity whose <code>Script.module</code> matches the behavior module.</p>\n<h2 id=\"scene-wide-system-helper\">Scene-wide system helper<a class=\"heading-anchor\" href=\"#scene-wide-system-helper\" aria-label=\"Link to Scene-wide system helper\">#</a></h2>\n<p>Use <code>furry.system</code> when a module is a system rather than an entity behavior:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">return furry.system(&quot;scripts.DamageSystem&quot;, {\n  reads = { &quot;RuntimeTransform&quot;, &quot;RenderShape&quot; },\n  writes = {},\n  query = {\n    all = { &quot;RuntimeTransform&quot;, &quot;RenderShape&quot; },\n    groups = { &quot;damageable&quot; }\n  }\n}, function(world, dt)\n  for event in world:events(&quot;damage&quot;) do\n    if event.target ~= nil and world:is_in_group(event.target, &quot;damageable&quot;) then\n      local health = world:entity_state_get(event.target, &quot;health&quot;, 3) - (event.value or 1)\n      world:entity_state_set(event.target, &quot;health&quot;, health)\n    end\n  end\nend)</code></pre></div>\n<p><code>furry.system(module, options, update_fn)</code> runs once per runtime update. Its options are:</p>\n<ul>\n<li><code>reads</code> and <code>writes</code>: required component access declarations.</li>\n<li><code>query</code>: an optional native query condition; the update is skipped when it has no match.</li>\n<li><code>interval</code>: an optional native timer interval for logic that need not run every frame.</li>\n</ul>\n<p>Modules update in stable module-name order. Structural changes and emitted events do not depend on hash-table iteration order.</p>\n<h2 id=\"script-properties\">Script Properties<a class=\"heading-anchor\" href=\"#script-properties\" aria-label=\"Link to Script Properties\">#</a></h2>\n<p>Script component properties are authored as USD attributes named <code>furry:prop:&lt;name&gt;</code>. Lua reads them as fields on the <code>script</code> userdata:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">local speed = script.speed or 3.0\nlocal enabled = script.enabled ~= false\nlocal label = script.label or &quot;Player&quot;</code></pre></div>\n<p>Supported property values are currently number, bool, string, and token-like strings. <code>script.module</code> is read-only.</p>\n<h2 id=\"components\">Components<a class=\"heading-anchor\" href=\"#components\" aria-label=\"Link to Components\">#</a></h2>\n<p>Known script-visible components:</p>\n<div class=\"table-scroll\"><table><thead><tr>\n<th>Component</th>\n<th>Read</th>\n<th>Write</th>\n<th>Notes</th>\n</tr></thead><tbody>\n<tr>\n<td><code>Entity</code></td>\n<td>yes</td>\n<td>no</td>\n<td>Handles expose <code>id</code>, <code>path</code>, <code>name</code>, <code>parent</code>, <code>enabled</code>, and <code>enabled_self</code>.</td>\n</tr>\n<tr>\n<td><code>Script</code></td>\n<td>yes</td>\n<td>no</td>\n<td><code>module</code> plus authored script properties.</td>\n</tr>\n<tr>\n<td><code>RuntimeTransform</code></td>\n<td>yes</td>\n<td>yes</td>\n<td>Frame-local translation/scale fields; persistent transforms and continuous motion use native World commands.</td>\n</tr>\n<tr>\n<td><code>AudioSource</code></td>\n<td>yes</td>\n<td>yes</td>\n<td>Volume, loop, autoplay; <code>asset_id</code> is read-only.</td>\n</tr>\n<tr>\n<td><code>AnimationPlayback</code></td>\n<td>yes</td>\n<td>helper only</td>\n<td>Read playback state; use the named clip, named clip subrange, or legacy range APIs to write.</td>\n</tr>\n<tr>\n<td><code>RuntimeUvAnimation</code></td>\n<td>yes</td>\n<td>helper only</td>\n<td>Per-instance UV transform state; use <code>world:tween_uv</code> so native ECS advances it and the GPU applies it.</td>\n</tr>\n<tr>\n<td><code>RuntimePhysicsControl</code></td>\n<td>no direct userdata</td>\n<td>helper only</td>\n<td>Use movement helpers or <code>world:set_velocity_2d</code>.</td>\n</tr>\n<tr>\n<td><code>UiText</code></td>\n<td>query only</td>\n<td>helper only</td>\n<td>Use <code>world:ui_set_text</code>; native code retains and renders the result.</td>\n</tr>\n<tr>\n<td><code>UiLayout</code></td>\n<td>query only</td>\n<td>helper only</td>\n<td>Use <code>world:ui_set_visible</code>; native code recomputes layout only when dirty.</td>\n</tr>\n<tr>\n<td><code>UiInteraction</code></td>\n<td>query only</td>\n<td>helper only</td>\n<td>Use <code>world:ui_set_enabled</code> and <code>world:ui_focus</code>.</td>\n</tr>\n</tbody></table></div>\n<p>Native queries can also test <code>Name</code>, <code>Parent</code>, <code>Transform</code>, <code>RigidBody</code>, <code>RenderShape</code>, <code>MeshAsset</code>, <code>MaterialBinding</code>, <code>Light</code>, <code>Camera</code>, <code>RuntimeLifetime</code>, <code>RuntimeGroups</code>, and <code>RuntimeDisabled</code>. These are query predicates, not mutable Lua objects. Add every queried component to <code>reads</code> or <code>writes</code> except the always-available <code>Entity</code> identity.</p>\n<p><code>RuntimeTransform</code> fields:</p>\n<ul>\n<li><code>translation_x</code>, <code>translation_y</code>, <code>translation_z</code></li>\n<li><code>scale_x</code>, <code>scale_y</code>, <code>scale_z</code></li>\n<li><code>translate(x, y, z)</code></li>\n<li>read-only quaternion fields <code>rotation_w</code>, <code>rotation_x</code>, <code>rotation_y</code>, <code>rotation_z</code></li>\n<li><code>rotate(xDegrees, yDegrees, zDegrees)</code> for a frame-local rotation</li>\n</ul>\n<p><code>AudioSource</code> fields:</p>\n<ul>\n<li><code>asset_id</code> read-only</li>\n<li><code>volume</code></li>\n<li><code>loop</code></li>\n<li><code>autoplay</code></li>\n</ul>\n<p><code>AnimationPlayback</code> is exposed as a read-only table:</p>\n<ul>\n<li><code>start_frame</code></li>\n<li><code>end_frame</code></li>\n<li><code>frames_per_second</code></li>\n<li><code>elapsed_seconds</code></li>\n<li><code>loop</code></li>\n<li><code>playing</code></li>\n<li><code>clip</code> when named-clip playback is active</li>\n<li><code>clip_range_enabled</code>, plus <code>clip_start_frame</code> and <code>clip_end_frame</code> when a named subrange is active</li>\n<li><code>speed</code></li>\n<li><code>transition_alpha</code></li>\n</ul>\n<p><code>RuntimeUvAnimation</code> is exposed as a read-only table with <code>offset</code>, <code>scale</code>, <code>rotation_degrees</code>, <code>elapsed_seconds</code>, <code>duration_seconds</code>, and <code>playing</code>.</p>\n<h2 id=\"world-api\">World API<a class=\"heading-anchor\" href=\"#world-api\" aria-label=\"Link to World API\">#</a></h2>\n<dl>\n<dt><code>world:view(component, ...)</code></dt><dd>Iterates enabled entities that have all requested script-readable component userdata. This is the concise path for <code>Script</code>, <code>RuntimeTransform</code>, <code>AudioSource</code>, and <code>AnimationPlayback</code>.</dd>\n</dl>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">for entity, script, transform in world:view(&quot;Script&quot;, &quot;RuntimeTransform&quot;) do\n  transform:translate(0.0, 1.0 * dt, 0.0)\nend</code></pre></div>\n<dl>\n<dt><code>world:query(options)</code></dt><dd>Iterates entity handles selected by a native ECS query. Options are <code>all</code>, <code>any</code>, <code>none</code>, <code>groups</code>, and <code>include_disabled</code>.</dd>\n</dl>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">for entity in world:query({\n  all = { &quot;RuntimeTransform&quot;, &quot;RigidBody&quot; },\n  none = { &quot;RuntimeDisabled&quot; },\n  groups = { &quot;enemy&quot;, &quot;active&quot; }\n}) do\n  -- make a small number of gameplay decisions\nend</code></pre></div>\n<p><code>world:query_count(options)</code> and <code>world:query_first(options)</code> use the same native query. <code>world:has_component(entity, name)</code> tests one entity. Queries are backed by EnTT component storage rather than a Lua scan of every entity; Lua is entered only for results the script chooses to iterate.</p>\n<dl>\n<dt><code>world:find_path(path)</code></dt><dd>Returns an entity handle for a prim path, or <code>nil</code>.</dd>\n<dt><code>world:find_name(name, root, includeDisabled)</code></dt><dd>Finds by authored name, optionally below a root entity.</dd>\n<dt><code>world:find_transform_overlap(root, center, halfExtents, includeDisabled)</code></dt><dd>Returns the first descendant of <code>root</code> whose world-space transform volume overlaps the supplied axis-aligned query box, or <code>nil</code>. Descendant discovery is cached and the hierarchy traversal, world-transform composition, and overlap test run in native C++; use this instead of recursively scanning a stable volume hierarchy in Lua every frame. Declare <code>RuntimeTransform</code> in <code>reads</code> or <code>writes</code>.</dd>\n</dl>\n<h3 id=\"hierarchy-groups-and-enable-state\">Hierarchy, groups, and enable state<a class=\"heading-anchor\" href=\"#hierarchy-groups-and-enable-state\" aria-label=\"Link to Hierarchy, groups, and enable state\">#</a></h3>\n<ul>\n<li><code>world:parent(entity)</code>, <code>world:children(entity, recursive, includeDisabled)</code>, and <code>world:root(entity)</code> navigate stable entity handles.</li>\n<li><code>world:reparent(entity, parentOrNil, preserveWorld)</code> changes hierarchy after the current Lua update. <code>preserveWorld</code> defaults to true and cycle-forming requests are rejected.</li>\n<li><code>world:set_enabled(entity, enabled)</code> changes local enable state after the current Lua update. Disabled parents disable their descendants for scripts, native simulation, physics, rendering, lights, cameras, and audio.</li>\n<li><code>world:is_enabled(entity, effective)</code> reads effective hierarchical state by default; pass <code>false</code> to read only local state.</li>\n<li><code>world:add_to_group</code>, <code>remove_from_group</code>, and <code>is_in_group</code> provide lightweight runtime grouping without adding a Lua object to every entity.</li>\n</ul>\n<p>Structural commands are deferred like an ECS command buffer. A query that is already being iterated is never invalidated by spawn, destroy, reparent, group, or enable operations.</p>\n<h3 id=\"events\">Events<a class=\"heading-anchor\" href=\"#events\" aria-label=\"Link to Events\">#</a></h3>\n<p><code>world:emit(name, valueOrOptions)</code> queues a typed gameplay event. An options table may provide <code>source</code>, <code>target</code>, and a scalar <code>value</code> (number, boolean, or string). <code>world:events(name, target)</code> iterates matching events on the next update:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">world:emit(&quot;damage&quot;, { source = attacker, target = victim, value = 2.0 })\n\nfor event in world:events(&quot;damage&quot;, victim) do\n  -- event.name, source, target, value\nend</code></pre></div>\n<p>Events are broadcast deterministically for that update; one system does not consume an event before another system can observe it.</p>\n<h3 id=\"game-ui\">Game UI<a class=\"heading-anchor\" href=\"#game-ui\" aria-label=\"Link to Game UI\">#</a></h3>\n<p>Native UI interactions emit <code>ui.action</code> events. <code>event.target</code> is the stable UI entity and <code>event.value</code> is the semantic action string authored on its <code>UiInteraction</code> component.</p>\n<dl>\n<dt><code>world:ui_set_text(entity, text)</code></dt><dd>Replaces an entity's displayed text. Declare <code>UiText</code> in <code>schema.writes</code>.</dd>\n<dt><code>world:ui_set_visible(entity, visible)</code></dt><dd>Changes retained UI visibility and invalidates native layout. Declare <code>UiLayout</code> in <code>schema.writes</code>.</dd>\n<dt><code>world:ui_set_enabled(entity, enabled)</code></dt><dd>Enables or disables native hit testing for an interaction. Declare <code>UiInteraction</code> in <code>schema.writes</code>.</dd>\n<dt><code>world:ui_focus(entity)</code></dt><dd>Moves native UI focus to an enabled, focusable interaction. Declare <code>UiInteraction</code> in <code>schema.writes</code>.</dd>\n</dl>\n<p>See <a href=\"/docs/game-ui\">Game UI</a> for the authored components and a complete example.</p>\n<dl>\n<dt><code>world:input_axis()</code></dt><dd>Returns combined keyboard/game/touch movement as <code>x, y</code>. Gamepad sticks preserve their analogue magnitude after the platform radial deadzone. The combined vector is normalized only when its length exceeds one, so partial stick movement stays partial and diagonals never receive a speed boost.</dd>\n<dt><code>world:input()</code></dt><dd>Returns the complete gameplay input snapshot for the current simulation tick: <code>{ move_x, move_y, look_x, look_y, primary_down, primary_pressed, primary_released, secondary_down, secondary_pressed, secondary_released, jump_pressed }</code>. Movement and held actions persist while active. Look deltas and pressed/released edges are consumed once by the simulation tick.</dd>\n<dt><code>world:look_delta()</code></dt><dd>Returns device-independent look motion as <code>x, y</code> degrees for the current simulation tick. Pointer motion, touch drags, and gamepad right-stick input use this same contract. Right-stick magnitude remains analogue and is kept separate from the movement axis.</dd>\n<dt><code>world:set_character_velocity(entity, x, y, z)</code></dt><dd>Drives player or NPC locomotion through native physics while suppressing restitution for that controlled character. Contacts settle against walls and preserve tangential motion for smooth sliding. Generic dynamic bodies should continue using <code>world:set_linear_velocity</code> so their authored bounce remains intact. Declare <code>RuntimePhysicsControl</code> in <code>writes</code>.</dd>\n<dt><code>world:set_character_movement(entity, { step_height, ground_snap, step_down_extra, max_slope_degrees })</code></dt><dd>Declares the game's character movement feel for one character. The engine ships inert defaults - no stair climbing, no ground snap, Jolt's neutral 50-degree slope limit - so each game's controller script owns its own feel, exactly like move speed. Omitted keys fall back to those inert defaults. The declaration is sticky for the character but cheap to call, so setting it from the controller's update alongside <code>set_character_velocity</code> is the recommended pattern (it then also survives runtime state restores). Declare <code>RuntimePhysicsControl</code> in <code>writes</code>.</dd>\n<dt><code>world:primary_down()</code> / <code>world:primary_pressed()</code></dt><dd>Return the primary gameplay action state. The first is held state; the second is true only on the press tick. Secondary action equivalents are also available.</dd>\n<dt><code>world:tilt()</code></dt><dd>Returns device tilt/gravity as <code>x, y, z</code>, or zeros when unavailable.</dd>\n<dt><code>world:touch()</code></dt><dd>Returns a table:</dd>\n</dl>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">{\n  active = false,\n  x = 0.0,\n  y = 0.0,\n  axis_x = 0.0,\n  axis_y = 0.0,\n  delta_x = 0.0,\n  delta_y = 0.0,\n  tap_count = 0,\n  tap_x = 0.0,\n  tap_y = 0.0\n}</code></pre></div>\n<dl>\n<dt><code>world:jump_pressed()</code></dt><dd>Returns true only on the frame a jump input was pressed.</dd>\n<dt><code>world:is_on_ground(entity, probeDistance)</code></dt><dd>Returns true when a dynamic rigid body is touching or just above static ground. <code>probeDistance</code> defaults to <code>0.08</code>.</dd>\n<dt><code>world:raycast(origin, direction, maxDistance, ignoreEntity)</code></dt><dd>Runs a native Jolt ray query and returns <code>nil</code> or <code>{ entity, position, normal, distance }</code>. Declare <code>RigidBody</code> in <code>reads</code>.</dd>\n<dt><code>world:sweep_box(center, halfExtents, rotationDegrees, displacement, ignoreEntity)</code></dt><dd>Sweeps one oriented box through Jolt's native broad and narrow phases and returns the same hit table as <code>raycast</code>, or <code>nil</code>. <code>rotationDegrees</code> is an Euler <code>{ x, y, z }</code> orientation and <code>displacement</code> is the complete motion vector for the cast. Use one swept hull for vehicle or moving-volume collision prediction instead of issuing a fan of individual Lua raycasts. Declare <code>RigidBody</code> in <code>reads</code>.</dd>\n<dt><code>world:get_linear_velocity(entity)</code> / <code>world:get_angular_velocity(entity)</code></dt><dd>Return native rigid-body velocity as <code>{ x, y, z }</code>, or <code>nil</code>.</dd>\n</dl>\n<p><code>world:set_linear_velocity</code>, <code>set_angular_velocity</code>, <code>apply_impulse</code>, and</p>\n<dl>\n<dt><code>apply_angular_impulse</code></dt><dd>Queue native 3D body controls. Declare <code>RuntimePhysicsControl</code> in <code>writes</code>. Impulses are applied once even when the renderer catches up with several fixed physics steps in one frame.</dd>\n<dt><code>world:move_by_input(entity, speed, dt)</code></dt><dd>Applies engine-owned movement. Dynamic rigid bodies receive physics velocity; other entities move their <code>RuntimeTransform</code>.</dd>\n<dt><code>world:move_by_input_3d(entity, speed, dt, jumpVelocity)</code></dt><dd>Like <code>move_by_input</code>, but can jump and rotates the entity toward the X/Z run direction.</dd>\n<dt><code>world:move_by_input_2d(entity, speed, dt, jumpVelocity)</code></dt><dd>Side-scroller movement on the X/Y plane. Dynamic bodies are kept on <code>Z = 0</code>.</dd>\n<dt><code>world:set_velocity_2d(entity, xVelocityOrNil, yVelocityOrNil)</code></dt><dd>Sets selected 2D physics velocity axes. Declare <code>RuntimePhysicsControl</code> in <code>schema.writes</code>.</dd>\n<dt><code>world:play_animation_range(entity, startFrame, endFrame, framesPerSecond, loop)</code></dt><dd>Starts or updates cooked mesh animation playback. Declare <code>AnimationPlayback</code> in <code>schema.writes</code>.</dd>\n<dt><code>world:play_animation(entity, clipName, fadeSeconds, loop, speed)</code></dt><dd>Starts a named cooked clip and asks the native runtime/GPU to crossfade from the current clip. Lua selects gameplay state; it does not sample joints or advance animation frames. Repeating the active clip request does not restart it. Declare <code>AnimationPlayback</code> in <code>schema.writes</code>.</dd>\n<dt><code>world:play_animation_clip_range(entity, clipName, startFrame, endFrame, fadeSeconds, loop, speed)</code></dt><dd>Starts a frame range local to a named cooked clip. Native code advances, crossfades, and GPU-skins the range, so gameplay can reuse short reactions from larger imported clips without duplicating animation data or updating frames in Lua. Declare <code>AnimationPlayback</code> in <code>schema.writes</code>.</dd>\n<dt><code>world:tween_uv(entity, options)</code></dt><dd>Starts or replaces native per-instance UV channels. Options accept <code>offset={u,v}</code>, <code>scale={u,v}</code>, <code>rotation_degrees</code>, <code>duration</code>, and <code>relative</code>. The scalar forms <code>offset_u</code>, <code>offset_v</code>, <code>scale_u</code>, and <code>scale_v</code> update one channel without disturbing a simultaneous tween on the other channel. Declare <code>RuntimeUvAnimation</code> in <code>schema.writes</code>.</dd>\n<dt><code>world:stop_uv_animation(entity, reset)</code></dt><dd>Stops the native UV channels. Pass <code>true</code> to reset the transform to identity; otherwise the last sampled UV transform remains visible.</dd>\n<dt><code>world:play_sound(assetRef, volume)</code></dt><dd>Queues a one-shot sound. <code>assetRef</code> may be a numeric asset id, a full asset reference path, a store URI, or a short sound name that resolves under the asset registry.</dd>\n</dl>\n<h3 id=\"entity-lifecycle\">Entity lifecycle<a class=\"heading-anchor\" href=\"#entity-lifecycle\" aria-label=\"Link to Entity lifecycle\">#</a></h3>\n<p><code>world:spawn(template, options)</code> clones an existing runtime entity. <code>template</code> may be an entity handle or scene path. The clone is installed after all Lua modules finish their current update, so an ECS view is never invalidated during iteration. A reserved entity handle is returned immediately and can be passed to other queued commands in the same update.</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">local enemy = world:spawn(&quot;/World/Templates/Enemy&quot;, {\n  name = &quot;Enemy&quot;,\n  parent = world:find_path(&quot;/World/Enemies&quot;),\n  position = { 4, 0, -2 },\n  rotation = { 0, 90, 0 }, -- degrees\n  scale = 1.25,\n  lifetime = 15.0,\n  include_children = true,\n  include_scripts = false\n})</code></pre></div>\n<p>Scripts are not copied by default, which prevents a spawner template from accidentally recursively spawning. Spawned entities are runtime-only and are removed when play mode resets. <code>world:spawn_effect</code> is an alias intended for short-lived visual templates.</p>\n<ul>\n<li><code>world:destroy(entity, recursive)</code> safely destroys at the end of the Lua update.</li>\n<li><code>world:destroy_after(entity, seconds)</code> installs a native lifetime component.</li>\n<li><code>world:is_alive(entity)</code> also recognizes a spawn reserved during this update.</li>\n</ul>\n<h3 id=\"state-and-timers\">State and timers<a class=\"heading-anchor\" href=\"#state-and-timers\" aria-label=\"Link to State and timers\">#</a></h3>\n<p>State is held in a typed native store, survives ordinary frames and script hot reload, and resets with the game. Values are number, boolean, string, or nil.</p>\n<ul>\n<li><code>world:state_get(key, default)</code> / <code>state_set(key, value)</code> are shared game state, so separate behavior modules can coordinate.</li>\n<li><code>world:state_add(key, delta, initial)</code> is an atomic numeric update.</li>\n<li><code>world:state_toggle(key)</code> is an atomic boolean update.</li>\n<li><code>world:entity_state_get(entity, key, default)</code> / <code>entity_state_set</code> add entity scope.</li>\n<li><code>world:time()</code> returns native simulation time.</li>\n<li><code>world:after(key, seconds)</code> fires once after the key is first registered.</li>\n<li><code>world:every(key, seconds)</code> fires repeatedly without a Lua delta accumulator.</li>\n<li><code>world:cancel_timer(key)</code> removes either kind of timer.</li>\n</ul>\n<p>Timer keys must distinguish behavior instances when appropriate:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">if world:every(&quot;spawn:&quot; .. tostring(entity.id), 1.5) then\n  -- make one orchestration decision; native systems do the continuing work\nend</code></pre></div>\n<h3 id=\"local-world-and-persistent-transforms\">Local, world, and persistent transforms<a class=\"heading-anchor\" href=\"#local-world-and-persistent-transforms\" aria-label=\"Link to Local, world, and persistent transforms\">#</a></h3>\n<p>The fields returned by <code>world:view(&quot;RuntimeTransform&quot;)</code> remain useful for a frame-local procedural offset. For persistent gameplay changes, use the World commands below. Declare <code>RuntimeTransform</code> in <code>schema.writes</code>.</p>\n<ul>\n<li><code>world:get_transform(entity)</code> returns the local ECS transform as <code>{ position, rotation_quaternion, scale }</code>.</li>\n<li><code>world:set_transform(entity, { position, rotation, scale })</code> sets supplied local fields.</li>\n<li><code>world:get_world_transform</code> and <code>world:set_world_transform</code> perform explicit hierarchy conversion while keeping component storage local.</li>\n<li><code>world:transform_point(entity, value, inverse)</code> converts points between local and world space, including translation, rotation, and scale.</li>\n<li><code>world:transform_direction(entity, value, inverse)</code> converts directions using world orientation without applying translation or scale.</li>\n<li><code>world:translate(entity, x, y, z)</code>, <code>rotate(entity, xDeg, yDeg, zDeg)</code>, and <code>scale_by(entity, x, y, z)</code> persist a relative change.</li>\n<li><code>world:look_at(entity, target)</code> and <code>distance(a, b)</code> use world-space native transform math.</li>\n<li><code>world:tween_transform(entity, options)</code> interpolates in C++. Options are <code>position</code>, <code>rotation</code>, <code>scale</code>, <code>duration</code>, and <code>easing</code> (<code>linear</code>, <code>smooth</code>, <code>ease_in</code>, or <code>ease_out</code>).</li>\n<li><code>world:follow(entity, target, { offset, response, copy_rotation, local_offset, look_at })</code> follows in native code after physics. <code>local_offset</code> rotates the offset with the target, and <code>look_at</code> smoothly tracks the target.</li>\n<li><code>world:spin</code>, <code>orbit</code>, <code>ping_pong</code>, and <code>pulse</code> install native motion jobs.</li>\n<li><code>world:stop_motion(entity)</code> cancels those jobs without discarding the final transform.</li>\n</ul>\n<p>Lua should not implement a tween by adding a small amount every update. A native motion job avoids Lua calls for every moving entity and uses the same high-performance ECS path on Mac, iPad, and web.</p>\n<p>For dynamic rigid bodies, persistent translation and rotation commands are forwarded to Jolt as native position/rotation targets. A discrete scale change rebuilds the collider once. A scale tween updates the rendered scale every frame and rebuilds the collider at completion; <code>pulse</code> is intentionally a visual effect. Rebuilding a dynamic collision shape every animation frame would be disproportionately expensive and is not hidden behind the Lua API.</p>\n<h3 id=\"animation-audio-effects-and-streaming\">Animation, audio, effects, and streaming<a class=\"heading-anchor\" href=\"#animation-audio-effects-and-streaming\" aria-label=\"Link to Animation, audio, effects, and streaming\">#</a></h3>\n<ul>\n<li><code>world:load_scene(sceneId)</code> requests an asynchronous replace-mode transition using a project-relative id such as <code>Scenes/Game.usda</code>. The current scene remains live while the destination loads. Once its authoritative snapshot arrives, Warp atomically replaces the runtime world and unloads the previous entities, assets, UI, physics, scripts, and playing audio. If loading fails, the current scene remains visible and usable. Absolute paths, URLs, and paths containing <code>..</code> are rejected so the script stays portable across players.</li>\n<li><code>world:pause_animation</code>, <code>resume_animation</code>, and <code>stop_animation(entity, reset)</code> complement named and range playback.</li>\n<li><code>world:tween_uv(entity, options)</code> changes UV targets only when animation commands change. Native ECS interpolates independent U/V scale, offset, and rotation channels, while the renderer applies one compact transform per instance without mutating vertices, duplicating materials, or re-uploading textures. <code>world:stop_uv_animation(entity, reset)</code> stops or resets it.</li>\n<li><code>world:set_loop_sound(key, assetRef, playing, volume)</code> owns a named loop and prevents overlapping copies.</li>\n<li><code>world:preload_asset(assetRef)</code> asynchronously schedules existing threaded mesh/audio decode or material load and pins the result.</li>\n<li><code>world:release_asset(assetRef)</code> removes the script pin. It does not synchronously evict a resource that visible geometry may still use.</li>\n<li>Effects are ordinary template entities with a native lifetime and native motion. This keeps Lua independent of a particular particle renderer.</li>\n</ul>\n<h2 id=\"demo-library\">Demo library<a class=\"heading-anchor\" href=\"#demo-library\" aria-label=\"Link to Demo library\">#</a></h2>\n<p><a href=\"/docs/lua-examples\">\u00000\u0000</a> contains ready-to-copy examples for scene-wide ECS systems, native queries, groups/events, hierarchy and enable state, local/world transforms, native raycasts and impulses, spawning and killing objects, timed waves, typed state, streaming, motion, animation, audio, proximity triggers, and short-lived effects. The same core examples are available from the New Script example pickers in both web and Mac editors.</p>\n<dl>\n<dt><code>furry.random()</code></dt><dd>Returns deterministic pseudo-random numbers from the script context.</dd>\n</dl>\n<h2 id=\"examples\">Examples<a class=\"heading-anchor\" href=\"#examples\" aria-label=\"Link to Examples\">#</a></h2>\n<h3 id=\"3d-character-controller\">3D Character Controller<a class=\"heading-anchor\" href=\"#3d-character-controller\" aria-label=\"Link to 3D Character Controller\">#</a></h3>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">return furry.behavior(&quot;scripts.CharacterController3D&quot;, {\n  properties = {\n    speed = { type = &quot;number&quot;, default = 3.0 },\n    jumpSpeed = { type = &quot;number&quot;, default = 6.0 }\n  }\n}, function(world, entity, dt, script)\n  world:move_by_input_3d(entity, script.speed or 3.0, dt, script.jumpSpeed or 6.0)\nend)</code></pre></div>\n<h3 id=\"touch-and-tilt-controller\">Touch And Tilt Controller<a class=\"heading-anchor\" href=\"#touch-and-tilt-controller\" aria-label=\"Link to Touch And Tilt Controller\">#</a></h3>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">return furry.behavior(&quot;scripts.TouchTiltController&quot;, {\n  properties = {\n    moveSpeed = { type = &quot;number&quot;, default = 3.0 },\n    tiltSpeed = { type = &quot;number&quot;, default = 2.0 },\n    tapImpulse = { type = &quot;number&quot;, default = 0.35 }\n  }\n}, function(world, entity, dt, script)\n  local axisX, axisY = world:input_axis()\n  local tiltX, tiltY, _ = world:tilt()\n  local touch = world:touch()\n  for e, s, transform in world:view(&quot;Script&quot;, &quot;RuntimeTransform&quot;) do\n    if e.id == entity.id then\n      transform:translate(\n        (axisX * (script.moveSpeed or 3.0) + tiltX * (script.tiltSpeed or 2.0)) * dt,\n        (axisY * (script.moveSpeed or 3.0) - tiltY * (script.tiltSpeed or 2.0)) * dt,\n        touch.tap_count * (script.tapImpulse or 0.35))\n    end\n  end\nend)</code></pre></div>\n<h3 id=\"one-shot-sound\">One-Shot Sound<a class=\"heading-anchor\" href=\"#one-shot-sound\" aria-label=\"Link to One-Shot Sound\">#</a></h3>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">return furry.behavior(&quot;scripts.SoundTest&quot;, {\n  reads = { &quot;Script&quot; },\n  writes = {}\n}, function(world, entity, dt, script)\n  if world:jump_pressed() then\n    world:play_sound(&quot;Jump&quot;, script.volume or 1.0)\n  end\nend)</code></pre></div>\n<h3 id=\"named-animation\">Named Animation<a class=\"heading-anchor\" href=\"#named-animation\" aria-label=\"Link to Named Animation\">#</a></h3>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">return furry.behavior(&quot;scripts.GuardAnimation&quot;, {\n  writes = { &quot;AnimationPlayback&quot; }\n}, function(world, entity, dt, script)\n  if world:entity_state_get(entity, &quot;moving&quot;, false) then\n    world:play_animation(entity, &quot;walking&quot;, 0.15, true, 1.0)\n  else\n    world:play_animation(entity, &quot;idle&quot;, 0.15, true, 1.0)\n  end\nend)</code></pre></div>\n<h3 id=\"legacy-animation-range\">Legacy Animation Range<a class=\"heading-anchor\" href=\"#legacy-animation-range\" aria-label=\"Link to Legacy Animation Range\">#</a></h3>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">return furry.behavior(&quot;scripts.PlayRun&quot;, {\n  writes = { &quot;RuntimeTransform&quot;, &quot;AnimationPlayback&quot; }\n}, function(world, entity, dt, script)\n  world:play_animation_range(entity, 1, 30, 24.0, true)\nend)</code></pre></div>\n<h2 id=\"editing-scripts\">Editing Scripts<a class=\"heading-anchor\" href=\"#editing-scripts\" aria-label=\"Link to Editing Scripts\">#</a></h2>\n<h3 id=\"in-the-editors\">In The Editors<a class=\"heading-anchor\" href=\"#in-the-editors\" aria-label=\"Link to In The Editors\">#</a></h3>\n<p>Both web and Mac editors create Lua files under the project <code>Scripts/</code> folder and register them as script assets. Adding script examples should create the asset immediately and use the filename/module name derived from the script name.</p>\n<p>A runnable behavior has two linked pieces: a registered project script asset, and a <code>Script</code> component on an entity whose <code>module</code> names that asset. Agent tools complete this lifecycle together. <code>warp_write_script</code> accepts optional <code>targetEntities</code> to write, register, and attach in one call. To attach an existing source without rewriting it, use a <code>warp_apply_operations</code> <code>scriptWrites</code> entry with <code>module</code> and <code>targetEntities</code> only; Warp verifies and re-registers the asset before attaching it. <code>warp_list_scripts</code> reports <code>registered</code>, <code>attachedEntities</code>, and <code>lifecycleHealthy</code>, and <code>warp_validate</code> rejects missing or unregistered module references.</p>\n<h3 id=\"vs-code\">VS Code<a class=\"heading-anchor\" href=\"#vs-code\" aria-label=\"Link to VS Code\">#</a></h3>\n<p>The web editor exposes an <strong>Open in VS Code</strong> flow through the share menu. It creates a short-lived token and opens the Warp Scripts extension with a <code>vscode://</code> URI.</p>\n<p>Install the extension:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/install_vscode_furry_scripts.sh</code></pre></div>\n<p>Once connected, the VS Code side panel lists modules such as <code>scripts.PlayerMovement</code>. Opening a module gives a normal editable Lua document. Saving pushes the complete source back to the server.</p>\n<h3 id=\"http-endpoints\">HTTP Endpoints<a class=\"heading-anchor\" href=\"#http-endpoints\" aria-label=\"Link to HTTP Endpoints\">#</a></h3>\n<p>Remote script editing is served by the web editor server:</p>\n<ul>\n<li><code>GET /api/scripts</code></li>\n<li><code>GET /api/script?module=scripts.Name</code></li>\n<li><code>PUT /api/script?module=scripts.Name</code></li>\n<li><code>POST /api/vscode-session</code></li>\n</ul>\n<p>Tokens are short-lived and scoped to script read/write for the current project. Modules are validated server-side and mapped only to <code>Scripts/&lt;Name&gt;.lua</code>.</p>\n<h3 id=\"cli-agent-editing\">CLI / Agent Editing<a class=\"heading-anchor\" href=\"#cli-agent-editing\" aria-label=\"Link to CLI / Agent Editing\">#</a></h3>\n<p>For an active local editor session, prefer <code>furry_ai</code>:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/furry_ai context\n./tools/furry_ai validate</code></pre></div>\n<p>Write or update a script:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">cat &lt;&lt;&#x27;JSON&#x27; | ./tools/furry_ai write-script\n{\n  &quot;scriptWrites&quot;: [\n    {\n      &quot;module&quot;: &quot;scripts.PlayerMovement&quot;,\n      &quot;contents&quot;: &quot;return furry.behavior(\\&quot;scripts.PlayerMovement\\&quot;, {}, function(world, entity, dt, script)\\\\n  world:move_by_input_3d(entity, script.speed or 3.0, dt, 6.0)\\\\nend)\\\\n&quot;\n    }\n  ]\n}\nJSON</code></pre></div>\n<p>Write and attach a script to an entity:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">cat &lt;&lt;&#x27;JSON&#x27; | ./tools/furry_ai apply\n{\n  &quot;scriptWrites&quot;: [\n    {\n      &quot;module&quot;: &quot;scripts.PlayerMovement&quot;,\n      &quot;targetEntities&quot;: [&quot;/World/Player&quot;],\n      &quot;contents&quot;: &quot;return furry.behavior(\\&quot;scripts.PlayerMovement\\&quot;, {}, function(world, entity, dt, script)\\\\n  world:move_by_input_3d(entity, script.speed or 3.0, dt, 6.0)\\\\nend)\\\\n&quot;\n    }\n  ]\n}\nJSON</code></pre></div>\n<p>Always validate after meaningful edits:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/furry_ai validate</code></pre></div>\n<p>For hosted/remote projects, use <code>tools/furry_remote</code>:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/furry_remote connect --server https://warp.billrey.net\n./tools/furry_remote scripts\n./tools/furry_remote read-script scripts.PlayerMovement\n./tools/furry_remote write-script scripts.PlayerMovement ./PlayerMovement.lua</code></pre></div>\n<p>Local Claude can be launched with a prompt that includes the remote project context and CLI commands:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/furry_remote claude &quot;make PlayerMovement jump on tap&quot;</code></pre></div>\n<h2 id=\"runtime-behavior\">Runtime Behavior<a class=\"heading-anchor\" href=\"#runtime-behavior\" aria-label=\"Link to Runtime Behavior\">#</a></h2>\n<ul>\n<li>Lua is enabled only in builds configured with <code>FURRY_ENABLE_LUA=ON</code>.</li>\n<li>The web play bundle is built with Lua on.</li>\n<li>The web editor/preview bundle may be built with Lua off.</li>\n<li>Native <code>full-demo</code> builds enable Lua.</li>\n<li>Script assets are cooked into snapshots and live patches.</li>\n<li>Hot reload refreshes script modules from changed script assets.</li>\n<li>Failed script loads keep diagnostics and prevent the broken module from becoming useful until fixed.</li>\n</ul>\n<h2 id=\"guardrails\">Guardrails<a class=\"heading-anchor\" href=\"#guardrails\" aria-label=\"Link to Guardrails\">#</a></h2>\n<ul>\n<li>Declare every component mutation in <code>schema.writes</code>.</li>\n<li>Declare queried and inspected components in <code>schema.reads</code> (or <code>writes</code>).</li>\n<li>Unknown components, invalid schemas, and disallowed access fail with structured diagnostics.</li>\n<li><code>while true do</code> and <code>repeat</code> are rejected as obvious unbounded loops.</li>\n<li>Runtime execution has a per-frame instruction budget.</li>\n<li>The sandbox opens only the base, table, string, and math Lua libraries.</li>\n<li><code>require</code>, <code>dofile</code>, <code>loadfile</code>, and <code>collectgarbage</code> are unavailable.</li>\n<li>Scripts should send complete source when edited remotely; the server replaces the script asset and the live runtime refreshes from that asset path.</li>\n</ul>",
      "icon": "code",
      "order": 2,
      "searchText": "Furry Lua Scripting Furry scripts are small sandboxed Lua system modules. They run inside the runtime ECS, not inside the authoring server, and they are meant for gameplay logic that can be hot-reloaded from the editor, VS Code, or CLI/agent tools. The API is ECS-first. A module declares its component access, queries native component storage, and queues structural changes that are committed after the current update. Lua orchestrates decisions; native C++ systems own dense iteration, physics, animation, audio, rendering, streaming, and continuous motion. Scripts are not native plugins. They do not get raw EnTT handles, C++ pointers, filesystem access, OS access, package loading, renderer internals, or direct USD access. Minimal Behavior Most scripts should use furry.behavior : return furry.behavior(\"scripts.PlayerMovement\", { properties = { speed = { type = \"number\", default = 3.0 } } }, function(world, entity, dt, script) world:move by input(entity, script.speed or 3.0, dt) end) The module name is the stable script address. For v1 remote editing, modules are flat: scripts.PlayerMovement - Scripts/PlayerMovement.lua Use alphanumeric and underscore stems. Do not use slashes, traversal, nested modules, or arbitrary filesystem paths. Manual Module Shape furry.behavior returns this table shape for you, but a script may return it directly: return { schema = { reads = { \"Script\", \"RuntimeTransform\" }, writes = { \"RuntimeTransform\" } }, update = function(world, dt) for entity, script, transform in world:view(\"Script\", \"RuntimeTransform\") do if script.module == \"scripts.LiveSpeed\" then transform.translation x = transform.translation x + (script.speed or 0.0) dt end end end } schema.reads declares components the script inspects. schema.writes declares components the script mutates. Reads and writes not declared in the schema fail with diagnostics. This explicit access model keeps dependencies visible and allows native query paths to evolve without turning Lua modules into opaque object scripts. Manual modules must declare Script when they query it. furry.behavior adds its own engine-owned Script read automatically because the wrapper uses that component to select matching entities. Behavior Helper furry.behavior(module, options, update fn) options may contain: - properties : editor-visible script properties. - reads : replace the user-visible default reads. The wrapper always adds its engine-owned Script read if it is not already present. - writes : override default writes. Defaults: reads = { \"Script\", \"RuntimeTransform\" } writes = { \"RuntimeTransform\" } update fn(world, entity, dt, script) is called for each entity whose Script.module matches the behavior module. Scene-wide system helper Use furry.system when a module is a system rather than an entity behavior: return furry.system(\"scripts.DamageSystem\", { reads = { \"RuntimeTransform\", \"RenderShape\" }, writes = {}, query = { all = { \"RuntimeTransform\", \"RenderShape\" }, groups = { \"damageable\" } } }, function(world, dt) for event in world:events(\"damage\") do if event.target ~= nil and world:is in group(event.target, \"damageable\") then local health = world:entity state get(event.target, \"health\", 3) - (event.value or 1) world:entity state set(event.target, \"health\", health) end end end) furry.system(module, options, update fn) runs once per runtime update. Its options are: - reads and writes : required component access declarations. - query : an optional native query condition; the update is skipped when it has no match. - interval : an optional native timer interval for logic that need not run every frame. Modules update in stable module-name order. Structural changes and emitted events do not depend on hash-table iteration order. Script Properties Script component properties are authored as USD attributes named furry:prop:<name . Lua reads them as fields on the script userdata: local speed = script.speed or 3.0 local enabled = script.enabled ~= false local label = script.label or \"Player\" Supported property values are currently number, bool, string, and token-like strings. script.module is read-only. Components Known script-visible components: Component Read Write Notes --- --- --- --- Entity yes no Handles expose id , path , name , parent , enabled , and enabled self . Script yes no module plus authored script properties. RuntimeTransform yes yes Frame-local translation/scale fields; persistent transforms and continuous motion use native World commands. AudioSource yes yes Volume, loop, autoplay; asset id is read-only. AnimationPlayback yes helper only Read playback state; use the named clip, named clip subrange, or legacy range APIs to write. RuntimeUvAnimation yes helper only Per-instance UV transform state; use world:tween uv so native ECS advances it and the GPU applies it. RuntimePhysicsControl no direct userdata helper only Use movement helpers or world:set velocity 2d . UiText query only helper only Use world:ui set text ; native code retains and renders the result. UiLayout query only helper only Use world:ui set visible ; native code recomputes layout only when dirty. UiInteraction query only helper only Use world:ui set enabled and world:ui focus . Native queries can also test Name , Parent , Transform , RigidBody , RenderShape , MeshAsset , MaterialBinding , Light , Camera , RuntimeLifetime , RuntimeGroups , and RuntimeDisabled . These are query predicates, not mutable Lua objects. Add every queried component to reads or writes except the always-available Entity identity. RuntimeTransform fields: - translation x , translation y , translation z - scale x , scale y , scale z - translate(x, y, z) - read-only quaternion fields rotation w , rotation x , rotation y , rotation z - rotate(xDegrees, yDegrees, zDegrees) for a frame-local rotation AudioSource fields: - asset id read-only - volume - loop - autoplay AnimationPlayback is exposed as a read-only table: - start frame - end frame - frames per second - elapsed seconds - loop - playing - clip when named-clip playback is active - clip range enabled , plus clip start frame and clip end frame when a named subrange is active - speed - transition alpha RuntimeUvAnimation is exposed as a read-only table with offset , scale , rotation degrees , elapsed seconds , duration seconds , and playing . World API world:view(component, ...) : Iterates enabled entities that have all requested script-readable component userdata. This is the concise path for Script , RuntimeTransform , AudioSource , and AnimationPlayback . for entity, script, transform in world:view(\"Script\", \"RuntimeTransform\") do transform:translate(0.0, 1.0 dt, 0.0) end world:query(options) : Iterates entity handles selected by a native ECS query. Options are all , any , none , groups , and include disabled . for entity in world:query({ all = { \"RuntimeTransform\", \"RigidBody\" }, none = { \"RuntimeDisabled\" }, groups = { \"enemy\", \"active\" } }) do -- make a small number of gameplay decisions end world:query count(options) and world:query first(options) use the same native query. world:has component(entity, name) tests one entity. Queries are backed by EnTT component storage rather than a Lua scan of every entity; Lua is entered only for results the script chooses to iterate. world:find path(path) : Returns an entity handle for a prim path, or nil . world:find name(name, root, includeDisabled) : Finds by authored name, optionally below a root entity. world:find transform overlap(root, center, halfExtents, includeDisabled) : Returns the first descendant of root whose world-space transform volume overlaps the supplied axis-aligned query box, or nil . Descendant discovery is cached and the hierarchy traversal, world-transform composition, and overlap test run in native C++; use this instead of recursively scanning a stable volume hierarchy in Lua every frame. Declare RuntimeTransform in reads or writes . Hierarchy, groups, and enable state - world:parent(entity) , world:children(entity, recursive, includeDisabled) , and world:root(entity) navigate stable entity handles. - world:reparent(entity, parentOrNil, preserveWorld) changes hierarchy after the current Lua update. preserveWorld defaults to true and cycle-forming requests are rejected. - world:set enabled(entity, enabled) changes local enable state after the current Lua update. Disabled parents disable their descendants for scripts, native simulation, physics, rendering, lights, cameras, and audio. - world:is enabled(entity, effective) reads effective hierarchical state by default; pass false to read only local state. - world:add to group , remove from group , and is in group provide lightweight runtime grouping without adding a Lua object to every entity. Structural commands are deferred like an ECS command buffer. A query that is already being iterated is never invalidated by spawn, destroy, reparent, group, or enable operations. Events world:emit(name, valueOrOptions) queues a typed gameplay event. An options table may provide source , target , and a scalar value (number, boolean, or string). world:events(name, target) iterates matching events on the next update: world:emit(\"damage\", { source = attacker, target = victim, value = 2.0 }) for event in world:events(\"damage\", victim) do -- event.name, source, target, value end Events are broadcast deterministically for that update; one system does not consume an event before another system can observe it. Game UI Native UI interactions emit ui.action events. event.target is the stable UI entity and event.value is the semantic action string authored on its UiInteraction component. world:ui set text(entity, text) : Replaces an entity's displayed text. Declare UiText in schema.writes . world:ui set visible(entity, visible) : Changes retained UI visibility and invalidates native layout. Declare UiLayout in schema.writes . world:ui set enabled(entity, enabled) : Enables or disables native hit testing for an interaction. Declare UiInteraction in schema.writes . world:ui focus(entity) : Moves native UI focus to an enabled, focusable interaction. Declare UiInteraction in schema.writes . See Game UI for the authored components and a complete example. world:input axis() : Returns combined keyboard/game/touch movement as x, y . Gamepad sticks preserve their analogue magnitude after the platform radial deadzone. The combined vector is normalized only when its length exceeds one, so partial stick movement stays partial and diagonals never receive a speed boost. world:input() : Returns the complete gameplay input snapshot for the current simulation tick: { move x, move y, look x, look y, primary down, primary pressed, primary released, secondary down, secondary pressed, secondary released, jump pressed } . Movement and held actions persist while active. Look deltas and pressed/released edges are consumed once by the simulation tick. world:look delta() : Returns device-independent look motion as x, y degrees for the current simulation tick. Pointer motion, touch drags, and gamepad right-stick input use this same contract. Right-stick magnitude remains analogue and is kept separate from the movement axis. world:set character velocity(entity, x, y, z) : Drives player or NPC locomotion through native physics while suppressing restitution for that controlled character. Contacts settle against walls and preserve tangential motion for smooth sliding. Generic dynamic bodies should continue using world:set linear velocity so their authored bounce remains intact. Declare RuntimePhysicsControl in writes . world:set character movement(entity, { step height, ground snap, step down extra, max slope degrees }) : Declares the game's character movement feel for one character. The engine ships inert defaults - no stair climbing, no ground snap, Jolt's neutral 50-degree slope limit - so each game's controller script owns its own feel, exactly like move speed. Omitted keys fall back to those inert defaults. The declaration is sticky for the character but cheap to call, so setting it from the controller's update alongside set character velocity is the recommended pattern (it then also survives runtime state restores). Declare RuntimePhysicsControl in writes . world:primary down() / world:primary pressed() : Return the primary gameplay action state. The first is held state; the second is true only on the press tick. Secondary action equivalents are also available. world:tilt() : Returns device tilt/gravity as x, y, z , or zeros when unavailable. world:touch() : Returns a table: { active = false, x = 0.0, y = 0.0, axis x = 0.0, axis y = 0.0, delta x = 0.0, delta y = 0.0, tap count = 0, tap x = 0.0, tap y = 0.0 } world:jump pressed() : Returns true only on the frame a jump input was pressed. world:is on ground(entity, probeDistance) : Returns true when a dynamic rigid body is touching or just above static ground. probeDistance defaults to 0.08 . world:raycast(origin, direction, maxDistance, ignoreEntity) : Runs a native Jolt ray query and returns nil or { entity, position, normal, distance } . Declare RigidBody in reads . world:sweep box(center, halfExtents, rotationDegrees, displacement, ignoreEntity) : Sweeps one oriented box through Jolt's native broad and narrow phases and returns the same hit table as raycast , or nil . rotationDegrees is an Euler { x, y, z } orientation and displacement is the complete motion vector for the cast. Use one swept hull for vehicle or moving-volume collision prediction instead of issuing a fan of individual Lua raycasts. Declare RigidBody in reads . world:get linear velocity(entity) / world:get angular velocity(entity) : Return native rigid-body velocity as { x, y, z } , or nil . world:set linear velocity , set angular velocity , apply impulse , and apply angular impulse : Queue native 3D body controls. Declare RuntimePhysicsControl in writes . Impulses are applied once even when the renderer catches up with several fixed physics steps in one frame. world:move by input(entity, speed, dt) : Applies engine-owned movement. Dynamic rigid bodies receive physics velocity; other entities move their RuntimeTransform . world:move by input 3d(entity, speed, dt, jumpVelocity) : Like move by input , but can jump and rotates the entity toward the X/Z run direction. world:move by input 2d(entity, speed, dt, jumpVelocity) : Side-scroller movement on the X/Y plane. Dynamic bodies are kept on Z = 0 . world:set velocity 2d(entity, xVelocityOrNil, yVelocityOrNil) : Sets selected 2D physics velocity axes. Declare RuntimePhysicsControl in schema.writes . world:play animation range(entity, startFrame, endFrame, framesPerSecond, loop) : Starts or updates cooked mesh animation playback. Declare AnimationPlayback in schema.writes . world:play animation(entity, clipName, fadeSeconds, loop, speed) : Starts a named cooked clip and asks the native runtime/GPU to crossfade from the current clip. Lua selects gameplay state; it does not sample joints or advance animation frames. Repeating the active clip request does not restart it. Declare AnimationPlayback in schema.writes . world:play animation clip range(entity, clipName, startFrame, endFrame, fadeSeconds, loop, speed) : Starts a frame range local to a named cooked clip. Native code advances, crossfades, and GPU-skins the range, so gameplay can reuse short reactions from larger imported clips without duplicating animation data or updating frames in Lua. Declare AnimationPlayback in schema.writes . world:tween uv(entity, options) : Starts or replaces native per-instance UV channels. Options accept offset={u,v} , scale={u,v} , rotation degrees , duration , and relative . The scalar forms offset u , offset v , scale u , and scale v update one channel without disturbing a simultaneous tween on the other channel. Declare RuntimeUvAnimation in schema.writes . world:stop uv animation(entity, reset) : Stops the native UV channels. Pass true to reset the transform to identity; otherwise the last sampled UV transform remains visible. world:play sound(assetRef, volume) : Queues a one-shot sound. assetRef may be a numeric asset id, a full asset reference path, a store URI, or a short sound name that resolves under the asset registry. Entity lifecycle world:spawn(template, options) clones an existing runtime entity. template may be an entity handle or scene path. The clone is installed after all Lua modules finish their current update, so an ECS view is never invalidated during iteration. A reserved entity handle is returned immediately and can be passed to other queued commands in the same update. local enemy = world:spawn(\"/World/Templates/Enemy\", { name = \"Enemy\", parent = world:find path(\"/World/Enemies\"), position = { 4, 0, -2 }, rotation = { 0, 90, 0 }, -- degrees scale = 1.25, lifetime = 15.0, include children = true, include scripts = false }) Scripts are not copied by default, which prevents a spawner template from accidentally recursively spawning. Spawned entities are runtime-only and are removed when play mode resets. world:spawn effect is an alias intended for short-lived visual templates. - world:destroy(entity, recursive) safely destroys at the end of the Lua update. - world:destroy after(entity, seconds) installs a native lifetime component. - world:is alive(entity) also recognizes a spawn reserved during this update. State and timers State is held in a typed native store, survives ordinary frames and script hot reload, and resets with the game. Values are number, boolean, string, or nil. - world:state get(key, default) / state set(key, value) are shared game state, so separate behavior modules can coordinate. - world:state add(key, delta, initial) is an atomic numeric update. - world:state toggle(key) is an atomic boolean update. - world:entity state get(entity, key, default) / entity state set add entity scope. - world:time() returns native simulation time. - world:after(key, seconds) fires once after the key is first registered. - world:every(key, seconds) fires repeatedly without a Lua delta accumulator. - world:cancel timer(key) removes either kind of timer. Timer keys must distinguish behavior instances when appropriate: if world:every(\"spawn:\" .. tostring(entity.id), 1.5) then -- make one orchestration decision; native systems do the continuing work end Local, world, and persistent transforms The fields returned by world:view(\"RuntimeTransform\") remain useful for a frame-local procedural offset. For persistent gameplay changes, use the World commands below. Declare RuntimeTransform in schema.writes . - world:get transform(entity) returns the local ECS transform as { position, rotation quaternion, scale } . - world:set transform(entity, { position, rotation, scale }) sets supplied local fields. - world:get world transform and world:set world transform perform explicit hierarchy conversion while keeping component storage local. - world:transform point(entity, value, inverse) converts points between local and world space, including translation, rotation, and scale. - world:transform direction(entity, value, inverse) converts directions using world orientation without applying translation or scale. - world:translate(entity, x, y, z) , rotate(entity, xDeg, yDeg, zDeg) , and scale by(entity, x, y, z) persist a relative change. - world:look at(entity, target) and distance(a, b) use world-space native transform math. - world:tween transform(entity, options) interpolates in C++. Options are position , rotation , scale , duration , and easing ( linear , smooth , ease in , or ease out ). - world:follow(entity, target, { offset, response, copy rotation, local offset, look at }) follows in native code after physics. local offset rotates the offset with the target, and look at smoothly tracks the target. - world:spin , orbit , ping pong , and pulse install native motion jobs. - world:stop motion(entity) cancels those jobs without discarding the final transform. Lua should not implement a tween by adding a small amount every update. A native motion job avoids Lua calls for every moving entity and uses the same high-performance ECS path on Mac, iPad, and web. For dynamic rigid bodies, persistent translation and rotation commands are forwarded to Jolt as native position/rotation targets. A discrete scale change rebuilds the collider once. A scale tween updates the rendered scale every frame and rebuilds the collider at completion; pulse is intentionally a visual effect. Rebuilding a dynamic collision shape every animation frame would be disproportionately expensive and is not hidden behind the Lua API. Animation, audio, effects, and streaming - world:load scene(sceneId) requests an asynchronous replace-mode transition using a project-relative id such as Scenes/Game.usda . The current scene remains live while the destination loads. Once its authoritative snapshot arrives, Warp atomically replaces the runtime world and unloads the previous entities, assets, UI, physics, scripts, and playing audio. If loading fails, the current scene remains visible and usable. Absolute paths, URLs, and paths containing .. are rejected so the script stays portable across players. - world:pause animation , resume animation , and stop animation(entity, reset) complement named and range playback. - world:tween uv(entity, options) changes UV targets only when animation commands change. Native ECS interpolates independent U/V scale, offset, and rotation channels, while the renderer applies one compact transform per instance without mutating vertices, duplicating materials, or re-uploading textures. world:stop uv animation(entity, reset) stops or resets it. - world:set loop sound(key, assetRef, playing, volume) owns a named loop and prevents overlapping copies. - world:preload asset(assetRef) asynchronously schedules existing threaded mesh/audio decode or material load and pins the result. - world:release asset(assetRef) removes the script pin. It does not synchronously evict a resource that visible geometry may still use. - Effects are ordinary template entities with a native lifetime and native motion. This keeps Lua independent of a particular particle renderer. Demo library examples/lua contains ready-to-copy examples for scene-wide ECS systems, native queries, groups/events, hierarchy and enable state, local/world transforms, native raycasts and impulses, spawning and killing objects, timed waves, typed state, streaming, motion, animation, audio, proximity triggers, and short-lived effects. The same core examples are available from the New Script example pickers in both web and Mac editors. furry.random() : Returns deterministic pseudo-random numbers from the script context. Examples 3D Character Controller return furry.behavior(\"scripts.CharacterController3D\", { properties = { speed = { type = \"number\", default = 3.0 }, jumpSpeed = { type = \"number\", default = 6.0 } } }, function(world, entity, dt, script) world:move by input 3d(entity, script.speed or 3.0, dt, script.jumpSpeed or 6.0) end) Touch And Tilt Controller return furry.behavior(\"scripts.TouchTiltController\", { properties = { moveSpeed = { type = \"number\", default = 3.0 }, tiltSpeed = { type = \"number\", default = 2.0 }, tapImpulse = { type = \"number\", default = 0.35 } } }, function(world, entity, dt, script) local axisX, axisY = world:input axis() local tiltX, tiltY, = world:tilt() local touch = world:touch() for e, s, transform in world:view(\"Script\", \"RuntimeTransform\") do if e.id == entity.id then transform:translate( (axisX (script.moveSpeed or 3.0) + tiltX (script.tiltSpeed or 2.0)) dt, (axisY (script.moveSpeed or 3.0) - tiltY (script.tiltSpeed or 2.0)) dt, touch.tap count (script.tapImpulse or 0.35)) end end end) One-Shot Sound return furry.behavior(\"scripts.SoundTest\", { reads = { \"Script\" }, writes = {} }, function(world, entity, dt, script) if world:jump pressed() then world:play sound(\"Jump\", script.volume or 1.0) end end) Named Animation return furry.behavior(\"scripts.GuardAnimation\", { writes = { \"AnimationPlayback\" } }, function(world, entity, dt, script) if world:entity state get(entity, \"moving\", false) then world:play animation(entity, \"walking\", 0.15, true, 1.0) else world:play animation(entity, \"idle\", 0.15, true, 1.0) end end) Legacy Animation Range return furry.behavior(\"scripts.PlayRun\", { writes = { \"RuntimeTransform\", \"AnimationPlayback\" } }, function(world, entity, dt, script) world:play animation range(entity, 1, 30, 24.0, true) end) Editing Scripts In The Editors Both web and Mac editors create Lua files under the project Scripts/ folder and register them as script assets. Adding script examples should create the asset immediately and use the filename/module name derived from the script name. A runnable behavior has two linked pieces: a registered project script asset, and a Script component on an entity whose module names that asset. Agent tools complete this lifecycle together. warp write script accepts optional targetEntities to write, register, and attach in one call. To attach an existing source without rewriting it, use a warp apply operations scriptWrites entry with module and targetEntities only; Warp verifies and re-registers the asset before attaching it. warp list scripts reports registered , attachedEntities , and lifecycleHealthy , and warp validate rejects missing or unregistered module references. VS Code The web editor exposes an Open in VS Code flow through the share menu. It creates a short-lived token and opens the Warp Scripts extension with a vscode:// URI. Install the extension: ./tools/install vscode furry scripts.sh Once connected, the VS Code side panel lists modules such as scripts.PlayerMovement . Opening a module gives a normal editable Lua document. Saving pushes the complete source back to the server. HTTP Endpoints Remote script editing is served by the web editor server: - GET /api/scripts - GET /api/script?module=scripts.Name - PUT /api/script?module=scripts.Name - POST /api/vscode-session Tokens are short-lived and scoped to script read/write for the current project. Modules are validated server-side and mapped only to Scripts/<Name .lua . CLI / Agent Editing For an active local editor session, prefer furry ai : ./tools/furry ai context ./tools/furry ai validate Write or update a script: cat <<'JSON' ./tools/furry ai write-script { \"scriptWrites\": [ { \"module\": \"scripts.PlayerMovement\", \"contents\": \"return furry.behavior(\\\"scripts.PlayerMovement\\\", {}, function(world, entity, dt, script)\\\\n world:move by input 3d(entity, script.speed or 3.0, dt, 6.0)\\\\nend)\\\\n\" } ] } JSON Write and attach a script to an entity: cat <<'JSON' ./tools/furry ai apply { \"scriptWrites\": [ { \"module\": \"scripts.PlayerMovement\", \"targetEntities\": [\"/World/Player\"], \"contents\": \"return furry.behavior(\\\"scripts.PlayerMovement\\\", {}, function(world, entity, dt, script)\\\\n world:move by input 3d(entity, script.speed or 3.0, dt, 6.0)\\\\nend)\\\\n\" } ] } JSON Always validate after meaningful edits: ./tools/furry ai validate For hosted/remote projects, use tools/furry remote : ./tools/furry remote connect --server https://warp.billrey.net ./tools/furry remote scripts ./tools/furry remote read-script scripts.PlayerMovement ./tools/furry remote write-script scripts.PlayerMovement ./PlayerMovement.lua Local Claude can be launched with a prompt that includes the remote project context and CLI commands: ./tools/furry remote claude \"make PlayerMovement jump on tap\" Runtime Behavior - Lua is enabled only in builds configured with FURRY ENABLE LUA=ON . - The web play bundle is built with Lua on. - The web editor/preview bundle may be built with Lua off. - Native full-demo builds enable Lua. - Script assets are cooked into snapshots and live patches. - Hot reload refreshes script modules from changed script assets. - Failed script loads keep diagnostics and prevent the broken module from becoming useful until fixed. Guardrails - Declare every component mutation in schema.writes . - Declare queried and inspected components in schema.reads (or writes ). - Unknown components, invalid schemas, and disallowed access fail with structured diagnostics. - while true do and repeat are rejected as obvious unbounded loops. - Runtime execution has a per-frame instruction budget. - The sandbox opens only the base, table, string, and math Lua libraries. - require , dofile , loadfile , and collectgarbage are unavailable. - Scripts should send complete source when edited remotely; the server replaces the script asset and the live runtime refreshes from that asset path.",
      "slug": "lua-scripting",
      "source": "docs/SCRIPTING.md",
      "sourceHash": "4d3b183989086fb9d1093959bd8e2edff588757bffe49d14175e3b7fd067af0d",
      "summary": "Use Lua for ECS-oriented game logic while native C++ systems perform continuous and performance-sensitive work.",
      "tags": [
        "lua",
        "ecs",
        "gameplay",
        "physics",
        "animation",
        "audio"
      ],
      "title": "Lua gameplay scripting"
    },
    {
      "audience": "Game developers",
      "group": "create",
      "headings": [],
      "html": "<p>These modules demonstrate Lua as a gameplay orchestration layer. Continuous motion, interpolation, timers, entity lifetime, animation sampling, audio mixing, asset decoding, and ECS mutation are all performed by native C++ systems.</p>\n<p>Copy a file into a project's <code>Scripts/</code> directory and attach its matching <code>scripts.&lt;FileName&gt;</code> module to an entity. Examples that spawn or follow objects expect the template or target paths exposed as script properties to exist in the scene.</p>\n<p>The examples deliberately avoid per-frame Lua interpolation and large entity loops. Prefer <code>world:tween_transform</code>, <code>world:follow</code>, <code>world:spin</code>, <code>world:orbit</code>, <code>world:ping_pong</code>, <code>world:pulse</code>, <code>world:every</code>, and native lifetime commands for work that continues over time.</p>\n<p>The ECS-oriented examples use a RealityKit-like split:</p>\n<ul>\n<li><code>EcsDamageSystem.lua</code> is a scene-wide system with a native component/group query and next-update events.</li>\n<li><code>HierarchyAndEnable.lua</code> demonstrates deferred reparenting, groups, and hierarchical enable state.</li>\n<li><code>PhysicsRaycast.lua</code> delegates ray queries and impulses to native Jolt code.</li>\n<li><code>WorldSpaceTransform.lua</code> makes the local/world transform boundary explicit.</li>\n<li><code>ui_menu.lua</code> handles semantic <code>ui.action</code> events and requests coarse text and interaction changes while native code owns layout, rendering, and input.</li>\n</ul>\n<p>Use <code>furry.system</code> for orchestration that runs once per scene update. Use <code>furry.behavior</code> when logic belongs to entities carrying a matching <code>Script</code> component. Both declare component access up front; neither should replace a native C++ system for dense per-entity work.</p>",
      "icon": "examples",
      "order": 3,
      "searchText": "Furry Lua gameplay examples These modules demonstrate Lua as a gameplay orchestration layer. Continuous motion, interpolation, timers, entity lifetime, animation sampling, audio mixing, asset decoding, and ECS mutation are all performed by native C++ systems. Copy a file into a project's Scripts/ directory and attach its matching scripts.<FileName module to an entity. Examples that spawn or follow objects expect the template or target paths exposed as script properties to exist in the scene. The examples deliberately avoid per-frame Lua interpolation and large entity loops. Prefer world:tween transform , world:follow , world:spin , world:orbit , world:ping pong , world:pulse , world:every , and native lifetime commands for work that continues over time. The ECS-oriented examples use a RealityKit-like split: - EcsDamageSystem.lua is a scene-wide system with a native component/group query and next-update events. - HierarchyAndEnable.lua demonstrates deferred reparenting, groups, and hierarchical enable state. - PhysicsRaycast.lua delegates ray queries and impulses to native Jolt code. - WorldSpaceTransform.lua makes the local/world transform boundary explicit. - ui menu.lua handles semantic ui.action events and requests coarse text and interaction changes while native code owns layout, rendering, and input. Use furry.system for orchestration that runs once per scene update. Use furry.behavior when logic belongs to entities carrying a matching Script component. Both declare component access up front; neither should replace a native C++ system for dense per-entity work.",
      "slug": "lua-examples",
      "source": "examples/lua/README.md",
      "sourceHash": "81cf5cf1c4aa81cfabb926f763eb1fae8507f7d62b0c3a0d25a3a1238d315b5d",
      "summary": "A map of ready-to-copy behaviors and systems for movement, spawning, state, physics, animation, audio, and effects.",
      "tags": [
        "examples",
        "lua",
        "recipes",
        "gameplay"
      ],
      "title": "Lua example library"
    },
    {
      "audience": "Game developers",
      "featured": true,
      "group": "create",
      "headings": [
        {
          "id": "what-ships-in-the-lite-version",
          "level": 2,
          "title": "What ships in the lite version"
        },
        {
          "id": "create-a-simple-button",
          "level": 2,
          "title": "Create a simple button"
        },
        {
          "id": "responsive-edge-layout",
          "level": 2,
          "title": "Responsive edge layout"
        },
        {
          "id": "authored-gameplay-controls",
          "level": 2,
          "title": "Authored gameplay controls"
        },
        {
          "id": "custom-fonts",
          "level": 2,
          "title": "Custom fonts"
        },
        {
          "id": "lua-api",
          "level": 2,
          "title": "Lua API"
        },
        {
          "id": "performance-model",
          "level": 2,
          "title": "Performance model"
        }
      ],
      "html": "<p>Warp UI Lite is a small retained-mode UI built into the cooked ECS runtime. Authoring uses ordinary OpenUSD component data, but players receive only compact, versioned component payloads. OpenUSD is never linked into or loaded by the runtime.</p>\n<h2 id=\"what-ships-in-the-lite-version\">What ships in the lite version<a class=\"heading-anchor\" href=\"#what-ships-in-the-lite-version\" aria-label=\"Link to What ships in the lite version\">#</a></h2>\n<div class=\"table-scroll\"><table><thead><tr>\n<th>Component</th>\n<th>Purpose</th>\n</tr></thead><tbody>\n<tr>\n<td><code>UiCanvas</code></td>\n<td>Reference resolution, scale matching, visibility, and draw order</td>\n</tr>\n<tr>\n<td><code>UiLayout</code></td>\n<td>Row/column flow, alignment, edge-anchored absolute positioning, pixels/percent/auto sizing, padding, margin, gaps, clipping, and flex growth</td>\n</tr>\n<tr>\n<td><code>UiVisual</code></td>\n<td>Background, opacity, border, and rounded corners</td>\n</tr>\n<tr>\n<td><code>UiImage</code></td>\n<td>Cooked texture image with contain, cover, or stretch fitting</td>\n</tr>\n<tr>\n<td><code>UiText</code></td>\n<td>Text, size, color, alignment, and basic wrapping intent</td>\n</tr>\n<tr>\n<td><code>UiInteraction</code></td>\n<td>Native hover, press, focus, pointer capture, and a semantic action string</td>\n</tr>\n</tbody></table></div>\n<p>The layout, rendering, image residency, clipping, hit testing, pointer capture, and interaction tinting happen in C++. Lua does not run per widget or per pixel.</p>\n<h2 id=\"create-a-simple-button\">Create a simple button<a class=\"heading-anchor\" href=\"#create-a-simple-button\" aria-label=\"Link to Create a simple button\">#</a></h2>\n<ol>\n<li>Add an entity with <code>UiCanvas</code>. Its children form that canvas's UI tree.</li>\n<li>Add <code>UiLayout</code>, <code>UiVisual</code>, <code>UiText</code>, and <code>UiInteraction</code> to a child entity.</li>\n<li>Set its <code>UiInteraction.action</code> to a stable semantic name such as <code>menu.play</code>.</li>\n<li>Handle that action in a Lua system.</li>\n</ol>\n<p>The editor's component menu and inspector expose all of these components on web and Mac. Changes use the normal authoring transaction and appear in connected players as component-level live patches.</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>lua</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-lua\">return {\n  schema = {\n    reads = {},\n    writes = { &quot;UiText&quot;, &quot;UiLayout&quot;, &quot;UiInteraction&quot; },\n  },\n\n  update = function(world, dt)\n    for event in world:events(&quot;ui.action&quot;) do\n      if event.value == &quot;menu.play&quot; then\n        world:ui_set_text(event.target, &quot;Starting...&quot;)\n        world:ui_set_enabled(event.target, false)\n      end\n    end\n  end,\n}</code></pre></div>\n<p>Open <span class=\"unresolved-link\">\u00000\u0000</span> for a ready-made responsive panel and button. <span class=\"unresolved-link\">\u00001\u0000</span> shows the matching action handler.</p>\n<h2 id=\"responsive-edge-layout\">Responsive edge layout<a class=\"heading-anchor\" href=\"#responsive-edge-layout\" aria-label=\"Link to Responsive edge layout\">#</a></h2>\n<p>Absolute UI can anchor to <code>left</code>, <code>top</code>, <code>right</code>, and <code>bottom</code>. Leave the opposite edge's unit as <code>auto</code>: for example, a fixed-size Jump button with <code>right = 36 pixels</code> and <code>bottom = 40 pixels</code> stays inside the lower-right edge at every aspect ratio. When width is <code>auto</code> and both left and right are set, the element stretches between those edges; height behaves the same way with top and bottom.</p>\n<p>Keep <code>UiCanvas.respectSafeArea</code> enabled for player-facing UI. Native players then lay out the canvas inside platform safe-area insets, including iPad and iPhone screen edges and system UI.</p>\n<p>The web and Mac inspectors provide a <strong>Responsive</strong> menu on every <code>UiLayout</code>. Use a corner preset to keep a fixed-size control attached to a safe-area edge, <strong>Stretch inside safe area</strong> for panels, or <strong>Scale bounds with screen</strong> to convert existing pixel bounds to percentages of the nearest canvas in one step. Layout remains retained: these choices do not add per-frame layout work.</p>\n<h2 id=\"authored-gameplay-controls\">Authored gameplay controls<a class=\"heading-anchor\" href=\"#authored-gameplay-controls\" aria-label=\"Link to Authored gameplay controls\">#</a></h2>\n<p>Players do not add a platform-owned D-pad or Jump button. A game opts into the controls it needs by authoring ordinary <code>UiInteraction</code> actions. These reserved actions feed the same normalized input frame as keyboard and gamepad input:</p>\n<div class=\"table-scroll\"><table><thead><tr>\n<th>Action</th>\n<th>Gameplay input</th>\n</tr></thead><tbody>\n<tr>\n<td><code>input.move.left</code> / <code>input.move.right</code></td>\n<td>Horizontal movement axis</td>\n</tr>\n<tr>\n<td><code>input.move.up</code> / <code>input.move.down</code></td>\n<td>Vertical movement axis</td>\n</tr>\n<tr>\n<td><code>input.move.stick</code></td>\n<td>Radial analogue movement from the interaction's center</td>\n</tr>\n<tr>\n<td><code>input.look.stick</code></td>\n<td>Radial analogue camera look from the interaction's center</td>\n</tr>\n<tr>\n<td><code>input.jump</code></td>\n<td>One jump press when the control is first pressed</td>\n</tr>\n<tr>\n<td><code>input.primary</code> / <code>input.secondary</code></td>\n<td>Held and edge-triggered primary/secondary actions</td>\n</tr>\n</tbody></table></div>\n<p>Controls support simultaneous pointers, so a player can hold a direction and press Jump or another action at the same time. The authored UI is rendered and hit-tested by the same retained runtime on web, macOS, iPadOS, Windows, and Linux. <span class=\"unresolved-link\">\u00000\u0000</span> contains a reusable D-pad and Jump arrangement; games can copy, restyle, remove, or replace any of its controls without changing a player app.</p>\n<p>Analogue stick interactions use the shorter layout dimension as their radial range, apply a small rescaled center deadzone, preserve partial magnitude, and keep pointer capture outside the visible base. A visual child of the stick is moved with the captured pointer, which provides a native retained-UI thumb indicator without Lua updates. <code>input.look.stick</code> uses the same 150-degree-per- second maximum look rate as a hardware right stick.</p>\n<p>For touch-only controls, set <code>UiCanvas.requireDirectTouch</code> and <code>UiCanvas.hideWhenGamepadConnected</code>. The canvas then appears only on devices with a direct touch screen, disappears while a hardware game controller is connected, and returns when the controller disconnects. A Mac trackpad does not count as direct touch, so desktop players keep their keyboard controls.</p>\n<h2 id=\"custom-fonts\">Custom fonts<a class=\"heading-anchor\" href=\"#custom-fonts\" aria-label=\"Link to Custom fonts\">#</a></h2>\n<p>Import a <code>.ttf</code> or <code>.otf</code> file into the project on web or Mac, then choose it from <code>UiText.fontAssetPath</code>. Warp registers it as a <code>font</code> asset and cooks it on the authoring side into an immutable signed-distance-field atlas, glyph metrics, Unicode lookup, and sparse kerning table. Players never parse a font program and never link OpenUSD or a font rasterizer.</p>\n<p>The lite character set includes ASCII, Latin-1, common punctuation, and any extra ranges added by the cooker. Missing glyphs use the font's <code>?</code> glyph. Complex shaping, bidirectional layout, color emoji, and variable-font axes are intentionally outside this first version.</p>\n<p>When no custom font is assigned, Warp uses its built-in clean sans-serif SDF font. Native players package the approximately 72 KB compressed cooked atlas. The browser keeps it outside Wasm and the initial scene payload, then streams it from an immutable content-hashed URL only when the first visible label needs it. The decoded atlas is uploaded once and shared across every default label. The old pixel font is retained only as a temporary or emergency fallback while the streamed default is unavailable.</p>\n<h2 id=\"lua-api\">Lua API<a class=\"heading-anchor\" href=\"#lua-api\" aria-label=\"Link to Lua API\">#</a></h2>\n<dl>\n<dt><code>world:events(&quot;ui.action&quot;)</code></dt><dd>Iterates UI actions on the next script update. <code>event.target</code> is the stable UI entity and <code>event.value</code> is the authored action string.</dd>\n<dt><code>world:ui_set_text(entity, text)</code></dt><dd>Replaces <code>UiText.text</code>. Declare <code>UiText</code> in <code>schema.writes</code>.</dd>\n<dt><code>world:ui_set_visible(entity, visible)</code></dt><dd>Changes <code>UiLayout.visible</code> and invalidates layout. Declare <code>UiLayout</code> in <code>schema.writes</code>.</dd>\n<dt><code>world:ui_set_enabled(entity, enabled)</code></dt><dd>Enables or disables hit testing for <code>UiInteraction</code>. Declare <code>UiInteraction</code> in <code>schema.writes</code>.</dd>\n<dt><code>world:ui_focus(entity)</code></dt><dd>Moves native UI focus to a focusable interaction. Declare <code>UiInteraction</code> in <code>schema.writes</code>.</dd>\n</dl>\n<h2 id=\"performance-model\">Performance model<a class=\"heading-anchor\" href=\"#performance-model\" aria-label=\"Link to Performance model\">#</a></h2>\n<p>Layout is retained and recomputed only after a component change, Lua UI mutation, framebuffer-size change, or safe-area change. Draw commands and hit-test order are cached. Texture payloads use the existing asynchronous decode, GPU upload, content-addressed residency, and warm-cache path. Interaction state updates only the affected draw commands instead of rebuilding the tree.</p>\n<p>Custom fonts and the built-in default preserve the same contract. An unused font performs no runtime decode, upload, pipeline creation, or layout. The first visible label lazily creates its atlas and the single shared SDF pipeline. Glyph vertices are retained by a signature of the text, font, size, bounds, alignment, and color, so static labels do no layout or vertex upload per frame. Atlas residency is shared by content hash across project and workspace switches and bounded by an LRU; the cooker never repacks atlases at runtime.</p>\n<p>This first version deliberately omits scroll views, text input, localization shaping, accessibility adapters, animation timelines, and a general styling system. Those can extend the same cooked component model without replacing game UI with an editor/debug immediate-mode layer.</p>",
      "icon": "viewport",
      "order": 4,
      "searchText": "Game UI Warp UI Lite is a small retained-mode UI built into the cooked ECS runtime. Authoring uses ordinary OpenUSD component data, but players receive only compact, versioned component payloads. OpenUSD is never linked into or loaded by the runtime. What ships in the lite version Component Purpose --- --- UiCanvas Reference resolution, scale matching, visibility, and draw order UiLayout Row/column flow, alignment, edge-anchored absolute positioning, pixels/percent/auto sizing, padding, margin, gaps, clipping, and flex growth UiVisual Background, opacity, border, and rounded corners UiImage Cooked texture image with contain, cover, or stretch fitting UiText Text, size, color, alignment, and basic wrapping intent UiInteraction Native hover, press, focus, pointer capture, and a semantic action string The layout, rendering, image residency, clipping, hit testing, pointer capture, and interaction tinting happen in C++. Lua does not run per widget or per pixel. Create a simple button 1. Add an entity with UiCanvas . Its children form that canvas's UI tree. 2. Add UiLayout , UiVisual , UiText , and UiInteraction to a child entity. 3. Set its UiInteraction.action to a stable semantic name such as menu.play . 4. Handle that action in a Lua system. The editor's component menu and inspector expose all of these components on web and Mac. Changes use the normal authoring transaction and appear in connected players as component-level live patches. return { schema = { reads = {}, writes = { \"UiText\", \"UiLayout\", \"UiInteraction\" }, }, update = function(world, dt) for event in world:events(\"ui.action\") do if event.value == \"menu.play\" then world:ui set text(event.target, \"Starting...\") world:ui set enabled(event.target, false) end end end, } Open examples/ui/LiteMenu.usda for a ready-made responsive panel and button. examples/lua/ui menu.lua shows the matching action handler. Responsive edge layout Absolute UI can anchor to left , top , right , and bottom . Leave the opposite edge's unit as auto : for example, a fixed-size Jump button with right = 36 pixels and bottom = 40 pixels stays inside the lower-right edge at every aspect ratio. When width is auto and both left and right are set, the element stretches between those edges; height behaves the same way with top and bottom. Keep UiCanvas.respectSafeArea enabled for player-facing UI. Native players then lay out the canvas inside platform safe-area insets, including iPad and iPhone screen edges and system UI. The web and Mac inspectors provide a Responsive menu on every UiLayout . Use a corner preset to keep a fixed-size control attached to a safe-area edge, Stretch inside safe area for panels, or Scale bounds with screen to convert existing pixel bounds to percentages of the nearest canvas in one step. Layout remains retained: these choices do not add per-frame layout work. Authored gameplay controls Players do not add a platform-owned D-pad or Jump button. A game opts into the controls it needs by authoring ordinary UiInteraction actions. These reserved actions feed the same normalized input frame as keyboard and gamepad input: Action Gameplay input --- --- input.move.left / input.move.right Horizontal movement axis input.move.up / input.move.down Vertical movement axis input.move.stick Radial analogue movement from the interaction's center input.look.stick Radial analogue camera look from the interaction's center input.jump One jump press when the control is first pressed input.primary / input.secondary Held and edge-triggered primary/secondary actions Controls support simultaneous pointers, so a player can hold a direction and press Jump or another action at the same time. The authored UI is rendered and hit-tested by the same retained runtime on web, macOS, iPadOS, Windows, and Linux. examples/ui/TouchControls.usda contains a reusable D-pad and Jump arrangement; games can copy, restyle, remove, or replace any of its controls without changing a player app. Analogue stick interactions use the shorter layout dimension as their radial range, apply a small rescaled center deadzone, preserve partial magnitude, and keep pointer capture outside the visible base. A visual child of the stick is moved with the captured pointer, which provides a native retained-UI thumb indicator without Lua updates. input.look.stick uses the same 150-degree-per- second maximum look rate as a hardware right stick. For touch-only controls, set UiCanvas.requireDirectTouch and UiCanvas.hideWhenGamepadConnected . The canvas then appears only on devices with a direct touch screen, disappears while a hardware game controller is connected, and returns when the controller disconnects. A Mac trackpad does not count as direct touch, so desktop players keep their keyboard controls. Custom fonts Import a .ttf or .otf file into the project on web or Mac, then choose it from UiText.fontAssetPath . Warp registers it as a font asset and cooks it on the authoring side into an immutable signed-distance-field atlas, glyph metrics, Unicode lookup, and sparse kerning table. Players never parse a font program and never link OpenUSD or a font rasterizer. The lite character set includes ASCII, Latin-1, common punctuation, and any extra ranges added by the cooker. Missing glyphs use the font's ? glyph. Complex shaping, bidirectional layout, color emoji, and variable-font axes are intentionally outside this first version. When no custom font is assigned, Warp uses its built-in clean sans-serif SDF font. Native players package the approximately 72 KB compressed cooked atlas. The browser keeps it outside Wasm and the initial scene payload, then streams it from an immutable content-hashed URL only when the first visible label needs it. The decoded atlas is uploaded once and shared across every default label. The old pixel font is retained only as a temporary or emergency fallback while the streamed default is unavailable. Lua API world:events(\"ui.action\") : Iterates UI actions on the next script update. event.target is the stable UI entity and event.value is the authored action string. world:ui set text(entity, text) : Replaces UiText.text . Declare UiText in schema.writes . world:ui set visible(entity, visible) : Changes UiLayout.visible and invalidates layout. Declare UiLayout in schema.writes . world:ui set enabled(entity, enabled) : Enables or disables hit testing for UiInteraction . Declare UiInteraction in schema.writes . world:ui focus(entity) : Moves native UI focus to a focusable interaction. Declare UiInteraction in schema.writes . Performance model Layout is retained and recomputed only after a component change, Lua UI mutation, framebuffer-size change, or safe-area change. Draw commands and hit-test order are cached. Texture payloads use the existing asynchronous decode, GPU upload, content-addressed residency, and warm-cache path. Interaction state updates only the affected draw commands instead of rebuilding the tree. Custom fonts and the built-in default preserve the same contract. An unused font performs no runtime decode, upload, pipeline creation, or layout. The first visible label lazily creates its atlas and the single shared SDF pipeline. Glyph vertices are retained by a signature of the text, font, size, bounds, alignment, and color, so static labels do no layout or vertex upload per frame. Atlas residency is shared by content hash across project and workspace switches and bounded by an LRU; the cooker never repacks atlases at runtime. This first version deliberately omits scroll views, text input, localization shaping, accessibility adapters, animation timelines, and a general styling system. Those can extend the same cooked component model without replacing game UI with an editor/debug immediate-mode layer.",
      "slug": "game-ui",
      "source": "docs/GAME_UI.md",
      "sourceHash": "2032d5a76bea5d962242a0ec28174d9615a49cecf60361f2dfea04af99380b46",
      "summary": "Build responsive, cooked game interfaces with native layout, visuals, images, text, interactions, and Lua action orchestration.",
      "tags": [
        "ui",
        "layout",
        "buttons",
        "text",
        "images",
        "lua"
      ],
      "title": "Game UI"
    },
    {
      "audience": "Teams and agents",
      "featured": true,
      "group": "collaborate",
      "headings": [
        {
          "id": "the-safe-authoring-model",
          "level": 2,
          "title": "The safe authoring model"
        },
        {
          "id": "lore-as-a-remote-filesystem",
          "level": 3,
          "title": "Lore as a remote filesystem"
        },
        {
          "id": "game-generation",
          "level": 2,
          "title": "Game generation"
        },
        {
          "id": "prompt-to-game-quality-harness",
          "level": 3,
          "title": "Prompt-to-game quality harness"
        },
        {
          "id": "mcp-workflow-self-test",
          "level": 3,
          "title": "MCP workflow self-test"
        },
        {
          "id": "open-in-codex-and-claude-code",
          "level": 2,
          "title": "Open in Codex and Claude Code"
        },
        {
          "id": "local-editor-automation",
          "level": 2,
          "title": "Local editor automation"
        },
        {
          "id": "fail-closed-generated-data",
          "level": 2,
          "title": "Fail-closed generated data"
        },
        {
          "id": "script-authoring",
          "level": 2,
          "title": "Script authoring"
        },
        {
          "id": "runtime-introspection",
          "level": 2,
          "title": "Runtime introspection"
        },
        {
          "id": "deterministic-scenarios",
          "level": 2,
          "title": "Deterministic scenarios"
        },
        {
          "id": "deep-reproducibility-links",
          "level": 2,
          "title": "Deep reproducibility links"
        },
        {
          "id": "agent-readable-documentation",
          "level": 2,
          "title": "Agent-readable documentation"
        }
      ],
      "html": "<p>Warp treats agents as collaborators operating on live, structured project state. An agent should not need a screenshot-only description, a private filesystem path, or a manually reconstructed scene. It can receive the exact project, workspace, scene, layer, selection, revision, and—when needed—runtime tick.</p>\n<h2 id=\"the-safe-authoring-model\">The safe authoring model<a class=\"heading-anchor\" href=\"#the-safe-authoring-model\" aria-label=\"Link to The safe authoring model\">#</a></h2>\n<p>The hosted Warp project is the source of truth. Remote agents connect through Warp's authenticated MCP surface.</p>\n<p>The MCP catalog is task-oriented rather than compatibility-oriented. The normal path is <code>warp_workspace</code> once, one batched <code>warp_inspect</code>, one checked <code>warp_change</code>, and <code>warp_verify</code>. Structural mutation schemas are inline, while exact component/property contracts are requested only when needed through <code>warp_inspect.schemas</code> and remain fail-closed at the mutation boundary. A fresh agent does not spend turns discovering internal command names or guessing nested arguments. <code>warp_inspect</code> can return focused Lore workspace files, stage, asset, script, schema, Lua API, runtime, diagnosis, and diff evidence together; <code>warp_change</code> covers hierarchy, references, payloads, components, USD properties, layers, assets, and scripts and validates the result automatically.</p>\n<p>The expected flow is efficient and iterative:</p>\n<ol>\n<li>Select exact scope and receive readiness with <code>warp_workspace</code>.</li>\n<li>Request every relevant entity, component, script, asset, schema, and runtime section in one <code>warp_inspect</code> call.</li>\n<li>Make one coherent idempotent <code>warp_change</code>. Warp automatically creates a separate agent workspace on the first write and returns its link. Treat the returned workspace id as the change's durable destination for all later edits. An explicit <code>warp_workspace</code> selection always opens exactly the named workspace; use <code>changeMode: &quot;continue&quot;</code> only when deliberately resuming an existing child workspace.</li>\n<li>Run one <code>warp_verify</code> with focused readback and a real input-driven playtest. Input actions advance their requested frame count while held.</li>\n<li>Use <code>warp_observe</code> for rendered evidence, then submit the final change for human review when requested.</li>\n</ol>\n<p>Agents cannot approve or merge their own changes. This keeps automation fast without making it invisible or irreversible.</p>\n<h3 id=\"lore-as-a-remote-filesystem\">Lore as a remote filesystem<a class=\"heading-anchor\" href=\"#lore-as-a-remote-filesystem\" aria-label=\"Link to Lore as a remote filesystem\">#</a></h3>\n<p>Agents do not need a second project database or a complete project dump in their prompt. <code>warp_inspect.files</code> exposes the selected Lore workspace like a bounded remote filesystem:</p>\n<ul>\n<li><code>list</code> discovers project-relative files and directories, optionally with a glob;</li>\n<li><code>stat</code> reads metadata for exact files;</li>\n<li><code>read</code> reads exact text ranges using <code>offset</code> and <code>maxBytes</code>;</li>\n<li><code>search</code> finds bounded text matches across the workspace.</li>\n</ul>\n<p>Every response identifies the exact Lore workspace and head. Server paths, Lore internals, derived data, cooked data, and content-addressed payload bytes are not exposed. For example:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>json</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-json\">{&quot;files&quot;:{&quot;action&quot;:&quot;search&quot;,&quot;path&quot;:&quot;Scripts&quot;,&quot;glob&quot;:&quot;**/*.lua&quot;,&quot;query&quot;:&quot;on_collision&quot;,&quot;limit&quot;:20}}</code></pre></div>\n<p>This is the remote equivalent of using <code>find</code>, <code>rg</code>, and ranged file reads in a local checkout. It reads the authored Lore state directly and therefore has no separate index to become stale. Composed scene questions remain distinct: <code>warp_inspect.stage</code> asks OpenUSD to compose the selected scene and returns a focused semantic projection of that live result.</p>\n<h2 id=\"game-generation\">Game generation<a class=\"heading-anchor\" href=\"#game-generation\" aria-label=\"Link to Game generation\">#</a></h2>\n<p>An agent can turn a project brief into a new hosted Warp project through the same authenticated MCP connection. This is generation of editable project data, not export of an opaque game binary.</p>\n<p>The default workflow targets a playable vertical slice, not the smallest scene that boots:</p>\n<ol>\n<li>Create the project with <code>warp_create_project</code>, then select its returned scope with <code>warp_workspace</code>.</li>\n<li>Use one <code>warp_inspect</code> request for the relevant stage, exact component schemas, assets, scripts, Lua topics, and runtime state.</li>\n<li>Build the first coherent slice with typed <code>warp_change</code> operations. The same call can add hierarchy, components, OpenUSD references or payloads, layers, assets, and readable Lua.</li>\n<li>Supply a deterministic typed scenario to <code>warp_change</code>, or run it with the final <code>warp_verify</code>. Fix failed evidence rather than assuming that a plausible scene is playable.</li>\n<li>Use <code>warp_observe</code> for correlated authored, runtime, and rendered evidence. Refine until the result works visually and behaviorally.</li>\n<li>Inspect the returned Lore diff and submit the isolated workspace for human review.</li>\n</ol>\n<p><code>warp_apply_game_spec</code> compiles named entities into ordinary OpenUSD prims, inspector-visible Warp components, project assets, and readable Lua modules. The result appears in the hierarchy and Assets pane and can be selected, moved, duplicated, reparented, restyled, rescripted, layered, reviewed, or edited by another person or agent. <code>WarpGameSpec</code> is an input recipe and audit artifact; it never replaces the authored OpenUSD scene as the source of truth.</p>\n<p>The repository's internal bulk-generation contract lives at <code>docs/schemas/warp-game-spec-v1.schema.json</code>. Its specific game fixture exists only to regression-test the generic compiler and is never returned as agent guidance or used as an authoring template. Remote agents do not need that compatibility surface: they use the same typed <code>warp_change</code> operations as ordinary edits and the exact scenario schema exposed by <code>warp_verify</code>.</p>\n<h3 id=\"prompt-to-game-quality-harness\">Prompt-to-game quality harness<a class=\"heading-anchor\" href=\"#prompt-to-game-quality-harness\" aria-label=\"Link to Prompt-to-game quality harness\">#</a></h3>\n<p>The quality preflight is expressed through focused <code>warp_inspect</code> evidence followed by <code>warp_verify</code>. It has no pre-authored mechanics, project-specific entity assumptions, or genre templates. A playable result requires:</p>\n<ul>\n<li>readable project-owned behavior where the request needs behavior;</li>\n<li>an explicit scenario that uses the behavior's real input, authored UI event, lifecycle boundary, or time advance and observes the result;</li>\n<li>at least two assertions covering relevant transforms, state, components, camera state, or another requested outcome;</li>\n<li>an intentional presentation appropriate to the project rather than a mandatory rendering, input, physics, or interface setup;</li>\n<li>native validation and headless runtime success before the result can be called playable.</li>\n</ul>\n<p>Use <code>draft</code> only for intentionally minimal experiments. The <code>polished</code> target additionally requires intentional presentation, typed runtime inspection evidence, reusable architecture where meaningful repetition exists, and final rendered evidence. A playtest without a scenario is marked <code>structuralOnly</code>; it proves that cooking succeeds and entities exist, not that the requested behavior works.</p>\n<p>For any repeated authored hierarchy, use <code>warp_change.operations.repeatedSubtrees</code>. Each entry contains one arbitrary typed compact-entity subtree and any number of named transform placements. Warp stores the definition once in a private USD namespace and authors internal OpenUSD references at the requested scene paths; the private definition never appears as a runtime entity or hierarchy row. <code>instanceable</code> is optional and defaults to false so descendants remain editable. This contract is general rather than tied to any particular asset or game concept. Use runtime <code>world:spawn</code> only for copies whose lifecycle begins during play.</p>\n<p>For repeated art already stored in a project USD asset, keep runtime roots, physics, and scripts local while placing shared visuals with project-relative <code>referenceURI</code> values and an optional exact <code>referencePrimPath</code>. When iterating on shared art, begin the upload with <code>replaceExisting: true</code>; Warp preserves the stable sanitized filename instead of silently creating a numbered replacement, so all references continue to point at one source of truth.</p>\n<p>The visual player pass still matters. Headless tests cannot identify camera jitter, z-fighting, overlapping ground surfaces, illegible type, poor composition, or weak feedback. Use <code>warp_observe</code> with <code>includeFrame: true</code>. It waits for correlated authored/runtime evidence and returns the actual Sokol player canvas as an MCP <code>image/png</code> content block when a player is connected. Inspect the image and its revision, tick, dimensions, and asset-readiness metadata; correct visible problems and observe again. A successful generation remains <code>ready-for-visual-review</code> until this pass is healthy, after which it can be submitted with <code>warp_submit_for_review</code>.</p>\n<p>A single frame proves rendered composition, not motion over time. Use deterministic playtests and typed runtime inspection for behavior, and take frames before and after important states when motion or feedback must be checked. An asset wait timeout is reported explicitly in capture metadata instead of being mistaken for a fully loaded frame. Camera smoothing should be deterministic and frame-rate independent, and adjacent surfaces should share deliberate topology rather than overlap coplanar meshes.</p>\n<h3 id=\"mcp-workflow-self-test\">MCP workflow self-test<a class=\"heading-anchor\" href=\"#mcp-workflow-self-test\" aria-label=\"Link to MCP workflow self-test\">#</a></h3>\n<p><code>tools/self_test_warp_mcp.py</code> drives a running Warp server through the whole ordinary agent workflow over the real <code>/mcp</code> HTTP endpoint: mint an agent session, open an explicit scope, switch scenes, read the composed asset catalog, upload a sound, create a scene inside the isolated agent workspace, apply prim/component operations including an asset reference, read the stage back, write and attach a Lua script, validate, run a headless playtest, and read the change diff. Every mutation stays inside a dedicated scratch project (<code>McpSelfTest</code> by default) and an isolated Lore agent workspace, so it is safe to run against a production server.</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\"># Against a local server without auth:\ntools/self_test_warp_mcp.py --server http://127.0.0.1:8765\n\n# Against the hosted server (guest or user credentials mint the agent session):\ntools/self_test_warp_mcp.py --server https://warp.billrey.net \\\n  --login-email &quot;$WARP_AUTH_GUEST_EMAIL&quot; --login-password &quot;$WARP_AUTH_GUEST_PASSWORD&quot;</code></pre></div>\n<p>Run it after every server deploy. It exits non-zero when any ordinary operation regresses — including the historical failure classes it encodes as explicit regression checks: a scene created in an agent workspace must remain visible and editable through <code>warp_change</code>, and project assets inherited through the Lore parent chain must stay visible through <code>warp_inspect.assets</code> from every scene. The offline contract checks in <code>tools/check_warp_agent_contracts.py</code> (which also run the <code>--self-test-mcp-*</code> suites built into the server binary) complement it and need no running server.</p>\n<p>Every project-scoped MCP response includes <code>resultLocation</code> with the effective <code>projectId</code>, <code>workspaceId</code>, <code>sceneId</code>, and a directly usable <code>editorUrl</code>. This is the authoritative destination after Warp rewrites a base-workspace request into an isolated Lore change workspace. Agents must name that workspace and include the link when reporting authored work; they must never infer the destination from an earlier prompt or silently describe a different workspace.</p>\n<h2 id=\"open-in-codex-and-claude-code\">Open in Codex and Claude Code<a class=\"heading-anchor\" href=\"#open-in-codex-and-claude-code\" aria-label=\"Link to Open in Codex and Claude Code\">#</a></h2>\n<p>Choose <strong>Open in Codex</strong> or <strong>Open in Claude Code</strong> from either editor. The action installs or updates the official Warp plugin, completes OAuth, verifies the trusted connection, then opens the selected agent surface with project context available through normal agent tools.</p>\n<p>The connection is configuration, not prompt text. You should not have to paste server tokens, project paths, or setup instructions into every task.</p>\n<p>Warp has one endpoint: <code>https://warp.billrey.net/mcp</code>. Claude Code and Codex install it only through the official Warp plugin. Claude, Claude Desktop, and Cowork use the native Warp Connector backed by the same endpoint. Warp never advertises or silently creates a direct CLI registration.</p>\n<p>The installation surfaces are deliberately distinct: <code>/connect/claude-code</code> installs the Claude Code plugin, <code>/connect/codex</code> installs the Codex plugin, and <code>/connect/claude</code> opens the native Connector path. These are human setup pages, never MCP server addresses.</p>\n<p>An already-running agent task has a fixed tool list and cannot discover an MCP server added halfway through that task. <strong>Open in Codex</strong> and <strong>Open in Claude Code</strong> avoid making the user recover from that client limitation: Warp installs and authenticates out of process, then opens a fresh task with the tools already loaded. When setup is run manually from Terminal or from inside an existing agent conversation, open one new task after the command succeeds. No app restart, configuration editing, or repeated <code>/mcp</code> retry is required. Never hand-edit <code>.mcp.json</code> or add the endpoint with an MCP CLI command. Plugin setup preserves unrelated MCP entries and only migrates a stale entry after it has verified that the entry belongs to Warp.</p>\n<p>Codex setup also stores <code>default_tools_approval_mode = &quot;approve&quot;</code> on the permanent <code>warp</code> server entry. This is required for <code>codex exec</code> and other non-interactive sessions: otherwise Codex cannot surface an MCP write approval and reports the automatic rejection as if the user cancelled it. The trust is narrowly attached to the immutable Warp connection URL. Warp still creates all agent mutations in a separate Lore child workspace, retains revision history, and does not expose an agent tool that can approve or merge its own review. This allows ordinary authoring without weakening the shell or filesystem sandbox and without using <code>--dangerously-bypass-approvals-and-sandbox</code>.</p>\n<p>Claude Code shows its normal first-use tool approval in interactive sessions. For a non-interactive <code>claude -p</code> acceptance run, scope the allow rule to the connected Warp plugin with <code>--allowedTools &#x27;mcp__plugin_warp_warp__*&#x27;</code>. That wildcard keeps local shell and file tools unavailable while allowing the complete Warp toolset to evolve without a brittle hand-maintained list of MCP method names.</p>\n<p>After authentication, call <code>warp_workspace</code>. With no ids it returns the accessible project catalog. With exact project, workspace, and scene ids it selects the scope and returns readiness in the same call. Its <code>setup</code> block reports whether project binding, the composed OpenUSD stage, authoring backend, Lua contract, and Lore review isolation are ready.</p>\n<p>Scenes are first-class OpenUSD documents in a Lore workspace. Each workspace entry in the <code>warp_workspace</code> catalog includes every composed scene visible there, including scenes created only in a Lore child workspace. Every scene record returns exact <code>sceneId</code>, <code>referenceURI</code>, <code>payloadURI</code>, and <code>luaSceneId</code> values. Create or duplicate one with idempotent <code>warp_create_scene</code>, then select the returned exact scope with <code>warp_workspace</code>. <code>warp_create_scene.contents</code> can instead create a complete USDA document; Warp validates it before advancing the Lore workspace head. Use a returned <code>referenceURI</code> or <code>payloadURI</code> in a <code>warp_change</code> prim operation to compose one workspace-visible document into another. These are stable project ids, never server filesystem paths. In compact agent sessions, request <code>scenes: {}</code> through <code>warp_inspect</code> to get the complete scene catalog for the selected workspace without depending on the bounded project overview. The compatibility <code>warp_list_scenes</code> tool returns the same workspace-native scene identities.</p>\n<p>Ordinary hierarchy and composition work uses the fail-closed <code>primOps</code> and <code>layerOps</code> variants on <code>warp_change.operations</code>. <code>primOps</code> supports add, delete, duplicate, reparent, references, and payloads; reference and payload targets must be project-relative ids returned by Warp. <code>layerOps</code> supports add, remove, reorder, mute, active target, clear, merge down, and complete USDA import. Import is the native OpenUSD escape hatch for variants, inherits, specializes, relationships, and other declarative composition while still being parsed and project-scope validated by OpenUSD.</p>\n<p>Project assets are discoverable through <code>warp_inspect.assets</code>. This queries the composed Warp project asset catalog for the exact project/workspace. The catalog follows Lore inheritance and is available from every scene; it is not the same thing as the raw authored-file access in <code>warp_inspect.files</code>. The result covers meshes, textures, sounds, animations, scripts, materials, and fonts and returns stable asset ids, <code>/World/FurryAssets/...</code> paths, short names, and exact component/Lua reference forms. A scene component such as <code>AudioSource</code> or <code>MeshAsset</code> is a usage of an asset, not proof that the asset exists. Agents must query this catalog before guessing a reference or uploading, and must not re-upload an asset merely because no scene object currently uses it.</p>\n<p>The <code>finish</code> phase of <code>warp_upload_asset</code> returns the stable Warp asset identity immediately while preview generation and cooking continue in the background. The asset is immediately discoverable through <code>warp_inspect.assets</code> by that id or path. Asset payloads remain content-addressed and on-demand; neither catalog inspection nor scene authoring reads raw server files into the agent context.</p>\n<p>The same resumable upload path accepts a complete ZIP project tree for rich OpenUSD imports. Set <code>projectTreeRoot</code> to a portable destination under <code>Assets/</code>, <code>entryUsd</code> to the archive-relative composition entry point, and optionally <code>entryPrimPath</code>, <code>parentPath</code>, and <code>primName</code>. Warp validates every archive path, rejects traversal and symlink entries, installs the tree atomically in the isolated Lore workspace, and retains all authored sublayers, references, payloads, materials, textures, and relative paths. It returns the portable entry <code>sourceURI</code>; when <code>parentPath</code> is supplied it also authors the open composition reference. Ordinary single assets and composition trees use this one upload mechanism rather than separate overlapping import systems.</p>\n<p><code>warp_inspect.stage</code> returns compact composed prim summaries by default. They retain paths, component values, and short scalar attributes while omitting mesh arrays and verbose provenance that would crowd out the actual authoring task. An exact path returns that prim alone; request <code>includeDescendants: true</code> only when the whole subtree is relevant. Use <code>includeDetails</code> only for focused follow-up inspection. Broad <code>/World</code> queries are deliberately capped and return a query hint so the agent continues with focused subsets instead of spilling a scene dump into a local file or exhausting its model context.</p>\n<p>Before authoring an unfamiliar component, include it in <code>warp_inspect.schemas</code>. It reads the same closed-world component rule table used at Warp's mutation boundary and returns exact property names, scalar/vector channels, vector arities, ranges, and enum values. When those components already occur in the selected stage, it also includes the editor metadata and a bounded set of example paths. This is a discovery surface over the enforced contract, not a second permissive schema.</p>\n<p>Codex app, Codex CLI, the Codex IDE extension, and supported ChatGPT desktop workflows can use the same remote MCP configuration. Hosted ChatGPT workspaces use the Warp plugin backed by that same MCP server rather than reading local desktop configuration.</p>\n<h2 id=\"local-editor-automation\">Local editor automation<a class=\"heading-anchor\" href=\"#local-editor-automation\" aria-label=\"Link to Local editor automation\">#</a></h2>\n<p>When a local editor is running, it writes the active authoring session to <code>.derived/furry_ai_session.json</code>. The repository wrapper discovers the active scene, address, author, workspace, and server:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/furry_ai context\n./tools/furry_ai validate</code></pre></div>\n<p>Apply ordinary entity and component edits as structured JSON:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">cat &lt;&lt;&#x27;JSON&#x27; | ./tools/furry_ai apply\n{\n  &quot;componentOps&quot;: [\n    {\n      &quot;op&quot;: &quot;set&quot;,\n      &quot;entity&quot;: &quot;/World/Player&quot;,\n      &quot;component&quot;: &quot;Transform&quot;,\n      &quot;data&quot;: {\n        &quot;translation&quot;: [0, 2, 0]\n      }\n    }\n  ],\n  &quot;notes&quot;: &quot;Raise the player spawn point&quot;\n}\nJSON\n\n./tools/furry_ai validate</code></pre></div>\n<p>Structured authoring preserves destination scope, live updates, revision checks, undo/history, and actionable diagnostics. Directly editing USD text bypasses those guarantees and is not the normal agent path.</p>\n<p>The hosted MCP surface uses the same component operation contract. For example, this sets a child attachment one metre above its parent in local space:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>json</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-json\">{\n  &quot;componentOps&quot;: [{\n    &quot;kind&quot;: &quot;set&quot;,\n    &quot;entityPath&quot;: &quot;/World/Parent/Child&quot;,\n    &quot;componentType&quot;: &quot;Transform&quot;,\n    &quot;propertyName&quot;: &quot;translation&quot;,\n    &quot;values&quot;: [0, 1, 0]\n  }]\n}</code></pre></div>\n<p>Use <code>componentOps</code> for inspector-visible data. Reserve <code>usdPropertyOps</code> for native OpenUSD properties that do not belong to a Warp component schema.</p>\n<h2 id=\"fail-closed-generated-data\">Fail-closed generated data<a class=\"heading-anchor\" href=\"#fail-closed-generated-data\" aria-label=\"Link to Fail-closed generated data\">#</a></h2>\n<p><code>WarpGameSpec</code> is a closed-world authoring contract, not a bag of suggestive JSON fields. The published schema declares the supported entity fields, component names, component-specific property names and value shapes, asset operations, and action-specific playtest fields. The server repeats the semantic checks at the mutation boundary and rejects the entire specification before producing any authoring operations when it finds:</p>\n<ul>\n<li>an unknown component or property name;</li>\n<li>the wrong scalar type, scalar/vector channel, or vector arity;</li>\n<li>an unsupported enum value or an out-of-range value;</li>\n<li>duplicate entity ids, prim paths, components, or properties;</li>\n<li>a malformed OpenUSD prim name or unresolved parent/script target;</li>\n<li>an unknown playtest action, an action-specific extra field, or an assertion without a real predicate.</li>\n</ul>\n<p>Diagnostics include the exact JSON path, such as <code>spec.scene.entities[2].components[0].properties[1].values</code>. Schema validation is useful for editor completion and early feedback, but it is never trusted as the mutation guard: <code>warp_change</code>, project generation, and <code>warp_verify</code> playtests all pass through the server-side semantic validator. Invalid input cannot partially create layers, prims, assets, or scripts.</p>\n<p>Use <code>warp_inspect.schemas</code> for exact component contracts and the inline <code>warp_verify.scenario</code> schema for exact playtest actions. This keeps an agent from relying on plausible names that the running engine does not implement.</p>\n<h2 id=\"script-authoring\">Script authoring<a class=\"heading-anchor\" href=\"#script-authoring\" aria-label=\"Link to Script authoring\">#</a></h2>\n<p>Create and attach Lua modules through the same structured change path. Lua should make coarse gameplay decisions; native engine systems own dense queries, continuous motion, physics, animation, audio, rendering, streaming, timers, and lifetime.</p>\n<p>For remote MCP work, prefer one complete lifecycle operation: include a <code>warp_change.operations.scriptWrites</code> entry with <code>module</code>, <code>contents</code>, and <code>targetEntities</code>. Warp validates the source, registers the script asset, and attaches <code>Script.module</code> to the targets before reporting success. An attach-only <code>scriptWrites</code> entry omits source and supplies <code>module</code> plus <code>targetEntities</code>; the server verifies and re-registers the existing asset without rewriting it. <code>furry.behavior</code> automatically declares the internal <code>Script</code> read that its module-selection wrapper needs, so agents only declare the components their own behavior reads or writes.</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">cat &lt;&lt;&#x27;JSON&#x27; | ./tools/furry_ai write-script\n{\n  &quot;scriptWrites&quot;: [\n    {\n      &quot;module&quot;: &quot;scripts.OpenDoor&quot;,\n      &quot;targetEntities&quot;: [&quot;/World/Door&quot;],\n      &quot;contents&quot;: &quot;return furry.behavior(\\&quot;scripts.OpenDoor\\&quot;, { writes = { \\&quot;RuntimeTransform\\&quot; } }, function(world, entity, dt, script) world:tween_transform(entity, { position = { 0, 3, 0 }, duration = 0.4, easing = \\&quot;smooth\\&quot; }) end)\\n&quot;\n    }\n  ]\n}\nJSON</code></pre></div>\n<p>Read <a href=\"/docs/lua-scripting\">Lua gameplay scripting</a> for the verified runtime API.</p>\n<h2 id=\"runtime-introspection\">Runtime introspection<a class=\"heading-anchor\" href=\"#runtime-introspection\" aria-label=\"Link to Runtime introspection\">#</a></h2>\n<p>Authoring context explains what the scene should be. Runtime inspection explains what the game is doing now.</p>\n<p>Agents and test runners can:</p>\n<ul>\n<li>discover connected runtimes for the exact destination;</li>\n<li>query typed ECS components by path, component, group, or stable entity ID;</li>\n<li>inspect Lua-owned state, timers, random state, physics transforms, and velocities;</li>\n<li>pause and step deterministically;</li>\n<li>enable a bounded every-tick recording;</li>\n<li>capture or restore a State Capsule;</li>\n<li>diff two captured states.</li>\n<li>capture the actual rendered web Sokol player as an MCP image;</li>\n<li>start, mark, and stop a bounded connected-player performance trace with <code>warp_profile_runtime</code>, receiving compact frame percentiles and hotspots plus a Perfetto-compatible artifact;</li>\n<li>inspect authored and runtime state together with <code>warp_diagnose</code>;</li>\n<li>capture revision-correlated authored, runtime, and visual evidence with <code>warp_observe</code>;</li>\n<li>emit typed observations at arbitrary points in a headless playtest.</li>\n</ul>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/furry_ai runtime-list\n./tools/furry_ai runtime-query --runtime PLAYER_ID --input query.json\n./tools/furry_ai runtime-pause --runtime PLAYER_ID\n./tools/furry_ai runtime-step --runtime PLAYER_ID --input &#x27;{&quot;steps&quot;:1}&#x27;\n./tools/furry_ai runtime-capture --runtime PLAYER_ID --output failure.warpcap\n./tools/furry_ai runtime-restore --runtime PLAYER_ID --capsule failure.warpcap</code></pre></div>\n<p>State Capsules are versioned and project-scoped. Normal capsules reference immutable cooked assets by content hash rather than copying large payloads. Restore is transactional and fails closed on incompatible or malformed state.</p>\n<p>For the normal remote-agent loop, prefer <code>warp_observe</code> over manually joining several unrelated responses. One call returns focused composed prims and validation at an authored revision, matching live runtime state and tick when a player is connected, and an optional real Sokol frame. The <code>consistency</code> block states whether every requested source reached <code>targetRevision</code>; a concurrent edit that advances the scope high-water mark makes the evidence explicitly not ready instead of producing a misleading mixed snapshot.</p>\n<p>Every observation has a content-stable <code>evidenceId</code>. Pass it back as <code>compareToEvidenceId</code> after a mutation to receive bounded semantic changes for authored prim/component/property fields and runtime entity/component fields, plus revision and tick deltas. Evidence is retained in memory for one hour, bounded to 128 records, and comparison is allowed only for the same authenticated profile, project, workspace, and scene. The focused stage and runtime filters must also match; otherwise Warp refuses the semantic comparison instead of misreporting omitted records as deletions.</p>\n<h2 id=\"deterministic-scenarios\">Deterministic scenarios<a class=\"heading-anchor\" href=\"#deterministic-scenarios\" aria-label=\"Link to Deterministic scenarios\">#</a></h2>\n<p>Headless scenarios run the native ECS, Lua, and physics systems without rendering. They can drive ticks and input, pause or step, assert entity/component state, and emit JSON plus JUnit reports.</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">build/full-demo/furry_runtime_headless \\\n  --snapshot .derived/cooked/scene.bin \\\n  --scenario examples/runtime/basic_state.warptest.json \\\n  --report .derived/test-results/basic-state.json \\\n  --junit .derived/test-results/basic-state.xml \\\n  --failure-capsule .derived/test-results/basic-state.warpcap</code></pre></div>\n<p>A failed scenario can save the exact restorable runtime state. That turns “it failed once on an iPad” into a deterministic artifact an agent can inspect locally.</p>\n<p>An <code>inspect</code> scenario action is deliberately non-assertive: it returns the matching hierarchy, authored components, runtime components, Lua state, and physics values at that tick. This supports discovery and self-correction when an agent does not yet know the right expected value. After inspecting, the agent applies a minimal authored correction and reruns <code>warp_verify</code> until it reports healthy state.</p>\n<p><code>warp_inspect.diagnose</code> is compact by default: it returns validation, authored-stage counts, runtime health, issues, and next actions without copying the complete composed stage into the model context. Pass a focused path/prefix/search (or explicitly request stage details) only when the diagnosis needs prim-level evidence, and otherwise use <code>warp_inspect.stage</code> for the implicated objects.</p>\n<h2 id=\"deep-reproducibility-links\">Deep reproducibility links<a class=\"heading-anchor\" href=\"#deep-reproducibility-links\" aria-label=\"Link to Deep reproducibility links\">#</a></h2>\n<p>A strong issue or review link should carry as much stable context as necessary:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>text</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-text\">/p/PROJECT/state/CAPSULE?s=Scenes%2FMain.usda&amp;w=playtest&amp;o=%2FWorld%2FPlayer</code></pre></div>\n<p>Opening the link loads the project destination, waits for a matching runtime, restores the capsule, selects the object, and exposes Runtime Debugger. Read <a href=\"/docs/deep-links\">Deep links</a> and <a href=\"/docs/runtime-introspection\">Runtime introspection</a> for the wire and compatibility contracts.</p>\n<h2 id=\"agent-readable-documentation\">Agent-readable documentation<a class=\"heading-anchor\" href=\"#agent-readable-documentation\" aria-label=\"Link to Agent-readable documentation\">#</a></h2>\n<p>The public documentation is generated from versioned Markdown and a strict catalog. Automation can consume:</p>\n<ul>\n<li><code>/docs/manifest.json</code> for structured article metadata, rendered content, headings, source hashes, and API verification;</li>\n<li><code>/docs/llms.txt</code> for a compact documentation map;</li>\n<li><code>/docs/llms-full.txt</code> for the complete source corpus.</li>\n</ul>\n<p>The generator compares the documented Lua surface with the World methods exported by <code>LuaScriptSystem.cpp</code>. A missing method makes documentation validation fail instead of silently publishing a stale reference.</p>",
      "icon": "spark",
      "order": 5,
      "searchText": "AI and automation Warp treats agents as collaborators operating on live, structured project state. An agent should not need a screenshot-only description, a private filesystem path, or a manually reconstructed scene. It can receive the exact project, workspace, scene, layer, selection, revision, and—when needed—runtime tick. The safe authoring model The hosted Warp project is the source of truth. Remote agents connect through Warp's authenticated MCP surface. The MCP catalog is task-oriented rather than compatibility-oriented. The normal path is warp workspace once, one batched warp inspect , one checked warp change , and warp verify . Structural mutation schemas are inline, while exact component/property contracts are requested only when needed through warp inspect.schemas and remain fail-closed at the mutation boundary. A fresh agent does not spend turns discovering internal command names or guessing nested arguments. warp inspect can return focused Lore workspace files, stage, asset, script, schema, Lua API, runtime, diagnosis, and diff evidence together; warp change covers hierarchy, references, payloads, components, USD properties, layers, assets, and scripts and validates the result automatically. The expected flow is efficient and iterative: 1. Select exact scope and receive readiness with warp workspace . 2. Request every relevant entity, component, script, asset, schema, and runtime section in one warp inspect call. 3. Make one coherent idempotent warp change . Warp automatically creates a separate agent workspace on the first write and returns its link. Treat the returned workspace id as the change's durable destination for all later edits. An explicit warp workspace selection always opens exactly the named workspace; use changeMode: \"continue\" only when deliberately resuming an existing child workspace. 4. Run one warp verify with focused readback and a real input-driven playtest. Input actions advance their requested frame count while held. 5. Use warp observe for rendered evidence, then submit the final change for human review when requested. Agents cannot approve or merge their own changes. This keeps automation fast without making it invisible or irreversible. Lore as a remote filesystem Agents do not need a second project database or a complete project dump in their prompt. warp inspect.files exposes the selected Lore workspace like a bounded remote filesystem: - list discovers project-relative files and directories, optionally with a glob; - stat reads metadata for exact files; - read reads exact text ranges using offset and maxBytes ; - search finds bounded text matches across the workspace. Every response identifies the exact Lore workspace and head. Server paths, Lore internals, derived data, cooked data, and content-addressed payload bytes are not exposed. For example: {\"files\":{\"action\":\"search\",\"path\":\"Scripts\",\"glob\":\" / .lua\",\"query\":\"on collision\",\"limit\":20}} This is the remote equivalent of using find , rg , and ranged file reads in a local checkout. It reads the authored Lore state directly and therefore has no separate index to become stale. Composed scene questions remain distinct: warp inspect.stage asks OpenUSD to compose the selected scene and returns a focused semantic projection of that live result. Game generation An agent can turn a project brief into a new hosted Warp project through the same authenticated MCP connection. This is generation of editable project data, not export of an opaque game binary. The default workflow targets a playable vertical slice, not the smallest scene that boots: 1. Create the project with warp create project , then select its returned scope with warp workspace . 2. Use one warp inspect request for the relevant stage, exact component schemas, assets, scripts, Lua topics, and runtime state. 3. Build the first coherent slice with typed warp change operations. The same call can add hierarchy, components, OpenUSD references or payloads, layers, assets, and readable Lua. 4. Supply a deterministic typed scenario to warp change , or run it with the final warp verify . Fix failed evidence rather than assuming that a plausible scene is playable. 5. Use warp observe for correlated authored, runtime, and rendered evidence. Refine until the result works visually and behaviorally. 6. Inspect the returned Lore diff and submit the isolated workspace for human review. warp apply game spec compiles named entities into ordinary OpenUSD prims, inspector-visible Warp components, project assets, and readable Lua modules. The result appears in the hierarchy and Assets pane and can be selected, moved, duplicated, reparented, restyled, rescripted, layered, reviewed, or edited by another person or agent. WarpGameSpec is an input recipe and audit artifact; it never replaces the authored OpenUSD scene as the source of truth. The repository's internal bulk-generation contract lives at docs/schemas/warp-game-spec-v1.schema.json . Its specific game fixture exists only to regression-test the generic compiler and is never returned as agent guidance or used as an authoring template. Remote agents do not need that compatibility surface: they use the same typed warp change operations as ordinary edits and the exact scenario schema exposed by warp verify . Prompt-to-game quality harness The quality preflight is expressed through focused warp inspect evidence followed by warp verify . It has no pre-authored mechanics, project-specific entity assumptions, or genre templates. A playable result requires: - readable project-owned behavior where the request needs behavior; - an explicit scenario that uses the behavior's real input, authored UI event, lifecycle boundary, or time advance and observes the result; - at least two assertions covering relevant transforms, state, components, camera state, or another requested outcome; - an intentional presentation appropriate to the project rather than a mandatory rendering, input, physics, or interface setup; - native validation and headless runtime success before the result can be called playable. Use draft only for intentionally minimal experiments. The polished target additionally requires intentional presentation, typed runtime inspection evidence, reusable architecture where meaningful repetition exists, and final rendered evidence. A playtest without a scenario is marked structuralOnly ; it proves that cooking succeeds and entities exist, not that the requested behavior works. For any repeated authored hierarchy, use warp change.operations.repeatedSubtrees . Each entry contains one arbitrary typed compact-entity subtree and any number of named transform placements. Warp stores the definition once in a private USD namespace and authors internal OpenUSD references at the requested scene paths; the private definition never appears as a runtime entity or hierarchy row. instanceable is optional and defaults to false so descendants remain editable. This contract is general rather than tied to any particular asset or game concept. Use runtime world:spawn only for copies whose lifecycle begins during play. For repeated art already stored in a project USD asset, keep runtime roots, physics, and scripts local while placing shared visuals with project-relative referenceURI values and an optional exact referencePrimPath . When iterating on shared art, begin the upload with replaceExisting: true ; Warp preserves the stable sanitized filename instead of silently creating a numbered replacement, so all references continue to point at one source of truth. The visual player pass still matters. Headless tests cannot identify camera jitter, z-fighting, overlapping ground surfaces, illegible type, poor composition, or weak feedback. Use warp observe with includeFrame: true . It waits for correlated authored/runtime evidence and returns the actual Sokol player canvas as an MCP image/png content block when a player is connected. Inspect the image and its revision, tick, dimensions, and asset-readiness metadata; correct visible problems and observe again. A successful generation remains ready-for-visual-review until this pass is healthy, after which it can be submitted with warp submit for review . A single frame proves rendered composition, not motion over time. Use deterministic playtests and typed runtime inspection for behavior, and take frames before and after important states when motion or feedback must be checked. An asset wait timeout is reported explicitly in capture metadata instead of being mistaken for a fully loaded frame. Camera smoothing should be deterministic and frame-rate independent, and adjacent surfaces should share deliberate topology rather than overlap coplanar meshes. MCP workflow self-test tools/self test warp mcp.py drives a running Warp server through the whole ordinary agent workflow over the real /mcp HTTP endpoint: mint an agent session, open an explicit scope, switch scenes, read the composed asset catalog, upload a sound, create a scene inside the isolated agent workspace, apply prim/component operations including an asset reference, read the stage back, write and attach a Lua script, validate, run a headless playtest, and read the change diff. Every mutation stays inside a dedicated scratch project ( McpSelfTest by default) and an isolated Lore agent workspace, so it is safe to run against a production server. Against a local server without auth: tools/self test warp mcp.py --server http://127.0.0.1:8765 Against the hosted server (guest or user credentials mint the agent session): tools/self test warp mcp.py --server https://warp.billrey.net \\ --login-email \"$WARP AUTH GUEST EMAIL\" --login-password \"$WARP AUTH GUEST PASSWORD\" Run it after every server deploy. It exits non-zero when any ordinary operation regresses — including the historical failure classes it encodes as explicit regression checks: a scene created in an agent workspace must remain visible and editable through warp change , and project assets inherited through the Lore parent chain must stay visible through warp inspect.assets from every scene. The offline contract checks in tools/check warp agent contracts.py (which also run the --self-test-mcp- suites built into the server binary) complement it and need no running server. Every project-scoped MCP response includes resultLocation with the effective projectId , workspaceId , sceneId , and a directly usable editorUrl . This is the authoritative destination after Warp rewrites a base-workspace request into an isolated Lore change workspace. Agents must name that workspace and include the link when reporting authored work; they must never infer the destination from an earlier prompt or silently describe a different workspace. Open in Codex and Claude Code Choose Open in Codex or Open in Claude Code from either editor. The action installs or updates the official Warp plugin, completes OAuth, verifies the trusted connection, then opens the selected agent surface with project context available through normal agent tools. The connection is configuration, not prompt text. You should not have to paste server tokens, project paths, or setup instructions into every task. Warp has one endpoint: https://warp.billrey.net/mcp . Claude Code and Codex install it only through the official Warp plugin. Claude, Claude Desktop, and Cowork use the native Warp Connector backed by the same endpoint. Warp never advertises or silently creates a direct CLI registration. The installation surfaces are deliberately distinct: /connect/claude-code installs the Claude Code plugin, /connect/codex installs the Codex plugin, and /connect/claude opens the native Connector path. These are human setup pages, never MCP server addresses. An already-running agent task has a fixed tool list and cannot discover an MCP server added halfway through that task. Open in Codex and Open in Claude Code avoid making the user recover from that client limitation: Warp installs and authenticates out of process, then opens a fresh task with the tools already loaded. When setup is run manually from Terminal or from inside an existing agent conversation, open one new task after the command succeeds. No app restart, configuration editing, or repeated /mcp retry is required. Never hand-edit .mcp.json or add the endpoint with an MCP CLI command. Plugin setup preserves unrelated MCP entries and only migrates a stale entry after it has verified that the entry belongs to Warp. Codex setup also stores default tools approval mode = \"approve\" on the permanent warp server entry. This is required for codex exec and other non-interactive sessions: otherwise Codex cannot surface an MCP write approval and reports the automatic rejection as if the user cancelled it. The trust is narrowly attached to the immutable Warp connection URL. Warp still creates all agent mutations in a separate Lore child workspace, retains revision history, and does not expose an agent tool that can approve or merge its own review. This allows ordinary authoring without weakening the shell or filesystem sandbox and without using --dangerously-bypass-approvals-and-sandbox . Claude Code shows its normal first-use tool approval in interactive sessions. For a non-interactive claude -p acceptance run, scope the allow rule to the connected Warp plugin with --allowedTools 'mcp plugin warp warp ' . That wildcard keeps local shell and file tools unavailable while allowing the complete Warp toolset to evolve without a brittle hand-maintained list of MCP method names. After authentication, call warp workspace . With no ids it returns the accessible project catalog. With exact project, workspace, and scene ids it selects the scope and returns readiness in the same call. Its setup block reports whether project binding, the composed OpenUSD stage, authoring backend, Lua contract, and Lore review isolation are ready. Scenes are first-class OpenUSD documents in a Lore workspace. Each workspace entry in the warp workspace catalog includes every composed scene visible there, including scenes created only in a Lore child workspace. Every scene record returns exact sceneId , referenceURI , payloadURI , and luaSceneId values. Create or duplicate one with idempotent warp create scene , then select the returned exact scope with warp workspace . warp create scene.contents can instead create a complete USDA document; Warp validates it before advancing the Lore workspace head. Use a returned referenceURI or payloadURI in a warp change prim operation to compose one workspace-visible document into another. These are stable project ids, never server filesystem paths. In compact agent sessions, request scenes: {} through warp inspect to get the complete scene catalog for the selected workspace without depending on the bounded project overview. The compatibility warp list scenes tool returns the same workspace-native scene identities. Ordinary hierarchy and composition work uses the fail-closed primOps and layerOps variants on warp change.operations . primOps supports add, delete, duplicate, reparent, references, and payloads; reference and payload targets must be project-relative ids returned by Warp. layerOps supports add, remove, reorder, mute, active target, clear, merge down, and complete USDA import. Import is the native OpenUSD escape hatch for variants, inherits, specializes, relationships, and other declarative composition while still being parsed and project-scope validated by OpenUSD. Project assets are discoverable through warp inspect.assets . This queries the composed Warp project asset catalog for the exact project/workspace. The catalog follows Lore inheritance and is available from every scene; it is not the same thing as the raw authored-file access in warp inspect.files . The result covers meshes, textures, sounds, animations, scripts, materials, and fonts and returns stable asset ids, /World/FurryAssets/... paths, short names, and exact component/Lua reference forms. A scene component such as AudioSource or MeshAsset is a usage of an asset, not proof that the asset exists. Agents must query this catalog before guessing a reference or uploading, and must not re-upload an asset merely because no scene object currently uses it. The finish phase of warp upload asset returns the stable Warp asset identity immediately while preview generation and cooking continue in the background. The asset is immediately discoverable through warp inspect.assets by that id or path. Asset payloads remain content-addressed and on-demand; neither catalog inspection nor scene authoring reads raw server files into the agent context. The same resumable upload path accepts a complete ZIP project tree for rich OpenUSD imports. Set projectTreeRoot to a portable destination under Assets/ , entryUsd to the archive-relative composition entry point, and optionally entryPrimPath , parentPath , and primName . Warp validates every archive path, rejects traversal and symlink entries, installs the tree atomically in the isolated Lore workspace, and retains all authored sublayers, references, payloads, materials, textures, and relative paths. It returns the portable entry sourceURI ; when parentPath is supplied it also authors the open composition reference. Ordinary single assets and composition trees use this one upload mechanism rather than separate overlapping import systems. warp inspect.stage returns compact composed prim summaries by default. They retain paths, component values, and short scalar attributes while omitting mesh arrays and verbose provenance that would crowd out the actual authoring task. An exact path returns that prim alone; request includeDescendants: true only when the whole subtree is relevant. Use includeDetails only for focused follow-up inspection. Broad /World queries are deliberately capped and return a query hint so the agent continues with focused subsets instead of spilling a scene dump into a local file or exhausting its model context. Before authoring an unfamiliar component, include it in warp inspect.schemas . It reads the same closed-world component rule table used at Warp's mutation boundary and returns exact property names, scalar/vector channels, vector arities, ranges, and enum values. When those components already occur in the selected stage, it also includes the editor metadata and a bounded set of example paths. This is a discovery surface over the enforced contract, not a second permissive schema. Codex app, Codex CLI, the Codex IDE extension, and supported ChatGPT desktop workflows can use the same remote MCP configuration. Hosted ChatGPT workspaces use the Warp plugin backed by that same MCP server rather than reading local desktop configuration. Local editor automation When a local editor is running, it writes the active authoring session to .derived/furry ai session.json . The repository wrapper discovers the active scene, address, author, workspace, and server: ./tools/furry ai context ./tools/furry ai validate Apply ordinary entity and component edits as structured JSON: cat <<'JSON' ./tools/furry ai apply { \"componentOps\": [ { \"op\": \"set\", \"entity\": \"/World/Player\", \"component\": \"Transform\", \"data\": { \"translation\": [0, 2, 0] } } ], \"notes\": \"Raise the player spawn point\" } JSON ./tools/furry ai validate Structured authoring preserves destination scope, live updates, revision checks, undo/history, and actionable diagnostics. Directly editing USD text bypasses those guarantees and is not the normal agent path. The hosted MCP surface uses the same component operation contract. For example, this sets a child attachment one metre above its parent in local space: { \"componentOps\": [{ \"kind\": \"set\", \"entityPath\": \"/World/Parent/Child\", \"componentType\": \"Transform\", \"propertyName\": \"translation\", \"values\": [0, 1, 0] }] } Use componentOps for inspector-visible data. Reserve usdPropertyOps for native OpenUSD properties that do not belong to a Warp component schema. Fail-closed generated data WarpGameSpec is a closed-world authoring contract, not a bag of suggestive JSON fields. The published schema declares the supported entity fields, component names, component-specific property names and value shapes, asset operations, and action-specific playtest fields. The server repeats the semantic checks at the mutation boundary and rejects the entire specification before producing any authoring operations when it finds: - an unknown component or property name; - the wrong scalar type, scalar/vector channel, or vector arity; - an unsupported enum value or an out-of-range value; - duplicate entity ids, prim paths, components, or properties; - a malformed OpenUSD prim name or unresolved parent/script target; - an unknown playtest action, an action-specific extra field, or an assertion without a real predicate. Diagnostics include the exact JSON path, such as spec.scene.entities[2].components[0].properties[1].values . Schema validation is useful for editor completion and early feedback, but it is never trusted as the mutation guard: warp change , project generation, and warp verify playtests all pass through the server-side semantic validator. Invalid input cannot partially create layers, prims, assets, or scripts. Use warp inspect.schemas for exact component contracts and the inline warp verify.scenario schema for exact playtest actions. This keeps an agent from relying on plausible names that the running engine does not implement. Script authoring Create and attach Lua modules through the same structured change path. Lua should make coarse gameplay decisions; native engine systems own dense queries, continuous motion, physics, animation, audio, rendering, streaming, timers, and lifetime. For remote MCP work, prefer one complete lifecycle operation: include a warp change.operations.scriptWrites entry with module , contents , and targetEntities . Warp validates the source, registers the script asset, and attaches Script.module to the targets before reporting success. An attach-only scriptWrites entry omits source and supplies module plus targetEntities ; the server verifies and re-registers the existing asset without rewriting it. furry.behavior automatically declares the internal Script read that its module-selection wrapper needs, so agents only declare the components their own behavior reads or writes. cat <<'JSON' ./tools/furry ai write-script { \"scriptWrites\": [ { \"module\": \"scripts.OpenDoor\", \"targetEntities\": [\"/World/Door\"], \"contents\": \"return furry.behavior(\\\"scripts.OpenDoor\\\", { writes = { \\\"RuntimeTransform\\\" } }, function(world, entity, dt, script) world:tween transform(entity, { position = { 0, 3, 0 }, duration = 0.4, easing = \\\"smooth\\\" }) end)\\n\" } ] } JSON Read Lua gameplay scripting for the verified runtime API. Runtime introspection Authoring context explains what the scene should be. Runtime inspection explains what the game is doing now. Agents and test runners can: - discover connected runtimes for the exact destination; - query typed ECS components by path, component, group, or stable entity ID; - inspect Lua-owned state, timers, random state, physics transforms, and velocities; - pause and step deterministically; - enable a bounded every-tick recording; - capture or restore a State Capsule; - diff two captured states. - capture the actual rendered web Sokol player as an MCP image; - start, mark, and stop a bounded connected-player performance trace with warp profile runtime , receiving compact frame percentiles and hotspots plus a Perfetto-compatible artifact; - inspect authored and runtime state together with warp diagnose ; - capture revision-correlated authored, runtime, and visual evidence with warp observe ; - emit typed observations at arbitrary points in a headless playtest. ./tools/furry ai runtime-list ./tools/furry ai runtime-query --runtime PLAYER ID --input query.json ./tools/furry ai runtime-pause --runtime PLAYER ID ./tools/furry ai runtime-step --runtime PLAYER ID --input '{\"steps\":1}' ./tools/furry ai runtime-capture --runtime PLAYER ID --output failure.warpcap ./tools/furry ai runtime-restore --runtime PLAYER ID --capsule failure.warpcap State Capsules are versioned and project-scoped. Normal capsules reference immutable cooked assets by content hash rather than copying large payloads. Restore is transactional and fails closed on incompatible or malformed state. For the normal remote-agent loop, prefer warp observe over manually joining several unrelated responses. One call returns focused composed prims and validation at an authored revision, matching live runtime state and tick when a player is connected, and an optional real Sokol frame. The consistency block states whether every requested source reached targetRevision ; a concurrent edit that advances the scope high-water mark makes the evidence explicitly not ready instead of producing a misleading mixed snapshot. Every observation has a content-stable evidenceId . Pass it back as compareToEvidenceId after a mutation to receive bounded semantic changes for authored prim/component/property fields and runtime entity/component fields, plus revision and tick deltas. Evidence is retained in memory for one hour, bounded to 128 records, and comparison is allowed only for the same authenticated profile, project, workspace, and scene. The focused stage and runtime filters must also match; otherwise Warp refuses the semantic comparison instead of misreporting omitted records as deletions. Deterministic scenarios Headless scenarios run the native ECS, Lua, and physics systems without rendering. They can drive ticks and input, pause or step, assert entity/component state, and emit JSON plus JUnit reports. build/full-demo/furry runtime headless \\ --snapshot .derived/cooked/scene.bin \\ --scenario examples/runtime/basic state.warptest.json \\ --report .derived/test-results/basic-state.json \\ --junit .derived/test-results/basic-state.xml \\ --failure-capsule .derived/test-results/basic-state.warpcap A failed scenario can save the exact restorable runtime state. That turns “it failed once on an iPad” into a deterministic artifact an agent can inspect locally. An inspect scenario action is deliberately non-assertive: it returns the matching hierarchy, authored components, runtime components, Lua state, and physics values at that tick. This supports discovery and self-correction when an agent does not yet know the right expected value. After inspecting, the agent applies a minimal authored correction and reruns warp verify until it reports healthy state. warp inspect.diagnose is compact by default: it returns validation, authored-stage counts, runtime health, issues, and next actions without copying the complete composed stage into the model context. Pass a focused path/prefix/search (or explicitly request stage details) only when the diagnosis needs prim-level evidence, and otherwise use warp inspect.stage for the implicated objects. Deep reproducibility links A strong issue or review link should carry as much stable context as necessary: /p/PROJECT/state/CAPSULE?s=Scenes%2FMain.usda&w=playtest&o=%2FWorld%2FPlayer Opening the link loads the project destination, waits for a matching runtime, restores the capsule, selects the object, and exposes Runtime Debugger. Read Deep links and Runtime introspection for the wire and compatibility contracts. Agent-readable documentation The public documentation is generated from versioned Markdown and a strict catalog. Automation can consume: - /docs/manifest.json for structured article metadata, rendered content, headings, source hashes, and API verification; - /docs/llms.txt for a compact documentation map; - /docs/llms-full.txt for the complete source corpus. The generator compares the documented Lua surface with the World methods exported by LuaScriptSystem.cpp . A missing method makes documentation validation fail instead of silently publishing a stale reference.",
      "slug": "ai-and-automation",
      "source": "docs/AI_AND_AUTOMATION.md",
      "sourceHash": "23fea3689dd395a187ea73fd6544be5787b12d7dcdd8110d89645d04a4ad68af",
      "summary": "Give agents exact project context, isolate their changes, validate the result, and reproduce runtime state.",
      "tags": [
        "ai",
        "codex",
        "claude",
        "mcp",
        "automation",
        "validation"
      ],
      "title": "AI and automation"
    },
    {
      "audience": "Everyone",
      "group": "collaborate",
      "headings": [],
      "html": "<p>Warp links identify projects with a stable opaque ID stored in the project package's <code>project.json</code>. The ID moves with the package, so routing does not depend on the server's filesystem layout.</p>\n<p>Canonical links use:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>text</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-text\">https://warp.example.com/p/7Kq2mP9vDx4NcR8B\nhttps://warp.example.com/p/7Kq2mP9vDx4NcR8B?s=Scenes/Level2.usda&amp;w=playtest&amp;o=/World/Player\nhttps://warp.example.com/p/7Kq2mP9vDx4NcR8B/r/review-id\nwarp-player://connect?p=7Kq2mP9vDx4NcR8B&amp;server=https%3A%2F%2Fwarp.example.com&amp;w=playtest</code></pre></div>\n<p>The compact keys are:</p>\n<ul>\n<li><code>p</code>: project ID</li>\n<li><code>s</code>: project-relative scene</li>\n<li><code>w</code>: workspace</li>\n<li><code>o</code>: selected object path</li>\n</ul>\n<p>The project manifest's main scene and the <code>main</code> workspace are implicit, so canonical links omit <code>s</code> and <code>w</code> for that default scope. A link to Main is therefore normally just <code>/p/&lt;project-id&gt;</code>, with <code>o</code> added only when it needs to select a specific object.</p>\n<p>The origin names the deployment, while everything after it is portable. Moving a project package to another server preserves its ID and therefore the route; keeping the public hostname or redirecting the old hostname preserves existing absolute web URLs.</p>\n<p>Existing links using <code>projectRoot</code>, absolute <code>scene</code>, <code>workspace</code>, <code>ws</code>, <code>path</code>, or <code>select</code> remain readable. Once loaded, the web editor replaces them in browser history with the canonical compact route. Native players resolve portable app links through the server's authenticated <code>/api/deep-link</code> endpoint, and only send credentials scoped to that server.</p>\n<p><code>warp-player://</code> targets the installed player explicitly. Existing <code>warp://</code> and <code>furry://</code> links remain readable for compatibility.</p>",
      "icon": "link",
      "order": 6,
      "searchText": "Deep links Warp links identify projects with a stable opaque ID stored in the project package's project.json . The ID moves with the package, so routing does not depend on the server's filesystem layout. Canonical links use: https://warp.example.com/p/7Kq2mP9vDx4NcR8B https://warp.example.com/p/7Kq2mP9vDx4NcR8B?s=Scenes/Level2.usda&w=playtest&o=/World/Player https://warp.example.com/p/7Kq2mP9vDx4NcR8B/r/review-id warp-player://connect?p=7Kq2mP9vDx4NcR8B&server=https%3A%2F%2Fwarp.example.com&w=playtest The compact keys are: - p : project ID - s : project-relative scene - w : workspace - o : selected object path The project manifest's main scene and the main workspace are implicit, so canonical links omit s and w for that default scope. A link to Main is therefore normally just /p/<project-id , with o added only when it needs to select a specific object. The origin names the deployment, while everything after it is portable. Moving a project package to another server preserves its ID and therefore the route; keeping the public hostname or redirecting the old hostname preserves existing absolute web URLs. Existing links using projectRoot , absolute scene , workspace , ws , path , or select remain readable. Once loaded, the web editor replaces them in browser history with the canonical compact route. Native players resolve portable app links through the server's authenticated /api/deep-link endpoint, and only send credentials scoped to that server. warp-player:// targets the installed player explicitly. Existing warp:// and furry:// links remain readable for compatibility.",
      "slug": "deep-links",
      "source": "docs/DEEP_LINKS.md",
      "sourceHash": "67fa28f177e1b99caa57bb9f23f032173ea6de83289b8fad9c157d0342f31015",
      "summary": "Create stable links to projects, workspaces, scenes, selected objects, reviews, players, and captured runtime states.",
      "tags": [
        "links",
        "sharing",
        "project",
        "scene",
        "selection"
      ],
      "title": "Deep links"
    },
    {
      "audience": "Developers, QA, and agents",
      "featured": true,
      "group": "collaborate",
      "headings": [
        {
          "id": "observation-modes",
          "level": 2,
          "title": "Observation modes"
        },
        {
          "id": "editor-controls",
          "level": 2,
          "title": "Editor controls"
        },
        {
          "id": "player-qa-links",
          "level": 2,
          "title": "Player QA links"
        },
        {
          "id": "agent-cli",
          "level": 2,
          "title": "Agent CLI"
        },
        {
          "id": "headless-scenarios",
          "level": 2,
          "title": "Headless scenarios"
        },
        {
          "id": "remote-agent-inspection",
          "level": 2,
          "title": "Remote agent inspection"
        },
        {
          "id": "performance-profiling",
          "level": 2,
          "title": "Performance profiling"
        },
        {
          "id": "rendered-frame-evidence",
          "level": 2,
          "title": "Rendered frame evidence"
        },
        {
          "id": "compatibility-and-failure-behavior",
          "level": 2,
          "title": "Compatibility and failure behavior"
        },
        {
          "id": "performance-model",
          "level": 2,
          "title": "Performance model"
        }
      ],
      "html": "<p>Warp exposes live ECS state as a first-class, versioned interface. Agents and tests can inspect a running player, pause it at an exact tick, capture a project-scoped State Capsule, restore that capsule, and run deterministic headless scenarios. The feature is off by default; ordinary gameplay pays only a mode check per tick.</p>\n<h2 id=\"observation-modes\">Observation modes<a class=\"heading-anchor\" href=\"#observation-modes\" aria-label=\"Link to Observation modes\">#</a></h2>\n<ul>\n<li><code>off</code> is the production default. No history is captured.</li>\n<li><code>observe</code> enables live queries and on-demand capture without retaining history.</li>\n<li><code>record</code> retains an exact, bounded, every-tick history. Asset payloads are content-addressed and retained once rather than copied into every frame. The default memory budget is 128 MiB.</li>\n</ul>\n<p>State frames contain the authored entity/component snapshot plus native runtime components, Lua-owned world/entity values, Lua timers and random states, current input, physics body transforms and velocities, simulation clocks, pause state, world settings, and referenced cooked assets. Capsules include a format version, engine build, platform, UTC creation time, project/workspace/scene identity, revision, and tick. Normal captures contain content-addressed references to immutable cooked assets instead of recopying their bytes; restoring first loads the project and resolves matching hashes from its payload/cache layer. The C++ API can still request an embedded-asset capsule for deliberately self-contained export.</p>\n<p>Raw Lua VM memory, graphics-driver objects, sockets, audio device buffers, and other process-local handles are deliberately excluded. Their durable native state is captured and the subsystem rebuilds those handles on restore.</p>\n<h2 id=\"editor-controls\">Editor controls<a class=\"heading-anchor\" href=\"#editor-controls\" aria-label=\"Link to Editor controls\">#</a></h2>\n<p>The web editor's Runtime Debugger button discovers runtimes connected to the current project, scene, and workspace. It can inspect a path prefix, pause/resume, single-step, enable bounded recording, and capture a capsule. Capture returns a shareable link such as:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>text</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-text\">https://warp.billrey.net/p/PROJECT/state/sha256%3A...?...&amp;s=Scenes%2FMain.usda&amp;w=feature</code></pre></div>\n<p>Opening that link loads the correct scope, waits for the retained web viewport to register, restores the capsule directly into that local viewport, pauses it, and selects the inspected entity. The experimental Runtime Debugger stays closed unless the user opens it from the menu. The macOS editor exposes the same debugging operations in its Debug menu; query JSON and captured deep links are copied to the clipboard.</p>\n<p>The transport uses the player's existing outbound authenticated gRPC stream. Players do not open inbound ports. Read-only queries and capsule capture/fetch require the project viewer role. Remotely controlling another runtime requires editor access; opening a QA link restores only into the local player and does not use that mutation endpoint.</p>\n<h2 id=\"player-qa-links\">Player QA links<a class=\"heading-anchor\" href=\"#player-qa-links\" aria-label=\"Link to Player QA links\">#</a></h2>\n<p>Web, Mac, iPad/iPhone, and Windows players expose <code>Deep State Link</code> and <code>Mark Issue</code> from their <code>…</code> or File menu. Both actions capture a State Capsule only when invoked. <code>Deep State Link</code> puts the canonical URL on the clipboard. <code>Mark Issue</code> hands that same portable URL to the platform share sheet, or copies it when no share sheet is available, so it can be pasted into Jira, GitHub, Slack, or another system.</p>\n<p>Players also expose <code>Copy Project Link</code> plus one <code>Open Link</code> field. The project link identifies the current project, scene, and non-main workspace without capturing runtime state. The same Open Link field accepts those normal project/workspace URLs and Deep State Links; opening a Deep State Link additionally restores and pauses the captured runtime capsule. In a browser, the address bar provides the equivalent open-link flow.</p>\n<p>Warp deliberately does not create an issue, comment, ticket, replay window, or tracker-specific record. The content-addressed capsule and its URL are the portable repro artifact.</p>\n<p>Opening the URL resolves the project/workspace/scene, fetches the immutable capsule, restores it transactionally into the local player that opened it, pauses at the captured tick, and restores the captured runtime camera plus any inspect/selection context carried by the link. It never authors the workspace and cannot change another running player.</p>\n<h2 id=\"agent-cli\">Agent CLI<a class=\"heading-anchor\" href=\"#agent-cli\" aria-label=\"Link to Agent CLI\">#</a></h2>\n<p>Start the normal editor so <code>.derived/furry_ai_session.json</code> exists, then use the shared wrapper:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/furry_ai runtime-list\n./tools/furry_ai runtime-schema --runtime PLAYER_ID\n./tools/furry_ai runtime-query --runtime PLAYER_ID --input query.json\n./tools/furry_ai runtime-pause --runtime PLAYER_ID\n./tools/furry_ai runtime-step --runtime PLAYER_ID --input &#x27;{&quot;steps&quot;:1}&#x27;\n./tools/furry_ai runtime-record --runtime PLAYER_ID\n./tools/furry_ai runtime-at-tick --runtime PLAYER_ID --input &#x27;{&quot;tick&quot;:240}&#x27;\n./tools/furry_ai runtime-capture --runtime PLAYER_ID --output failure.warpcap\n./tools/furry_ai runtime-restore --runtime PLAYER_ID --capsule failure.warpcap\n./tools/furry_ai runtime-resume --runtime PLAYER_ID\n./tools/furry_ai runtime-off --runtime PLAYER_ID</code></pre></div>\n<p><code>runtime-query</code> accepts <code>pathPrefix</code>, string <code>entityIds</code>, <code>components</code>, <code>includeDisabled</code>, and <code>maxEntities</code>. Results contain stable entity and parent IDs plus typed, versioned runtime component values. If only one matching runtime is connected, <code>--runtime</code> may be omitted.</p>\n<p>On capture the CLI writes a local <code>.warpcap</code>, stores the same bytes in the project's content-addressed mirror, and prints the content key, URL, and canonical deep link. Restore only accepts the explicit capsule file supplied by the caller and expects the matching project content to be loaded.</p>\n<h2 id=\"headless-scenarios\">Headless scenarios<a class=\"heading-anchor\" href=\"#headless-scenarios\" aria-label=\"Link to Headless scenarios\">#</a></h2>\n<p>Run the same native ECS, Lua, and physics systems without rendering:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">build/full-demo/furry_runtime_headless \\\n  --snapshot .derived/cooked/scene.bin \\\n  --scenario examples/runtime/basic_state.warptest.json \\\n  --report .derived/test-results/basic-state.json \\\n  --junit .derived/test-results/basic-state.xml \\\n  --failure-capsule .derived/test-results/basic-state.warpcap</code></pre></div>\n<p>Actions are <code>tick</code>/<code>wait</code>, <code>input</code>, <code>pause</code>, <code>resume</code>, <code>step</code>, <code>inspect</code>, and <code>assert</code>. An <code>inspect</code> action returns a typed observation at that exact point in the scenario, filtered by path and component names. Every assertion uses <code>action: &quot;assert&quot;</code>; aliases such as <code>assertState</code> and <code>assertTransform</code> are rejected. Assertions can check entity existence, enabled state, translation, scale, required runtime components, revision, exact tick, exact typed Lua world state with <code>stateKey</code> plus <code>stateEquals</code>, or a numeric lower bound with <code>stateNumberAtLeast</code>. A failed assertion emits JSON/JUnit diagnostics and an exact restorable failure capsule.</p>\n<p>Hosted playtests use an action-discriminated, fail-closed contract. Each action accepts only the fields that its runtime implementation consumes; unknown actions and ignored-looking extra fields are rejected. Assertions must contain at least one predicate, entity predicates require a path, vectors have exact arities, and numeric limits are checked before the runtime starts. This keeps a plausible-looking scenario from being reported as meaningful evidence when the runtime would have ignored part of it.</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>json</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-json\">{\n  &quot;action&quot;: &quot;inspect&quot;,\n  &quot;label&quot;: &quot;player-after-acceleration&quot;,\n  &quot;path&quot;: &quot;/World/Player&quot;,\n  &quot;components&quot;: [&quot;RuntimeTransform&quot;, &quot;RigidBody&quot;],\n  &quot;includeDisabled&quot;: true,\n  &quot;maxEntities&quot;: 16\n}</code></pre></div>\n<p>Scenario JSON reports include an <code>observations</code> array. Each observation contains the action index, label, revision, tick, input, Lua state, matching hierarchy, authored components, derived runtime components, and selected physics bodies. This lets an agent inspect evidence before and after an interaction rather than guessing which assertion to write first.</p>\n<h2 id=\"remote-agent-inspection\">Remote agent inspection<a class=\"heading-anchor\" href=\"#remote-agent-inspection\" aria-label=\"Link to Remote agent inspection\">#</a></h2>\n<p>The hosted MCP bridge exposes the same state without SSH:</p>\n<ul>\n<li><code>warp_inspect_project</code> validates and filters the composed editable OpenUSD stage;</li>\n<li><code>warp_list_assets</code> queries the composed Lore-backed project asset catalog and returns stable, copy-safe references for every supported asset kind from every scene without exposing raw project files;</li>\n<li><code>warp_list_runtimes</code> discovers players attached to the exact project, workspace, and scene;</li>\n<li><code>warp_runtime_schema</code> describes typed inspectable runtime components;</li>\n<li><code>warp_query_runtime</code> returns live ECS, Lua, physics, input, tick, and revision state;</li>\n<li><code>warp_profile_runtime</code> controls one bounded, portable performance capture and returns a compact summary plus a content-addressed trace;</li>\n<li><code>warp_diagnose</code> combines authored validation with live runtime evidence and actionable issues;</li>\n<li><code>warp_describe_schema</code> exposes the exact fail-closed authored component/property contract;</li>\n<li><code>warp_observe</code> captures one revision-correlated authored/runtime/render evidence bundle and compares it with a prior evidence id;</li>\n<li><code>warp_run_playtest</code> accepts <code>inspect</code> actions for deterministic headless evidence when no player is connected.</li>\n<li><code>warp_capture_player_frame</code> returns the actual canvas of a connected web Sokol player as an MCP PNG image, scoped to the exact project, workspace, scene, and minimum revision.</li>\n</ul>\n<p>Agents should normally repeat <code>observe -&gt; edit -&gt; observe with compareToEvidenceId -&gt; playtest</code> until <code>consistency.ready</code> is true and the semantic delta matches the requested change. <code>warp_observe</code> records authored revision, runtime revision/tick, optional rendered-frame revision, and the ending scope high-water mark. If another edit lands during capture, the evidence remains inspectable but is marked not ready rather than silently joining mismatched states. Focused query and diagnose tools remain available for follow-up detail. Inspection is read-only and never publishes editor selection or scene mutations.</p>\n<h2 id=\"performance-profiling\">Performance profiling<a class=\"heading-anchor\" href=\"#performance-profiling\" aria-label=\"Link to Performance profiling\">#</a></h2>\n<p>Warp emits semantic frame, simulation, Lua, physics, rendering, GPU, asset, live-update, network, audio, and runtime events into industry-standard profiler paths. The native recorder is dormant until armed. A disabled event costs one relaxed atomic check; Apple signposts and Windows ETW use their platform enabled checks, so there is no background trace serialization, polling, or file I/O.</p>\n<p>For human profiling on Mac, open Apple Instruments, select the running <strong>Warp Player</strong> process, choose the standard Game Performance, Time Profiler, Metal System Trace, Allocations, or another template, and record normally. Attach Xcode's Metal debugger to Warp Player when a GPU frame capture is needed. Warp does not duplicate those controls inside the player. Portable captures are reserved for MCP and automation, use Chrome Trace Event JSON, and open directly in <a href=\"https://ui.perfetto.dev/\" target=\"_blank\" rel=\"noreferrer\">Perfetto</a>. Sokol render passes carry GPU debug-group names, so Metal and PIX captures identify scene, shadow, selection, ambient-occlusion, and display work instead of anonymous command buffers.</p>\n<p>Remote agents use exactly one MCP tool, <code>warp_profile_runtime</code>, against an explicitly selected runtime:</p>\n<ol>\n<li><code>action: &quot;capabilities&quot;</code> reports the runtime's native and portable backends.</li>\n<li><code>action: &quot;start&quot;</code> arms a bounded capture; optional <code>categories</code> and <code>maxEvents</code> keep evidence focused.</li>\n<li><code>action: &quot;mark&quot;</code> adds a revision/tick-correlated point before or after a suspected interaction.</li>\n<li><code>action: &quot;stop&quot;</code> returns frame average/p95/p99/max, over-budget counts, top CPU hotspots, dropped-event count, and a content-addressed Perfetto trace URL.</li>\n</ol>\n<p>Profiling never authors or merges project data and never creates an agent workspace. Every event carries the current project, workspace, scene, runtime, authored revision, simulation tick, and frame where applicable, allowing profiler evidence to line up with <code>warp_observe</code>, State Capsules, and review history.</p>\n<p>Mac release packaging also emits a sibling <code>*-symbols.zip</code> containing UUID-matched dSYMs and a <code>UUIDs.txt</code> manifest. Retain this artifact with each shipped build so Instruments recordings and crash reports remain symbolizable after a newer player is released.</p>\n<p>Evidence ids are content-stable within the exact authenticated project scope. The server retains at most 128 records for one hour and rejects comparisons across profiles, projects, workspaces, or scenes. Semantic comparison is bounded and keyed by prim path or stable runtime entity identity, so an agent receives changed component/property fields instead of a second complete scene dump. The normalized path/component/runtime filters must match the baseline; different filters produce an explicit non-comparable result rather than false additions or deletions.</p>\n<h2 id=\"rendered-frame-evidence\">Rendered frame evidence<a class=\"heading-anchor\" href=\"#rendered-frame-evidence\" aria-label=\"Link to Rendered frame evidence\">#</a></h2>\n<p>Use <code>warp_open_player</code> to obtain the current agent-workspace player link, then call <code>warp_capture_player_frame</code> while that web Sokol player is connected. Frame capture is revision-scoped: by default it rejects stale rendered state, waits up to five seconds for streamed mesh payloads, settles two additional frames, and returns both an MCP <code>image/png</code> block and structured metadata. The metadata includes runtime identity, project/workspace/scene, revision, tick, renderer, dimensions, byte length, and whether assets were ready or the wait timed out. The same PNG is stored in the project's content-addressed store and returned with its immutable content key and URL.</p>\n<p>This is visual evidence from the real Sokol render target, not a reconstruction from scene data or renderer statistics. It currently requires a connected web Sokol player; if none is available, the tool returns an actionable error instead of claiming visual success. One frame is not a substitute for deterministic scenarios or runtime inspection, so behavior and motion should use typed evidence and multiple captures where appropriate.</p>\n<h2 id=\"compatibility-and-failure-behavior\">Compatibility and failure behavior<a class=\"heading-anchor\" href=\"#compatibility-and-failure-behavior\" aria-label=\"Link to Compatibility and failure behavior\">#</a></h2>\n<p>Capsule decoding is fail-closed on unknown format versions, malformed/trailing bytes, or unknown serialized Lua values. Restore is transactional from the caller's perspective: errors are returned as actionable messages and never reported as success. The renderer rejects a capsule from another project or workspace. Scene routing is enforced by runtime discovery and command targeting, while the capsule's scene metadata remains diagnostic so the same project state can move between native and web path conventions.</p>\n<p>All entity references use stable 64-bit IDs. IDs are represented as JSON strings so browser clients do not lose precision. Runtime component payloads carry independent versions to permit future component migrations without changing the whole capsule format.</p>\n<h2 id=\"performance-model\">Performance model<a class=\"heading-anchor\" href=\"#performance-model\" aria-label=\"Link to Performance model\">#</a></h2>\n<p>Queries, QA links, State Capsules, rendered frame captures, and portable performance traces are on-demand. The player QA controls add no frame-loop polling, serialization, copying, traversal, history, locking, or bookkeeping while idle. A rendered capture waits and encodes PNG data only for the targeted QA request. Runtime recording is explicitly opt-in and bounded by bytes; performance tracing is explicitly opt-in and bounded by event count. Recorded asset payloads are deduplicated by stable asset ID and content hash; later frames copy metadata only. When the budget is exceeded, the oldest complete frames and unreferenced payloads are evicted together, so every retained tick remains independently restorable.</p>\n<p>For production, leave observation <code>off</code>. For interactive debugging, use <code>observe</code> and capture only when needed. Enable <code>record</code> for short repro windows, automated tests, or a failure trigger, then return to <code>off</code>.</p>",
      "icon": "inspect",
      "order": 7,
      "searchText": "Runtime introspection and State Capsules Warp exposes live ECS state as a first-class, versioned interface. Agents and tests can inspect a running player, pause it at an exact tick, capture a project-scoped State Capsule, restore that capsule, and run deterministic headless scenarios. The feature is off by default; ordinary gameplay pays only a mode check per tick. Observation modes - off is the production default. No history is captured. - observe enables live queries and on-demand capture without retaining history. - record retains an exact, bounded, every-tick history. Asset payloads are content-addressed and retained once rather than copied into every frame. The default memory budget is 128 MiB. State frames contain the authored entity/component snapshot plus native runtime components, Lua-owned world/entity values, Lua timers and random states, current input, physics body transforms and velocities, simulation clocks, pause state, world settings, and referenced cooked assets. Capsules include a format version, engine build, platform, UTC creation time, project/workspace/scene identity, revision, and tick. Normal captures contain content-addressed references to immutable cooked assets instead of recopying their bytes; restoring first loads the project and resolves matching hashes from its payload/cache layer. The C++ API can still request an embedded-asset capsule for deliberately self-contained export. Raw Lua VM memory, graphics-driver objects, sockets, audio device buffers, and other process-local handles are deliberately excluded. Their durable native state is captured and the subsystem rebuilds those handles on restore. Editor controls The web editor's Runtime Debugger button discovers runtimes connected to the current project, scene, and workspace. It can inspect a path prefix, pause/resume, single-step, enable bounded recording, and capture a capsule. Capture returns a shareable link such as: https://warp.billrey.net/p/PROJECT/state/sha256%3A...?...&s=Scenes%2FMain.usda&w=feature Opening that link loads the correct scope, waits for the retained web viewport to register, restores the capsule directly into that local viewport, pauses it, and selects the inspected entity. The experimental Runtime Debugger stays closed unless the user opens it from the menu. The macOS editor exposes the same debugging operations in its Debug menu; query JSON and captured deep links are copied to the clipboard. The transport uses the player's existing outbound authenticated gRPC stream. Players do not open inbound ports. Read-only queries and capsule capture/fetch require the project viewer role. Remotely controlling another runtime requires editor access; opening a QA link restores only into the local player and does not use that mutation endpoint. Player QA links Web, Mac, iPad/iPhone, and Windows players expose Deep State Link and Mark Issue from their … or File menu. Both actions capture a State Capsule only when invoked. Deep State Link puts the canonical URL on the clipboard. Mark Issue hands that same portable URL to the platform share sheet, or copies it when no share sheet is available, so it can be pasted into Jira, GitHub, Slack, or another system. Players also expose Copy Project Link plus one Open Link field. The project link identifies the current project, scene, and non-main workspace without capturing runtime state. The same Open Link field accepts those normal project/workspace URLs and Deep State Links; opening a Deep State Link additionally restores and pauses the captured runtime capsule. In a browser, the address bar provides the equivalent open-link flow. Warp deliberately does not create an issue, comment, ticket, replay window, or tracker-specific record. The content-addressed capsule and its URL are the portable repro artifact. Opening the URL resolves the project/workspace/scene, fetches the immutable capsule, restores it transactionally into the local player that opened it, pauses at the captured tick, and restores the captured runtime camera plus any inspect/selection context carried by the link. It never authors the workspace and cannot change another running player. Agent CLI Start the normal editor so .derived/furry ai session.json exists, then use the shared wrapper: ./tools/furry ai runtime-list ./tools/furry ai runtime-schema --runtime PLAYER ID ./tools/furry ai runtime-query --runtime PLAYER ID --input query.json ./tools/furry ai runtime-pause --runtime PLAYER ID ./tools/furry ai runtime-step --runtime PLAYER ID --input '{\"steps\":1}' ./tools/furry ai runtime-record --runtime PLAYER ID ./tools/furry ai runtime-at-tick --runtime PLAYER ID --input '{\"tick\":240}' ./tools/furry ai runtime-capture --runtime PLAYER ID --output failure.warpcap ./tools/furry ai runtime-restore --runtime PLAYER ID --capsule failure.warpcap ./tools/furry ai runtime-resume --runtime PLAYER ID ./tools/furry ai runtime-off --runtime PLAYER ID runtime-query accepts pathPrefix , string entityIds , components , includeDisabled , and maxEntities . Results contain stable entity and parent IDs plus typed, versioned runtime component values. If only one matching runtime is connected, --runtime may be omitted. On capture the CLI writes a local .warpcap , stores the same bytes in the project's content-addressed mirror, and prints the content key, URL, and canonical deep link. Restore only accepts the explicit capsule file supplied by the caller and expects the matching project content to be loaded. Headless scenarios Run the same native ECS, Lua, and physics systems without rendering: build/full-demo/furry runtime headless \\ --snapshot .derived/cooked/scene.bin \\ --scenario examples/runtime/basic state.warptest.json \\ --report .derived/test-results/basic-state.json \\ --junit .derived/test-results/basic-state.xml \\ --failure-capsule .derived/test-results/basic-state.warpcap Actions are tick / wait , input , pause , resume , step , inspect , and assert . An inspect action returns a typed observation at that exact point in the scenario, filtered by path and component names. Every assertion uses action: \"assert\" ; aliases such as assertState and assertTransform are rejected. Assertions can check entity existence, enabled state, translation, scale, required runtime components, revision, exact tick, exact typed Lua world state with stateKey plus stateEquals , or a numeric lower bound with stateNumberAtLeast . A failed assertion emits JSON/JUnit diagnostics and an exact restorable failure capsule. Hosted playtests use an action-discriminated, fail-closed contract. Each action accepts only the fields that its runtime implementation consumes; unknown actions and ignored-looking extra fields are rejected. Assertions must contain at least one predicate, entity predicates require a path, vectors have exact arities, and numeric limits are checked before the runtime starts. This keeps a plausible-looking scenario from being reported as meaningful evidence when the runtime would have ignored part of it. { \"action\": \"inspect\", \"label\": \"player-after-acceleration\", \"path\": \"/World/Player\", \"components\": [\"RuntimeTransform\", \"RigidBody\"], \"includeDisabled\": true, \"maxEntities\": 16 } Scenario JSON reports include an observations array. Each observation contains the action index, label, revision, tick, input, Lua state, matching hierarchy, authored components, derived runtime components, and selected physics bodies. This lets an agent inspect evidence before and after an interaction rather than guessing which assertion to write first. Remote agent inspection The hosted MCP bridge exposes the same state without SSH: - warp inspect project validates and filters the composed editable OpenUSD stage; - warp list assets queries the composed Lore-backed project asset catalog and returns stable, copy-safe references for every supported asset kind from every scene without exposing raw project files; - warp list runtimes discovers players attached to the exact project, workspace, and scene; - warp runtime schema describes typed inspectable runtime components; - warp query runtime returns live ECS, Lua, physics, input, tick, and revision state; - warp profile runtime controls one bounded, portable performance capture and returns a compact summary plus a content-addressed trace; - warp diagnose combines authored validation with live runtime evidence and actionable issues; - warp describe schema exposes the exact fail-closed authored component/property contract; - warp observe captures one revision-correlated authored/runtime/render evidence bundle and compares it with a prior evidence id; - warp run playtest accepts inspect actions for deterministic headless evidence when no player is connected. - warp capture player frame returns the actual canvas of a connected web Sokol player as an MCP PNG image, scoped to the exact project, workspace, scene, and minimum revision. Agents should normally repeat observe - edit - observe with compareToEvidenceId - playtest until consistency.ready is true and the semantic delta matches the requested change. warp observe records authored revision, runtime revision/tick, optional rendered-frame revision, and the ending scope high-water mark. If another edit lands during capture, the evidence remains inspectable but is marked not ready rather than silently joining mismatched states. Focused query and diagnose tools remain available for follow-up detail. Inspection is read-only and never publishes editor selection or scene mutations. Performance profiling Warp emits semantic frame, simulation, Lua, physics, rendering, GPU, asset, live-update, network, audio, and runtime events into industry-standard profiler paths. The native recorder is dormant until armed. A disabled event costs one relaxed atomic check; Apple signposts and Windows ETW use their platform enabled checks, so there is no background trace serialization, polling, or file I/O. For human profiling on Mac, open Apple Instruments, select the running Warp Player process, choose the standard Game Performance, Time Profiler, Metal System Trace, Allocations, or another template, and record normally. Attach Xcode's Metal debugger to Warp Player when a GPU frame capture is needed. Warp does not duplicate those controls inside the player. Portable captures are reserved for MCP and automation, use Chrome Trace Event JSON, and open directly in Perfetto. Sokol render passes carry GPU debug-group names, so Metal and PIX captures identify scene, shadow, selection, ambient-occlusion, and display work instead of anonymous command buffers. Remote agents use exactly one MCP tool, warp profile runtime , against an explicitly selected runtime: 1. action: \"capabilities\" reports the runtime's native and portable backends. 2. action: \"start\" arms a bounded capture; optional categories and maxEvents keep evidence focused. 3. action: \"mark\" adds a revision/tick-correlated point before or after a suspected interaction. 4. action: \"stop\" returns frame average/p95/p99/max, over-budget counts, top CPU hotspots, dropped-event count, and a content-addressed Perfetto trace URL. Profiling never authors or merges project data and never creates an agent workspace. Every event carries the current project, workspace, scene, runtime, authored revision, simulation tick, and frame where applicable, allowing profiler evidence to line up with warp observe , State Capsules, and review history. Mac release packaging also emits a sibling -symbols.zip containing UUID-matched dSYMs and a UUIDs.txt manifest. Retain this artifact with each shipped build so Instruments recordings and crash reports remain symbolizable after a newer player is released. Evidence ids are content-stable within the exact authenticated project scope. The server retains at most 128 records for one hour and rejects comparisons across profiles, projects, workspaces, or scenes. Semantic comparison is bounded and keyed by prim path or stable runtime entity identity, so an agent receives changed component/property fields instead of a second complete scene dump. The normalized path/component/runtime filters must match the baseline; different filters produce an explicit non-comparable result rather than false additions or deletions. Rendered frame evidence Use warp open player to obtain the current agent-workspace player link, then call warp capture player frame while that web Sokol player is connected. Frame capture is revision-scoped: by default it rejects stale rendered state, waits up to five seconds for streamed mesh payloads, settles two additional frames, and returns both an MCP image/png block and structured metadata. The metadata includes runtime identity, project/workspace/scene, revision, tick, renderer, dimensions, byte length, and whether assets were ready or the wait timed out. The same PNG is stored in the project's content-addressed store and returned with its immutable content key and URL. This is visual evidence from the real Sokol render target, not a reconstruction from scene data or renderer statistics. It currently requires a connected web Sokol player; if none is available, the tool returns an actionable error instead of claiming visual success. One frame is not a substitute for deterministic scenarios or runtime inspection, so behavior and motion should use typed evidence and multiple captures where appropriate. Compatibility and failure behavior Capsule decoding is fail-closed on unknown format versions, malformed/trailing bytes, or unknown serialized Lua values. Restore is transactional from the caller's perspective: errors are returned as actionable messages and never reported as success. The renderer rejects a capsule from another project or workspace. Scene routing is enforced by runtime discovery and command targeting, while the capsule's scene metadata remains diagnostic so the same project state can move between native and web path conventions. All entity references use stable 64-bit IDs. IDs are represented as JSON strings so browser clients do not lose precision. Runtime component payloads carry independent versions to permit future component migrations without changing the whole capsule format. Performance model Queries, QA links, State Capsules, rendered frame captures, and portable performance traces are on-demand. The player QA controls add no frame-loop polling, serialization, copying, traversal, history, locking, or bookkeeping while idle. A rendered capture waits and encodes PNG data only for the targeted QA request. Runtime recording is explicitly opt-in and bounded by bytes; performance tracing is explicitly opt-in and bounded by event count. Recorded asset payloads are deduplicated by stable asset ID and content hash; later frames copy metadata only. When the budget is exceeded, the oldest complete frames and unreferenced payloads are evicted together, so every retained tick remains independently restorable. For production, leave observation off . For interactive debugging, use observe and capture only when needed. Enable record for short repro windows, automated tests, or a failure trigger, then return to off .",
      "slug": "runtime-introspection",
      "source": "docs/RUNTIME_INTROSPECTION.md",
      "sourceHash": "624ecdddf39f0e5330215d0a38296594a2806c6f51f74c8b34de22406ff43cb0",
      "summary": "Inspect live ECS state, pause and step players, record exact ticks, and recreate bugs with State Capsules.",
      "tags": [
        "runtime",
        "debugging",
        "state capsule",
        "reproduction",
        "testing"
      ],
      "title": "Runtime introspection"
    },
    {
      "audience": "Engine developers",
      "group": "reference",
      "headings": [
        {
          "id": "shared-client-behavior-native-presentation",
          "level": 2,
          "title": "Shared client behavior, native presentation"
        },
        {
          "id": "desktop-browser-authentication",
          "level": 3,
          "title": "Desktop browser authentication"
        },
        {
          "id": "design-principles",
          "level": 2,
          "title": "Design Principles"
        },
        {
          "id": "project-package",
          "level": 2,
          "title": "Project Package"
        },
        {
          "id": "source-format",
          "level": 2,
          "title": "Source Format"
        },
        {
          "id": "layers-and-edit-targets",
          "level": 2,
          "title": "Layers and Edit Targets"
        },
        {
          "id": "cooked-format",
          "level": 2,
          "title": "Cooked Format"
        },
        {
          "id": "runtime-format",
          "level": 2,
          "title": "Runtime Format"
        },
        {
          "id": "runtime-transform-hierarchy",
          "level": 3,
          "title": "Runtime transform hierarchy"
        },
        {
          "id": "authoring-server",
          "level": 2,
          "title": "Authoring Server"
        },
        {
          "id": "live-transport",
          "level": 2,
          "title": "Live Transport"
        },
        {
          "id": "editors-and-clients",
          "level": 2,
          "title": "Editors and Clients"
        },
        {
          "id": "web-editor",
          "level": 3,
          "title": "Web Editor"
        },
        {
          "id": "mac-editor",
          "level": 3,
          "title": "Mac Editor"
        },
        {
          "id": "remote-script-and-agent-tooling",
          "level": 3,
          "title": "Remote Script and Agent Tooling"
        },
        {
          "id": "rendering",
          "level": 2,
          "title": "Rendering"
        },
        {
          "id": "physics-scripting-and-audio",
          "level": 2,
          "title": "Physics, Scripting, and Audio"
        },
        {
          "id": "asset-and-content-flow",
          "level": 2,
          "title": "Asset and Content Flow"
        },
        {
          "id": "content-store",
          "level": 2,
          "title": "Content Store"
        },
        {
          "id": "lore-integration",
          "level": 2,
          "title": "Lore Integration"
        },
        {
          "id": "workspaces-and-reviews",
          "level": 2,
          "title": "Workspaces and Reviews"
        },
        {
          "id": "build-variants",
          "level": 2,
          "title": "Build Variants"
        },
        {
          "id": "end-to-end-edit-flow",
          "level": 2,
          "title": "End-to-End Edit Flow"
        },
        {
          "id": "invariants",
          "level": 2,
          "title": "Invariants"
        }
      ],
      "html": "<h2 id=\"shared-client-behavior-native-presentation\">Shared client behavior, native presentation<a class=\"heading-anchor\" href=\"#shared-client-behavior-native-presentation\" aria-label=\"Link to Shared client behavior, native presentation\">#</a></h2>\n<p>Editors and players use <code>warp_client_core</code> for the behavior that must remain identical across platforms. The core owns canonical project/scene/workspace/ layer targets, deep-link parsing, catalog normalization, switch generations, write barriers, and stale-result rejection. <code>main</code> is normalized as an ordinary workspace rather than handled as a separate client mode.</p>\n<p>Platform shells remain responsible for presentation and operating-system effects. SwiftUI, UIKit, AppKit, the browser DOM, and Windows controls render the shared state using their native interaction, accessibility, menu, and animation conventions. Their adapters perform URLSession/fetch/WinHTTP calls, secure token storage, system URL delivery, and dialogs.</p>\n<p>Destination switching is the deliberate exception to otherwise native-only UI composition. <code>WarpAppleClientUI</code> provides one adaptive SwiftUI project/workspace picker and compact workspace menu for the Mac editor, Mac player, and iPad/iPhone player. The players reach it through a stable C ABI; the editor imports the same Swift package directly. Platform adapters only provide the catalog items and receive the selected stable ID, so presentation never adds a resolution request or bypasses the shared fast-switch coordinator.</p>\n<p><code>destination_picker_copy</code> in <code>warp_client_core</code> is the cross-platform content contract. It owns titles, descriptions, action labels, loading/empty/error wording, and the common current-selection convention. The web editor consumes its generated JSON manifest, Windows consumes it directly, and the Apple SwiftUI caller receives the same fields. Web and Windows retain native layout and controls, but use the same order and concepts: searchable destination list, visible current state, one primary Open action, optional client-specific secondary actions, and creation as a separate capability. A future interactive Linux shell must consume this contract rather than inventing another picker; the current Linux runtime is headless and has no project/workspace UI surface.</p>\n<p>The C++ players link the core directly. The Mac editor uses the stable C bridge in <code>WarpClientStateBridge</code>, while the web editor loads the small WebAssembly bridge built as <code>warp_client_core_web</code>. New project, workspace, link, or session behavior belongs in the shared core first; platform code should only translate native events into core inputs and core outcomes into native UI.</p>\n<p>The shared coordinator is a synchronous ordering primitive, not a loading layer. Known workspace targets reuse the live authoring endpoint directly and must not add a resolution request. Project and workspace presentation keeps the current viewport and content-addressed payload caches alive while authoritative state catches up; only missing or changed payloads are fetched.</p>\n<p>This contract applies to the web editor and player/runtime, the SwiftUI Mac editor and its native viewport, the Mac player/runtime, the iPad/iPhone player, and the Windows player. Native project/workspace pickers reuse destinations already authorized by their catalog response; arbitrary cross-server links still resolve once for correctness. A repeated active-scope request is a no-op unless it explicitly supplies a different network route.</p>\n<h3 id=\"desktop-browser-authentication\">Desktop browser authentication<a class=\"heading-anchor\" href=\"#desktop-browser-authentication\" aria-label=\"Link to Desktop browser authentication\">#</a></h3>\n<p>Windows and Linux authenticate with Warp in the user's system browser; the player does not embed Chromium or Electron. <code>DesktopBrowserAuth</code> opens a loopback-only callback before launching the browser, attaches a cryptographically random state nonce, and accepts a session only when the callback path and nonce both match. The blocking socket wait runs on a worker thread in the Windows player and has no render-loop cost.</p>\n<p>The Windows player stores the resulting token in Windows Credential Manager. The Linux/headless entry point exposes <code>furry_runtime_headless --login [--server URL]</code>; it launches <code>xdg-open</code>, waits up to five minutes, then atomically stores the session in an owner-only file below <code>$XDG_STATE_HOME/warp-headless</code> (or <code>~/.local/state/warp-headless</code>). <code>--login --no-browser</code> prints the URL for environments where the browser launcher is unavailable.</p>\n<p>Warp is a C++20/OpenUSD live authoring engine. It keeps authoring data, cooked runtime data, and live player state deliberately separate so the Mac editor, web editor, browser player, native viewport, and automation tools can all operate on the same project without turning the runtime into an OpenUSD editor.</p>\n<p>At a high level:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>text</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-text\">OpenUSD project package\n  -&gt; Authoring server and stage inspector\n  -&gt; Cooked scene snapshots, patches, and asset descriptors\n  -&gt; EnTT runtime world\n  -&gt; Sokol native/web renderer and play mode</code></pre></div>\n<p>The current stack includes:</p>\n<ul>\n<li>C++20 core, runtime, content pipeline, and authoring services.</li>\n<li>OpenUSD for source scenes, composition, layers, relationships, metadata, and DCC-facing data.</li>\n<li>SwiftUI macOS editor and native Sokol/Metal viewport.</li>\n<li>Browser editor and browser player with Sokol WebGPU/WASM through Emscripten.</li>\n<li>gRPC/protobuf live update streams for native/editor/runtime tools.</li>\n<li>HTTP and browser APIs for hosted editor, web player, asset upload, content fetches, and remote script editing.</li>\n<li>Lua scripting for behavior modules.</li>\n<li>Jolt Physics for native and web play mode builds.</li>\n<li>Epic Lore backed content/workspace storage when available, with local fallback paths.</li>\n</ul>\n<h2 id=\"design-principles\">Design Principles<a class=\"heading-anchor\" href=\"#design-principles\" aria-label=\"Link to Design Principles\">#</a></h2>\n<ul>\n<li>OpenUSD is the editable source of truth. Runtime code should not open or mutate USD files directly.</li>\n<li>Cooked records are the runtime contract. Initial load and live edits use the same data shape.</li>\n<li>Live edit patches are narrow. Editors submit authored operations, the server determines affected entities/assets, and runtimes replace only what changed.</li>\n<li>Asset bytes are content, not control messages. Large cooked blobs move through content URLs or blob endpoints instead of live edit streams.</li>\n<li>There is exactly one derived-artifact system. Every derivation of source content — textures, meshes, audio, physics data, and asset thumbnails — is an immutable, content-addressed artifact whose identity is the asset's Lore content identity plus a versioned recipe (see <code>docs/DERIVED_ARTIFACTS.md</code>). Never add a parallel cache, fingerprint scheme, or cooking path for a new derivation kind, and never derive artifact identity from timestamps, checkout paths, workspace names, or client-supplied parameters.</li>\n<li>Selection, inspector state, and hover state are presentation state. They should stay local and responsive in each editor. Asset thumbnails are not presentation state: they are derived artifacts of the asset's content identity, shared across editors and workspaces like any other cooked bytes.</li>\n<li>Mac, web, and automation clients should share authoring semantics rather than each inventing a parallel editing path.</li>\n</ul>\n<h2 id=\"project-package\">Project Package<a class=\"heading-anchor\" href=\"#project-package\" aria-label=\"Link to Project Package\">#</a></h2>\n<p>A Warp project is a package rooted at a <code>.furryproject</code> directory. Important paths include:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>text</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-text\">Project.furryproject/\n  project.json\n  Scenes/\n    Main.usda\n    Layers/\n      Example.usda\n  Scripts/\n    PlayerMovement.lua\n  Content/\n    sha256/\n      ab/cd/&lt;digest&gt;.blob\n      ab/cd/&lt;digest&gt;.json\n  Workspaces/\n    workspaces.json\n  .derived/</code></pre></div>\n<p><code>project.json</code> records project-level settings such as the content store backend. Source scene data lives under <code>Scenes/</code>. User-created USD sublayers are created under <code>Scenes/Layers/</code>. Script source lives under <code>Scripts/</code>. Content-addressed blobs live under <code>Content/</code> for local storage, or in Lore storage for Lore-backed projects.</p>\n<p><code>Workspaces/workspaces.json</code> stores Warp workspace metadata such as names, parent workspace IDs, base scene paths, archive markers, and author metadata. The durable branch/content backend may be Lore, but Warp keeps enough local metadata to make the editor UI fast and robust.</p>\n<h2 id=\"source-format\">Source Format<a class=\"heading-anchor\" href=\"#source-format\" aria-label=\"Link to Source Format\">#</a></h2>\n<p>OpenUSD is the editable authority. It is used for:</p>\n<ul>\n<li>prim hierarchy and transforms;</li>\n<li>component data authored as USD attributes and relationships;</li>\n<li>material and asset bindings;</li>\n<li>user sublayers and composition;</li>\n<li>muting/enabling layers through USD composition;</li>\n<li>DCC interoperability and source asset references.</li>\n</ul>\n<p>Furry should prefer USD-native relationships and metadata over custom string databases. Asset/material bindings should be represented as relationship targets where possible, and legacy custom string forms are read for compatibility.</p>\n<p>The hidden Furry asset registry should not appear as ordinary authoring content in scene hierarchy UIs. It is implementation data, not user scene structure.</p>\n<h2 id=\"layers-and-edit-targets\">Layers and Edit Targets<a class=\"heading-anchor\" href=\"#layers-and-edit-targets\" aria-label=\"Link to Layers and Edit Targets\">#</a></h2>\n<p>Layer support is built on native USD composition:</p>\n<ul>\n<li>The stage exposes the root layer and direct root sublayers as <code>LayerInfo</code>.</li>\n<li>Layer operations include add, remove from root sublayers, move, mute, set active, and clear.</li>\n<li>New layers are created under <code>Scenes/Layers/&lt;Name&gt;.usda</code>.</li>\n<li>Removing a layer removes it from the root layer's subLayerPaths; it does not delete the file.</li>\n<li>Muting uses USD layer muting for the scene session.</li>\n<li>Active edit targets are tracked per client/author and default to a user-editable layer.</li>\n</ul>\n<p>Prim edits, component edits, and USD property edits route through the active <code>UsdEditTarget</code> for that client. Web and Mac clients may have different active edit targets at the same time. Toggling or reordering layers must go through USD composition, not a custom override system.</p>\n<h2 id=\"cooked-format\">Cooked Format<a class=\"heading-anchor\" href=\"#cooked-format\" aria-label=\"Link to Cooked Format\">#</a></h2>\n<p>Cooked data is the runtime-facing representation of OpenUSD-authored scenes. It contains compact entity, component, asset, material, animation, and script payloads keyed by stable IDs.</p>\n<p>The main cooked shapes are:</p>\n<ul>\n<li><code>CookedSceneSnapshot</code>: full runtime state for a scene/session.</li>\n<li><code>LiveEditPatch</code>: incremental cooked replacements/deletions plus revision metadata.</li>\n<li><code>CookedAssetBlob</code>: stable asset descriptor with kind, stable asset ID, source URI, content hash, byte sizes, cook status, content key/URL, and optional inline payload.</li>\n<li>component payloads encoded by type ID, for example transform, render shape, mesh asset, material binding, audio source, script, physics body, collider, light, and camera.</li>\n</ul>\n<p>The runtime does not need to know whether cooked records came from first load, a live edit, a script replacement, a layer recomposition, or an uploaded asset finishing its cook.</p>\n<h2 id=\"runtime-format\">Runtime Format<a class=\"heading-anchor\" href=\"#runtime-format\" aria-label=\"Link to Runtime Format\">#</a></h2>\n<p>Runtime state is an EnTT registry plus runtime systems:</p>\n<ul>\n<li><code>RuntimeWorld</code> owns entity/component state, simulation state, script system, physics system, and stable ID mappings.</li>\n<li><code>EntityFactory</code> converts cooked component payloads into runtime ECS components.</li>\n<li><code>LiveEditApplier</code> applies snapshot and patch semantics.</li>\n<li><code>LuaScriptSystem</code> runs behavior modules with a restricted API and diagnostics.</li>\n<li><code>PhysicsSystem</code> integrates Jolt when enabled.</li>\n</ul>\n<h3 id=\"runtime-transform-hierarchy\">Runtime transform hierarchy<a class=\"heading-anchor\" href=\"#runtime-transform-hierarchy\" aria-label=\"Link to Runtime transform hierarchy\">#</a></h3>\n<p>Authored <code>Transform</code> values are always parent-local: translation is measured in metres, rotation is stored in the component's documented angle convention, and scale is multiplicative. The runtime keeps a parent-first hierarchy order and rebuilds that order only when entities are added, removed, or reparented.</p>\n<p>Each simulation frame captures local transforms into retained storage, lets scripts and Jolt update their authorities, and performs one linear hierarchy composition pass. A dynamic rigid body owns its final world pose; ordinary children then compose their retained local pose from that result. Rendering, runtime queries, cameras, and playtests consume the resulting world transform directly and must not walk the parent chain a second time.</p>\n<p>This contract is shared by native Metal, WebGPU, iPadOS, Windows, and headless playtests because it lives in <code>RuntimeWorld</code>, below every renderer and client.</p>\n<p>The runtime is intentionally not a USD host. It consumes snapshots and patches, stores assets by stable asset ID/content hash, and runs the game loop.</p>\n<h2 id=\"authoring-server\">Authoring Server<a class=\"heading-anchor\" href=\"#authoring-server\" aria-label=\"Link to Authoring Server\">#</a></h2>\n<p>The OpenUSD authoring side is split into a stage store and an authoring server:</p>\n<ul>\n<li><code>UsdStageStore</code> opens composed stages, reads hierarchy/detail/layer data, applies USD operations, cooks affected source entities/assets, and resolves source files.</li>\n<li><code>AuthoringServer</code> accepts operation diffs, manages revision history, undo/redo, active edit targets, layer operations, script asset replacement, and cooked patch generation.</li>\n<li><code>furry_usd_inspector</code> exposes composed stage/project data and cooked mesh/sound blob helpers for editor/server processes.</li>\n<li><code>furry_edit_client</code> and <code>furry_tool_client</code> are CLI/client helpers for structured edits.</li>\n</ul>\n<p>The authoring server applies operations to USD first, then exports affected cooked records. It is the boundary that preserves undo/history, revision checks, live diagnostics, and consistent authoring semantics.</p>\n<h2 id=\"live-transport\">Live Transport<a class=\"heading-anchor\" href=\"#live-transport\" aria-label=\"Link to Live Transport\">#</a></h2>\n<p>Native/editor communication uses protobuf/gRPC when <code>FURRY_ENABLE_GRPC=ON</code>.</p>\n<p>Important streams and calls include:</p>\n<ul>\n<li><code>StreamUpdates</code>: sends snapshots, patches, asset changes, authoring previews, and reset/open-scene events to runtimes.</li>\n<li><code>SubmitUsdDiff</code>: submits prim, component, USD property, asset, script, and layer operation diffs.</li>\n<li><code>OpenScene</code>: switches the active scene/workspace for a client/session.</li>\n<li><code>StreamToolState</code> and <code>SubmitToolEvent</code>: synchronize selection/tool state across clients.</li>\n<li><code>SubmitAuthoringPreview</code>: sends transient preview updates for drag/slider interactions.</li>\n</ul>\n<p>The browser editor uses HTTP APIs plus web player glue for hosted operation. The live Sokol web player consumes binary snapshots/patches and fetches mesh/sound content on demand.</p>\n<h2 id=\"editors-and-clients\">Editors and Clients<a class=\"heading-anchor\" href=\"#editors-and-clients\" aria-label=\"Link to Editors and Clients\">#</a></h2>\n<h3 id=\"web-editor\">Web Editor<a class=\"heading-anchor\" href=\"#web-editor\" aria-label=\"Link to Web Editor\">#</a></h3>\n<p>The web editor is served by <code>furry_web_editor_server</code>. It owns the browser UI for:</p>\n<ul>\n<li>hierarchy, layer pane, project/assets pane, inspector, AI/agent pane, and viewport frame;</li>\n<li>web upload/import flows with per-asset progress;</li>\n<li>script editing endpoints and VS Code launch integration;</li>\n<li>workspace creation, switching, review flow, and archive UI;</li>\n<li>hosted asset/content endpoints;</li>\n<li>browser player/viewer URLs.</li>\n</ul>\n<p>The web editor keeps a local presentation model in JavaScript. Selection and inspector changes are local and immediate; expensive stage/project refreshes happen separately.</p>\n<h3 id=\"mac-editor\">Mac Editor<a class=\"heading-anchor\" href=\"#mac-editor\" aria-label=\"Link to Mac Editor\">#</a></h3>\n<p>The macOS editor is SwiftUI. It uses the same authoring and live update semantics, but should behave like the web editor:</p>\n<ul>\n<li>local presentation store for selected prims/assets/materials/store items;</li>\n<li>precomputed project, material, store, hierarchy, and inspector records;</li>\n<li>synchronous selection reducers;</li>\n<li>deferred/debounced slider commits and lightweight preview updates;</li>\n<li>native share sheet and hosted/web/VS Code launch actions;</li>\n<li>native Sokol/Metal viewport process.</li>\n</ul>\n<p>The Mac editor should not put network refresh, USD scans, asset lookup scans, or broad project recompute in the click/selection path.</p>\n<h3 id=\"remote-script-and-agent-tooling\">Remote Script and Agent Tooling<a class=\"heading-anchor\" href=\"#remote-script-and-agent-tooling\" aria-label=\"Link to Remote Script and Agent Tooling\">#</a></h3>\n<p>Remote script editing is exposed through server endpoints such as:</p>\n<ul>\n<li><code>GET /api/scripts</code></li>\n<li><code>GET /api/script?module=scripts.Name</code></li>\n<li><code>PUT /api/script?module=scripts.Name</code></li>\n<li><code>POST /api/vscode-session</code></li>\n</ul>\n<p>Scripts are addressed by flat module names like <code>scripts.PlayerMovement</code>, mapped to <code>Scripts/PlayerMovement.lua</code>. Server-side validation rejects paths, traversal, nested modules for v1, and non-script assets.</p>\n<p>CLI/agent workflows should prefer structured authoring tools such as <code>./tools/furry_ai</code> for scene/component/script edits when an editor session is active. That preserves live updates, revision checks, undo/history, and diagnostics.</p>\n<h2 id=\"rendering\">Rendering<a class=\"heading-anchor\" href=\"#rendering\" aria-label=\"Link to Rendering\">#</a></h2>\n<p>Rendering is provided by <code>SokolRenderer</code>.</p>\n<p>Native:</p>\n<ul>\n<li>Sokol with Metal on Apple platforms.</li>\n<li><code>furry_runtime_sokol</code> and <code>WarpPlayerMac</code> for native runtime/player builds.</li>\n<li><code>furry_runtime_viewport</code> plus <code>furry_viewport_client</code> for the Mac editor viewport process.</li>\n</ul>\n<p>Web:</p>\n<ul>\n<li>Sokol WebGPU backend compiled to WASM through Emscripten/emdawnwebgpu.</li>\n<li>Separate editor viewer and play-mode bundles.</li>\n<li>Web mesh/sound assets fetched asynchronously by content URL/blob endpoints.</li>\n<li>Runtime web audio uses WebAudio callback-style streaming to avoid render-loop buffer starvation.</li>\n</ul>\n<p>The renderer owns camera controls, transform gizmos, object markers, retained mesh buffers, loading stand-ins, selection/drop overlays, shadow targets, and memory/performance diagnostics.</p>\n<h2 id=\"physics-scripting-and-audio\">Physics, Scripting, and Audio<a class=\"heading-anchor\" href=\"#physics-scripting-and-audio\" aria-label=\"Link to Physics, Scripting, and Audio\">#</a></h2>\n<p>Lua scripts are ECS system/behavior modules, not general plugins. They run in a sandboxed Lua state with an instruction budget and explicit component-access schemas. Native EnTT queries and schedules reject irrelevant systems before Lua is entered; structural operations commit after module iteration. Dense iteration, hierarchy resolution, continuous motion, physics, rendering, streaming, audio, and animation stay in C++. See <code>docs/SCRIPTING.md</code>.</p>\n<p>Jolt Physics is integrated through <code>PhysicsSystem</code> and updates runtime transforms in play mode. Runtime refresh paths should update only changed transform data when possible, instead of forcing broad render-item rebuilds each frame.</p>\n<p>Audio assets are cooked to a stable runtime format. Native and web both use decoded clip data, but web playback uses a WebAudio callback path to avoid crackle/slowdown from render-loop underruns.</p>\n<h2 id=\"asset-and-content-flow\">Asset and Content Flow<a class=\"heading-anchor\" href=\"#asset-and-content-flow\" aria-label=\"Link to Asset and Content Flow\">#</a></h2>\n<p>Assets follow the same source/cooked/runtime boundary:</p>\n<ol>\n<li>Source assets such as USD, FBX, images, scripts, sounds, and materials are registered in project metadata/USD.</li>\n<li>The authoring side resolves and cooks them into runtime-ready blobs.</li>\n<li>Asset descriptors enter snapshots/patches by stable asset ID and content hash.</li>\n<li>Large binary payloads are published through <code>ContentStore</code> and served by content/blob endpoints.</li>\n<li>Clients fetch missing blobs asynchronously and keep local caches.</li>\n<li>Runtime renderers show a loading/failed stand-in without blocking live edit streams.</li>\n</ol>\n<p>Hosted projects should avoid pushing large binary bytes through live-edit control streams. Web snapshots/patches should carry descriptors and content URLs for meshes/sounds when possible. After decode, web runtimes should release duplicate compressed payloads from WASM-side storage.</p>\n<h2 id=\"content-store\">Content Store<a class=\"heading-anchor\" href=\"#content-store\" aria-label=\"Link to Content Store\">#</a></h2>\n<p>The local content store is digest-addressed:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>text</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-text\">Project.furryproject/\n  Content/\n    sha256/\n      ab/\n        cd/\n          abcdef....blob\n          abcdef....json</code></pre></div>\n<p>Blobs are immutable. Project asset records reference hashes; deleting an asset removes the reference, not necessarily the blob. Garbage collection can remove unreferenced blobs later.</p>\n<p>Hosted content endpoints should support immutable cache headers, <code>ETag</code>, and range requests for large data. Current endpoints also expose cooked mesh/sound blob helpers for web clients.</p>\n<h2 id=\"lore-integration\">Lore Integration<a class=\"heading-anchor\" href=\"#lore-integration\" aria-label=\"Link to Lore Integration\">#</a></h2>\n<p>Lore is an optional durable backend for content and workspace history. It sits below Warp's authoring semantics rather than replacing them.</p>\n<p><code>LocalContentStore</code> writes SHA-256 blobs into the project package. <code>LoreContentStore</code> is compiled when CMake is configured with <code>FURRY_ENABLE_LORE=ON</code> and <code>FURRY_LORE_ROOT</code> points at an extracted <code>liblore</code> release.</p>\n<p>Warp projects can declare:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>json</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-json\">{\n  &quot;contentStore&quot;: &quot;lore&quot;,\n  &quot;loreRepository&quot;: &quot;.&quot;\n}</code></pre></div>\n<p>At runtime, Lore-backed deployments use environment such as:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">FURRY_CONTENT_STORE_BACKEND=lore\nFURRY_LORE_WORKSPACE=/path/to/lore/repository-or-storage-workspace\nWARP_LORE_REMOTE_URL=https://optional-lore-service.example\n# Compatibility alias: FURRY_LORE_REMOTE_URL</code></pre></div>\n<p>Furry talks to Lore native storage APIs for content-addressed storage and branch-style workspaces. Local workspace metadata is still important: it carries Furry names, parent workspace IDs, base scenes, archive markers, and UI-facing metadata. Local archive markers intentionally hide workspaces immediately even if remote Lore archive propagation is slow.</p>\n<h2 id=\"workspaces-and-reviews\">Workspaces and Reviews<a class=\"heading-anchor\" href=\"#workspaces-and-reviews\" aria-label=\"Link to Workspaces and Reviews\">#</a></h2>\n<p>Project workspaces are branch-style editing contexts. The main workspace is the default. Additional workspaces can be created from a parent, opened independently, reviewed, merged, and archived.</p>\n<p>Key rules:</p>\n<ul>\n<li><code>main</code> cannot be archived.</li>\n<li>Archiving hides a workspace from normal picker/list APIs; files/branches are not deleted.</li>\n<li>Local archive state wins over stale remote branch state for UI purposes.</li>\n<li>Active client counts are presentation/session state maintained by the web server.</li>\n<li>Reviews are visible only when both source and target workspaces are still active and the review is not merged/closed.</li>\n</ul>\n<h2 id=\"build-variants\">Build Variants<a class=\"heading-anchor\" href=\"#build-variants\" aria-label=\"Link to Build Variants\">#</a></h2>\n<p>CMake options gate heavier integrations:</p>\n<ul>\n<li><code>FURRY_BUILD_AUTHORING</code></li>\n<li><code>FURRY_ENABLE_GRPC</code></li>\n<li><code>FURRY_ENABLE_SOKOL</code></li>\n<li><code>FURRY_ENABLE_LUA</code></li>\n<li><code>FURRY_ENABLE_JOLT</code></li>\n<li><code>FURRY_ENABLE_LORE</code></li>\n<li><code>FURRY_ENABLE_USD_LAYERS</code></li>\n<li><code>FURRY_BUILD_IOS_PLAYER</code></li>\n</ul>\n<p>Common bundles:</p>\n<ul>\n<li><code>furry_core</code>: cooked formats, content store, project/workspace metadata, export pipeline.</li>\n<li><code>furry_runtime</code>: runtime world, Lua, physics, headless systems.</li>\n<li><code>furry_authoring</code>: OpenUSD stage store and authoring server.</li>\n<li><code>furry_transport_*</code>: protobuf/gRPC transport.</li>\n<li><code>furry_sokol_renderer</code>: shared native/web renderer.</li>\n<li><code>furry_web_editor_server</code>: hosted/local web editor, HTTP APIs, content endpoints.</li>\n<li><code>furry_web_sokol_viewer</code>: Emscripten/WebGPU viewer/player bundle.</li>\n<li><code>furry_runtime_viewport</code> and <code>furry_viewport_client</code>: Mac editor viewport runtime.</li>\n</ul>\n<h2 id=\"end-to-end-edit-flow\">End-to-End Edit Flow<a class=\"heading-anchor\" href=\"#end-to-end-edit-flow\" aria-label=\"Link to End-to-End Edit Flow\">#</a></h2>\n<p>Typical prim/component edit:</p>\n<ol>\n<li>User edits in Mac, web, CLI, VS Code, or agent tooling.</li>\n<li>Client submits a structured operation with revision, client ID, scene URI, workspace, and active layer.</li>\n<li>Authoring server validates the operation and chooses the client's active USD edit target.</li>\n<li><code>UsdStageStore</code> applies the operation to OpenUSD.</li>\n<li>The dependency/export path identifies affected entities/assets.</li>\n<li>The server emits a cooked live patch and revision.</li>\n<li>Runtime clients apply the patch to ECS state and asset caches.</li>\n<li>Editors refresh local presentation stores and keep current selection if still valid.</li>\n</ol>\n<p>Typical asset upload/import:</p>\n<ol>\n<li>User drops one or more files.</li>\n<li>The editor creates optimistic asset rows/cards immediately.</li>\n<li>Source bytes upload to the server/content store.</li>\n<li>Cook jobs produce runtime blobs and descriptors.</li>\n<li>Project/stage data refreshes with the new asset records.</li>\n<li>Renderers fetch cooked blobs on demand and replace loading stand-ins.</li>\n</ol>\n<h2 id=\"invariants\">Invariants<a class=\"heading-anchor\" href=\"#invariants\" aria-label=\"Link to Invariants\">#</a></h2>\n<ul>\n<li>Authoring operations should be structured JSON/protobuf operations, not ad hoc USD text edits, except for explicit one-off migrations.</li>\n<li>Runtimes consume cooked data and do not author USD.</li>\n<li>Browser and native players should share runtime semantics.</li>\n<li>Web performance work should prefer on-demand content, payload release, throttled stats/logging, and lazy GPU resources before reducing render sharpness.</li>\n<li>UI selection should be local and instant.</li>\n<li>Layer behavior must follow USD composition rules.</li>\n<li>Workspaces hide/archive through metadata and branch state, not destructive deletion.</li>\n</ul>",
      "icon": "architecture",
      "order": 8,
      "searchText": "Architecture Shared client behavior, native presentation Editors and players use warp client core for the behavior that must remain identical across platforms. The core owns canonical project/scene/workspace/ layer targets, deep-link parsing, catalog normalization, switch generations, write barriers, and stale-result rejection. main is normalized as an ordinary workspace rather than handled as a separate client mode. Platform shells remain responsible for presentation and operating-system effects. SwiftUI, UIKit, AppKit, the browser DOM, and Windows controls render the shared state using their native interaction, accessibility, menu, and animation conventions. Their adapters perform URLSession/fetch/WinHTTP calls, secure token storage, system URL delivery, and dialogs. Destination switching is the deliberate exception to otherwise native-only UI composition. WarpAppleClientUI provides one adaptive SwiftUI project/workspace picker and compact workspace menu for the Mac editor, Mac player, and iPad/iPhone player. The players reach it through a stable C ABI; the editor imports the same Swift package directly. Platform adapters only provide the catalog items and receive the selected stable ID, so presentation never adds a resolution request or bypasses the shared fast-switch coordinator. destination picker copy in warp client core is the cross-platform content contract. It owns titles, descriptions, action labels, loading/empty/error wording, and the common current-selection convention. The web editor consumes its generated JSON manifest, Windows consumes it directly, and the Apple SwiftUI caller receives the same fields. Web and Windows retain native layout and controls, but use the same order and concepts: searchable destination list, visible current state, one primary Open action, optional client-specific secondary actions, and creation as a separate capability. A future interactive Linux shell must consume this contract rather than inventing another picker; the current Linux runtime is headless and has no project/workspace UI surface. The C++ players link the core directly. The Mac editor uses the stable C bridge in WarpClientStateBridge , while the web editor loads the small WebAssembly bridge built as warp client core web . New project, workspace, link, or session behavior belongs in the shared core first; platform code should only translate native events into core inputs and core outcomes into native UI. The shared coordinator is a synchronous ordering primitive, not a loading layer. Known workspace targets reuse the live authoring endpoint directly and must not add a resolution request. Project and workspace presentation keeps the current viewport and content-addressed payload caches alive while authoritative state catches up; only missing or changed payloads are fetched. This contract applies to the web editor and player/runtime, the SwiftUI Mac editor and its native viewport, the Mac player/runtime, the iPad/iPhone player, and the Windows player. Native project/workspace pickers reuse destinations already authorized by their catalog response; arbitrary cross-server links still resolve once for correctness. A repeated active-scope request is a no-op unless it explicitly supplies a different network route. Desktop browser authentication Windows and Linux authenticate with Warp in the user's system browser; the player does not embed Chromium or Electron. DesktopBrowserAuth opens a loopback-only callback before launching the browser, attaches a cryptographically random state nonce, and accepts a session only when the callback path and nonce both match. The blocking socket wait runs on a worker thread in the Windows player and has no render-loop cost. The Windows player stores the resulting token in Windows Credential Manager. The Linux/headless entry point exposes furry runtime headless --login [--server URL] ; it launches xdg-open , waits up to five minutes, then atomically stores the session in an owner-only file below $XDG STATE HOME/warp-headless (or ~/.local/state/warp-headless ). --login --no-browser prints the URL for environments where the browser launcher is unavailable. Warp is a C++20/OpenUSD live authoring engine. It keeps authoring data, cooked runtime data, and live player state deliberately separate so the Mac editor, web editor, browser player, native viewport, and automation tools can all operate on the same project without turning the runtime into an OpenUSD editor. At a high level: OpenUSD project package - Authoring server and stage inspector - Cooked scene snapshots, patches, and asset descriptors - EnTT runtime world - Sokol native/web renderer and play mode The current stack includes: - C++20 core, runtime, content pipeline, and authoring services. - OpenUSD for source scenes, composition, layers, relationships, metadata, and DCC-facing data. - SwiftUI macOS editor and native Sokol/Metal viewport. - Browser editor and browser player with Sokol WebGPU/WASM through Emscripten. - gRPC/protobuf live update streams for native/editor/runtime tools. - HTTP and browser APIs for hosted editor, web player, asset upload, content fetches, and remote script editing. - Lua scripting for behavior modules. - Jolt Physics for native and web play mode builds. - Epic Lore backed content/workspace storage when available, with local fallback paths. Design Principles - OpenUSD is the editable source of truth. Runtime code should not open or mutate USD files directly. - Cooked records are the runtime contract. Initial load and live edits use the same data shape. - Live edit patches are narrow. Editors submit authored operations, the server determines affected entities/assets, and runtimes replace only what changed. - Asset bytes are content, not control messages. Large cooked blobs move through content URLs or blob endpoints instead of live edit streams. - There is exactly one derived-artifact system. Every derivation of source content — textures, meshes, audio, physics data, and asset thumbnails — is an immutable, content-addressed artifact whose identity is the asset's Lore content identity plus a versioned recipe (see docs/DERIVED ARTIFACTS.md ). Never add a parallel cache, fingerprint scheme, or cooking path for a new derivation kind, and never derive artifact identity from timestamps, checkout paths, workspace names, or client-supplied parameters. - Selection, inspector state, and hover state are presentation state. They should stay local and responsive in each editor. Asset thumbnails are not presentation state: they are derived artifacts of the asset's content identity, shared across editors and workspaces like any other cooked bytes. - Mac, web, and automation clients should share authoring semantics rather than each inventing a parallel editing path. Project Package A Warp project is a package rooted at a .furryproject directory. Important paths include: Project.furryproject/ project.json Scenes/ Main.usda Layers/ Example.usda Scripts/ PlayerMovement.lua Content/ sha256/ ab/cd/<digest .blob ab/cd/<digest .json Workspaces/ workspaces.json .derived/ project.json records project-level settings such as the content store backend. Source scene data lives under Scenes/ . User-created USD sublayers are created under Scenes/Layers/ . Script source lives under Scripts/ . Content-addressed blobs live under Content/ for local storage, or in Lore storage for Lore-backed projects. Workspaces/workspaces.json stores Warp workspace metadata such as names, parent workspace IDs, base scene paths, archive markers, and author metadata. The durable branch/content backend may be Lore, but Warp keeps enough local metadata to make the editor UI fast and robust. Source Format OpenUSD is the editable authority. It is used for: - prim hierarchy and transforms; - component data authored as USD attributes and relationships; - material and asset bindings; - user sublayers and composition; - muting/enabling layers through USD composition; - DCC interoperability and source asset references. Furry should prefer USD-native relationships and metadata over custom string databases. Asset/material bindings should be represented as relationship targets where possible, and legacy custom string forms are read for compatibility. The hidden Furry asset registry should not appear as ordinary authoring content in scene hierarchy UIs. It is implementation data, not user scene structure. Layers and Edit Targets Layer support is built on native USD composition: - The stage exposes the root layer and direct root sublayers as LayerInfo . - Layer operations include add, remove from root sublayers, move, mute, set active, and clear. - New layers are created under Scenes/Layers/<Name .usda . - Removing a layer removes it from the root layer's subLayerPaths; it does not delete the file. - Muting uses USD layer muting for the scene session. - Active edit targets are tracked per client/author and default to a user-editable layer. Prim edits, component edits, and USD property edits route through the active UsdEditTarget for that client. Web and Mac clients may have different active edit targets at the same time. Toggling or reordering layers must go through USD composition, not a custom override system. Cooked Format Cooked data is the runtime-facing representation of OpenUSD-authored scenes. It contains compact entity, component, asset, material, animation, and script payloads keyed by stable IDs. The main cooked shapes are: - CookedSceneSnapshot : full runtime state for a scene/session. - LiveEditPatch : incremental cooked replacements/deletions plus revision metadata. - CookedAssetBlob : stable asset descriptor with kind, stable asset ID, source URI, content hash, byte sizes, cook status, content key/URL, and optional inline payload. - component payloads encoded by type ID, for example transform, render shape, mesh asset, material binding, audio source, script, physics body, collider, light, and camera. The runtime does not need to know whether cooked records came from first load, a live edit, a script replacement, a layer recomposition, or an uploaded asset finishing its cook. Runtime Format Runtime state is an EnTT registry plus runtime systems: - RuntimeWorld owns entity/component state, simulation state, script system, physics system, and stable ID mappings. - EntityFactory converts cooked component payloads into runtime ECS components. - LiveEditApplier applies snapshot and patch semantics. - LuaScriptSystem runs behavior modules with a restricted API and diagnostics. - PhysicsSystem integrates Jolt when enabled. Runtime transform hierarchy Authored Transform values are always parent-local: translation is measured in metres, rotation is stored in the component's documented angle convention, and scale is multiplicative. The runtime keeps a parent-first hierarchy order and rebuilds that order only when entities are added, removed, or reparented. Each simulation frame captures local transforms into retained storage, lets scripts and Jolt update their authorities, and performs one linear hierarchy composition pass. A dynamic rigid body owns its final world pose; ordinary children then compose their retained local pose from that result. Rendering, runtime queries, cameras, and playtests consume the resulting world transform directly and must not walk the parent chain a second time. This contract is shared by native Metal, WebGPU, iPadOS, Windows, and headless playtests because it lives in RuntimeWorld , below every renderer and client. The runtime is intentionally not a USD host. It consumes snapshots and patches, stores assets by stable asset ID/content hash, and runs the game loop. Authoring Server The OpenUSD authoring side is split into a stage store and an authoring server: - UsdStageStore opens composed stages, reads hierarchy/detail/layer data, applies USD operations, cooks affected source entities/assets, and resolves source files. - AuthoringServer accepts operation diffs, manages revision history, undo/redo, active edit targets, layer operations, script asset replacement, and cooked patch generation. - furry usd inspector exposes composed stage/project data and cooked mesh/sound blob helpers for editor/server processes. - furry edit client and furry tool client are CLI/client helpers for structured edits. The authoring server applies operations to USD first, then exports affected cooked records. It is the boundary that preserves undo/history, revision checks, live diagnostics, and consistent authoring semantics. Live Transport Native/editor communication uses protobuf/gRPC when FURRY ENABLE GRPC=ON . Important streams and calls include: - StreamUpdates : sends snapshots, patches, asset changes, authoring previews, and reset/open-scene events to runtimes. - SubmitUsdDiff : submits prim, component, USD property, asset, script, and layer operation diffs. - OpenScene : switches the active scene/workspace for a client/session. - StreamToolState and SubmitToolEvent : synchronize selection/tool state across clients. - SubmitAuthoringPreview : sends transient preview updates for drag/slider interactions. The browser editor uses HTTP APIs plus web player glue for hosted operation. The live Sokol web player consumes binary snapshots/patches and fetches mesh/sound content on demand. Editors and Clients Web Editor The web editor is served by furry web editor server . It owns the browser UI for: - hierarchy, layer pane, project/assets pane, inspector, AI/agent pane, and viewport frame; - web upload/import flows with per-asset progress; - script editing endpoints and VS Code launch integration; - workspace creation, switching, review flow, and archive UI; - hosted asset/content endpoints; - browser player/viewer URLs. The web editor keeps a local presentation model in JavaScript. Selection and inspector changes are local and immediate; expensive stage/project refreshes happen separately. Mac Editor The macOS editor is SwiftUI. It uses the same authoring and live update semantics, but should behave like the web editor: - local presentation store for selected prims/assets/materials/store items; - precomputed project, material, store, hierarchy, and inspector records; - synchronous selection reducers; - deferred/debounced slider commits and lightweight preview updates; - native share sheet and hosted/web/VS Code launch actions; - native Sokol/Metal viewport process. The Mac editor should not put network refresh, USD scans, asset lookup scans, or broad project recompute in the click/selection path. Remote Script and Agent Tooling Remote script editing is exposed through server endpoints such as: - GET /api/scripts - GET /api/script?module=scripts.Name - PUT /api/script?module=scripts.Name - POST /api/vscode-session Scripts are addressed by flat module names like scripts.PlayerMovement , mapped to Scripts/PlayerMovement.lua . Server-side validation rejects paths, traversal, nested modules for v1, and non-script assets. CLI/agent workflows should prefer structured authoring tools such as ./tools/furry ai for scene/component/script edits when an editor session is active. That preserves live updates, revision checks, undo/history, and diagnostics. Rendering Rendering is provided by SokolRenderer . Native: - Sokol with Metal on Apple platforms. - furry runtime sokol and WarpPlayerMac for native runtime/player builds. - furry runtime viewport plus furry viewport client for the Mac editor viewport process. Web: - Sokol WebGPU backend compiled to WASM through Emscripten/emdawnwebgpu. - Separate editor viewer and play-mode bundles. - Web mesh/sound assets fetched asynchronously by content URL/blob endpoints. - Runtime web audio uses WebAudio callback-style streaming to avoid render-loop buffer starvation. The renderer owns camera controls, transform gizmos, object markers, retained mesh buffers, loading stand-ins, selection/drop overlays, shadow targets, and memory/performance diagnostics. Physics, Scripting, and Audio Lua scripts are ECS system/behavior modules, not general plugins. They run in a sandboxed Lua state with an instruction budget and explicit component-access schemas. Native EnTT queries and schedules reject irrelevant systems before Lua is entered; structural operations commit after module iteration. Dense iteration, hierarchy resolution, continuous motion, physics, rendering, streaming, audio, and animation stay in C++. See docs/SCRIPTING.md . Jolt Physics is integrated through PhysicsSystem and updates runtime transforms in play mode. Runtime refresh paths should update only changed transform data when possible, instead of forcing broad render-item rebuilds each frame. Audio assets are cooked to a stable runtime format. Native and web both use decoded clip data, but web playback uses a WebAudio callback path to avoid crackle/slowdown from render-loop underruns. Asset and Content Flow Assets follow the same source/cooked/runtime boundary: 1. Source assets such as USD, FBX, images, scripts, sounds, and materials are registered in project metadata/USD. 2. The authoring side resolves and cooks them into runtime-ready blobs. 3. Asset descriptors enter snapshots/patches by stable asset ID and content hash. 4. Large binary payloads are published through ContentStore and served by content/blob endpoints. 5. Clients fetch missing blobs asynchronously and keep local caches. 6. Runtime renderers show a loading/failed stand-in without blocking live edit streams. Hosted projects should avoid pushing large binary bytes through live-edit control streams. Web snapshots/patches should carry descriptors and content URLs for meshes/sounds when possible. After decode, web runtimes should release duplicate compressed payloads from WASM-side storage. Content Store The local content store is digest-addressed: Project.furryproject/ Content/ sha256/ ab/ cd/ abcdef....blob abcdef....json Blobs are immutable. Project asset records reference hashes; deleting an asset removes the reference, not necessarily the blob. Garbage collection can remove unreferenced blobs later. Hosted content endpoints should support immutable cache headers, ETag , and range requests for large data. Current endpoints also expose cooked mesh/sound blob helpers for web clients. Lore Integration Lore is an optional durable backend for content and workspace history. It sits below Warp's authoring semantics rather than replacing them. LocalContentStore writes SHA-256 blobs into the project package. LoreContentStore is compiled when CMake is configured with FURRY ENABLE LORE=ON and FURRY LORE ROOT points at an extracted liblore release. Warp projects can declare: { \"contentStore\": \"lore\", \"loreRepository\": \".\" } At runtime, Lore-backed deployments use environment such as: FURRY CONTENT STORE BACKEND=lore FURRY LORE WORKSPACE=/path/to/lore/repository-or-storage-workspace WARP LORE REMOTE URL=https://optional-lore-service.example Compatibility alias: FURRY LORE REMOTE URL Furry talks to Lore native storage APIs for content-addressed storage and branch-style workspaces. Local workspace metadata is still important: it carries Furry names, parent workspace IDs, base scenes, archive markers, and UI-facing metadata. Local archive markers intentionally hide workspaces immediately even if remote Lore archive propagation is slow. Workspaces and Reviews Project workspaces are branch-style editing contexts. The main workspace is the default. Additional workspaces can be created from a parent, opened independently, reviewed, merged, and archived. Key rules: - main cannot be archived. - Archiving hides a workspace from normal picker/list APIs; files/branches are not deleted. - Local archive state wins over stale remote branch state for UI purposes. - Active client counts are presentation/session state maintained by the web server. - Reviews are visible only when both source and target workspaces are still active and the review is not merged/closed. Build Variants CMake options gate heavier integrations: - FURRY BUILD AUTHORING - FURRY ENABLE GRPC - FURRY ENABLE SOKOL - FURRY ENABLE LUA - FURRY ENABLE JOLT - FURRY ENABLE LORE - FURRY ENABLE USD LAYERS - FURRY BUILD IOS PLAYER Common bundles: - furry core : cooked formats, content store, project/workspace metadata, export pipeline. - furry runtime : runtime world, Lua, physics, headless systems. - furry authoring : OpenUSD stage store and authoring server. - furry transport : protobuf/gRPC transport. - furry sokol renderer : shared native/web renderer. - furry web editor server : hosted/local web editor, HTTP APIs, content endpoints. - furry web sokol viewer : Emscripten/WebGPU viewer/player bundle. - furry runtime viewport and furry viewport client : Mac editor viewport runtime. End-to-End Edit Flow Typical prim/component edit: 1. User edits in Mac, web, CLI, VS Code, or agent tooling. 2. Client submits a structured operation with revision, client ID, scene URI, workspace, and active layer. 3. Authoring server validates the operation and chooses the client's active USD edit target. 4. UsdStageStore applies the operation to OpenUSD. 5. The dependency/export path identifies affected entities/assets. 6. The server emits a cooked live patch and revision. 7. Runtime clients apply the patch to ECS state and asset caches. 8. Editors refresh local presentation stores and keep current selection if still valid. Typical asset upload/import: 1. User drops one or more files. 2. The editor creates optimistic asset rows/cards immediately. 3. Source bytes upload to the server/content store. 4. Cook jobs produce runtime blobs and descriptors. 5. Project/stage data refreshes with the new asset records. 6. Renderers fetch cooked blobs on demand and replace loading stand-ins. Invariants - Authoring operations should be structured JSON/protobuf operations, not ad hoc USD text edits, except for explicit one-off migrations. - Runtimes consume cooked data and do not author USD. - Browser and native players should share runtime semantics. - Web performance work should prefer on-demand content, payload release, throttled stats/logging, and lazy GPU resources before reducing render sharpness. - UI selection should be local and instant. - Layer behavior must follow USD composition rules. - Workspaces hide/archive through metadata and branch state, not destructive deletion.",
      "slug": "architecture",
      "source": "docs/ARCHITECTURE.md",
      "sourceHash": "cba8aff4c0d569a1fb2cd38cc09aa22c8944d0c9f28bae650148abe67262af82",
      "summary": "How OpenUSD authoring, cooked content, ECS runtime state, live patches, native clients, and browser clients fit together.",
      "tags": [
        "architecture",
        "openusd",
        "ecs",
        "cooking",
        "streaming"
      ],
      "title": "Architecture"
    },
    {
      "audience": "Client developers",
      "group": "reference",
      "headings": [],
      "html": "<p>Warp clients treat server error codes as a stable wire contract. User-facing copy and actions are defined in <code>src/client/WarpConnectionState.c</code>; clients must not display raw response bodies, HTTP diagnostics, or gRPC diagnostics.</p>\n<p>The shared states cover:</p>\n<ul>\n<li><code>login_required</code> / <code>session_expired</code>: sign in again.</li>\n<li><code>project_access_denied</code>: request access or choose another project.</li>\n<li><code>project_not_found</code>: choose another project.</li>\n<li><code>network_unavailable</code>: retry without discarding local state.</li>\n<li><code>server_error</code>: retry; no mutation is assumed to have succeeded.</li>\n<li><code>invalid_link</code>: ask for a new project link.</li>\n</ul>\n<p>The Mac and iPad players parse HTTP responses through <code>src/runtime/NativeWarpClient.mm</code>. The Mac editor imports the same C state table through <code>WarpClientStateBridge</code>. gRPC stream failures are converted to the same codes by <code>LiveUpdateClient</code>.</p>\n<p>When adding a connection error:</p>\n<ol>\n<li>Add a stable server <code>code</code>.</li>\n<li>Add its state, copy, and permitted actions to <code>WarpConnectionState.c</code>.</li>\n<li>Add classification tests to <code>WarpConnectionStateTests.cpp</code>.</li>\n<li>Keep diagnostics in logs; render only the shared title/message in clients.</li>\n</ol>",
      "icon": "connection",
      "order": 9,
      "searchText": "Client Connection States Warp clients treat server error codes as a stable wire contract. User-facing copy and actions are defined in src/client/WarpConnectionState.c ; clients must not display raw response bodies, HTTP diagnostics, or gRPC diagnostics. The shared states cover: - login required / session expired : sign in again. - project access denied : request access or choose another project. - project not found : choose another project. - network unavailable : retry without discarding local state. - server error : retry; no mutation is assumed to have succeeded. - invalid link : ask for a new project link. The Mac and iPad players parse HTTP responses through src/runtime/NativeWarpClient.mm . The Mac editor imports the same C state table through WarpClientStateBridge . gRPC stream failures are converted to the same codes by LiveUpdateClient . When adding a connection error: 1. Add a stable server code . 2. Add its state, copy, and permitted actions to WarpConnectionState.c . 3. Add classification tests to WarpConnectionStateTests.cpp . 4. Keep diagnostics in logs; render only the shared title/message in clients.",
      "slug": "client-connection-states",
      "source": "docs/CLIENT_CONNECTION_STATES.md",
      "sourceHash": "60aee10967f422ece1f5bed2e2ca4a7d1d9d65de45908016ea1f4fc1ab645b8d",
      "summary": "The shared error and recovery contract used by Warp editors and players on every platform.",
      "tags": [
        "connection",
        "errors",
        "recovery",
        "clients"
      ],
      "title": "Client connection states"
    },
    {
      "audience": "Engine developers",
      "group": "reference",
      "headings": [
        {
          "id": "quick-start",
          "level": 2,
          "title": "Quick Start"
        },
        {
          "id": "cmake-options",
          "level": 2,
          "title": "CMake Options"
        },
        {
          "id": "dependency-map",
          "level": 2,
          "title": "Dependency Map"
        },
        {
          "id": "openusd",
          "level": 2,
          "title": "OpenUSD"
        },
        {
          "id": "grpc-and-protobuf",
          "level": 2,
          "title": "gRPC And Protobuf"
        },
        {
          "id": "web-builds",
          "level": 2,
          "title": "Web Builds"
        },
        {
          "id": "mac-editor-and-hosted-editor",
          "level": 2,
          "title": "Mac Editor And Hosted Editor"
        },
        {
          "id": "packaging-the-mac-editor",
          "level": 3,
          "title": "Packaging The Mac Editor"
        },
        {
          "id": "ipad-player",
          "level": 2,
          "title": "iPad Player"
        },
        {
          "id": "fbx-and-usdfbx",
          "level": 2,
          "title": "FBX And usdFBX"
        },
        {
          "id": "lore-content-store",
          "level": 2,
          "title": "Lore Content Store"
        },
        {
          "id": "vs-code-script-extension",
          "level": 2,
          "title": "VS Code Script Extension"
        },
        {
          "id": "adding-a-dependency",
          "level": 2,
          "title": "Adding A Dependency"
        },
        {
          "id": "troubleshooting",
          "level": 2,
          "title": "Troubleshooting"
        }
      ],
      "html": "<p>Warp has one CMake dependency entry point: <code>cmake/FurryDependencies.cmake</code>. The project is deliberately split between a USD-heavy authoring side and a small cooked-runtime side:</p>\n<ul>\n<li>OpenUSD, usdFBX, the authoring server, inspectors, and mutation tools live on the authoring side.</li>\n<li>The runtime, web player, Mac player, and iPad player consume cooked scene data and live patches. They must not link OpenUSD.</li>\n<li>Small source dependencies are pinned and fetched with CMake <code>FetchContent</code>.</li>\n<li>Heavy SDK-like dependencies are installed or bootstrapped into <code>.derived/</code>.</li>\n</ul>\n<h2 id=\"quick-start\">Quick Start<a class=\"heading-anchor\" href=\"#quick-start\" aria-label=\"Link to Quick Start\">#</a></h2>\n<p>For the normal local Mac authoring stack:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/run_usd_editor.sh</code></pre></div>\n<p>By default that launches the hosted/remote authoring workflow. To build and run the local authoring server instead:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/run_usd_editor.sh --local</code></pre></div>\n<p>For a simple build and test pass of the default OpenUSD authoring pipeline:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/build_and_test.sh</code></pre></div>\n<p>If CMake is not on <code>PATH</code>, bootstrap a local copy:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/bootstrap_cmake.sh</code></pre></div>\n<h2 id=\"cmake-options\">CMake Options<a class=\"heading-anchor\" href=\"#cmake-options\" aria-label=\"Link to CMake Options\">#</a></h2>\n<p>The main build switches are defined in <code>CMakeLists.txt</code>:</p>\n<div class=\"table-scroll\"><table><thead><tr>\n<th>Option</th>\n<th>Default</th>\n<th>Purpose</th>\n</tr></thead><tbody>\n<tr>\n<td><code>FURRY_FETCH_DEPS</code></td>\n<td><code>ON</code></td>\n<td>Fetch pinned source dependencies with <code>FetchContent</code>.</td>\n</tr>\n<tr>\n<td><code>FURRY_BUILD_TESTS</code></td>\n<td><code>ON</code></td>\n<td>Build and register unit tests.</td>\n</tr>\n<tr>\n<td><code>FURRY_BUILD_AUTHORING</code></td>\n<td><code>ON</code></td>\n<td>Build OpenUSD authoring libraries/tools. Turn this off for runtime-only targets.</td>\n</tr>\n<tr>\n<td><code>FURRY_BUILD_EXAMPLES</code></td>\n<td><code>ON</code></td>\n<td>Build runnable sample/player targets when their dependencies are enabled.</td>\n</tr>\n<tr>\n<td><code>FURRY_BUILD_IOS_PLAYER</code></td>\n<td><code>OFF</code></td>\n<td>Build the iPadOS player target.</td>\n</tr>\n<tr>\n<td><code>FURRY_ENABLE_GRPC</code></td>\n<td><code>OFF</code></td>\n<td>Enable protobuf/gRPC transport and generated protocol sources.</td>\n</tr>\n<tr>\n<td><code>FURRY_ENABLE_JOLT</code></td>\n<td><code>OFF</code></td>\n<td>Enable Jolt physics.</td>\n</tr>\n<tr>\n<td><code>FURRY_ENABLE_LUA</code></td>\n<td><code>OFF</code></td>\n<td>Enable Lua script execution.</td>\n</tr>\n<tr>\n<td><code>FURRY_ENABLE_LORE</code></td>\n<td><code>OFF</code></td>\n<td>Enable Epic Lore native content-store integration.</td>\n</tr>\n<tr>\n<td><code>FURRY_ENABLE_USD_LAYERS</code></td>\n<td><code>OFF</code></td>\n<td>Enable USD layer editing and per-author edit targets.</td>\n</tr>\n<tr>\n<td><code>FURRY_ENABLE_SOKOL</code></td>\n<td><code>OFF</code></td>\n<td>Enable Sokol renderer/player targets.</td>\n</tr>\n</tbody></table></div>\n<p>The common presets are:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">cmake --preset dev\ncmake --build --preset dev\nctest --preset dev\n\ncmake --preset with-lua\ncmake --preset with-jolt\ncmake --preset with-sokol\ncmake --preset with-grpc\ncmake --preset full-demo</code></pre></div>\n<p><code>full-demo</code> enables gRPC, Jolt, Lua, and Sokol. It is the preset used by the local editor scripts and the live viewport stack.</p>\n<h2 id=\"dependency-map\">Dependency Map<a class=\"heading-anchor\" href=\"#dependency-map\" aria-label=\"Link to Dependency Map\">#</a></h2>\n<div class=\"table-scroll\"><table><thead><tr>\n<th>Dependency</th>\n<th>Route</th>\n<th>Used By</th>\n<th>Notes</th>\n</tr></thead><tbody>\n<tr>\n<td>OpenUSD</td>\n<td>Local SDK prefix from <code>tools/bootstrap_openusd.sh</code></td>\n<td>Authoring server, USD inspector, project mutation tools</td>\n<td>Enabled when <code>FURRY_BUILD_AUTHORING=ON</code>; runtime targets must not link it.</td>\n</tr>\n<tr>\n<td>EnTT</td>\n<td><code>FetchContent</code>, pinned to <code>v3.15.0</code></td>\n<td>Core/runtime ECS-style storage</td>\n<td>Enabled through <code>FURRY_FETCH_DEPS</code>.</td>\n</tr>\n<tr>\n<td>zlib</td>\n<td>System library, Emscripten port, or <code>find_package(ZLIB)</code></td>\n<td>Cooked asset compression</td>\n<td>Linked through <code>furry_external_zlib</code>.</td>\n</tr>\n<tr>\n<td>miniaudio</td>\n<td>Vendored header under <code>third_party/miniaudio</code></td>\n<td>WAV, FLAC, and MP3 source import plus platform audio device I/O</td>\n<td>No network fetch; public domain or MIT No Attribution. Runtime assets are not stored in a miniaudio-specific format.</td>\n</tr>\n<tr>\n<td>stb_vorbis</td>\n<td><code>FetchContent</code>, pinned to <code>f0569113c93ad095470c54bf34a17b36646bbbb5</code></td>\n<td>Ogg Vorbis source import</td>\n<td>Authoring-only; public domain or MIT. Imported bytes are recooked to Ogg Opus.</td>\n</tr>\n<tr>\n<td>libogg 1.3.5</td>\n<td><code>FetchContent</code>, pinned to <code>v1.3.5</code></td>\n<td>Canonical Ogg Opus container writing, indexing, and bounded page decode</td>\n<td>BSD 3-Clause.</td>\n</tr>\n<tr>\n<td>libopus 1.5.2</td>\n<td><code>FetchContent</code>, pinned to <code>v1.5.2</code></td>\n<td>Canonical 48 kHz Opus encode/decode</td>\n<td>BSD 3-Clause.</td>\n</tr>\n<tr>\n<td>ufbx</td>\n<td>Vendored source under <code>third_party/ufbx</code></td>\n<td>Authoring/import path</td>\n<td>Used by <code>furry_authoring</code>; no runtime OpenUSD dependency.</td>\n</tr>\n<tr>\n<td>Lua 5.4.7</td>\n<td><code>FetchContent</code> URL</td>\n<td>Runtime script system</td>\n<td>Enabled with <code>FURRY_ENABLE_LUA=ON</code>.</td>\n</tr>\n<tr>\n<td>Jolt Physics 5.3.0</td>\n<td><code>FetchContent</code> Git pin</td>\n<td>Runtime physics</td>\n<td>Enabled with <code>FURRY_ENABLE_JOLT=ON</code>; used by native, web player, and iPad builds when enabled.</td>\n</tr>\n<tr>\n<td>Sokol</td>\n<td><code>FetchContent</code> Git pin</td>\n<td>WebGPU/Metal/OpenGL renderer frontends</td>\n<td>Enabled with <code>FURRY_ENABLE_SOKOL=ON</code>.</td>\n</tr>\n<tr>\n<td>protobuf/gRPC</td>\n<td>Installed CMake packages from Homebrew/bootstrap scripts</td>\n<td>Live authoring transport, clients, viewport services, iPad transport</td>\n<td>Enabled with <code>FURRY_ENABLE_GRPC=ON</code>.</td>\n</tr>\n<tr>\n<td>Remedy usdFBX</td>\n<td>Optional OpenUSD plugin under <code>.derived/usdfbx</code></td>\n<td>USD composition/import of FBX source files</td>\n<td>Requires Autodesk FBX SDK. Loaded with <code>PXR_PLUGINPATH_NAME</code>; never linked by runtime targets.</td>\n</tr>\n<tr>\n<td>Epic Lore/liblore</td>\n<td>Optional extracted SDK</td>\n<td>Hosted/native content store backend</td>\n<td>Enabled with <code>FURRY_ENABLE_LORE=ON</code> and <code>FURRY_LORE_ROOT</code>.</td>\n</tr>\n<tr>\n<td>Emscripten + emdawnwebgpu</td>\n<td>External toolchain</td>\n<td>Web Sokol viewer/player</td>\n<td>Required for <code>tools/build_web_sokol_viewer.sh</code>.</td>\n</tr>\n<tr>\n<td>Xcode/iOS SDK</td>\n<td>External Apple toolchain</td>\n<td>Mac editor/player and iPad player</td>\n<td>iPad builds also need signing for device deployment.</td>\n</tr>\n<tr>\n<td>VS Code + Node/npm</td>\n<td>External tools</td>\n<td>Remote Lua script-editing extension</td>\n<td>Packaging uses <code>npx @vscode/vsce</code>; install uses the <code>code</code> CLI.</td>\n</tr>\n</tbody></table></div>\n<h2 id=\"openusd\">OpenUSD<a class=\"heading-anchor\" href=\"#openusd\" aria-label=\"Link to OpenUSD\">#</a></h2>\n<p>OpenUSD is an authoring SDK. Build it into Warp's derived tree:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">OPENUSD_PREFIX=&quot;$(./tools/bootstrap_openusd.sh)&quot;\ncmake --preset dev -DCMAKE_PREFIX_PATH=&quot;$OPENUSD_PREFIX&quot;\ncmake --build --preset dev\nctest --preset dev</code></pre></div>\n<p><code>tools/bootstrap_openusd.sh</code> currently defaults to <code>OPENUSD_VERSION=v26.05</code>. It builds a small USD distribution with no imaging, Python, tools, examples, tutorials, tests, or MaterialX. On first run it can take tens of minutes and several GB under <code>.derived/openusd/</code>.</p>\n<p>After <code>.derived/openusd/install</code> exists, CMake auto-discovers it for authoring builds.</p>\n<h2 id=\"grpc-and-protobuf\">gRPC And Protobuf<a class=\"heading-anchor\" href=\"#grpc-and-protobuf\" aria-label=\"Link to gRPC And Protobuf\">#</a></h2>\n<p>Desktop gRPC uses Homebrew packages:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">GRPC_PREFIX_PATH=&quot;$(./tools/bootstrap_grpc.sh)&quot;\ncmake --preset with-grpc -DCMAKE_PREFIX_PATH=&quot;$GRPC_PREFIX_PATH&quot;\ncmake --build --preset with-grpc</code></pre></div>\n<p><code>tools/bootstrap_grpc.sh</code> installs or finds Homebrew <code>protobuf</code> and <code>grpc</code>, then prints a semicolon-separated prefix path. When enabled, <code>cmake/FurryProto.cmake</code> generates C++ sources from <code>proto/furry_runtime.proto</code>.</p>\n<p>The live editor stack combines gRPC and OpenUSD:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">GRPC_PREFIX_PATH=&quot;$(./tools/bootstrap_grpc.sh)&quot;\nOPENUSD_PREFIX=&quot;$(./tools/bootstrap_openusd.sh)&quot;\ncmake --preset full-demo -DCMAKE_PREFIX_PATH=&quot;$GRPC_PREFIX_PATH;$OPENUSD_PREFIX&quot;\ncmake --build --preset full-demo</code></pre></div>\n<h2 id=\"web-builds\">Web Builds<a class=\"heading-anchor\" href=\"#web-builds\" aria-label=\"Link to Web Builds\">#</a></h2>\n<p>Web targets are runtime-only Emscripten builds with Sokol's WebGPU backend. They do not build authoring tools and they do not link OpenUSD.</p>\n<p>Build both web bundles:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/build_web_sokol_viewer.sh</code></pre></div>\n<p>That script produces:</p>\n<ul>\n<li><code>build/web-sokol-viewer/furry_web_sokol_viewer.html</code></li>\n<li>editor/preview viewer</li>\n<li>Jolt off</li>\n<li>Lua off</li>\n<li><code>build/web-sokol-player/furry_web_sokol_viewer.html</code></li>\n<li>play-mode player</li>\n<li>Jolt on</li>\n<li>Lua on</li>\n</ul>\n<p>Requirements:</p>\n<ul>\n<li><code>emcmake</code> on <code>PATH</code></li>\n<li>Emscripten with the <code>emdawnwebgpu</code> port available</li>\n<li>Python 3 for post-build JS patch scripts</li>\n</ul>\n<p>Useful overrides:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">FURRY_WEB_SOKOL_BUILD_TYPE=Debug ./tools/build_web_sokol_viewer.sh\n\nFURRY_WEB_SOKOL_BUILD_DIR=build/web-custom \\\nFURRY_WEB_ENABLE_JOLT=ON \\\nFURRY_WEB_ENABLE_LUA=ON \\\n  ./tools/build_web_sokol_viewer.sh</code></pre></div>\n<h2 id=\"mac-editor-and-hosted-editor\">Mac Editor And Hosted Editor<a class=\"heading-anchor\" href=\"#mac-editor-and-hosted-editor\" aria-label=\"Link to Mac Editor And Hosted Editor\">#</a></h2>\n<p>The Mac editor is a SwiftUI package under <code>apps/usd_editor</code>. The launcher builds the C++ tools first, then starts Swift with the correct paths:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/run_usd_editor.sh</code></pre></div>\n<p>For the fastest edit-build-test loop, rebuild only the native clients. This is incremental, does not clean or reconfigure an existing build, and does not build or start any local server:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/build_mac_clients.sh</code></pre></div>\n<p>Use <code>--launch</code> to open both rebuilt apps, <code>--editor-only</code> or <code>--player-only</code> to build one client, and <code>--clean</code> only when an explicit clean rebuild is needed.</p>\n<p>Hosted mode connects to the remote authoring and web services by default and launches both the native Mac editor and the native Metal/Sokol Mac player against the same hosted scene and workspace:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/run_hosted_usd_editor.sh</code></pre></div>\n<p>Set <code>FURRY_USE_LOCAL_WEB_EDITOR=1</code> when local web assets are specifically needed. That opt-in also adds <code>furry_web_editor_server</code> to the targeted build.</p>\n<p>Common environment variables:</p>\n<div class=\"table-scroll\"><table><thead><tr>\n<th>Variable</th>\n<th>Purpose</th>\n</tr></thead><tbody>\n<tr>\n<td><code>WARP_HOST</code> / <code>FURRY_HOST</code></td>\n<td>Host for the remote editor service, defaulting to <code>warp.billrey.net</code>.</td>\n</tr>\n<tr>\n<td><code>WARP_ADDRESS</code> / <code>FURRY_ADDRESS</code></td>\n<td>gRPC address, default <code>grpcs://$HOST:443</code>.</td>\n</tr>\n<tr>\n<td><code>WARP_PROJECT_SERVER_URL</code> / <code>FURRY_PROJECT_SERVER_URL</code></td>\n<td>HTTP project/web service base URL.</td>\n</tr>\n<tr>\n<td><code>WARP_WEB_EDITOR_URL</code> / <code>FURRY_WEB_EDITOR_URL</code></td>\n<td>URL passed to the Mac editor for opening web views.</td>\n</tr>\n<tr>\n<td><code>FURRY_USE_LOCAL_WEB_EDITOR</code></td>\n<td><code>1</code> to build and serve local web assets, <code>0</code> to use the remote web editor URL (default).</td>\n</tr>\n<tr>\n<td><code>FURRY_SCENE</code></td>\n<td>Explicit scene path to open.</td>\n</tr>\n<tr>\n<td><code>WARP_WORKSPACE</code> / <code>FURRY_WORKSPACE</code></td>\n<td>Explicit hosted workspace for both the editor and player.</td>\n</tr>\n<tr>\n<td><code>FURRY_CLIENT_ID</code></td>\n<td>Author/client id for live edits and active layer state.</td>\n</tr>\n<tr>\n<td><code>WARP_LAUNCH_MAC_PLAYER</code> / <code>FURRY_LAUNCH_MAC_PLAYER</code></td>\n<td><code>1</code> to launch the native Mac player (default), <code>0</code> for editor-only startup.</td>\n</tr>\n<tr>\n<td><code>WARP_PLAYER_CLIENT_ID</code> / <code>FURRY_PLAYER_CLIENT_ID</code></td>\n<td>Client id used by the spawned native Mac player.</td>\n</tr>\n<tr>\n<td><code>FURRY_LORE_ROOT</code></td>\n<td>Location of an extracted Lore/liblore SDK.</td>\n</tr>\n</tbody></table></div>\n<p>On Apple platforms, native Sokol uses Metal and links the needed Apple frameworks from CMake.</p>\n<h3 id=\"packaging-the-mac-editor\">Packaging The Mac Editor<a class=\"heading-anchor\" href=\"#packaging-the-mac-editor\" aria-label=\"Link to Packaging The Mac Editor\">#</a></h3>\n<p>The direct-download Mac editor package is produced with:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/package_mac_editor.sh</code></pre></div>\n<p>This builds the hosted/cloud editor bundle, copies the Swift editor plus native helper tools into <code>Warp Editor.app</code>, vendors non-system dylibs into <code>Contents/Frameworks</code>, signs the bundle, and writes a DMG under <code>dist/mac-editor/</code>.</p>\n<p>For local smoke testing without a Developer ID certificate, the script falls back to ad-hoc signing:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/package_mac_editor.sh --no-notarize</code></pre></div>\n<p>For public distribution, install a Developer ID Application certificate and create or refresh the notarytool Keychain profile with the repository helper. It validates the credentials before saving them and prompts securely for the app-specific password, so the password does not enter shell history:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/setup_mac_notarization.sh --apple-id YOU@example.com</code></pre></div>\n<p>For an App Store Connect API key, which is better suited to durable automated releases:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/setup_mac_notarization.sh \\\n  --key /secure/path/AuthKey_KEYID.p8 \\\n  --key-id KEYID \\\n  --issuer ISSUER_UUID</code></pre></div>\n<p>The equivalent manual Apple ID command is:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">xcrun notarytool store-credentials warp-notary \\\n  --apple-id you@example.com \\\n  --team-id TEAMID</code></pre></div>\n<p>Then build the signed, notarized DMG:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">WARP_MAC_SIGN_IDENTITY=&quot;Developer ID Application: Your Name (TEAMID)&quot; \\\nWARP_NOTARY_KEYCHAIN_PROFILE=warp-notary \\\n./tools/package_mac_editor.sh --notarize</code></pre></div>\n<p>The generated DMG contains the app and an Applications symlink for drag-install. Public packaging first notarizes and staples the app, then embeds that exact app in the signed DMG and notarizes and staples the DMG. Submission results and full Apple notary logs are retained under <code>dist/mac-editor/notary-logs/</code> or <code>dist/mac-player/notary-logs/</code>. The release fails if either ticket, Gatekeeper assessment, or the app mounted from the final DMG does not validate.</p>\n<h2 id=\"ipad-player\">iPad Player<a class=\"heading-anchor\" href=\"#ipad-player\" aria-label=\"Link to iPad Player\">#</a></h2>\n<p>The iPad player is a runtime-only Xcode build. Build for device:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/build_ios_player.sh</code></pre></div>\n<p>Build for simulator:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">FURRY_IOS_VARIANT=simulator ./tools/build_ios_player.sh</code></pre></div>\n<p>The iOS build uses:</p>\n<ul>\n<li><code>tools/bootstrap_grpc_ios.sh</code> for iOS gRPC libraries</li>\n<li>host-side <code>protoc</code> and <code>grpc_cpp_plugin</code> built from the matching pinned source</li>\n<li>Xcode and the iOS SDK</li>\n<li><code>FURRY_IOS_DEVELOPMENT_TEAM</code> for device signing, or an Apple Development identity discoverable through Keychain</li>\n</ul>\n<p>The generated Xcode project is written under <code>build/ios-player-device/</code> or <code>build/ios-player-simulator/</code>.</p>\n<h2 id=\"fbx-and-usdfbx\">FBX And usdFBX<a class=\"heading-anchor\" href=\"#fbx-and-usdfbx\" aria-label=\"Link to FBX And usdFBX\">#</a></h2>\n<p>FBX is authoring/source data, not runtime data. Furry can use two paths:</p>\n<ul>\n<li><code>ufbx</code> for direct import/cooking in the authoring code.</li>\n<li>Remedy <code>usdFBX</code> as an OpenUSD file-format plugin so USD layers can reference <code>.fbx</code> files and compose them through USD.</li>\n</ul>\n<p>Build the usdFBX plugin after OpenUSD is available:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">OPENUSD_PREFIX=&quot;$(./tools/bootstrap_openusd.sh)&quot;\nADSK_FBX_LOCATION=&quot;/Applications/Autodesk/FBX SDK/2020.3.7&quot; \\\n  ./tools/bootstrap_usdfbx.sh &quot;$OPENUSD_PREFIX&quot;</code></pre></div>\n<p>The script installs plugin resources under:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">.derived/usdfbx/install/usdFbx/resources</code></pre></div>\n<p>Use it by setting <code>PXR_PLUGINPATH_NAME</code>, or let <code>tools/run_usd_editor.sh --local</code> do it automatically:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">export PXR_PLUGINPATH_NAME=&quot;$PWD/.derived/usdfbx/install/usdFbx/resources${PXR_PLUGINPATH_NAME:+:$PXR_PLUGINPATH_NAME}&quot;</code></pre></div>\n<p>The runtime never links usdFBX, the Autodesk FBX SDK, or OpenUSD.</p>\n<h2 id=\"lore-content-store\">Lore Content Store<a class=\"heading-anchor\" href=\"#lore-content-store\" aria-label=\"Link to Lore Content Store\">#</a></h2>\n<p>Lore is optional. If <code>FURRY_LORE_ROOT</code> or <code>.derived/lore-sdk-inspect</code> contains <code>lore.h</code> plus <code>liblore.dylib</code> or <code>liblore.so</code>, the editor launchers pass:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">-DFURRY_ENABLE_LORE=ON -DFURRY_LORE_ROOT=&lt;path&gt;</code></pre></div>\n<p>The scripts also extend <code>DYLD_LIBRARY_PATH</code>/<code>LD_LIBRARY_PATH</code> so the native tools can load the library. Builds without Lore still use the local content store path.</p>\n<h2 id=\"vs-code-script-extension\">VS Code Script Extension<a class=\"heading-anchor\" href=\"#vs-code-script-extension\" aria-label=\"Link to VS Code Script Extension\">#</a></h2>\n<p>The remote script editing workflow is implemented as a small VS Code extension under <code>tools/vscode-furry-scripts</code>.</p>\n<p>Install it locally:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/install_vscode_furry_scripts.sh</code></pre></div>\n<p>Package a reusable <code>.vsix</code> without installing:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/package_vscode_furry_scripts.sh</code></pre></div>\n<p>Requirements:</p>\n<ul>\n<li>VS Code 1.90 or newer</li>\n<li>the <code>code</code> CLI on <code>PATH</code> for install</li>\n<li>Node.js and npm for packaging</li>\n<li>network access for <code>npx --yes @vscode/vsce package</code></li>\n</ul>\n<p>For extension development, install a live symlink:</p>\n<div class=\"code-block\"><div class=\"code-toolbar\"><span>sh</span><button type=\"button\" data-copy-code aria-label=\"Copy code\">Copy</button></div><pre><code class=\"language-sh\">./tools/install_vscode_furry_scripts.sh --dev-link</code></pre></div>\n<h2 id=\"adding-a-dependency\">Adding A Dependency<a class=\"heading-anchor\" href=\"#adding-a-dependency\" aria-label=\"Link to Adding A Dependency\">#</a></h2>\n<ol>\n<li>Add a narrow CMake option in <code>CMakeLists.txt</code> if the dependency is optional.</li>\n<li>Add an interface target in <code>cmake/FurryDependencies.cmake</code>.</li>\n<li>Prefer <code>FetchContent</code> for small source dependencies and an installed prefix or bootstrap script for heavy SDKs.</li>\n<li>Link only the smallest target that actually needs the dependency.</li>\n<li>Keep authoring-only dependencies out of runtime targets.</li>\n<li>Add or update a preset/script when the dependency is part of a common workflow.</li>\n<li>Update this document with the exact bootstrap route and relevant environment variables.</li>\n</ol>\n<h2 id=\"troubleshooting\">Troubleshooting<a class=\"heading-anchor\" href=\"#troubleshooting\" aria-label=\"Link to Troubleshooting\">#</a></h2>\n<ul>\n<li><code>find_package(pxr)</code> fails: run <code>./tools/bootstrap_openusd.sh</code>, or pass its printed prefix through <code>-DCMAKE_PREFIX_PATH</code>.</li>\n<li>Web build cannot find <code>emcmake</code>: install and activate Emscripten before running <code>tools/build_web_sokol_viewer.sh</code>.</li>\n<li>gRPC configure fails: run <code>./tools/bootstrap_grpc.sh</code> and pass the printed prefixes to <code>CMAKE_PREFIX_PATH</code>.</li>\n<li>usdFBX fails to configure: install Autodesk FBX SDK and set <code>ADSK_FBX_LOCATION</code>.</li>\n<li>FBX references do not compose in USD: make sure <code>PXR_PLUGINPATH_NAME</code> includes <code>.derived/usdfbx/install/usdFbx/resources</code>.</li>\n<li>iPad device signing fails: set <code>FURRY_IOS_DEVELOPMENT_TEAM=&lt;TEAMID&gt;</code> or sign into Xcode with an Apple Development account.</li>\n<li>Runtime binaries link OpenUSD: that is a dependency bug. Keep OpenUSD on the authoring side and pass cooked/runtime data across the live-edit protocol.</li>\n</ul>",
      "icon": "build",
      "order": 10,
      "searchText": "Dependencies Warp has one CMake dependency entry point: cmake/FurryDependencies.cmake . The project is deliberately split between a USD-heavy authoring side and a small cooked-runtime side: - OpenUSD, usdFBX, the authoring server, inspectors, and mutation tools live on the authoring side. - The runtime, web player, Mac player, and iPad player consume cooked scene data and live patches. They must not link OpenUSD. - Small source dependencies are pinned and fetched with CMake FetchContent . - Heavy SDK-like dependencies are installed or bootstrapped into .derived/ . Quick Start For the normal local Mac authoring stack: ./tools/run usd editor.sh By default that launches the hosted/remote authoring workflow. To build and run the local authoring server instead: ./tools/run usd editor.sh --local For a simple build and test pass of the default OpenUSD authoring pipeline: ./tools/build and test.sh If CMake is not on PATH , bootstrap a local copy: ./tools/bootstrap cmake.sh CMake Options The main build switches are defined in CMakeLists.txt : Option Default Purpose --- --- --- FURRY FETCH DEPS ON Fetch pinned source dependencies with FetchContent . FURRY BUILD TESTS ON Build and register unit tests. FURRY BUILD AUTHORING ON Build OpenUSD authoring libraries/tools. Turn this off for runtime-only targets. FURRY BUILD EXAMPLES ON Build runnable sample/player targets when their dependencies are enabled. FURRY BUILD IOS PLAYER OFF Build the iPadOS player target. FURRY ENABLE GRPC OFF Enable protobuf/gRPC transport and generated protocol sources. FURRY ENABLE JOLT OFF Enable Jolt physics. FURRY ENABLE LUA OFF Enable Lua script execution. FURRY ENABLE LORE OFF Enable Epic Lore native content-store integration. FURRY ENABLE USD LAYERS OFF Enable USD layer editing and per-author edit targets. FURRY ENABLE SOKOL OFF Enable Sokol renderer/player targets. The common presets are: cmake --preset dev cmake --build --preset dev ctest --preset dev cmake --preset with-lua cmake --preset with-jolt cmake --preset with-sokol cmake --preset with-grpc cmake --preset full-demo full-demo enables gRPC, Jolt, Lua, and Sokol. It is the preset used by the local editor scripts and the live viewport stack. Dependency Map Dependency Route Used By Notes --- --- --- --- OpenUSD Local SDK prefix from tools/bootstrap openusd.sh Authoring server, USD inspector, project mutation tools Enabled when FURRY BUILD AUTHORING=ON ; runtime targets must not link it. EnTT FetchContent , pinned to v3.15.0 Core/runtime ECS-style storage Enabled through FURRY FETCH DEPS . zlib System library, Emscripten port, or find package(ZLIB) Cooked asset compression Linked through furry external zlib . miniaudio Vendored header under third party/miniaudio WAV, FLAC, and MP3 source import plus platform audio device I/O No network fetch; public domain or MIT No Attribution. Runtime assets are not stored in a miniaudio-specific format. stb vorbis FetchContent , pinned to f0569113c93ad095470c54bf34a17b36646bbbb5 Ogg Vorbis source import Authoring-only; public domain or MIT. Imported bytes are recooked to Ogg Opus. libogg 1.3.5 FetchContent , pinned to v1.3.5 Canonical Ogg Opus container writing, indexing, and bounded page decode BSD 3-Clause. libopus 1.5.2 FetchContent , pinned to v1.5.2 Canonical 48 kHz Opus encode/decode BSD 3-Clause. ufbx Vendored source under third party/ufbx Authoring/import path Used by furry authoring ; no runtime OpenUSD dependency. Lua 5.4.7 FetchContent URL Runtime script system Enabled with FURRY ENABLE LUA=ON . Jolt Physics 5.3.0 FetchContent Git pin Runtime physics Enabled with FURRY ENABLE JOLT=ON ; used by native, web player, and iPad builds when enabled. Sokol FetchContent Git pin WebGPU/Metal/OpenGL renderer frontends Enabled with FURRY ENABLE SOKOL=ON . protobuf/gRPC Installed CMake packages from Homebrew/bootstrap scripts Live authoring transport, clients, viewport services, iPad transport Enabled with FURRY ENABLE GRPC=ON . Remedy usdFBX Optional OpenUSD plugin under .derived/usdfbx USD composition/import of FBX source files Requires Autodesk FBX SDK. Loaded with PXR PLUGINPATH NAME ; never linked by runtime targets. Epic Lore/liblore Optional extracted SDK Hosted/native content store backend Enabled with FURRY ENABLE LORE=ON and FURRY LORE ROOT . Emscripten + emdawnwebgpu External toolchain Web Sokol viewer/player Required for tools/build web sokol viewer.sh . Xcode/iOS SDK External Apple toolchain Mac editor/player and iPad player iPad builds also need signing for device deployment. VS Code + Node/npm External tools Remote Lua script-editing extension Packaging uses npx @vscode/vsce ; install uses the code CLI. OpenUSD OpenUSD is an authoring SDK. Build it into Warp's derived tree: OPENUSD PREFIX=\"$(./tools/bootstrap openusd.sh)\" cmake --preset dev -DCMAKE PREFIX PATH=\"$OPENUSD PREFIX\" cmake --build --preset dev ctest --preset dev tools/bootstrap openusd.sh currently defaults to OPENUSD VERSION=v26.05 . It builds a small USD distribution with no imaging, Python, tools, examples, tutorials, tests, or MaterialX. On first run it can take tens of minutes and several GB under .derived/openusd/ . After .derived/openusd/install exists, CMake auto-discovers it for authoring builds. gRPC And Protobuf Desktop gRPC uses Homebrew packages: GRPC PREFIX PATH=\"$(./tools/bootstrap grpc.sh)\" cmake --preset with-grpc -DCMAKE PREFIX PATH=\"$GRPC PREFIX PATH\" cmake --build --preset with-grpc tools/bootstrap grpc.sh installs or finds Homebrew protobuf and grpc , then prints a semicolon-separated prefix path. When enabled, cmake/FurryProto.cmake generates C++ sources from proto/furry runtime.proto . The live editor stack combines gRPC and OpenUSD: GRPC PREFIX PATH=\"$(./tools/bootstrap grpc.sh)\" OPENUSD PREFIX=\"$(./tools/bootstrap openusd.sh)\" cmake --preset full-demo -DCMAKE PREFIX PATH=\"$GRPC PREFIX PATH;$OPENUSD PREFIX\" cmake --build --preset full-demo Web Builds Web targets are runtime-only Emscripten builds with Sokol's WebGPU backend. They do not build authoring tools and they do not link OpenUSD. Build both web bundles: ./tools/build web sokol viewer.sh That script produces: - build/web-sokol-viewer/furry web sokol viewer.html - editor/preview viewer - Jolt off - Lua off - build/web-sokol-player/furry web sokol viewer.html - play-mode player - Jolt on - Lua on Requirements: - emcmake on PATH - Emscripten with the emdawnwebgpu port available - Python 3 for post-build JS patch scripts Useful overrides: FURRY WEB SOKOL BUILD TYPE=Debug ./tools/build web sokol viewer.sh FURRY WEB SOKOL BUILD DIR=build/web-custom \\ FURRY WEB ENABLE JOLT=ON \\ FURRY WEB ENABLE LUA=ON \\ ./tools/build web sokol viewer.sh Mac Editor And Hosted Editor The Mac editor is a SwiftUI package under apps/usd editor . The launcher builds the C++ tools first, then starts Swift with the correct paths: ./tools/run usd editor.sh For the fastest edit-build-test loop, rebuild only the native clients. This is incremental, does not clean or reconfigure an existing build, and does not build or start any local server: ./tools/build mac clients.sh Use --launch to open both rebuilt apps, --editor-only or --player-only to build one client, and --clean only when an explicit clean rebuild is needed. Hosted mode connects to the remote authoring and web services by default and launches both the native Mac editor and the native Metal/Sokol Mac player against the same hosted scene and workspace: ./tools/run hosted usd editor.sh Set FURRY USE LOCAL WEB EDITOR=1 when local web assets are specifically needed. That opt-in also adds furry web editor server to the targeted build. Common environment variables: Variable Purpose --- --- WARP HOST / FURRY HOST Host for the remote editor service, defaulting to warp.billrey.net . WARP ADDRESS / FURRY ADDRESS gRPC address, default grpcs://$HOST:443 . WARP PROJECT SERVER URL / FURRY PROJECT SERVER URL HTTP project/web service base URL. WARP WEB EDITOR URL / FURRY WEB EDITOR URL URL passed to the Mac editor for opening web views. FURRY USE LOCAL WEB EDITOR 1 to build and serve local web assets, 0 to use the remote web editor URL (default). FURRY SCENE Explicit scene path to open. WARP WORKSPACE / FURRY WORKSPACE Explicit hosted workspace for both the editor and player. FURRY CLIENT ID Author/client id for live edits and active layer state. WARP LAUNCH MAC PLAYER / FURRY LAUNCH MAC PLAYER 1 to launch the native Mac player (default), 0 for editor-only startup. WARP PLAYER CLIENT ID / FURRY PLAYER CLIENT ID Client id used by the spawned native Mac player. FURRY LORE ROOT Location of an extracted Lore/liblore SDK. On Apple platforms, native Sokol uses Metal and links the needed Apple frameworks from CMake. Packaging The Mac Editor The direct-download Mac editor package is produced with: ./tools/package mac editor.sh This builds the hosted/cloud editor bundle, copies the Swift editor plus native helper tools into Warp Editor.app , vendors non-system dylibs into Contents/Frameworks , signs the bundle, and writes a DMG under dist/mac-editor/ . For local smoke testing without a Developer ID certificate, the script falls back to ad-hoc signing: ./tools/package mac editor.sh --no-notarize For public distribution, install a Developer ID Application certificate and create or refresh the notarytool Keychain profile with the repository helper. It validates the credentials before saving them and prompts securely for the app-specific password, so the password does not enter shell history: ./tools/setup mac notarization.sh --apple-id YOU@example.com For an App Store Connect API key, which is better suited to durable automated releases: ./tools/setup mac notarization.sh \\ --key /secure/path/AuthKey KEYID.p8 \\ --key-id KEYID \\ --issuer ISSUER UUID The equivalent manual Apple ID command is: xcrun notarytool store-credentials warp-notary \\ --apple-id you@example.com \\ --team-id TEAMID Then build the signed, notarized DMG: WARP MAC SIGN IDENTITY=\"Developer ID Application: Your Name (TEAMID)\" \\ WARP NOTARY KEYCHAIN PROFILE=warp-notary \\ ./tools/package mac editor.sh --notarize The generated DMG contains the app and an Applications symlink for drag-install. Public packaging first notarizes and staples the app, then embeds that exact app in the signed DMG and notarizes and staples the DMG. Submission results and full Apple notary logs are retained under dist/mac-editor/notary-logs/ or dist/mac-player/notary-logs/ . The release fails if either ticket, Gatekeeper assessment, or the app mounted from the final DMG does not validate. iPad Player The iPad player is a runtime-only Xcode build. Build for device: ./tools/build ios player.sh Build for simulator: FURRY IOS VARIANT=simulator ./tools/build ios player.sh The iOS build uses: - tools/bootstrap grpc ios.sh for iOS gRPC libraries - host-side protoc and grpc cpp plugin built from the matching pinned source - Xcode and the iOS SDK - FURRY IOS DEVELOPMENT TEAM for device signing, or an Apple Development identity discoverable through Keychain The generated Xcode project is written under build/ios-player-device/ or build/ios-player-simulator/ . FBX And usdFBX FBX is authoring/source data, not runtime data. Furry can use two paths: - ufbx for direct import/cooking in the authoring code. - Remedy usdFBX as an OpenUSD file-format plugin so USD layers can reference .fbx files and compose them through USD. Build the usdFBX plugin after OpenUSD is available: OPENUSD PREFIX=\"$(./tools/bootstrap openusd.sh)\" ADSK FBX LOCATION=\"/Applications/Autodesk/FBX SDK/2020.3.7\" \\ ./tools/bootstrap usdfbx.sh \"$OPENUSD PREFIX\" The script installs plugin resources under: .derived/usdfbx/install/usdFbx/resources Use it by setting PXR PLUGINPATH NAME , or let tools/run usd editor.sh --local do it automatically: export PXR PLUGINPATH NAME=\"$PWD/.derived/usdfbx/install/usdFbx/resources${PXR PLUGINPATH NAME:+:$PXR PLUGINPATH NAME}\" The runtime never links usdFBX, the Autodesk FBX SDK, or OpenUSD. Lore Content Store Lore is optional. If FURRY LORE ROOT or .derived/lore-sdk-inspect contains lore.h plus liblore.dylib or liblore.so , the editor launchers pass: -DFURRY ENABLE LORE=ON -DFURRY LORE ROOT=<path The scripts also extend DYLD LIBRARY PATH / LD LIBRARY PATH so the native tools can load the library. Builds without Lore still use the local content store path. VS Code Script Extension The remote script editing workflow is implemented as a small VS Code extension under tools/vscode-furry-scripts . Install it locally: ./tools/install vscode furry scripts.sh Package a reusable .vsix without installing: ./tools/package vscode furry scripts.sh Requirements: - VS Code 1.90 or newer - the code CLI on PATH for install - Node.js and npm for packaging - network access for npx --yes @vscode/vsce package For extension development, install a live symlink: ./tools/install vscode furry scripts.sh --dev-link Adding A Dependency 1. Add a narrow CMake option in CMakeLists.txt if the dependency is optional. 2. Add an interface target in cmake/FurryDependencies.cmake . 3. Prefer FetchContent for small source dependencies and an installed prefix or bootstrap script for heavy SDKs. 4. Link only the smallest target that actually needs the dependency. 5. Keep authoring-only dependencies out of runtime targets. 6. Add or update a preset/script when the dependency is part of a common workflow. 7. Update this document with the exact bootstrap route and relevant environment variables. Troubleshooting - find package(pxr) fails: run ./tools/bootstrap openusd.sh , or pass its printed prefix through -DCMAKE PREFIX PATH . - Web build cannot find emcmake : install and activate Emscripten before running tools/build web sokol viewer.sh . - gRPC configure fails: run ./tools/bootstrap grpc.sh and pass the printed prefixes to CMAKE PREFIX PATH . - usdFBX fails to configure: install Autodesk FBX SDK and set ADSK FBX LOCATION . - FBX references do not compose in USD: make sure PXR PLUGINPATH NAME includes .derived/usdfbx/install/usdFbx/resources . - iPad device signing fails: set FURRY IOS DEVELOPMENT TEAM=<TEAMID or sign into Xcode with an Apple Development account. - Runtime binaries link OpenUSD: that is a dependency bug. Keep OpenUSD on the authoring side and pass cooked/runtime data across the live-edit protocol.",
      "slug": "dependencies-and-builds",
      "source": "docs/DEPENDENCIES.md",
      "sourceHash": "4bdddd4ec95464f3443751b17968244a0e306b3c9bbccb5c7f48acd996613dac",
      "summary": "Build presets, platform toolchains, dependency boundaries, packaging, and runtime-only configurations.",
      "tags": [
        "build",
        "cmake",
        "dependencies",
        "mac",
        "web",
        "ipad",
        "windows",
        "linux"
      ],
      "title": "Dependencies and builds"
    }
  ],
  "engineVersion": "0.1.1",
  "generatorVersion": 1,
  "groups": [
    {
      "description": "Open Warp and make your first live change.",
      "id": "start",
      "title": "Start here"
    },
    {
      "description": "Author scenes and orchestrate gameplay.",
      "id": "create",
      "title": "Create"
    },
    {
      "description": "Share exact project and runtime context.",
      "id": "collaborate",
      "title": "Collaborate"
    },
    {
      "description": "Understand and extend the engine.",
      "id": "reference",
      "title": "Technical reference"
    }
  ],
  "schemaVersion": 1,
  "sourceRevision": "5f713c6c39e0da4d1697cd87df3f759a18120b21776892a83a0a4f3f87252308",
  "title": "Warp Documentation",
  "verification": {
    "luaApi": {
      "documentedWorldMethodCount": 115,
      "helpers": [
        "furry.behavior",
        "furry.system",
        "furry.random"
      ],
      "methods": [
        "world:add_to_group",
        "world:after",
        "world:apply_angular_impulse",
        "world:apply_impulse",
        "world:asset_id",
        "world:autoplay",
        "world:cancel_timer",
        "world:children",
        "world:destroy",
        "world:destroy_after",
        "world:distance",
        "world:emit",
        "world:enabled",
        "world:enabled_self",
        "world:entity_state_get",
        "world:entity_state_set",
        "world:events",
        "world:every",
        "world:find_name",
        "world:find_path",
        "world:find_transform_overlap",
        "world:follow",
        "world:get_angular_velocity",
        "world:get_linear_velocity",
        "world:get_transform",
        "world:get_world_transform",
        "world:has_component",
        "world:id",
        "world:input",
        "world:input_axis",
        "world:is_alive",
        "world:is_enabled",
        "world:is_in_group",
        "world:is_on_ground",
        "world:jump_pressed",
        "world:load_scene",
        "world:look_at",
        "world:look_delta",
        "world:loop",
        "world:module",
        "world:move_by_input",
        "world:move_by_input_2d",
        "world:move_by_input_3d",
        "world:name",
        "world:orbit",
        "world:parent",
        "world:path",
        "world:pause_animation",
        "world:ping_pong",
        "world:play_animation",
        "world:play_animation_clip_range",
        "world:play_animation_range",
        "world:play_sound",
        "world:playing",
        "world:preload_asset",
        "world:primary_down",
        "world:primary_pressed",
        "world:pulse",
        "world:query",
        "world:query_count",
        "world:query_first",
        "world:raycast",
        "world:release_asset",
        "world:remove_from_group",
        "world:reparent",
        "world:resume_animation",
        "world:root",
        "world:rotate",
        "world:rotation_w",
        "world:rotation_x",
        "world:rotation_y",
        "world:rotation_z",
        "world:scale_by",
        "world:scale_x",
        "world:scale_y",
        "world:scale_z",
        "world:secondary_down",
        "world:secondary_pressed",
        "world:set_angular_velocity",
        "world:set_character_movement",
        "world:set_character_velocity",
        "world:set_enabled",
        "world:set_linear_velocity",
        "world:set_loop_sound",
        "world:set_transform",
        "world:set_velocity_2d",
        "world:set_world_transform",
        "world:spawn",
        "world:spawn_effect",
        "world:spin",
        "world:state_add",
        "world:state_get",
        "world:state_set",
        "world:state_toggle",
        "world:stop_animation",
        "world:stop_motion",
        "world:stop_uv_animation",
        "world:sweep_box",
        "world:tilt",
        "world:time",
        "world:touch",
        "world:transform_direction",
        "world:transform_point",
        "world:translate",
        "world:translation_x",
        "world:translation_y",
        "world:translation_z",
        "world:tween_transform",
        "world:tween_uv",
        "world:ui_focus",
        "world:ui_set_enabled",
        "world:ui_set_text",
        "world:ui_set_visible",
        "world:view",
        "world:volume"
      ],
      "reference": "docs/SCRIPTING.md",
      "source": "src/runtime/LuaScriptSystem.cpp",
      "worldMethodCount": 115
    }
  }
}
