# WAI Extension: Scene Carriage > Status: Draft. Maps WAI's scene content — a world (`WWLD`, > [interactive-worlds](interactive-worlds.md)), an audio scene (`WSCN`, > [spatial-audio](spatial-audio.md)), the assets a world names, and timed media — onto two > scene-description formats, in both directions: ISO/IEC 23090-14 scene description, a set of > glTF 2.0 extensions (§4, §5), and a USD layer in the text format of the USD Core > Specification (§6, §7). A document WAI writes carries the WAI bytes exactly, beside the > presentation a player of that format reads; reading it back gives the same bytes, and is > refused unless writing them again gives the document. A document WAI did not write becomes > new WAI bytes under stated rules, and is refused wherever a value would not carry exactly. > No new capability and no transport. Reference implementation: the `scene_map` module of > `wai-rs` (feature `scene_map`); corpus [`live-conformance/scene/`](../live-conformance/scene/), > second implementation `live-conformance/scene_verify.py`. > Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. > > Texts read for this revision: glTF 2.0 as ISO/IEC 12113:2022; the descriptions and JSON > schemas of the extensions of ISO/IEC 23090-14 that the glTF extension registry publishes > (`MPEG_media`, `MPEG_buffer_circular`, `MPEG_accessor_timed`, `MPEG_audio_spatial`, > `MPEG_texture_video`, `MPEG_animation_timing`, `MPEG_scene_dynamic`, `MPEG_mesh_linking`, > `MPEG_viewport_recommended`; each states that it is based on ISO/IEC FDIS 23090-14, and > the registry marks their licence as not yet determined, so none is copied here); and the USD > Core Specification v1.0.1 (b7bc21a, 2025-12-12), published under CC-BY-ND-4.0. ISO/IEC > 23090-14's own text, including its Annex D (the attenuation functions) and the timed > accessor information header of its clause 5, was not read. ## 1. Scope In scope: the bundle a mapping reads and writes (§2); the numbers that cross it (§3); the document each direction writes or reads, for glTF (§4, §5) and for USD (§6, §7); the refusals (§8); the corpus (§9). Out of scope: - **Presenting a world after tick 0.** A world's op log changes its nodes through the sim that owns them; a scene description has no sim. A document presents the world's tick-0 graph and carries its op log and seal in the WAI bytes, so a WAI sink replays it. `MPEG_scene_dynamic` carries scene updates as JSON patches over a scene; an op is a sim mutation, not a patch, so this mapping does not write one. - **Mesh geometry in glTF.** A world names its assets by content hash and never embeds them (interactive-worlds §4.1); glTF 2.0 and the extensions read give a scene no reference to a separate glTF asset. So a `wai.mesh.glb` asset, like every asset of a world's table, travels in the glTF document by its hash, in the carried bytes, and a glTF player does not present it; a USD layer references it (§6.2), which USD composes where a reader for the asset exists. Gaussian splats in glTF are `KHR_gaussian_splatting` with `EXT_gaussian_splatting_compression_wai` (the exact-splat extension); this mapping does not carry them. - **Rendering.** The presentation is what each format says a player reads; how a player renders it, and how a WAI sink renders the carried bytes, is the renderer's. ## 2. The bundle ### 2.1 Worlds and audio scenes A **bundle** holds at most one world (a `WWLD` payload of a `wai.world.*` capability), at most one audio scene (a `WSCN` payload of `wai.audio.scene`) and a list of timed media items (§2.2). The mapping reads each container's section table (interactive-worlds §4: the magic, a little-endian u16 count, nine-byte entries of a kind, an offset from the end of the table and a length; every section within the payload, no kind twice) and: - of a world: the contract section (0x01), a JSON text whose `sim` is a string; the graph section (0x02), canonical state bytes (interactive-worlds §7.1: a u32 count, then each node's 16-byte id, a u32 length and its state, ids strictly ascending, nothing after); and the asset table (0x03) where present (interactive-worlds §4.1: a u32 count, then each entry's 32-byte hash, its role byte, a u32 name length and the UTF-8 name, nothing after). A world that breaks one of these, or a node whose sim §3.4 gives a position with a state too short to hold it, is `world_malformed`. - of an audio scene: the contract section (0x01), a JSON text with `sample_rate` (1 to 2^32 − 1) and `duration`, each a plain decimal integer; the object table (0x02, spatial-audio §2.2, read to its end and nothing after); the asset table (0x03) where present; and a seal (0x04) of 104 bytes where present. One that breaks these is `audio_malformed`. Other sections are carried and not read. The checks run world, then audio scene, then media items in order. ### 2.2 Timed media items A **timed media item** is a WAI object named by its content hash — the hash of interactive-worlds §4.1, over a complete envelope — with a `name` (a string), its `capability`, and for pictures a `width` and a `height`. Its capability is `wai.video.`, `wai.audio.` or `wai.image.` followed by the capability grammar of [http-delivery](http-delivery.md) §4.1 (segments of `a` to `z` and `0` to `9`, then segments that may also hold `_`); a picture's (`wai.video.*`, `wai.image.*`) width and height are each 1 to 65535, and an audio item has neither. An item that breaks this is `media_malformed`. A document names a WAI object by the URI `urn:wai:asset:` followed by its hash in 64 lowercase hexadecimal digits. ### 2.3 What a document carries Every document this mapping writes names its profile, `wai-scene/1`, and carries the bundle exactly: the world's and the audio scene's payloads in lowercase hexadecimal, and the media list as a JSON text — an array of objects, each of exactly `name`, `asset` (64 lowercase hexadecimal digits) and `capability`, then for a picture `width` and `height`, in that order, written as §4.1 writes JSON. A reader takes the WAI bytes from what is carried and nothing else; the rest of the document is presentation, a function of those bytes. ## 3. Numbers ### 3.1 Conversions A position in WAI is three Q32.32 values (`wai.det.fixed64`: a signed 64-bit integer `r` standing for `r / 2^32`). Writing one, the mapping takes the binary32 (for glTF) or binary64 (for USD) nearest `r / 2^32`, ties to even; every non-zero result is a normal number. A time is a sample index `s` of an audio scene at rate `R`; writing it, the mapping takes the binary32 nearest `s / R`, ties to even. Reading, a binary32 or binary64 value `v` is a Q32.32 value only when `v · 2^32` is an integer from −2^63 to 2^63 − 1 (`position_not_exact` otherwise), and a binary32 time `t ≥ 0` is a sample index only when `t · R` is an integer of at most 2^64 − 1 (`time_not_exact`). ### 3.2 JSON numbers glTF's JSON numbers are written as RFC 8785 §3.2.2.3 writes a binary64 (the shortest digits that read back to it, spelt as ECMAScript's `Number.prototype.toString` spells them), the binary64 being the binary32 value of §3.1 exactly. Integers are plain decimal. A reader takes a JSON number for a glTF float as glTF does: the binary32 nearest its decimal value, ties to even; one past the binary32 range is `document_malformed`. ### 3.3 USDA numbers USDA numbers are written as the USD Core Specification (*Double-Precision Floating Point Representation when Writing USDA content*) writes a binary64: the shortest digits that read back to it; scientific notation, one digit before the point and a signed exponent with no `+`, when the decimal exponent is below −6 or at least 15; decimal otherwise; no trailing point, no trailing zero in a fraction, no leading zero but before the point of a value below 1. Integers are plain decimal. A reader takes a USDA number as the binary64 nearest its decimal value. ### 3.4 Positions by sim A world node has a position when its sim's state begins with one: `wai.world.sim.kinematic0` and `wai.world.sim.ballistic0`, the first 24 bytes (three little-endian Q32.32 values); `wai.world.sim.truss0`, a particle (state byte 0 is 0), the 24 bytes after that byte. Other nodes and sims have none. A node with no position is presented with no translation. ## 4. Writing a glTF document ### 4.1 The document The document is one JSON text in UTF-8, followed by a line feed. Every object is written with the members this section lists, in the order it lists them, and none other; no whitespace; strings escaped as RFC 8785 §3.2.2.2 escapes them; numbers as §3.2 writes them. A member a step does not produce is not written. The document's members are, in order: `asset` (`{"version":"2.0","generator":"wai-scene/1"}`); `extensionsUsed` (the names of the extensions the document uses, in ascending byte order); `extensions`, holding `MPEG_media` when there are media items (§4.4); `scene` (0); `scenes` (one scene whose `nodes` lists every node, `[{}]` when there is none); `nodes`; `cameras`; `buffers`; `bufferViews`; `accessors`; `animations`; `textures`; `extras` (§4.5). First the bundle is checked (§2). ### 4.2 The world Each node of the world's tick-0 graph, in ascending id order, is a node `{"name":"node-<32 lowercase hexadecimal digits of its id>","translation":[x,y,z]}`, its translation the binary32 of §3.1 of its position (§3.4), absent when it has none. ### 4.3 The audio scene An audio scene adds a camera, `{"type":"perspective","perspective":{"yfov":1,"znear":0.25}}`, and after the world's nodes a listener node, `{"name":"listener","camera":0,"extensions":{"MPEG_audio_spatial":{"listener":{"id":0}}}}`. Then each object `i`, in table order, is a node `{"name":"object-","translation":[…],"extensions":{"MPEG_audio_spatial":{"sources":[{"id":i,"type":"Object","attenuation":"custom","accessors":[a]}]}}}`, its translation its first key's position (absent with no key), `a` its source's timed accessor (§4.4). Its keys MUST be in strictly ascending sample order, and their times (§3.1) strictly ascending, or the bundle is `not_presentable`. The attenuation is `custom`, which ISO/IEC 23090-14 leaves outside its scope: WAI's distance law and gain are in the carried bytes, and this mapping states no equivalence with a function of that standard's Annex D. An object with two or more keys is animated. The document's first buffer holds, object by object, its times (binary32, little-endian), then its positions (three binary32 each), and is `{"byteLength":n,"uri":"data:application/octet-stream;base64,"}`. For each animated object, in order: two buffer views, `{"buffer":0,"byteOffset":o,"byteLength":4k}` and `{"buffer":0,"byteOffset":o',"byteLength":12k}`; two accessors, `{"bufferView":v,"componentType":5126,"count":k,"type":"SCALAR","min":[t0],"max":[tlast]}` and `{"bufferView":v+1,"componentType":5126,"count":k,"type":"VEC3"}`; a sampler `{"input":a,"interpolation":"LINEAR","output":a+1}`; and a channel `{"sampler":s,"target":{"node":n,"path":"translation"}}`. One animation holds them all: `{"name":"audio","channels":[…],"samplers":[…]}`. glTF's linear interpolation of positions between keys is the presentation; WAI's own interpolation of keys is spatial-audio §2.2's, in the carried bytes. ### 4.4 Media `MPEG_media`'s `media` lists first the audio scene's sources, each once, in object order, then the bundle's other items in list order (an item whose hash is already listed is not listed again). Each is `{"name":…,"autoplay":true,"alternatives":[{"uri":"urn:wai:asset:","mimeType":…}]}`: for a source, the name of the first bundle item with its hash, else `source-` and the first 16 digits of its hash; the `mimeType` is `application/wai` ([http-delivery](http-delivery.md) §3) followed, when the item's capability is known, by `; codecs="wai1."`, the `codecs` grammar of http-delivery §4.1. Each source, and each other item of a picture capability, gets in media order a circular buffer, `{"byteLength":B,"extensions":{"MPEG_buffer_circular":{"media":m}}}`, a view `{"buffer":b,"byteLength":B}`, and a timed accessor: for a source, `{"bufferView":v,"componentType":5126,"count":1024,"type":"SCALAR","extensions":{"MPEG_accessor_timed":{"immutable":true}}}` with `B` = 4096 (one frame of 1024 mono binary32 samples); for a picture of width `w` and height `h`, `{"bufferView":v,"componentType":5121,"normalized":true,"count":w·h,"type":"VEC3","extensions":{"MPEG_accessor_timed":{"immutable":true}}}` with `B` = 3·w·h, and a texture `{"extensions":{"MPEG_texture_video":{"accessor":a,"width":w,"height":h,"format":"RGB"}}}`. Buffers, views and accessors follow the animation's. A circular buffer's `byteLength` is one frame; how many frames a pipeline keeps is `MPEG_buffer_circular`'s `count`, left at its default. An accessor with no header view is `immutable`, as `MPEG_accessor_timed`'s schema requires. The extensions used are `MPEG_audio_spatial` with an audio scene, `MPEG_media` with media items, `MPEG_buffer_circular` and `MPEG_accessor_timed` with a circular buffer, and `MPEG_texture_video` with a texture. ### 4.5 What is carried The document's `extras` is `{"wai":{"profile":"wai-scene/1","world":"","audio":"","media":""}}` (§2.3), `world`, `audio` and `media` present when the bundle has them. ## 5. Reading a glTF document ### 5.1 Which reading A document that is not a JSON text (RFC 8259, UTF-8, no member named twice, at most 64 levels) whose value is an object with `asset.version` `"2.0"` is `document_malformed`. One whose `extras` has a member `wai` is read as §5.2; any other, as §5.3. ### 5.2 A document WAI wrote `extras.wai` is an object of the members of §4.5 and no other, `profile` being `wai-scene/1`, `world` and `audio` lowercase hexadecimal of even length, and `media` a JSON text of §2.3's form; otherwise the document is `carried_malformed`. The bundle is what it carries. Writing that bundle (§4) MUST give the document's bytes: a bundle §4 refuses is `carried_malformed`, and other bytes are `presentation_mismatch`. So a player's view and the WAI bytes cannot drift apart unnoticed. ### 5.3 A document WAI did not write Reading one needs the key that mints its node ids (an `actor`, 32 bytes) and the contract of the world and of the audio scene it yields. The steps run in order, and the first refusal is the one reported; each index a step follows MUST be in range, and a member a step reads MUST have the type it names, or the document is `document_malformed`. 1. **The nodes.** The scene is `scenes[scene]` (`scene` defaulting to 0; no scenes, no nodes). Its nodes are visited depth first in preorder: its `nodes` in order, each node's `children` in order. A node visited twice is `document_malformed`. 2. **Positions**, in visiting order. A node with a `matrix`, a `rotation` other than (0, 0, 0, 1) or a `scale` other than (1, 1, 1), each read as binary32 (§3.2), is `transform_not_carried`: a node of the world has a position and nothing more. Its translation (zero when absent) is read as binary32 and then as Q32.32 (§3.1, `position_not_exact`), and its position is its parent's plus its translation (`position_not_exact` past the Q32.32 range). 3. **Kinds**, in visiting order: a node whose `extensions.MPEG_audio_spatial` has `sources` is an audio node; one with a `camera`, or with `MPEG_audio_spatial` and no sources, is skipped; every other node is a world node, and a world node with a `mesh` is reported as presentation not carried (`node : mesh`). 4. **Animations**, in document order, each animation's channels in order: a channel's `sampler` is followed first, then its `target`; a target with no `node` is ignored. A channel whose node is not an audio node, whose `path` is not `translation`, whose sampler's `interpolation` (`LINEAR` by default) is not `LINEAR`, or that is a node's second channel, is `animation_not_carried`. 5. **Audio objects**, audio nodes in visiting order, each node's sources in order. A source of `type` other than `Object` is `audio_not_object` (an `HOA` source has no position to carry); a `pregain` other than 0 is `gain_not_exact` (a gain in decibels has no exact linear value). Its first accessor's buffer view's buffer is a circular buffer whose `media` names an `MPEG_media` item; the source is the first of that item's alternatives whose `uri` is `urn:wai:asset:` (`media_not_wai` if none). Its keys: with no channel, one key at sample 0 at the node's position; with one, the audio contract is due now (`contract_absent` if absent, not JSON, or without a `sample_rate` of 1 to 2^32 − 1), and the sampler's input (`FLOAT`, `SCALAR`) and output (`FLOAT`, `VEC3`) accessors, neither sparse, read from views with no `byteStride` of a buffer whose `uri` is `data:application/octet-stream;base64,` and canonical base64, each within its view and buffer, the output three values to each input value, give its keys: each time a sample (§3.1, `time_not_exact`), each position the parent's position plus the output as Q32.32 (`position_not_exact`). Its gain is 1. 6. **Other media**, the `MPEG_media` items no source uses, in order: its first `urn:wai:asset` alternative (`media_not_wai` if none), whose `mimeType` names a capability as §4.4 writes it (`media_not_wai` otherwise); its name (empty when absent); for a picture capability, the width and height of the first `MPEG_texture_video` texture whose accessor's circular buffer is this item. An item that then breaks §2.2 is `media_not_wai`. 7. **The bytes.** World nodes need the world contract (`contract_absent` if absent or not JSON), whose `sim` MUST be `wai.world.sim.kinematic0` (`sim_not_importable`): the `i`-th world node, from 1 in visiting order, gets the id interactive-worlds §5.4 mints for the actor and lamport `i`, and a kinematic0 state at its position, at rest, with scalar 0. The world is the contract given, those nodes in ascending id order, no asset table, and an empty op log, laid out as interactive-worlds §4 lays a container out. Audio objects need the audio contract (as in step 5); the audio scene is the contract given, the objects in order (gain 1), and an asset table of each distinct source in object order, role 0, named by its item's name. The media list is step 6's. ## 6. Writing a USD layer ### 6.1 The layer The layer is UTF-8 text in USD's text format, every line ended by a line feed, indented four spaces a level: ``` #usda 1.0 ( customLayerData = { string "wai:audio" = "" string "wai:media" = string "wai:profile" = "wai-scene/1" string "wai:world" = "" } defaultPrim = "World" endTimeCode = startTimeCode = 0 timeCodesPerSecond = ) def Xform "World" { … } ``` `wai:audio`, `wai:media` and `wai:world` are written when the bundle has them, and the three time fields with an audio scene: time codes are its sample indices. The prims of §6.2 are `World`'s children, with one empty line before each but the first. First the bundle is checked (§2). ### 6.2 Prims - **World nodes**, in ascending id order: `def Xform "node_<32 hex>"`, holding `double3 xformOp:translate = (x, y, z)` and `uniform token[] xformOpOrder = ["xformOp:translate"]` when the node has a position (§3.4), each value the binary64 of §3.1 written as §3.3; the meaning of the translate operation is a domain specification's, which the core specification leaves to them, and the core specification fixes its syntax and value resolution. - **Assets**, when the world has an asset table: `def Scope "Assets"`, and in it, for entry `i` in table order, `def "asset_"` with the prim metadata `prepend references = @urn:wai:asset:@` and the properties `custom string wai:name = ` and `custom int wai:role = `. A reference is a composition arc to the asset. Where the reader cannot compute a layer stack for it — no reader for a WAI object — the core specification's composition treats it as a composition error and ignores that reference, and the rest of the stage composes. - **Audio objects**, when the audio scene has any: `def Scope "Audio"`, and in it, for object `i`, `def Xform "object_"` with `custom string wai:source = ""`, then, with keys, its first key's position as `xformOp:translate`, with two or more keys `double3 xformOp:translate.timeSamples = {` and one line `: (x, y, z),` per key and `}`, and the `xformOpOrder` above. Keys MUST be in strictly ascending sample order, and a sample, the duration and the rate at most 2^53, or the bundle is `not_presentable`. - **Media**, when the list has items: `def Scope "Media"`, and in it, for item `i`, `def "media_"` with `prepend references = @urn:wai:asset:@` and the properties `custom string wai:capability`, for a picture `custom int wai:height`, `custom string wai:name`, and for a picture `custom int wai:width`, in that order. Prims inside a scope are separated by one empty line. A prim with metadata is written `def "" (`, the metadata line, `)`, then its body `{` … `}` on the following lines; an empty body is `{` and `}`. ### 6.3 Strings A string is written in double quotes, with `\"`, `\\`, `\n`, `\r` and `\t`, and with `\x` and two uppercase hexadecimal digits for any other character below U+0020 and for U+007F. A character outside the grammar's `Utf8Character` ranges has no spelling, and a bundle that needs one is `not_presentable`. ## 7. Reading a USD layer ### 7.1 The subset read A layer that is not UTF-8, or does not begin `#usda`, is `document_malformed`; a version on that line other than `1.0` is `usd_not_read`. The rest is read as the core specification's grammar reads it, for this subset: - **Tokens.** Spaces, tabs and line breaks separate tokens; `#…` and `//…` to the end of a line and `/*…*/` are comments. Strings in `"` or `'`, on one line, with the escapes `\n`, `\r`, `\t`, `\a`, `\b`, `\f`, `\v`, `\\`, `\'`, `\"` and `\x` with one or two uppercase hexadecimal digits below 0x80; asset paths `@…@` on one line; paths `<…>`; numbers (`-`, digits, `.`, `e`, `E` and an exponent's sign, or `inf`); identifiers (`A`–`Z`, `a`–`z`, `0`–`9`, `_`, and `:` before a letter or `_`); and `( ) [ ] { } = : , ; .`. A triple-quoted string, a `@@@` asset path, another escape or character is `usd_not_read`; a string, path, asset path or comment that does not end is `document_malformed`. - **Values**: numbers, strings, identifiers, asset paths, paths, tuples `( … )`, lists `[ … ]` and dictionaries (`{` items `type key = value` or `dictionary key = { … }` `}`). - **Layer metadata** `( … )`: a bare string is a comment; `subLayers`, `relocates` and a list operation are `usd_not_read`; `customLayerData` is a dictionary and `defaultPrim` a string (`document_malformed` otherwise); other items are read and not used. - **Prims**: `def`, an optional type name, the name as a string, optional prim metadata, and a body. `over` and `class` are `usd_not_read`. In prim metadata, a bare string is a comment; `references`, alone or after `prepend` or `append`, is `None`, an asset path or a list of them, each without a prim path or parameters (`usd_not_read` otherwise; `delete` and other list operations on it too); `payload`, `inherits`, `specializes`, `variants`, `variantSets` and `relocates`, and a list operation on anything else, are `usd_not_read`; other items are read and not used. - **Bodies**: child prims (a name twice is `document_malformed`, as two root prims of one name are); `rel`, `variantSet`, `reorder` and list-operation statements are `usd_not_read`; and attributes, `[custom] [uniform|varying] type[[]] name` then `= value`, `.timeSamples = { time: value, … }` or nothing, then optional metadata. A `.` other than `.timeSamples` is `usd_not_read`; an attribute declared twice with two types, or given two defaults or two sets of time samples, is `document_malformed`. Anything else is `document_malformed`. ### 7.2 A layer WAI wrote A layer whose `customLayerData` has a string key beginning `wai:` is read as WAI wrote it: its `wai:` items MUST be the strings of §6.1, `wai:profile` being `wai-scene/1` (`carried_malformed` otherwise), and writing the bundle they carry (§6) MUST give the layer's bytes (`carried_malformed` for a bundle §6 refuses, `presentation_mismatch` for other bytes). ### 7.3 A layer WAI did not write The root is the prim `defaultPrim` names (`document_malformed` if no root prim has that name), else the first root prim (`document_malformed` if none). Prims are visited depth first in preorder from the root, the root included, children in written order. For each, in visiting order: 1. **Its offset**, from its properties in written order: `xformOpOrder` MUST be a list of the one string `xformOp:translate` with no time samples; `xformOp:translate` MUST have a default value and no time samples; any other `xformOp:` property is not carried; each breach is `transform_not_carried`. The translate's default is a tuple of three numbers (`document_malformed`), each read as binary64 (§3.3) and then as Q32.32 (`position_not_exact`). With no translate the offset is zero. Its position is its parent's plus its offset (`position_not_exact` past the range). 2. **Its kind.** A prim with references is a set of assets: each asset path MUST be `urn:wai:asset:` (`asset_not_wai`), and gives an asset entry of role 0 named by the prim. Otherwise an `Xform` prim with an `xformOp:translate` is a world node at its position. Otherwise a prim of a type other than none, `Xform` and `Scope` is reported as presentation not carried (`: `). Nodes or assets need the world contract (`contract_absent` if absent or not JSON), whose sim MUST be `wai.world.sim.kinematic0` (`sim_not_importable`). The world is built as §5.3 step 7 builds one, with an asset table of the entries in visiting order. A layer gives no audio scene and no media: the core specification defines no audio prims, so the audio and media of a layer are read only from the bytes a WAI-written layer carries (§7.2). ## 8. Refusals | code | where | |---|---| | `world_malformed` | §2.1: the world does not read | | `audio_malformed` | §2.1: the audio scene does not read | | `media_malformed` | §2.2: a media item's capability, width or height | | `not_presentable` | §4.3, §6.2, §6.3: keys out of order, two keys at one binary32 time, a time code past 2^53, a character with no USDA spelling | | `document_malformed` | §5.1, §5.3, §7.1, §7.3: the document does not read as its format, or a value a step needs is not there | | `carried_malformed` | §5.2, §7.2: what a WAI-written document carries does not read, or is a bundle §4 or §6 refuses | | `presentation_mismatch` | §5.2, §7.2: the document is not the export of the bundle it carries | | `usd_not_read` | §7.1: a construct outside the subset | | `transform_not_carried` | §5.3 step 2, §7.3 step 1: a rotation, scale, matrix or other transform operation | | `position_not_exact` | §3.1, §5.3, §7.3: a value that is not a Q32.32 value | | `animation_not_carried` | §5.3 step 4: an animation channel the mapping does not carry | | `time_not_exact` | §3.1, §5.3 step 5: a time that is not a whole sample | | `gain_not_exact` | §5.3 step 5: a pregain other than 0 | | `audio_not_object` | §5.3 step 5: a source that is not an object | | `media_not_wai` | §5.3 steps 5 and 6: media that names no WAI object or capability | | `asset_not_wai` | §7.3 step 2: a reference to something other than a WAI object | | `contract_absent` | §5.3, §7.3: nodes or objects and no contract to give them | | `sim_not_importable` | §5.3 step 7, §7.3: a world contract whose sim is not `wai.world.sim.kinematic0` | ## 9. Conformance corpus [`live-conformance/scene/`](../live-conformance/scene/) holds bundles — worlds built in code (kinematic0, ballistic0 and truss0 nodes, an asset table) and the worlds and audio scenes other corpora commit, each pinned by SHA-256 — each written as a glTF document and as a USD layer and read back; bundles refused on export; documents WAI wrote, altered and refused; and documents WAI did not write, built in code, read to new WAI bytes or refused with each code of §8. A vector is an *export* (a bundle to a document's bytes, or a code) or an *import* (a document, with the actor and contracts §5.3 needs, to a bundle and its report of presentation not carried, or a code). A verifier conforms when it reaches every vector's outcome and bytes. `wai_scene_vectors gen|verify` writes and checks it, `tests/scene_map.rs` regenerates it, and `live-conformance/scene_verify.py`, written from this text and sharing no code with `wai-rs`, is its second implementation. ## 10. Reference implementation The `scene_map` module of `wai-rs` (feature `scene_map`, on `world` and `audio_scene`): `Bundle`, `MediaItem`, `ImportParams` and `Imported`; `gltf::export` and `gltf::import`; `usda::export` and `usda::import`; `num` for §3; `corpus` for §9. Its unit tests check the conversions and spellings of §3 against fixed values, and that the containers §5.3 and §7.3 build are the bytes `world::WorldContainer` and `audio_scene::SpatialScene` write.