{
  "name": "BubGorge SDK",
  "version": "M31-F4",
  "schema_version": 1,
  "protocol_version": 4,
  "kernel_version": "0.1.0",
  "generated_at": null,
  "global_entry": "bubgorge",
  "docs": "/sdk-manifest.json",
  "design_docs": [],
  "center_flow": {
    "description": "M31 #75 coordinated client-to-center flow admission. This reserves write resources before any private upload; it does not validate or authorize the UGC content itself.",
    "signing_domain": "bubgorge-center-flow-v1",
    "canonical_encoding": "dag-cbor",
    "signature_algorithm": "ML-DSA-65",
    "tagged_author_public_key": "0x01 || ML-DSA-65 public key bytes",
    "tagged_author_signature": "0x01 || ML-DSA-65 signature bytes",
    "outer_request": {
      "ordinary": {
        "intent": "unpadded base64url canonical dag-cbor intent",
        "author_pubkey": "unpadded base64url tagged author public key",
        "signature": "unpadded base64url tagged signature over UTF-8 signing_domain + LF + canonical intent bytes"
      },
      "profile_bootstrap": {
        "intent": "unpadded base64url canonical dag-cbor intent",
        "author_pubkey": "unpadded base64url tagged author public key",
        "signature": "unpadded base64url tagged signature over UTF-8 signing_domain + LF + canonical intent bytes",
        "turnstile_token": "single-use Turnstile token for action profile_admission"
      }
    },
    "intent_common_fields": {
      "version": 1,
      "audience": "canonical center origin",
      "action": "profile_admission | publish_session",
      "author_pubkey_cid": "raw CID of the complete tagged author public-key blob",
      "profile_cid": "candidate genesis profile for profile_admission; center-current admitted profile for newly constructed publish_session requests",
      "declared_object_count": "positive safe integer",
      "declared_total_bytes": "positive safe integer",
      "idempotency_key": "UUID",
      "issued_at": "Unix epoch milliseconds",
      "expires_at": "Unix epoch milliseconds"
    },
    "ordinary_intent_extra_fields": {
      "root_cid": "dag-cbor CID of the exact signed root to publish"
    },
    "replay_rule": "After canonical verification and proof of possession, repeating (author_pubkey_cid, idempotency_key) returns the prior result only for the identical canonical intent digest, including after new-request intent expiry while durable state remains; a replay miss must pass freshness/current policy, and any changed signed field returns INTENT_CONFLICT.",
    "reservation_response": {
      "sessionId": "UUID or null when the root is already committed",
      "rootCid": "exact intent root",
      "state": "open | committed",
      "expiresAt": "Unix epoch milliseconds or null",
      "capability": "opaque operation capability or null for an already committed root"
    },
    "publisher_status_response": {
      "status": "absent | active | suspended",
      "genesisProfileCid": "first admitted profile CID, or null when absent",
      "currentProfileCid": "current center-admitted profile CID used to anchor new writes, or null when absent"
    },
    "operation_capability": {
      "wire": "Authorization: Bearer <capability>",
      "scope": "exact publish session",
      "required_on": "every session status, direct block, multipart block, abort, and commit route",
      "note": "This is short-lived operation state, not a login session. The center stores only its digest."
    }
  },
  "agent_guide": {
    "summary": "BubGorge is a UGC media player. Entities have capabilities (transform, texture, audio, markdown, mesh, camera, existence, render, constraint_kind). Each capability has fields (e.g. transform.translation). Writes are time-keyed samples in field buffers. The SDK lets you create entities, write/clear field samples, bind constraints, query state, and request GUI input capture.",
    "minimum_invariants": [
      "Single source of truth: ScriptEngine. No direct ECS access.",
      "Default `time` parameter on setters = bubgorge.time.now() (current pointer position).",
      "All state-mutation methods return a DispatchHandle with .ack() Promise.",
      "pointer.position is the sole authoritative timeline clock.",
      "Per-capability orthogonality: writing texture.opacity never affects audio.volume."
    ],
    "typical_calls": [
      "await bubgorge.hello()  // ONE call: completes handshake, hides L0 prompt, returns manifest summary — invariant #33",
      "bubgorge.commands.create('base layer')  // name unique within this UGC",
      "bubgorge.entity('base layer').existence.duration.set(10.0)  // existence default_time=0 — constant",
      "bubgorge.entity('base layer').markdown.content.set('# Title\\n\\nbody')  // full string; stored as ONE full-replacement TextOp at that time (#57)",
      "bubgorge.entity('base layer').transform.translation.set([1, 2, 3], { time: 2.5 })  // time-varying — keyframe at t=2.5",
      "await bubgorge.entity('hero').markdown.position.capture({ mode: 'Drag' })  // elicit a value from the user via the GUI widget — resolves with the captured FieldValue",
      "bubgorge.entity('hero').markdown.position.bind(bubgorge.entity('base layer').markdown.position, 'Delta', { param: [200, 0] })  // follow a source accessor; sources are accessor objects",
      "await bubgorge.entity('base layer').texture.opacity.get()  // latest authored sample + count",
      "await bubgorge.commands.query.entities()",
      "await bubgorge.context.instructions()  // full manifest if you need detail beyond hello()'s summary"
    ],
    "constant_field_rule": "Fields marked `default_time: 0` describe a property of the entity as a whole. SDK setters default `time = 0` so repeated calls overwrite one canonical sample. Other fields default `time = bubgorge.time.now()` and accumulate keyframes — see invariant #30 exception."
  },
  "capabilities": {
    "0x01": {
      "name": "transform",
      "description": "Entity world-space placement: translation (Vec3), rotation (Vec3 euler degrees), scale (Vec3).",
      "fields": {
        "0x00": {
          "name": "translation",
          "type": "Vec3",
          "value_source": "either"
        },
        "0x01": {
          "name": "rotation",
          "type": "Vec3",
          "value_source": "either"
        },
        "0x02": {
          "name": "scale",
          "type": "Vec3",
          "value_source": "either"
        }
      }
    },
    "0x02": {
      "name": "texture",
      "description": "Visual media (image / video). Sources are URLs with optional media_range trim.",
      "fields": {
        "0x00": {
          "name": "source",
          "type": "TextureSource",
          "value_source": "either"
        },
        "0x01": {
          "name": "opacity",
          "type": "Scalar",
          "value_source": "either"
        },
        "0x02": {
          "name": "volume",
          "type": "Scalar",
          "value_source": "either"
        },
        "0x03": {
          "name": "speed",
          "type": "Scalar",
          "value_source": "either"
        },
        "0x04": {
          "name": "loop",
          "type": "Bool",
          "value_source": "prompt"
        },
        "0x06": {
          "name": "natural_duration",
          "type": "Scalar",
          "derived": true,
          "read_only": true
        },
        "0x07": {
          "name": "natural_size",
          "type": "Vec2",
          "derived": true,
          "read_only": true
        }
      }
    },
    "0x03": {
      "name": "audio",
      "description": "Audio media. Spatial flag is reserved for future 3D positional audio.",
      "fields": {
        "0x00": {
          "name": "source",
          "type": "AudioSource",
          "value_source": "either"
        },
        "0x01": {
          "name": "volume",
          "type": "Scalar",
          "value_source": "either"
        },
        "0x02": {
          "name": "speed",
          "type": "Scalar",
          "value_source": "either"
        },
        "0x03": {
          "name": "loop",
          "type": "Bool",
          "value_source": "prompt"
        },
        "0x05": {
          "name": "spatial",
          "type": "Bool",
          "value_source": "prompt"
        },
        "0x06": {
          "name": "natural_duration",
          "type": "Scalar",
          "derived": true,
          "read_only": true
        }
      }
    },
    "0x04": {
      "name": "markdown",
      "description": "Markdown text overlay rendered as an egui window. Author content with markdown.content.set(text: string) — a full string; Rust encodes it as ONE full-replacement TextOp at that time (storage encoding is transducer-internal, invariant #57).",
      "fields": {
        "0x00": {
          "name": "content",
          "type": "String",
          "encoding": "TextOp",
          "note": "Write a full string via markdown.content.set(text); Rust stores it as one full-replacement TextOp ([0, cur_len] -> text) at that time. Authors never build a TextOp; the incremental-diff path belongs to the GUI keystroke editor only (#57).",
          "value_source": "either"
        },
        "0x01": {
          "name": "size",
          "type": "Vec2",
          "value_source": "either"
        },
        "0x02": {
          "name": "position",
          "type": "Vec2",
          "value_source": "either"
        },
        "0x03": {
          "name": "opacity",
          "type": "Scalar",
          "value_source": "either"
        },
        "0x04": {
          "name": "style",
          "type": "String",
          "reserved": true
        },
        "0x05": {
          "name": "content_opacity",
          "type": "Scalar",
          "value_source": "either"
        },
        "0x06": {
          "name": "embeds",
          "type": "EmbedSet",
          "note": "M24_11 (#2.6): the document's media-embed set. markdown.embeds.set([{ cid, range?, name? }, ...], { time }) declares the WHOLE, ORDERED list of media-UGC references active at `time` (whole-set value, #15). The list links POSITIONALLY to the U+FFFC (OBJECT REPLACEMENT) sentinels in the prose (content): the Nth sentinel <-> the Nth entry. `cid` is the media-UGC dir CID (#34), a `set` literal the author transcribes (#57); `name` an optional display label, NOT the link. The medium (image/audio/video) is derived from the sealed descriptor at resolve time. Storage plane (declaration); playback is the #61 runtime plane (user-triggered, never serialized).",
          "value_source": "either"
        }
      }
    },
    "0x05": {
      "name": "mesh",
      "description": "3D mesh placeholder — reserved: no SDK accessor, not agent-invocable (out of M27 scope). Its fields carry \"reserved\": true; the gen.mjs reachability guard (#56/#27) requires exactly that until a MeshAccessor is added.",
      "fields": {
        "0x00": {
          "name": "geometry",
          "type": "String",
          "reserved": true
        },
        "0x01": {
          "name": "material",
          "type": "String",
          "reserved": true
        },
        "0x02": {
          "name": "size_spec",
          "type": "Vec3",
          "reserved": true
        }
      }
    },
    "0x06": {
      "name": "camera",
      "description": "Camera intrinsics: projection (orthographic/perspective), FOV, znear/zfar, exposure, audio listener flag.",
      "fields": {
        "0x00": {
          "name": "projection",
          "type": "String",
          "value_source": "prompt"
        },
        "0x01": {
          "name": "fov",
          "type": "Scalar",
          "value_source": "either"
        },
        "0x02": {
          "name": "znear",
          "type": "Scalar",
          "value_source": "either"
        },
        "0x03": {
          "name": "zfar",
          "type": "Scalar",
          "value_source": "either"
        },
        "0x04": {
          "name": "exposure",
          "type": "Scalar",
          "value_source": "either"
        },
        "0x05": {
          "name": "audio_listener",
          "type": "Bool",
          "value_source": "prompt"
        }
      }
    },
    "0x07": {
      "name": "existence",
      "description": "Timeline presence. anchor + duration in parent-local time. An entity with no FIELD_DURATION sample is Pending (effective_timeline=[0,0]). Both fields are CONSTANT-BY-DESIGN — SDK setter default_time is 0 (not pointer.now()) so repeated .set() calls overwrite one canonical sample. See invariant #30 exception.",
      "fields": {
        "0x00": {
          "name": "anchor",
          "type": "Scalar",
          "default_time": 0,
          "value_source": "either"
        },
        "0x01": {
          "name": "duration",
          "type": "Scalar",
          "default_time": 0,
          "value_source": "either"
        }
      }
    },
    "0x08": {
      "name": "render",
      "description": "Render layer assignment + z-order priority.",
      "fields": {
        "0x00": {
          "name": "layer",
          "type": "Scalar",
          "value_source": "either"
        },
        "0x01": {
          "name": "z_order",
          "type": "Scalar",
          "value_source": "either"
        }
      }
    },
    "0x09": {
      "name": "constraint_kind",
      "description": "Custom constraint-kind handler entity. name registers the kind; source is the TS handler body. Used by ConstraintKind::Custom(name). Both fields are CONSTANT-BY-DESIGN — SDK setter default_time is 0 (not pointer.now()). Override with explicit {time} only if you want a runtime handler swap.",
      "fields": {
        "0x00": {
          "name": "name",
          "type": "String",
          "default_time": 0,
          "value_source": "prompt"
        },
        "0x01": {
          "name": "source",
          "type": "String",
          "default_time": 0,
          "value_source": "prompt"
        }
      }
    },
    "0x0A": {
      "name": "input",
      "description": "Viewer-side live transducer (M24 8c, #61). MULTI-CHANNEL + WIDGET-AGNOSTIC: channel c uses field c*2 = CONFIG (an opaque widget-descriptor string the GUI interprets, e.g. {\"w\":\"slider\",\"min\":0,\"max\":2,\"default\":1} — authored, CONSTANT-BY-DESIGN, serialized) and c*2+1 = VALUE (runtime FieldValue written each frame by the GUI widget at Provenance::Input, NEVER serialized — excluded like a derived field). A field becomes viewer-adjustable by binding Copy from a channel's VALUE; the bind serializes, the live value does not. Declare via bubgorge.entity(n).input.slider({min,max,default}); more widget kinds (checkbox/button/picker/...) are one SDK method + one GUI arm, zero kernel change. Fields are dynamic (no fixed FIELD_INPUT_* ids).",
      "fields": {}
    },
    "0x0B": {
      "name": "env",
      "description": "M26 Gate 3 (#65): the Observer's ambient environment as read-only, runtime-only Copy-bind SOURCES — never authored, never serialized (like CAP_INPUT VALUE, #61). Bind a content field to bubgorge.observer.* (viewportWidth/Height/Aspect, scaleFactor, theme, safeAreaTop/Right/Bottom/Left, orientation) to make it react to the viewer's environment; the bind serializes, the live value does not. All scalar: theme 0=light/1=dark, orientation 0=portrait/1=landscape, safe-area insets logical px. Single-/primary-viewport for now. There is NO .set() — these are sources only.",
      "fields": {
        "0x00": {
          "name": "env_viewport_width",
          "type": "Scalar",
          "read_only": true
        },
        "0x01": {
          "name": "env_viewport_height",
          "type": "Scalar",
          "read_only": true
        },
        "0x02": {
          "name": "env_viewport_aspect",
          "type": "Scalar",
          "read_only": true
        },
        "0x03": {
          "name": "env_scale_factor",
          "type": "Scalar",
          "read_only": true
        },
        "0x04": {
          "name": "env_theme",
          "type": "Scalar",
          "read_only": true
        },
        "0x05": {
          "name": "env_safe_area_top",
          "type": "Scalar",
          "read_only": true
        },
        "0x06": {
          "name": "env_safe_area_right",
          "type": "Scalar",
          "read_only": true
        },
        "0x07": {
          "name": "env_safe_area_bottom",
          "type": "Scalar",
          "read_only": true
        },
        "0x08": {
          "name": "env_safe_area_left",
          "type": "Scalar",
          "read_only": true
        },
        "0x09": {
          "name": "env_orientation",
          "type": "Scalar",
          "read_only": true
        }
      }
    },
    "0x0C": {
      "name": "portal",
      "description": "M26_6 portal: a content-plane viewport SURFACE placed like an entity. transform.* + size (world units, vec2) project through the primary eye to a world-anchored screen card whose face is the RTT texture rendered by the portal's own eye. eye: > 0 = follow the authored camera with that wire key; 0 = follow the existence cut. The card degrades like an anchor region: hidden behind the camera, below the viewer's perceptibility floor, or fully off-screen. source: the CID of an embedded closure — set it and that closure loads as an ISOLATED instance (fresh keys, a reserved render band) visible ONLY through this portal; unset shows the SAME world from the portal eye (devnote/M26_6_portal_content_viewport.md).",
      "fields": {
        "0x00": {
          "name": "portal_eye",
          "type": "Scalar",
          "value_source": "either"
        },
        "0x01": {
          "name": "portal_size",
          "type": "Vec2",
          "value_source": "either"
        },
        "0x02": {
          "name": "portal_source",
          "type": "String",
          "value_source": "either"
        },
        "0x03": {
          "name": "portal_natural_duration",
          "type": "Scalar",
          "derived": true,
          "read_only": true
        },
        "0x04": {
          "name": "portal_range",
          "type": "Vec2",
          "note": "The portal's window INTO its instance closure's timeline — [start, end] in INSTANCE-LOCAL seconds, the media_range mirror (M26_6 Gate 2d-c-2): instance-local start shows at the portal anchor (seek into the closure), instance activity is clamped to the portal window, and portal_natural_duration becomes end-start. Unset = the whole closure from 0.",
          "value_source": "either"
        },
        "0x05": {
          "name": "portal_volume",
          "type": "Scalar",
          "note": "The portal-UNIT volume (0..1, time-keyed; unset = 1): multiplies every instance medium's own volume — duck or silence the embedded closure as one unit (the texture.volume mirror). Deliberately no portal muted field: volume 0 IS silence; element-level muted is transducer-internal autoplay handling.",
          "value_source": "either"
        }
      }
    }
  },
  "types": {
    "FieldValue": {
      "description": "Tagged union of all field value kinds. Use bubgorge.fv.* constructors.",
      "variants": {
        "Scalar": {
          "shape": {
            "type": "Scalar",
            "v": "number"
          }
        },
        "Vec2": {
          "shape": {
            "type": "Vec2",
            "v": "[number, number]"
          }
        },
        "Vec3": {
          "shape": {
            "type": "Vec3",
            "v": "[number, number, number]"
          }
        },
        "Bool": {
          "shape": {
            "type": "Bool",
            "v": "boolean"
          }
        },
        "String": {
          "shape": {
            "type": "String",
            "v": "string"
          }
        },
        "TextureSource": {
          "shape": {
            "type": "TextureSource",
            "url": "string",
            "media_range": "[number, number] | null",
            "natural_size": "[number, number] | null"
          }
        },
        "AudioSource": {
          "shape": {
            "type": "AudioSource",
            "url": "string",
            "media_range": "[number, number] | null"
          }
        },
        "TextOp": {
          "shape": {
            "type": "TextOp",
            "range": "[number, number]",
            "content": "string"
          }
        },
        "EmbedSet": {
          "shape": {
            "type": "EmbedSet",
            "embeds": "{ url: string; range?: [number, number] | null; name?: string | null }[]"
          }
        }
      }
    },
    "SampleContext": {
      "description": "Per-sample evaluation context. Use bubgorge.ctx.* constructors.",
      "shape": {
        "interp": "InterpKind",
        "constraint": "ConstraintDecl | null"
      }
    },
    "InterpKind": {
      "variants": [
        "Step",
        "Linear",
        "CubicSpline"
      ]
    },
    "ConstraintKind": {
      "description": "Single-source kinds: Copy/Delta/Scale/AnchorOverlay/AnchorInsert. Multi-source: Additive/Min/Max. Extensible via Custom(name) — a TS-supplied transform registered via CAP_CONSTRAINT_KIND.",
      "variants": [
        "Delta",
        "Scale",
        "Copy",
        "AnchorOverlay",
        "AnchorInsert",
        "Additive",
        "Min",
        "Max",
        "ScreenAnchor",
        {
          "Custom": "string"
        }
      ]
    },
    "ConstraintSourceRef": {
      "shape": {
        "entity": "string",
        "capability": "number",
        "field": "number"
      }
    },
    "ScriptCommand": {
      "description": "Atomic mutation unit. The cross-boundary queue element; not a user-facing IR. Prefer high-level SDK helpers.",
      "variants_serde_tag": "op",
      "variants": {
        "CreateEntity": {
          "fields": {
            "name": "string"
          }
        },
        "RemoveEntity": {
          "fields": {
            "name": "string"
          }
        },
        "FieldSet": {
          "fields": {
            "capability": "number",
            "field": "number",
            "time": "number",
            "value": "FieldValue",
            "ctx": "SampleContext"
          }
        },
        "FieldBatch": {
          "fields": {
            "capability": "number",
            "field": "number",
            "samples": "Array<{ time: number; value: FieldValue; ctx: SampleContext }>"
          }
        },
        "ClearRange": {
          "fields": {
            "capability": "number",
            "field": "number",
            "start": "number",
            "end": "number"
          }
        },
        "ResetField": {
          "fields": {
            "capability": "number",
            "field": "number"
          }
        },
        "ResetCapability": {
          "fields": {
            "capability": "number"
          }
        },
        "ResetAll": {
          "fields": {}
        },
        "ConstraintBind": {
          "fields": {
            "target_entity": "string",
            "target_capability": "number",
            "target_field": "number",
            "sources": "ConstraintSourceRef[]",
            "kind": "ConstraintKind",
            "param": "number[] | null"
          }
        },
        "ConstraintUnbind": {
          "fields": {
            "target_entity": "string",
            "target_capability": "number",
            "target_field": "number"
          }
        },
        "Play": {
          "fields": {}
        },
        "Pause": {
          "fields": {}
        },
        "TogglePlayback": {
          "fields": {}
        },
        "Seek": {
          "fields": {
            "time": "number"
          }
        },
        "SetSpeed": {
          "fields": {
            "speed": "number"
          }
        },
        "SelectEntity": {
          "fields": {
            "name": "string"
          }
        },
        "SelectToggle": {
          "fields": {
            "key": "string"
          }
        },
        "SelectAdd": {
          "fields": {
            "key": "string"
          }
        },
        "Deselect": {
          "fields": {}
        },
        "CaptureRequest": {
          "fields": {
            "entity": "string",
            "capability": "number",
            "field": "number",
            "mode": "any"
          }
        }
      }
    },
    "CaptureMode": {
      "variants": [
        "Drag",
        "Set",
        "Slider",
        "Pick"
      ]
    },
    "QueryRequest": {
      "variants_serde_tag": "kind",
      "variants": {
        "entities": {
          "fields": {}
        },
        "channels": {
          "fields": {
            "index": "number"
          }
        },
        "timeline": {
          "fields": {}
        },
        "activeAt": {
          "fields": {
            "time": "number"
          }
        },
        "entityTimeline": {
          "fields": {
            "key": "number  // entity key, minted from the local name by the SDK"
          }
        },
        "field": {
          "fields": {
            "entity_key": "number  // entity key, minted from the local name by the SDK",
            "capability": "number",
            "field": "number"
          }
        },
        "dumpFields": {
          "fields": {
            "keys": "number[]  // entity keys; full authored sample history + bindings per key"
          },
          "note": "Serializer read surface (M24 Step 8a) — wire-internal, consumed by bubgorge.serializer.emit(); not an agent-facing query (#56). OpBuffer samples arrive pre-projected to full-replacement Strings (#57)."
        }
      }
    },
    "FieldSampleSnapshot": {
      "shape": {
        "time": "number",
        "value": "FieldValue"
      }
    },
    "DispatchHandle": {
      "description": "Returned by every state-mutation SDK call. Carries a monotonic client_id; .ack() polls for the matching response.",
      "members": {
        "client_id": "number (readonly)",
        "ack": "() => Promise<{ type: 'ok' | 'error', msg: string }>"
      }
    },
    "TimeSnapshot": {
      "shape": {
        "position": "number",
        "playing": "boolean",
        "speed": "number",
        "duration": "number"
      }
    },
    "EvalContext": {
      "description": "Per-task evaluation context passed to custom-kind handlers.",
      "shape": {
        "pointer_time": "number",
        "target_entity_id": "number",
        "target_uuid": "string"
      }
    }
  },
  "dag_edge_types": {
    "description": "Protocol registry of DAG import-edge types (#67). Every descriptor `imports` entry IS a typed import edge `[dependency dir CID, edge_type]`, canonically sorted + deduped and sealed into the directory CID (ugc-cid, #54). Three families: `build` (execute-compose), `source.*` (a field-value reference the type mirrors the consuming accessor path of), and `rel.*` (RELATIONAL / LINEAGE — provenance the client reads OFF the DAG, no consuming accessor). The same CID may carry multiple types (one edge each). The center walks ALL types uniformly — closure completeness is type-blind; execute-vs-register semantics are client-side, decided from the type. Registry evolution is a coordinated Protocol release (#63), never a permissionless mod. Source of truth: web/src/ugc/edges.ts.",
    "registry": {
      "build": {
        "root_executes": true,
        "description": "Execute-compose: the target's build() runs in the importer's shared world (#39). Cross-UGC constraint bind scopes REQUIRE this type — a Copy bind needs the foreign entities to exist in the shared world. Subtypes may later live under it (e.g. build.schema)."
      },
      "source.texture": {
        "root_executes": false,
        "description": "texture.source media reference (bare-CID url) — the serializer lifts every bare-CID field reference into its source.* edge at publish (#63)."
      },
      "source.audio": {
        "root_executes": false,
        "description": "audio.source media reference (bare-CID url), lifted at publish."
      },
      "source.markdown": {
        "root_executes": false,
        "description": "markdown embed reference (EmbedSet member CID), lifted at publish."
      },
      "source.portal": {
        "root_executes": false,
        "description": "portal target (portal.source CID): the closure instanced in an ISOLATED scope behind the portal at field-time — not a separate 'embed' primitive (#66 coverage: same signal shape as texture.source). Never root-executed; never constrains topo order."
      },
      "rel.profile": {
        "root_executes": false,
        "description": "rel.* family (relational/lineage, #62): the author's type:profile identity card (M25.6). A content UGC declares WHICH profile describes its author, read OFF the edge instead of scanning the store for a same-key profile (the dropped key-match heuristic, M26_7 §8/§11). ANTI-SPOOF stays the author-key match (#55): the named profile is honored only if signed by the SAME key as the content. Profile-as-of-publish (pinned immutable CID); rename = a new profile + a supersede edge. No consuming accessor, so it is not lifted from a field value — attached at descriptor finalization (publish({profile})). Never root-executed."
      },
      "rel.reference": {
        "root_executes": false,
        "description": "rel.* family (relational/lineage, #62, M27 P2 §13.2): a realization declares it references / is built from / realizes the target UGC — an idea, draft, setting, seed media, or finished work. NODE-TYPE-AGNOSTIC (any UGC can BE a reference); a UGC is MULTI-ROLE, the same CID may carry source.markdown AND rel.reference edges (#67 same-CID-multiple-types). No consuming accessor — read OFF the DAG; authority rides the REFERENCING node's signature (#55), the edge only NAMES the referent (no target anti-spoof, unlike rel.profile). Sealed at descriptor finalization (publish({references}) / putMediaDirectory({references})). Never root-executed."
      },
      "rel.comment": {
        "root_executes": false,
        "description": "rel.* family (relational/lineage, #62, M27 P2 §13.2): the UNIFIED ANNOTATION edge — a UGC posted ON / ABOUT a work (comment, danmaku/timed overlay, reaction). The annotating UGC IMPORTS the work with this edge (annotator -> annotated); one type covers comment/danmaku/reaction. DISTINCT from rel.reference (built-from/references an idea) and source.* (consumed as a field value). No consuming accessor; the USEFUL read is INCOMING ('what annotates this work', #68 — a graph query, center.commentsOn(workCid)), not a descriptor read. Sealed at descriptor finalization (publish({annotates}) / putMediaDirectory({annotates})). Authority rides the annotator's signature (#55). Never root-executed."
      },
      "rel.supersede": {
        "root_executes": false,
        "description": "rel.* family (relational/lineage, #62 branch governance), REALIZED M28 P0: a node declares it REPLACES the target as the chain head. Rotation/revocation freshness is chain-head reachability along this edge (a newer profile supersedes an older; a fresh keystore supersedes a rotated one). NODE-TYPE-AGNOSTIC. Authority rides the superseding node's signature (#55) — honored ONLY when signed by the SAME key as the superseded node (anti-spoof, as rel.profile). No consuming accessor (read OFF the DAG). Never root-executed."
      },
      "rel.grant": {
        "root_executes": false,
        "description": "rel.* family (relational/lineage, #62), M28 E2E (P0-registered): a grant node -> the encrypted work it UNLOCKS (the grant carries the sealed CEK wraps for its audience). Outgoing lineage the client reads OFF the DAG; no consuming accessor. Authority rides the granter's signature (#55). Never root-executed. Grant/keystore NODE runtime is P3; P0 only registers the edge type."
      },
      "rel.grant-to": {
        "root_executes": false,
        "description": "rel.* family (relational/lineage, #62), M28 E2E (P0-registered): a grant node -> the recipient PROFILE (whose enc/pubkey the CEK was wrapped to). Names the audience member; the useful read is the grant graph. No consuming accessor. Never root-executed."
      },
      "rel.revoke": {
        "root_executes": false,
        "description": "rel.* family (relational/lineage, #62), M28 E2E (P0-registered): a revocation node -> the grant/keystore it INVALIDATES. Freshness is chain-head reachability (a revoke supersedes the named grant); with rel.supersede it drives rotation/revocation. No consuming accessor. Authority rides the revoker's signature (#55). Never root-executed."
      },
      "keys.store": {
        "root_executes": false,
        "description": "keys.* family (M28 E2E, P0-registered) — its OWN family per #67: unlike rel.* (provenance, no consuming accessor) a keystore is a CONSUMED dependency, FETCHED by the decrypt path to obtain the sealed CEK wraps (enc/keys). NOT a field-value source.*, NOT provenance-only rel.*. Non-build prefix => never root-executed (Q6): consumed by decrypt, not the build topo; isBuildEdge stays build.*-only (the safe default — unknown/non-build prefix => opaque, walk-never-execute — already covers it). NO loader/center change in P0; P3 wires the fetch/register."
      }
    },
    "prefix_fallback": "A consumer resolves an unknown type to its LONGEST KNOWN PREFIX; a type with no known prefix is an OPAQUE dependency — walked for closure completeness, never executed. The load-bearing client predicate is root execution: `build` and anything under `build.` executes, everything else (all source.*, all rel.*, all keys.*, all unknowns) does not (safe default = non-execution). Root-executable set = nodes reachable from the closure root via build-prefixed edges only; source.*, rel.*, and keys.* edges never constrain topo order.",
    "defined_for_future": "rel.fork (#62 branch governance) is now the sole Protocol-defined-for-future rel.* sibling of rel.profile (rel.supersede is REALIZED as of M28 P0 — see the registry above); where exactly rel.fork attaches in the tree is finalized when realized. Adding new registry types never changes an existing CID — old nodes keep their sealed types.",
    "notes": "Every imports entry is an import edge — import is the ONLY DAG edge primitive (#62); influence.outflow ⊆ imports by CID projection. `reference` is NOT an edge type: it is the connection-layer typed-CID field resolution (#63), a separate axis. context.subgraph() edges carry these types verbatim in `kind`."
  },
  "alg_registry": {
    "description": "Protocol registry of the 1-byte algorithm codes that tag content-addressed crypto material (#41/#63), frozen at M28 P0. THREE role-scoped axes — the code is selected by the MEMBER's role, never by a blob byte, so each axis owns its own 0x01 and codes never collide across tables: sig (signatures — .author/{pubkey,signature} leaf blobs, #44), kem (key encapsulation — enc/pubkey.kem + wrap ct), aead (authenticated encryption — enc/meta.aead + the wrap envelope). Codes are content-addressed (they seal into CIDs), so an entry is APPEND-ONLY — never renumber, never reuse a byte. Codes are DERIVED from web/src/ugc/alg.ts (SIG_ALG / KEM_ALG / AEAD_ALG); the manifest is fail-loud both directions on the alg NAMES. 0xe0+ is the dev/experimental range. Source of truth: web/src/ugc/alg.ts.",
    "sig": {
      "ml-dsa-65": "0x01",
      "ed25519": "0x02",
      "ecdsa-p256-STANDIN": "0xE0"
    },
    "kem": {
      "ml-kem-768": "0x01"
    },
    "aead": {
      "aes-256-gcm-chunked-v1": "0x01"
    },
    "notes": "sig carries the M25 signing codes VERBATIM (no byte moves => no CID change). An X-Wing hybrid KEM stays RESERVED (a later kem code), not defined. A code is a NAME->CODE map value formatted 0xNN (uppercase); the reverse code->name map is derived at parse time in web/src/ugc/alg.ts (SIG_NAME/KEM_NAME/AEAD_NAME)."
  },
  "enc_members": {
    "description": "M28 E2E Protocol member schemas (#41/#63), frozen at P0 — the dag-cbor leaf shapes an encrypted node (or a recipient profile) carries. Members are encoded via the generic canonical dag-cbor encoder in ugc-cid (#54); NO field is a CID, so CBOR tag-42 never appears in enc/*. Byte-level crypto (KDF labels, chunk-nonce construction, AAD layout) lives as KAT-pinned constants in web/src/ugc/enc.ts + devnote/M28_1_e2e_encryption.md §4, NOT here (#41 requires members to EXIST for TS/agents; no consumer dispatches on a KDF label). STRUCTURAL PRECONDITION: an encrypted node MUST carry a .author/pubkey member — authorCommit = blake3(.author/pubkey blob) is an input to KEK derivation, so an author-less encrypted node is structurally undecryptable (extends #44 'anonymous => no flux' to 'anonymous => no E2E'). The AEAD/KEM runtime is P2; P0 freezes the member STRUCTURE + the alg codes. Field notes here are documentation, not a byte layout.",
    "members": {
      "enc/pubkey": {
        "schema_version": 1,
        "shape": "map",
        "role": "profile member — the recipient's KEM public key, sealed + signed into the profile dir CID (#55). The rotation index i is covered by the profile signature (self-describing, no separate framing).",
        "fields": {
          "kem": "int — KEM_ALG code (alg_registry.kem); = 0x01 (ml-kem-768)",
          "i": "int — u32 rotation index (same-ξ derivation, §8)",
          "ek": "bytes(1184) — ML-KEM-768 encapsulation key"
        },
        "alg": {
          "kem": "alg_registry.kem"
        }
      },
      "enc/meta": {
        "schema_version": 1,
        "shape": "map",
        "role": "encrypted-node member — the per-object AEAD metadata. salt is the single-source object anchor (objectSalt); sealed is the CEK-sealed innerMeta that carries authorCommit (there is NO plaintext authorCommit field — it is derived at load from the outer verified pubkey and echoed inside sealed).",
        "fields": {
          "v": "int — schema version (= 1)",
          "aead": "int — AEAD_ALG code (alg_registry.aead); = 0x01 (aes-256-gcm-chunked-v1)",
          "chunk": "int — chunk size in bytes (= 65536)",
          "salt": "bytes(32) — objectSalt, random, the single-source object anchor",
          "leaves": "list of map { name: str (canonical member name, NFC), n: int (u32 chunk count N), np: bytes(8) (per-leaf random nonce prefix; chunk nonce = np(8) || u32_be(i)) }",
          "sealed": "bytes — rand12 || AES-256-GCM(kMeta) over innerMeta; innerMeta carries authorCommit = blake3(.author/pubkey blob). NO plaintext authorCommit field in enc/meta."
        },
        "alg": {
          "aead": "alg_registry.aead"
        }
      },
      "enc/keys": {
        "schema_version": 1,
        "shape": "list",
        "item_shape": "map",
        "role": "sealed-audience wraps — one map entry per recipient. Each wrap is non-transplantable: KEK derivation + the wrap AAD both bind rk || authorCommit || objectSalt.",
        "fields": {
          "rk": "bytes(32) — blake3(recipient ek), the recipient tag",
          "kem": "int — KEM_ALG code (alg_registry.kem); = 0x01 (ml-kem-768)",
          "ct": "bytes(1088) — ML-KEM-768 ciphertext (encaps to the recipient ek)",
          "wrap": "bytes — rand12 || AES-256-GCM(KEK) over CEK(32)"
        },
        "alg": {
          "kem": "alg_registry.kem"
        }
      }
    }
  },
  "tools": {
    "entity": {
      "signature": "bubgorge.entity(name: string) => EntityHandle",
      "description": "Open a typed accessor for an entity (by local name). Sub-accessors per capability; every field accessor carries the complete op set {set, capture, bind, get} (#56/#57) — see field_ops. The per-capability blocks below list each field's typed set() signature.",
      "field_ops": {
        "description": "Uniform ops on EVERY field accessor (bubgorge.entity(name).<capability>.<field>). set() is typed per field — see the capability blocks.",
        "capture": "(opts: { mode: 'Drag'|'Set'|'Slider'|'Pick', pollIntervalMs?, timeoutMs?, acceptExisting?, prompt? }) => Promise<FieldValue> — elicit a value via the GUI transducer: activates the widget on THIS field, resolves with the user's value (#31/#57). The agent picks the mode; it never fills the value. NOTE: prompt is reserved (M24 Step 8) — accepted but not yet displayed.",
        "bind": "(source: BindSource | BindSource[], kind: ConstraintKind, opts?: { param?: number[] }) => DispatchHandle — make this field follow source accessor(s). Sources are accessor OBJECTS (e.g. bubgorge.entity('b').markdown.position), never cap/field numbers (#56). Single-source kinds: Copy/Delta/Scale/AnchorOverlay/AnchorInsert; multi-source: Additive/Min/Max. A local source (bubgorge.entity(name)) resolves within the SAME UGC; a cross-UGC source is bubgorge.imported(depCid).entity(name).<cap>.<field> — a read-only SourceRef (#62) carrying the dependency CID so its key resolves under that scope (M24_6 §8 item 8).",
        "unbind": "() => DispatchHandle — remove this field's binding (the engine keeps ONE binding per target field; a new bind replaces the old)",
        "get": "() => Promise<{ last_sample, sample_count }> — latest AUTHORED sample (constraint/preview overlays are runtime-only and not visible here)",
        "samples": "(range?: [number, number]) => Promise<Array<{ time, value, interp }>> — authored sample HISTORY (values unwrapped to plain data), optional [start,end] filter; NON-MINTING (unknown entity -> []). The read surface, §8 item 6.",
        "clearRange": "(start: number, end: number) => DispatchHandle",
        "reset": "() => DispatchHandle"
      },
      "accessors": {
        "transform": {
          "translation.set": "(v: [number,number,number], opts?: SetOptions) => DispatchHandle",
          "rotation.set": "(v: [number,number,number], opts?: SetOptions) => DispatchHandle",
          "scale.set": "(v: [number,number,number], opts?: SetOptions) => DispatchHandle"
        },
        "texture": {
          "source.set": "(url: string, opts?: SetOptions & { media_range?, natural_size? }) => DispatchHandle",
          "opacity.set": "(v: number, opts?: SetOptions) => DispatchHandle",
          "volume.set": "(v: number, opts?: SetOptions) => DispatchHandle",
          "speed.set": "(v: number, opts?: SetOptions) => DispatchHandle",
          "loop_.set": "(v: boolean, opts?: SetOptions) => DispatchHandle",
          "muted.set": "(v: boolean, opts?: SetOptions) => DispatchHandle",
          "naturalDuration": "READ-ONLY derived field (#20) — no .set; use as a bind/withConstraint SOURCE: e.existence.duration.bind(e.texture.naturalDuration, 'Copy') is the canonical duration-from-media wiring",
          "naturalSize": "READ-ONLY derived field (#20) — no .set; bind/withConstraint source only"
        },
        "audio": {
          "source.set": "(url: string, opts?: SetOptions & { media_range? }) => DispatchHandle",
          "volume.set": "(v: number, opts?: SetOptions) => DispatchHandle",
          "speed.set": "(v: number, opts?: SetOptions) => DispatchHandle",
          "loop_.set": "(v: boolean, opts?: SetOptions) => DispatchHandle",
          "muted.set": "(v: boolean, opts?: SetOptions) => DispatchHandle",
          "spatial.set": "(v: boolean, opts?: SetOptions) => DispatchHandle",
          "naturalDuration": "READ-ONLY derived field (#20) — no .set; bind/withConstraint source only"
        },
        "markdown": {
          "content.set": "(text: string, opts?: SetOptions) => DispatchHandle  // full string; Rust stores ONE full-replacement TextOp at that time (encoding transducer-internal, #57)",
          "position.set": "(v: [number,number], opts?: SetOptions) => DispatchHandle",
          "size.set": "(v: [number,number], opts?: SetOptions) => DispatchHandle",
          "opacity.set": "(v: number, opts?: SetOptions) => DispatchHandle"
        },
        "render": {
          "layer.set": "(v: number, opts?: SetOptions) => DispatchHandle",
          "zOrder.set": "(v: number, opts?: SetOptions) => DispatchHandle"
        },
        "existence": {
          "anchor.set": "(v: number, opts?: SetOptions) => DispatchHandle",
          "duration.set": "(v: number, opts?: SetOptions) => DispatchHandle"
        },
        "constraintKind": {
          "name.set": "(v: string, opts?: SetOptions) => DispatchHandle",
          "source.set": "(v: string, opts?: SetOptions) => DispatchHandle"
        },
        "camera": {
          "description": "M26 Gate 4a: authored camera intrinsics. An entity with a camera lets a shot be seen from its pose (transform.*) at this fov/projection/clip; the cut (when it is the active camera) is existence.*.",
          "projection.set": "(v: 'perspective' | 'orthographic', opts?: SetOptions) => DispatchHandle  // set-once type flag (default_time 0)",
          "fov.set": "(v: number, opts?: SetOptions) => DispatchHandle  // vertical FOV in degrees (perspective)",
          "znear.set": "(v: number, opts?: SetOptions) => DispatchHandle",
          "zfar.set": "(v: number, opts?: SetOptions) => DispatchHandle",
          "exposure.set": "(v: number, opts?: SetOptions) => DispatchHandle",
          "audioListener.set": "(v: boolean, opts?: SetOptions) => DispatchHandle"
        },
        "resetAll": "() => DispatchHandle"
      }
    },
    "commands": {
      "description": "Non-per-field operations only: entity lifecycle, playback, cross-entity queries, creator draft. ALL per-field ops (set/capture/bind/get/clearRange/reset) are accessor ops — see tools.entity.field_ops (M24 Step 7).",
      "create": "(name: string) => DispatchHandle  // name is unique within this UGC; identity across UGCs is (cid, name)",
      "remove": "(name: string) => DispatchHandle",
      "select": "(ref: EntityRef) => DispatchHandle | undefined  // replace the selection (SESSION state, #61 - never serialized; agent and human share ONE selection, #29). ref: root-scope name, unambiguous loaded-closure name, raw key, or {cid, name}. NON-MINTING: an unknown ref warns and returns undefined",
      "selectToggle": "(ref: EntityRef) => DispatchHandle | undefined  // toggle the entity's selection-GROUP membership (the Shift+click/marquee op) - the agent's multi-select: call once per member. Same EntityRef resolution as select",
      "deselect": "() => DispatchHandle  // clear the selection (group + target)",
      "playback": {
        "play": "() => DispatchHandle",
        "pause": "() => DispatchHandle",
        "seek": "(time: number) => DispatchHandle",
        "speed": "(speed: number) => DispatchHandle"
      },
      "query": {
        "description": "Cross-entity reads only. The per-field read is the accessor op bubgorge.entity(name).<capability>.<field>.get() (#56).",
        "entities": "() => Promise<{ entities: EntityInfo[] }>",
        "timeline": "() => Promise<{ position, playing, duration }>",
        "channels": "(index: number) => Promise<{ channels: string[] }>",
        "activeAt": "(time: number) => Promise<{ time, entries: ActiveEntityEntry[] }>",
        "entityTimeline": "(name: string) => Promise<EntityTimelineSnapshot>"
      },
      "creator": {
        "addEntity": "(opts?: { name?: string }) => DispatchHandle",
        "removeEntity": "(index: number) => DispatchHandle"
      }
    },
    "imported": {
      "signature": "bubgorge.imported(depCid: string) => { cid, entity(name), url(), metadata() }",
      "description": "Reference an UPSTREAM dependency by content CID — KIND-AWARE (#62, M24_6 §8 item 8; §4.2). One handle serves both kinds: a BUILD dependency via .entity(name).<cap>.<field> (an ImportedFieldRef: a cross-UGC bind/withConstraint SOURCE that ALSO reads .samples(range?)/.get(), resolving under depCid's scope, never writable #62); a MEDIA/raw dependency via .cid (content-pure ref — pass to texture.source.set(handle.cid)), .url() (streamable bytes URL), .metadata() ({mime,size,kind}). The loader records non-build nodes as assets; .entity() on a loaded media/raw CID THROWS a clear diagnostic instead of a silent-empty mirror (M24_9 §6.2). The dependency must be in the loaded closure.",
      "read_ops": {
        "samples": "(range?: [number, number]) => Promise<Array<{ time, value, interp }>> — upstream field authored history (values unwrapped), NON-MINTING ([] if absent). The data plane (M24_6 §8 item 6).",
        "get": "() => Promise<{ last_sample, sample_count }> — latest authored sample + count of the upstream field"
      }
    },
    "scope": "bubgorge.scope() => { root: string[], imports: string[] } — the agent's write/read boundary (#62, M27 P0). root = ROOT-scope entity names writable via bubgorge.entity(name); imports = the loaded closure's dependency CIDs, READ-ONLY via bubgorge.imported(cid) (never written into). Ask this instead of inferring the boundary from entity() vs imported().",
    "time": {
      "now": "() => number — current pointer.position (1-frame-lagged)",
      "duration": "() => number | null — graph.total_duration",
      "speed": "() => number",
      "playing": "() => boolean",
      "snapshot": "() => TimeSnapshot",
      "seek": "(t: number) => DispatchHandle",
      "play": "() => DispatchHandle",
      "pause": "() => DispatchHandle",
      "setSpeed": "(s: number) => DispatchHandle",
      "watch": "(predicate: (s: TimeSnapshot) => boolean, opts?: { timeoutMs }) => Promise<TimeSnapshot>",
      "waitUntil": "(t: number) => Promise<TimeSnapshot>",
      "waitPlayingChange": "(expectStartState: boolean) => Promise<TimeSnapshot>",
      "activeAt": "(t: number) => Promise<ActiveAtResponse>",
      "entityTimeline": "(name: string) => Promise<EntityTimelineSnapshot>"
    },
    "observer": {
      "description": "M26 Gate 3/4c (#65): the Observer's viewer/environment degrees of freedom as read-only bind SOURCES (there is NO .set — sources only). Pass one to a field's .bind(source, kind), e.g. bubgorge.entity('hero').texture.opacity.bind(bubgorge.observer.viewportAspect, 'Copy'). Runtime-only, never serialized into a dependency CID (#61/#64). Ambient env is all scalar (theme 0=light/1=dark, orientation 0=portrait/1=landscape; safe-area insets logical px). Gate 4c adds the RESOLVED primary-viewport camera DOF (where the viewer is looking, 1-frame lag) reusing CAP_TRANSFORM/CAP_CAMERA so binds match reading a specific camera. Single-/primary-viewport for now.",
      "members": {
        "viewportWidth": "SourceRef — live viewport width (logical px)",
        "viewportHeight": "SourceRef — live viewport height (logical px)",
        "viewportAspect": "SourceRef — viewport width / height",
        "scaleFactor": "SourceRef — device pixel ratio (DPR)",
        "theme": "SourceRef — 0 = light, 1 = dark",
        "safeAreaTop": "SourceRef — safe-area inset top",
        "safeAreaRight": "SourceRef — safe-area inset right",
        "safeAreaBottom": "SourceRef — safe-area inset bottom",
        "safeAreaLeft": "SourceRef — safe-area inset left",
        "orientation": "SourceRef — 0 = portrait, 1 = landscape",
        "cameraPosition": "SourceRef (Vec3) — resolved primary-viewport camera world position (captures viewer pan / a followed camera)",
        "cameraRotation": "SourceRef (Vec3) — resolved camera rotation, Euler-XYZ radians",
        "cameraFov": "SourceRef (scalar, degrees) — resolved vertical fov; present ONLY when the primary camera is perspective (ortho fit resolves None → a bind cleanly skips)",
        "cameraProjection": "SourceRef (string) — 'perspective' | 'orthographic'",
        "cameraZnear": "SourceRef (scalar) — resolved near clip plane",
        "cameraZfar": "SourceRef (scalar) — resolved far clip plane"
      }
    },
    "fv": {
      "description": "FieldValue constructors. Use these instead of building objects by hand so the wire shape stays in sync.",
      "members": {
        "scalar": "(v: number) => FieldValue",
        "vec2": "(x: number, y: number) => FieldValue",
        "vec3": "(x: number, y: number, z: number) => FieldValue",
        "bool": "(v: boolean) => FieldValue",
        "string": "(v: string) => FieldValue",
        "textureSource": "(url: string, opts?) => FieldValue",
        "audioSource": "(url: string, opts?) => FieldValue"
      }
    },
    "ctx": {
      "description": "SampleContext builders, curated for the agent surface (#56): the per-sample constraint source is an accessor OBJECT, never cap/field numbers.",
      "members": {
        "step": "() => SampleContext { interp: 'Step' }",
        "linear": "() => SampleContext { interp: 'Linear' }",
        "spline": "() => SampleContext { interp: 'CubicSpline' }",
        "withConstraint": "(source: FieldAccessor, kind: ConstraintKind, opts?: { param?: number[], interp?: InterpKind }) => SampleContext — per-sample constraint (#12-#14): derive THIS sample's value from a source accessor (e.g. bubgorge.entity('b').markdown.position); overrides any persistent binding on the same target field"
      }
    },
    "batch": "bubgorge.batch() => CrossEntityBatch — accumulate typed accessor writes via .add(() => bubgorge.entity(...).<cap>.<field>.set(...)), flush() to dispatch in one tick",
    "hello": {
      "signature": "(arg?: string | { agent_name?, agent_version?, purpose?, capabilities_understood? }) => Promise<{ ok, session_id, app, sdk_manifest_url, protocol_version, message, connected_at, agent_guide, tree, drill_in, manifest_url }>",
      "description": "M23.6.1/M23.6.2 — the canonical ONE-CALL handshake (invariant #33). After this returns: (1) the L0 visual prompt is hidden — that's the close signal completing the loop; (2) a toast notifies the human user; (3) the return value is the ROOT of the tree-structured context: agent_guide (must-know block, inlined), tree (skeleton: capabilities/types/tools/diagnostics ids), drill_in (the exact bubgorge.context.* call for each subtree), manifest_url (full-manifest fallback) — one round-trip orientation, drill into detail per node. Accepts no-arg (anonymous), string ('Claude'), or full request object.",
      "status": "implemented",
      "example": "await bubgorge.hello()  // simplest; or bubgorge.hello({ agent_name: 'Claude', purpose: 'demo' })"
    },
    "handshake": {
      "signature": "Alias of hello() — same semantics, kept for callers that wrote bubgorge.handshake({...}) before M23.6.1.",
      "description": "Deprecated. Use bubgorge.hello() instead.",
      "status": "deprecated_alias"
    },
    "serializer": {
      "description": "Serializer (M24 Step 8a/8b, invariant #59): projects the ROOT-scope authoring session — everything authored via entity()/commands.create() this session; NOT loaded closures, NOT creator-draft (bit-63 key) entities — into a canonical UGC directory {buildJs, buffer} (#58): the state projection of 'SDK calls ARE the UGC'. Bulk continuous Linear runs externalize to buffer.bin (raw LE f32); discrete/structure/refs/code stay inline in buildJs. Deterministic: same intent → same bytes → stable CID (both streams). Cross-scope constraint sources are skipped with a console warning (M24_6 §8.8).",
      "emit": "() => Promise<{ buildJs: string, buffer: Uint8Array }> — the UGC directory: build.js module source + buffer.bin bytes (empty unless a run externalized)",
      "bindBuffer": "(bytes: Uint8Array | null) => void — bind a directory's buffer.bin before running its build() so .run() accessors resolve (loader + round-trip harness; load-time only)"
    },
    "constraints": {
      "registerKind": "(name: string, handler: (sources, params, ctx) => FieldValue | null) => void — register a custom ConstraintKind::Custom(name) transform (TS-supplied)",
      "unregisterKind": "(name: string) => boolean",
      "registeredKinds": "() => string[]"
    },
    "center": {
      "description": "Ecosystem SDK (#36/#70/#72/#75) — the canonical project-operated UGC center client. Reads are neutral and CID-addressed. A write first requires Turnstile genesis-profile admission or an active publisher whose AuthorKey separately signs a center-scoped resource intent; D1 reserves exact session capacity and returns an operation capability before private upload. Leaves remain hidden until the center recomputes every CID, validates descriptor/import/all-signature/Bao integrity, and atomically commits visibility plus flow settlement. Standalone bare-blob upload is not exposed.",
      "putDirectory": "(buildJs: string, buffer?: Uint8Array, options?) => Promise<{ dirCid, buildCid, bufferCid?, authorAlg?, authorPubkeyCid?, signatureCid? }> — atomically publish a signed build UGC directory and its members. Returns the directory CID.",
      "publish": "(options?) => Promise<{ dirCid, buildCid, bufferCid?, authorAlg?, authorPubkeyCid?, signatureCid? }> — serialize the current ROOT-scope authoring session (#59) and atomically publish it as a signed UGC.",
      "closure": "(cid: string) => Promise<UgcClosure { root, ugcs, dag }> — ask the center to flatten the import DAG reachable from cid (#37/#39/#40); the loader iterates it without recursing.",
      "list": "(opts?: { limit?: number, cursor?: string }) => Promise<{ objects: [{ cid, size, uploaded, contentType }], cursor, truncated }> — one page of atomically committed public blocks in the center's discovery index.",
      "listAll": "(pageLimit?: number) => Promise<[{ cid, size, uploaded, contentType }]> — every committed public block, following the cursor.",
      "get": "(cid: string) => Promise<Uint8Array> — verbatim bytes (verification / inspection; media playback uses url()).",
      "url": "(cid: string) => string — the streamable {origin}/{cid} URL (HTTP range).",
      "resolve": "(ref: string) => string — resolve a media reference (bare CID / cid: / full URL) to a concrete load URL.",
      "origin": "() => string — the configured canonical center origin. Local development may override it before SDK exposure."
    },
    "context": {
      "description": "M23.6 agent-context surface, tree-rooted (M23.6.2). Drill-down accessors return ONE node at a time; instructions() ships the full manifest. Data-context: frame/text/entity/subgraph are IMPLEMENTED (M24 read+observe surface — observe live engine content + the loaded imports DAG, NON-MINTING); summary.* remains reserved.",
      "tree": {
        "signature": "() => ContextTree { capabilities: {id->name}, types: string[], tools: string[], diagnostics: string[], data: string[] }",
        "status": "implemented",
        "notes": "Sync skeleton re-read — same shape hello() returns as `tree`."
      },
      "capability": {
        "signature": "(id: string|number) => Promise<one capability node> — e.g. bubgorge.context.capability('0x01')",
        "status": "implemented"
      },
      "type": {
        "signature": "(name: string) => Promise<one type node> — e.g. bubgorge.context.type('FieldValue')",
        "status": "implemented"
      },
      "tool": {
        "signature": "(name: string) => Promise<one tools.* node> — e.g. bubgorge.context.tool('entity')",
        "status": "implemented"
      },
      "diagnostic": {
        "signature": "(name: string) => Promise<one diagnostics node>",
        "status": "implemented"
      },
      "refresh": {
        "signature": "() => Promise<manifest> — re-fetch /sdk-manifest.json (cache-busting)",
        "status": "implemented"
      },
      "instructions": {
        "signature": "() => Promise<this manifest>",
        "status": "implemented",
        "notes": "Returns the same object as fetching /sdk-manifest.json."
      },
      "describe": {
        "signature": "() => Promise<{ instructions, data_summary }>",
        "status": "implemented (partial; data_summary is { available_in: 'M24' })"
      },
      "session": {
        "signature": "() => SessionState { connected, session_id, agent_name, agent_version, purpose, capabilities_understood, connected_at }",
        "status": "implemented",
        "notes": "Returns current handshake state. connected=false before any handshake call."
      },
      "disconnect": {
        "signature": "() => void",
        "status": "implemented",
        "notes": "Drops the current handshake — badge returns to idle. Re-handshaking has the same observable effect."
      },
      "frame": {
        "signature": "(atTime?: number) => Promise<{ time, active: Array<{ key, name, effective_timeline }> }> — scene snapshot: entities ACTIVE at atTime (default current pointer). Drill into entity(key)/text(key).",
        "status": "implemented"
      },
      "text": {
        "signature": "(ref: number | string | { cid, name }, atTime?: number) => Promise<string> — markdown text of an entity (key, ROOT-scope name, or { cid, name } for an imported dependency, #62) at atTime (default latest); '' if none. NON-MINTING.",
        "status": "implemented"
      },
      "entity": {
        "signature": "(ref: number | string | { cid, name }) => Promise<{ found, key, name, active, timeline, fields: [{ capability, field, name, sampleCount, samples }], bindings }> — full observed state (key from frame()/subgraph(), ROOT-scope name, or { cid, name } for an imported dependency, #62). NON-MINTING: { found:false } if absent.",
        "status": "implemented"
      },
      "subgraph": {
        "signature": "(opts?: { roots?: string[], depth?: number }) => Promise<{ loaded, root, roots, nodes: [{ cid, alias, entities: [{ key, name }] }], edges: [{ from, to, kind }] }> — DAG-subset observation (#37): walks the loaded closure's import edges from roots (default closure root) up to depth hops; ALL edge types traversed (discovery surface — execution gating is the loader's, #67). `kind` = the descriptor edge type verbatim (see dag_edge_types). Real-CID node ids. Drill into an entity via entity(key). Empty graph (loaded:false) when no closure loaded.",
        "status": "implemented"
      },
      "summary": {
        "description": "Context-summary-as-UGC. Reserved for M24+. See devnote/M24_2.md §8.9.",
        "put": {
          "signature": "(uuid_set, summary) => Promise<void>",
          "status": "reserved",
          "available_in": "M24+"
        },
        "get": {
          "signature": "(uuid_set) => Promise<ContextSummary | null>",
          "status": "reserved",
          "available_in": "M24+"
        }
      }
    },
    "constants": {
      "CAP_TRANSFORM": "0x01",
      "CAP_TEXTURE": "0x02",
      "CAP_AUDIO": "0x03",
      "CAP_MARKDOWN": "0x04",
      "CAP_MESH": "0x05",
      "CAP_CAMERA": "0x06",
      "CAP_EXISTENCE": "0x07",
      "CAP_RENDER": "0x08",
      "CAP_CONSTRAINT_KIND": "0x09",
      "CAP_INPUT": "0x0A",
      "CAP_ENV": "0x0B",
      "CAP_PORTAL": "0x0C",
      "FIELD_TRANSLATION": "0x00",
      "FIELD_ROTATION": "0x01",
      "FIELD_SCALE": "0x02",
      "FIELD_TEX_SOURCE": "0x00",
      "FIELD_TEX_OPACITY": "0x01",
      "FIELD_TEX_VOLUME": "0x02",
      "FIELD_TEX_SPEED": "0x03",
      "FIELD_TEX_LOOP": "0x04",
      "FIELD_TEX_NATURAL_DURATION": "0x06",
      "FIELD_TEX_NATURAL_SIZE": "0x07",
      "FIELD_AUD_SOURCE": "0x00",
      "FIELD_AUD_VOLUME": "0x01",
      "FIELD_AUD_SPEED": "0x02",
      "FIELD_AUD_LOOP": "0x03",
      "FIELD_AUD_SPATIAL": "0x05",
      "FIELD_AUD_NATURAL_DURATION": "0x06",
      "FIELD_MD_CONTENT": "0x00",
      "FIELD_MD_SIZE": "0x01",
      "FIELD_MD_POSITION": "0x02",
      "FIELD_MD_OPACITY": "0x03",
      "FIELD_MD_STYLE": "0x04",
      "FIELD_MD_CONTENT_OPACITY": "0x05",
      "FIELD_MD_EMBEDS": "0x06",
      "FIELD_MESH_GEOMETRY": "0x00",
      "FIELD_MESH_MATERIAL": "0x01",
      "FIELD_MESH_SIZE_SPEC": "0x02",
      "FIELD_CAM_PROJECTION": "0x00",
      "FIELD_CAM_FOV": "0x01",
      "FIELD_CAM_ZNEAR": "0x02",
      "FIELD_CAM_ZFAR": "0x03",
      "FIELD_CAM_EXPOSURE": "0x04",
      "FIELD_CAM_AUDIO_LISTENER": "0x05",
      "FIELD_ANCHOR": "0x00",
      "FIELD_DURATION": "0x01",
      "FIELD_RENDER_LAYER": "0x00",
      "FIELD_RENDER_Z_ORDER": "0x01",
      "FIELD_KIND_NAME": "0x00",
      "FIELD_KIND_SOURCE": "0x01",
      "FIELD_PORTAL_EYE": "0x00",
      "FIELD_PORTAL_SIZE": "0x01",
      "FIELD_PORTAL_SOURCE": "0x02",
      "FIELD_PORTAL_NATURAL_DURATION": "0x03",
      "FIELD_PORTAL_RANGE": "0x04",
      "FIELD_PORTAL_VOLUME": "0x05",
      "FIELD_ENV_VIEWPORT_WIDTH": "0x00",
      "FIELD_ENV_VIEWPORT_HEIGHT": "0x01",
      "FIELD_ENV_VIEWPORT_ASPECT": "0x02",
      "FIELD_ENV_SCALE_FACTOR": "0x03",
      "FIELD_ENV_THEME": "0x04",
      "FIELD_ENV_SAFE_AREA_TOP": "0x05",
      "FIELD_ENV_SAFE_AREA_RIGHT": "0x06",
      "FIELD_ENV_SAFE_AREA_BOTTOM": "0x07",
      "FIELD_ENV_SAFE_AREA_LEFT": "0x08",
      "FIELD_ENV_ORIENTATION": "0x09"
    }
  },
  "diagnostics": {
    "common_errors": {
      "wasmBindings not ready": "Wait for window.wasmBindings.bubgorge_enqueue to be a function before issuing commands.",
      "Entity not found": "The local name was never created (a typo mints a brand-new key — queries return empty, not an error) or the entity was removed. Call bubgorge.commands.query.entities() to enumerate names.",
      "ack timeout": "DispatchHandle.ack() polls for a matching client_id response; default 30 attempts × 50ms. Increase if the engine is under load."
    }
  },
  "tool_schemas": [
    {
      "name": "create_entity",
      "description": "Create an entity by local name (unique within this UGC).",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string"
          }
        },
        "required": [
          "name"
        ]
      }
    },
    {
      "name": "remove_entity",
      "description": "Remove an entity by local name.",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          }
        },
        "required": [
          "entity"
        ]
      }
    },
    {
      "name": "select",
      "description": "Replace the selection with an entity (session state, never serialized, #61).",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          }
        },
        "required": [
          "entity"
        ]
      }
    },
    {
      "name": "set_scalar",
      "description": "Set a scalar field to a literal value. Storage plane — recorded (#61). Use ONLY if the value is FULLY stated in the prompt (transcribe it). If it would have to be computed/elicited (a described position, \"a bit faster\", a gesture/运镜), do NOT set — that is capture (P2), #57.",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          },
          "field": {
            "type": "string",
            "enum": [
              "audio.speed",
              "audio.volume",
              "camera.exposure",
              "camera.fov",
              "camera.zfar",
              "camera.znear",
              "existence.anchor",
              "existence.duration",
              "markdown.content_opacity",
              "markdown.opacity",
              "portal.portal_eye",
              "portal.portal_volume",
              "render.layer",
              "render.z_order",
              "texture.opacity",
              "texture.speed",
              "texture.volume"
            ],
            "description": "the cap.field to set"
          },
          "value": {
            "type": "number"
          },
          "time": {
            "type": "number",
            "description": "optional pointer time (seconds); default = current playhead (#30)"
          }
        },
        "required": [
          "entity",
          "field",
          "value"
        ]
      }
    },
    {
      "name": "set_vec2",
      "description": "Set a vec2 [x,y] field to a literal value. Storage plane — recorded (#61). Use ONLY if the value is FULLY stated in the prompt (transcribe it). If it would have to be computed/elicited (a described position, \"a bit faster\", a gesture/运镜), do NOT set — that is capture (P2), #57.",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          },
          "field": {
            "type": "string",
            "enum": [
              "markdown.position",
              "markdown.size",
              "portal.portal_range",
              "portal.portal_size"
            ],
            "description": "the cap.field to set"
          },
          "value": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "minItems": 2,
            "maxItems": 2
          },
          "time": {
            "type": "number",
            "description": "optional pointer time (seconds); default = current playhead (#30)"
          }
        },
        "required": [
          "entity",
          "field",
          "value"
        ]
      }
    },
    {
      "name": "set_vec3",
      "description": "Set a vec3 [x,y,z] field to a literal value. Storage plane — recorded (#61). Use ONLY if the value is FULLY stated in the prompt (transcribe it). If it would have to be computed/elicited (a described position, \"a bit faster\", a gesture/运镜), do NOT set — that is capture (P2), #57.",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          },
          "field": {
            "type": "string",
            "enum": [
              "transform.rotation",
              "transform.scale",
              "transform.translation"
            ],
            "description": "the cap.field to set"
          },
          "value": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "minItems": 3,
            "maxItems": 3
          },
          "time": {
            "type": "number",
            "description": "optional pointer time (seconds); default = current playhead (#30)"
          }
        },
        "required": [
          "entity",
          "field",
          "value"
        ]
      }
    },
    {
      "name": "set_bool",
      "description": "Set a bool field to a literal value. Storage plane — recorded (#61). Use ONLY if the value is FULLY stated in the prompt (transcribe it). If it would have to be computed/elicited (a described position, \"a bit faster\", a gesture/运镜), do NOT set — that is capture (P2), #57.",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          },
          "field": {
            "type": "string",
            "enum": [
              "audio.loop",
              "audio.spatial",
              "camera.audio_listener",
              "texture.loop"
            ],
            "description": "the cap.field to set"
          },
          "value": {
            "type": "boolean"
          },
          "time": {
            "type": "number",
            "description": "optional pointer time (seconds); default = current playhead (#30)"
          }
        },
        "required": [
          "entity",
          "field",
          "value"
        ]
      }
    },
    {
      "name": "set_string",
      "description": "Set a string field to a literal value. Storage plane — recorded (#61). Use ONLY if the value is FULLY stated in the prompt (transcribe it). If it would have to be computed/elicited (a described position, \"a bit faster\", a gesture/运镜), do NOT set — that is capture (P2), #57.",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          },
          "field": {
            "type": "string",
            "enum": [
              "camera.projection",
              "constraint_kind.name",
              "constraint_kind.source",
              "markdown.content",
              "portal.portal_source"
            ],
            "description": "the cap.field to set"
          },
          "value": {
            "type": "string"
          },
          "time": {
            "type": "number",
            "description": "optional pointer time (seconds); default = current playhead (#30)"
          }
        },
        "required": [
          "entity",
          "field",
          "value"
        ]
      }
    },
    {
      "name": "set_media_source",
      "description": "Set a media-source field to a content CID (a media-UGC dir CID) or URL. Storage plane — recorded (#61). Use ONLY if the value is FULLY stated in the prompt (transcribe it). If it would have to be computed/elicited (a described position, \"a bit faster\", a gesture/运镜), do NOT set — that is capture (P2), #57.",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          },
          "field": {
            "type": "string",
            "enum": [
              "audio.source",
              "texture.source"
            ]
          },
          "url": {
            "type": "string",
            "description": "media-UGC dir CID or URL"
          },
          "media_range": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "minItems": 2,
            "maxItems": 2,
            "description": "optional [start,end] trim (seconds)"
          },
          "time": {
            "type": "number",
            "description": "optional pointer time (seconds); default = current playhead (#30)"
          }
        },
        "required": [
          "entity",
          "field",
          "url"
        ]
      }
    },
    {
      "name": "set_embeds",
      "description": "Declare the FULL ordered media-embed set of a markdown field (whole-set value, #15). Storage plane — recorded (#61). Use ONLY if the value is FULLY stated in the prompt (transcribe it). If it would have to be computed/elicited (a described position, \"a bit faster\", a gesture/运镜), do NOT set — that is capture (P2), #57.",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          },
          "field": {
            "type": "string",
            "enum": [
              "markdown.embeds"
            ]
          },
          "embeds": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "cid": {
                  "type": "string"
                },
                "range": {
                  "type": "array",
                  "items": {
                    "type": "number"
                  },
                  "minItems": 2,
                  "maxItems": 2
                },
                "name": {
                  "type": "string"
                }
              },
              "required": [
                "cid"
              ]
            }
          },
          "time": {
            "type": "number",
            "description": "optional pointer time (seconds); default = current playhead (#30)"
          }
        },
        "required": [
          "entity",
          "field",
          "embeds"
        ]
      }
    },
    {
      "name": "bind_field",
      "description": "Make a field FOLLOW a source field (persistent constraint; storage plane, #61). Target = your OWN writable field; source = a field (local, or an imported dependency via source_cid — read-only, #62).",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "target_entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          },
          "target_field": {
            "type": "string",
            "enum": [
              "audio.loop",
              "audio.source",
              "audio.spatial",
              "audio.speed",
              "audio.volume",
              "camera.audio_listener",
              "camera.exposure",
              "camera.fov",
              "camera.projection",
              "camera.zfar",
              "camera.znear",
              "constraint_kind.name",
              "constraint_kind.source",
              "existence.anchor",
              "existence.duration",
              "markdown.content",
              "markdown.content_opacity",
              "markdown.embeds",
              "markdown.opacity",
              "markdown.position",
              "markdown.size",
              "portal.portal_eye",
              "portal.portal_range",
              "portal.portal_size",
              "portal.portal_source",
              "portal.portal_volume",
              "render.layer",
              "render.z_order",
              "texture.loop",
              "texture.opacity",
              "texture.source",
              "texture.speed",
              "texture.volume",
              "transform.rotation",
              "transform.scale",
              "transform.translation"
            ],
            "description": "the cap.field to drive"
          },
          "source_entity": {
            "type": "string",
            "description": "source entity local name"
          },
          "source_field": {
            "type": "string",
            "enum": [
              "audio.loop",
              "audio.natural_duration",
              "audio.source",
              "audio.spatial",
              "audio.speed",
              "audio.volume",
              "camera.audio_listener",
              "camera.exposure",
              "camera.fov",
              "camera.projection",
              "camera.zfar",
              "camera.znear",
              "constraint_kind.name",
              "constraint_kind.source",
              "existence.anchor",
              "existence.duration",
              "markdown.content",
              "markdown.content_opacity",
              "markdown.embeds",
              "markdown.opacity",
              "markdown.position",
              "markdown.size",
              "portal.portal_eye",
              "portal.portal_natural_duration",
              "portal.portal_range",
              "portal.portal_size",
              "portal.portal_source",
              "portal.portal_volume",
              "render.layer",
              "render.z_order",
              "texture.loop",
              "texture.natural_duration",
              "texture.natural_size",
              "texture.opacity",
              "texture.source",
              "texture.speed",
              "texture.volume",
              "transform.rotation",
              "transform.scale",
              "transform.translation"
            ],
            "description": "the source cap.field (dotted)"
          },
          "source_cid": {
            "type": "string",
            "description": "optional dependency CID for a cross-UGC imported source (#62)"
          },
          "kind": {
            "type": "string",
            "enum": [
              "Delta",
              "Scale",
              "Copy",
              "AnchorOverlay",
              "AnchorInsert",
              "Additive",
              "Min",
              "Max",
              "ScreenAnchor"
            ],
            "description": "constraint kind"
          },
          "param": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "description": "optional kind params (e.g. Delta offset, Scale factor)"
          }
        },
        "required": [
          "target_entity",
          "target_field",
          "source_entity",
          "source_field",
          "kind"
        ]
      }
    },
    {
      "name": "unbind_field",
      "description": "Remove the constraint binding on a field.",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          },
          "field": {
            "type": "string",
            "enum": [
              "audio.loop",
              "audio.source",
              "audio.spatial",
              "audio.speed",
              "audio.volume",
              "camera.audio_listener",
              "camera.exposure",
              "camera.fov",
              "camera.projection",
              "camera.zfar",
              "camera.znear",
              "constraint_kind.name",
              "constraint_kind.source",
              "existence.anchor",
              "existence.duration",
              "markdown.content",
              "markdown.content_opacity",
              "markdown.embeds",
              "markdown.opacity",
              "markdown.position",
              "markdown.size",
              "portal.portal_eye",
              "portal.portal_range",
              "portal.portal_size",
              "portal.portal_source",
              "portal.portal_volume",
              "render.layer",
              "render.z_order",
              "texture.loop",
              "texture.opacity",
              "texture.source",
              "texture.speed",
              "texture.volume",
              "transform.rotation",
              "transform.scale",
              "transform.translation"
            ]
          }
        },
        "required": [
          "entity",
          "field"
        ]
      }
    },
    {
      "name": "clear_range",
      "description": "Delete samples in [start,end] on a field.",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          },
          "field": {
            "type": "string",
            "enum": [
              "audio.loop",
              "audio.source",
              "audio.spatial",
              "audio.speed",
              "audio.volume",
              "camera.audio_listener",
              "camera.exposure",
              "camera.fov",
              "camera.projection",
              "camera.zfar",
              "camera.znear",
              "constraint_kind.name",
              "constraint_kind.source",
              "existence.anchor",
              "existence.duration",
              "markdown.content",
              "markdown.content_opacity",
              "markdown.embeds",
              "markdown.opacity",
              "markdown.position",
              "markdown.size",
              "portal.portal_eye",
              "portal.portal_range",
              "portal.portal_size",
              "portal.portal_source",
              "portal.portal_volume",
              "render.layer",
              "render.z_order",
              "texture.loop",
              "texture.opacity",
              "texture.source",
              "texture.speed",
              "texture.volume",
              "transform.rotation",
              "transform.scale",
              "transform.translation"
            ]
          },
          "start": {
            "type": "number"
          },
          "end": {
            "type": "number"
          }
        },
        "required": [
          "entity",
          "field",
          "start",
          "end"
        ]
      }
    },
    {
      "name": "reset_field",
      "description": "Drop all samples on a field.",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          },
          "field": {
            "type": "string",
            "enum": [
              "audio.loop",
              "audio.source",
              "audio.spatial",
              "audio.speed",
              "audio.volume",
              "camera.audio_listener",
              "camera.exposure",
              "camera.fov",
              "camera.projection",
              "camera.zfar",
              "camera.znear",
              "constraint_kind.name",
              "constraint_kind.source",
              "existence.anchor",
              "existence.duration",
              "markdown.content",
              "markdown.content_opacity",
              "markdown.embeds",
              "markdown.opacity",
              "markdown.position",
              "markdown.size",
              "portal.portal_eye",
              "portal.portal_range",
              "portal.portal_size",
              "portal.portal_source",
              "portal.portal_volume",
              "render.layer",
              "render.z_order",
              "texture.loop",
              "texture.opacity",
              "texture.source",
              "texture.speed",
              "texture.volume",
              "transform.rotation",
              "transform.scale",
              "transform.translation"
            ]
          }
        },
        "required": [
          "entity",
          "field"
        ]
      }
    },
    {
      "name": "query_frame",
      "description": "Entities active at a pointer time (default: now) — the scene snapshot.",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "atTime": {
            "type": "number",
            "description": "optional pointer time (seconds); default = current playhead (#30)"
          }
        },
        "required": []
      }
    },
    {
      "name": "list_entities",
      "description": "List all entities.",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {},
        "required": []
      }
    },
    {
      "name": "query_entity",
      "description": "One entity full state: timeline + per-field authored samples + bindings.",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          }
        },
        "required": [
          "entity"
        ]
      }
    },
    {
      "name": "query_text",
      "description": "The markdown text of an entity at a time (default: latest).",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          },
          "atTime": {
            "type": "number",
            "description": "optional pointer time (seconds); default = current playhead (#30)"
          }
        },
        "required": [
          "entity"
        ]
      }
    },
    {
      "name": "query_subgraph",
      "description": "The loaded closure DAG subset (nodes + per-node entities + edges, #37).",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "depth": {
            "type": "number",
            "description": "optional max hops"
          }
        },
        "required": []
      }
    },
    {
      "name": "query_field",
      "description": "One field's value: the latest AUTHORED sample + count by default, or the RESOLVED runtime value (what actually drives the field — bind/observer/constraint output, #22) when resolved:true (P2 §13.1).",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          },
          "field": {
            "type": "string",
            "enum": [
              "audio.loop",
              "audio.natural_duration",
              "audio.source",
              "audio.spatial",
              "audio.speed",
              "audio.volume",
              "camera.audio_listener",
              "camera.exposure",
              "camera.fov",
              "camera.projection",
              "camera.zfar",
              "camera.znear",
              "constraint_kind.name",
              "constraint_kind.source",
              "existence.anchor",
              "existence.duration",
              "markdown.content",
              "markdown.content_opacity",
              "markdown.embeds",
              "markdown.opacity",
              "markdown.position",
              "markdown.size",
              "portal.portal_eye",
              "portal.portal_natural_duration",
              "portal.portal_range",
              "portal.portal_size",
              "portal.portal_source",
              "portal.portal_volume",
              "render.layer",
              "render.z_order",
              "texture.loop",
              "texture.natural_duration",
              "texture.natural_size",
              "texture.opacity",
              "texture.source",
              "texture.speed",
              "texture.volume",
              "transform.rotation",
              "transform.scale",
              "transform.translation"
            ]
          },
          "resolved": {
            "type": "boolean",
            "description": "read the resolved runtime value (bind/observer output), not the authored sample"
          },
          "time": {
            "type": "number",
            "description": "pointer time for the resolved read; default = now"
          }
        },
        "required": [
          "entity",
          "field"
        ]
      }
    },
    {
      "name": "get_media_frame",
      "description": "A media SOURCE's decoded frame at a time, as a reference image (data URL) to feed a generative model. Observation read — never writes or records (#57-safe: a frame is a fact, not an invented value). Address by the media-UGC CID; audio yields data_url:null. Single frame — loop for several times.",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "source": {
            "type": "string",
            "description": "media-UGC dir CID"
          },
          "time": {
            "type": "number",
            "description": "seconds into the source"
          },
          "format": {
            "type": "string",
            "enum": [
              "jpeg",
              "png"
            ]
          },
          "quality": {
            "type": "number",
            "description": "0..1 (jpeg)"
          },
          "maxDim": {
            "type": "number",
            "description": "downscale the longest side to this many px"
          }
        },
        "required": [
          "source",
          "time"
        ]
      }
    },
    {
      "name": "get_media_state",
      "description": "Media playback HEALTH for an entity (current_time, duration, paused, size) — tell a loaded/playing source from a failed or not-yet-loaded one after set_media_source. Read-only diagnostic; NOT a clock (pointer.position is the clock, #30).",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entity": {
            "type": "string",
            "description": "entity local name (ROOT authoring scope; create_entity it first)"
          }
        },
        "required": [
          "entity"
        ]
      }
    },
    {
      "name": "observer_state",
      "description": "The resolved viewer degrees of freedom (#65): viewport (width/height/aspect), dpr, theme, orientation, and the primary camera pose/fov. 'Overall state' context. Read-only, runtime-only (never serialized, #64); 1-frame lag.",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {},
        "required": []
      }
    },
    {
      "name": "playback",
      "description": "Timeline transport (session; pointer.position is the sole clock #30).",
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "play",
              "pause",
              "seek",
              "setSpeed"
            ]
          },
          "value": {
            "type": "number",
            "description": "seconds for seek; multiplier for setSpeed"
          }
        },
        "required": [
          "action"
        ]
      }
    }
  ]
}
