open-standards/wai/live-conformance/scene_verify.py
Transaction Science a1ecded5a2 wai: scene-carriage text, corpus README and independent Python verifier
extensions/scene-carriage.md: the bundle, the numbers that cross the
mapping, the glTF document (MPEG_media, MPEG_buffer_circular,
MPEG_accessor_timed, MPEG_audio_spatial, MPEG_texture_video) and the USD
layer each direction writes and reads, the refusals and the corpus.
scene_verify.py implements it from the text and agrees with wai-rs on
every vector; it also flips bytes of every exported document and
requires each copy to be caught.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-22 07:51:03 -04:00

1626 lines
58 KiB
Python

#!/usr/bin/env python3
"""Independent verifier of the scene-carriage corpus (extensions/scene-carriage.md §9).
Written from the extension's text: §2 (the bundle), §3 (numbers), §4 and §5 (glTF), §6 and
§7 (USD). It shares no code with wai-rs, which generates the corpus; it mints node ids with
BLAKE3 written from its specification (claim-conformance/_blake3.py) and reads §8's codes
from the extension.
For every vector it requires the outcome the corpus states: for an export, the document's
bytes or the code; for an import, the bundle's bytes, its media list and its report of
presentation not carried, or the code. Then it flips one byte of each document the corpus
exports, in several places, and requires every copy to be refused or to read to another
bundle.
python3 -I scene_verify.py <live-conformance/scene>
Exit status 0 only if every vector verifies and every mutated copy is caught.
"""
import base64
import binascii
import hashlib
import json
import os
import re
import struct
import sys
from fractions import Fraction
HERE = os.path.dirname(os.path.abspath(__file__))
WAI = os.path.normpath(os.path.join(HERE, ".."))
sys.path.insert(0, os.path.join(WAI, "claim-conformance"))
from _blake3 import blake3 # noqa: E402
EXTENSION = os.path.join(WAI, "extensions", "scene-carriage.md")
PROFILE = "wai-scene/1"
URN = "urn:wai:asset:"
KIN = "wai.world.sim.kinematic0"
class Refuse(Exception):
def __init__(self, code, why=""):
super().__init__(f"{code}: {why}")
self.code = code
def codes():
text = open(EXTENSION, encoding="utf-8").read()
sec = text.split("\n## 8.")[1].split("\n## ")[0]
return [m.group(1) for m in re.finditer(r"^\| `([a-z_]+)` \|", sec, re.M)]
# ------------------------------------------------------------------ §3 numbers
def round_binary(q, mant_bits, emin):
"""The binary floating-point value nearest the Fraction q, ties to even, as a Fraction:
mant_bits significand bits, emin the least normal exponent (unbounded above)."""
if q == 0:
return Fraction(0)
sign = -1 if q < 0 else 1
a = abs(q)
e = a.numerator.bit_length() - a.denominator.bit_length()
if Fraction(2) ** e > a:
e -= 1
while Fraction(2) ** (e + 1) <= a:
e += 1
e = max(e, emin)
scale = Fraction(2) ** (e - (mant_bits - 1))
m = a / scale
fl = m.numerator // m.denominator
rem = m - fl
if rem > Fraction(1, 2) or (rem == Fraction(1, 2) and fl % 2 == 1):
fl += 1
return sign * fl * scale
F32_MAX = (2 - Fraction(1, 2 ** 23)) * Fraction(2) ** 127
def to_f32(q):
"""§3.1, §3.2: the binary32 nearest q, ties to even; None past the range."""
v = round_binary(Fraction(q), 24, -126)
return None if abs(v) > F32_MAX else v
def fx_frac(r):
return Fraction(r, 1 << 32)
def to_fx(v):
"""§3.1: a value as Q32.32 when exact, else None."""
if isinstance(v, float) and (v != v or v in (float("inf"), float("-inf"))):
return None
q = Fraction(v) * (1 << 32)
if q.denominator != 1 or not -(1 << 63) <= q.numerator < (1 << 63):
return None
return q.numerator
def shortest(v):
"""(digits, e) with v = d1.d2... x 10^e, the shortest digits reading back to v."""
s = repr(abs(float(v)))
if "e" in s:
m, ex = s.split("e")
ex = int(ex)
else:
m, ex = s, 0
if "." in m:
ip, fp = m.split(".")
else:
ip, fp = m, ""
digits = (ip + fp).lstrip("0")
lead = len(ip.lstrip("0")) if ip.strip("0") else -(len(fp) - len(fp.lstrip("0")))
e = ex + (lead - 1 if ip.strip("0") else lead - 1)
digits = digits.rstrip("0") or "0"
return digits, e
def json_number(v):
"""§3.2: RFC 8785's spelling of a binary64."""
v = float(v)
if v == 0:
return "0"
d, e = shortest(v)
k, n = len(d), e + 1
if k <= n <= 21:
body = d + "0" * (n - k)
elif 0 < n <= 21:
body = d[:n] + "." + d[n:]
elif -6 < n <= 0:
body = "0." + "0" * (-n) + d
else:
body = d[0] + ("." + d[1:] if k > 1 else "") + "e" + ("+" if n - 1 >= 0 else "-") + str(abs(n - 1))
return ("-" if v < 0 else "") + body
def usd_number(v):
"""§3.3: the USD Core Specification's spelling of a binary64."""
v = float(v)
if v == 0:
return "0"
d, e = shortest(v)
k = len(d)
if e < -6 or e >= 15:
body = d[0] + ("." + d[1:] if k > 1 else "") + "e" + str(e)
elif e >= k - 1:
body = d + "0" * (e - (k - 1))
elif e >= 0:
body = d[:e + 1] + "." + d[e + 1:]
else:
body = "0." + "0" * (-e - 1) + d
return ("-" if v < 0 else "") + body
# ------------------------------------------------------------------ JSON
def jquote(s):
return json.dumps(s, ensure_ascii=False)
def strict_json(b):
"""§5.1: RFC 8259 over UTF-8, no member twice, at most 64 levels; numbers as spelt."""
try:
text = b.decode("utf-8")
except UnicodeDecodeError:
raise Refuse("document_malformed", "not UTF-8")
def pairs(ps):
seen = set()
for k, _ in ps:
if k in seen:
raise Refuse("document_malformed", "member twice")
seen.add(k)
return Obj(ps)
def bad(c):
raise Refuse("document_malformed", c)
try:
v = json.loads(text, object_pairs_hook=pairs, parse_int=Num, parse_float=Num, parse_constant=bad)
except Refuse:
raise
except (ValueError, RecursionError) as e:
raise Refuse("document_malformed", str(e))
def walk(x, d):
if d > 64:
raise Refuse("document_malformed", "too deep")
if isinstance(x, str):
try:
x.encode("utf-8")
except UnicodeEncodeError:
raise Refuse("document_malformed", "surrogate")
elif isinstance(x, list):
for y in x:
walk(y, d + 1)
elif isinstance(x, Obj):
for k, y in x.items:
walk(k, d)
walk(y, d + 1)
walk(v, 1)
return v
class Num(str):
pass
class Obj:
def __init__(self, items):
self.items = items
def get(self, k, default=None):
for n, v in self.items:
if n == k:
return v
return default
def has(self, k):
return any(n == k for n, _ in self.items)
def uint(x):
if isinstance(x, Num) and re.fullmatch(r"0|[1-9][0-9]*", x):
return int(x)
return None
# ------------------------------------------------------------------ §2 the bundle
def sections(b, magic, code):
if len(b) < 6 or b[:4] != magic:
raise Refuse(code, "magic")
n = struct.unpack_from("<H", b, 4)[0]
base = 6 + 9 * n
if base > len(b):
raise Refuse(code, "table")
out = {}
for i in range(n):
k, off, ln = struct.unpack_from("<BII", b, 6 + 9 * i)
if base + off + ln > len(b):
raise Refuse(code, "section past the end")
if k in out:
raise Refuse(code, "kind twice")
out[k] = b[base + off:base + off + ln]
return out
def assets_of(a, code):
if len(a) < 4:
raise Refuse(code, "assets")
n = struct.unpack_from("<I", a, 0)[0]
at, out = 4, []
for _ in range(n):
if at + 37 > len(a):
raise Refuse(code, "assets")
h, role, nl = a[at:at + 32], a[at + 32], struct.unpack_from("<I", a, at + 33)[0]
at += 37
if at + nl > len(a):
raise Refuse(code, "assets")
try:
name = a[at:at + nl].decode("utf-8")
except UnicodeDecodeError:
raise Refuse(code, "asset name")
at += nl
out.append((h, role, name))
if at != len(a):
raise Refuse(code, "assets")
return out
def position(sim, st):
"""§3.4."""
def take(b):
if len(b) < 24:
raise Refuse("world_malformed", "short state")
return list(struct.unpack_from("<3q", b, 0))
if sim in ("wai.world.sim.kinematic0", "wai.world.sim.ballistic0"):
return take(st)
if sim == "wai.world.sim.truss0":
if not st:
raise Refuse("world_malformed", "empty truss node")
return take(st[1:]) if st[0] == 0 else None
return None
def read_world(b):
s = sections(b, b"WWLD", "world_malformed")
if 1 not in s or 2 not in s:
raise Refuse("world_malformed", "sections")
try:
c = strict_json(s[1])
except Refuse:
raise Refuse("world_malformed", "contract")
sim = c.get("sim") if isinstance(c, Obj) else None
if not isinstance(sim, str):
raise Refuse("world_malformed", "sim")
g = s[2]
if len(g) < 4:
raise Refuse("world_malformed", "graph")
n = struct.unpack_from("<I", g, 0)[0]
at, nodes = 4, []
for _ in range(n):
if at + 20 > len(g):
raise Refuse("world_malformed", "graph")
nid, ln = g[at:at + 16], struct.unpack_from("<I", g, at + 16)[0]
at += 20
if at + ln > len(g):
raise Refuse("world_malformed", "graph")
if nodes and nodes[-1][0] >= nid:
raise Refuse("world_malformed", "order")
nodes.append((nid, g[at:at + ln]))
at += ln
if at != len(g):
raise Refuse("world_malformed", "graph")
assets = assets_of(s[3], "world_malformed") if 3 in s else []
for _, st in nodes:
position(sim, st)
return {"sim": sim, "nodes": nodes, "assets": assets}
def read_audio(b):
s = sections(b, b"WSCN", "audio_malformed")
if 1 not in s or 2 not in s:
raise Refuse("audio_malformed", "sections")
try:
c = strict_json(s[1])
except Refuse:
raise Refuse("audio_malformed", "contract")
rate = uint(c.get("sample_rate")) if isinstance(c, Obj) else None
dur = uint(c.get("duration")) if isinstance(c, Obj) else None
if rate is None or not 1 <= rate <= 0xFFFFFFFF or dur is None:
raise Refuse("audio_malformed", "contract")
t = s[2]
def rd(at, n):
if at + n > len(t):
raise Refuse("audio_malformed", "objects")
return t[at:at + n]
n = struct.unpack("<I", rd(0, 4))[0]
at, objs = 4, []
for _ in range(n):
src = rd(at, 32)
nk = struct.unpack("<H", rd(at + 40, 2))[0]
at += 42
keys = []
for _ in range(nk):
k = rd(at, 32)
keys.append((struct.unpack_from("<Q", k, 0)[0], list(struct.unpack_from("<3q", k, 8))))
at += 32
objs.append((bytes(src), keys))
if at != len(t):
raise Refuse("audio_malformed", "objects")
if 3 in s:
assets_of(s[3], "audio_malformed")
if 4 in s and len(s[4]) != 104:
raise Refuse("audio_malformed", "seal")
return {"rate": rate, "duration": dur, "objects": objs}
def cap_ok(c):
if not c.startswith("wai."):
return False
rest = c[4:]
if not rest.startswith(("video.", "audio.", "image.")):
return False
segs = rest.split(".")
return bool(re.fullmatch(r"[a-z0-9]+", segs[0])) and len(segs) > 1 and all(re.fullmatch(r"[a-z0-9_]+", x) for x in segs[1:])
def picture(c):
return c.startswith(("wai.video.", "wai.image."))
def check_media(m, code="media_malformed"):
if not cap_ok(m["capability"]):
raise Refuse(code, "capability")
if picture(m["capability"]):
if m.get("width") is None or not (1 <= m["width"] <= 65535 and 1 <= m["height"] <= 65535):
raise Refuse(code, "size")
elif m.get("width") is not None:
raise Refuse(code, "size")
def media_text(media):
out = []
for m in media:
s = f'{{"name":{jquote(m["name"])},"asset":"{m["asset"].hex()}","capability":{jquote(m["capability"])}'
if m.get("width") is not None:
s += f',"width":{m["width"]},"height":{m["height"]}'
out.append(s + "}")
return "[" + ",".join(out) + "]"
def media_from(v):
if not isinstance(v, list):
return None
out = []
for it in v:
if not isinstance(it, Obj):
return None
keys = [k for k, _ in it.items]
if keys not in (["name", "asset", "capability"], ["name", "asset", "capability", "width", "height"]):
return None
name, asset, cap = it.get("name"), it.get("asset"), it.get("capability")
if not (isinstance(name, str) and isinstance(asset, str) and isinstance(cap, str) and re.fullmatch(r"[0-9a-f]{64}", asset)):
return None
m = {"name": name, "asset": bytes.fromhex(asset), "capability": cap, "width": None, "height": None}
if len(keys) == 5:
w, h = uint(it.get("width")), uint(it.get("height"))
if w is None or h is None or w >= 1 << 32 or h >= 1 << 32:
return None
m["width"], m["height"] = w, h
out.append(m)
return out
def check_bundle(b):
w = read_world(b["world"]) if b["world"] is not None else None
a = read_audio(b["audio"]) if b["audio"] is not None else None
for m in b["media"]:
check_media(m)
return w, a
def mime(cap):
return "application/wai" if cap is None else f'application/wai; codecs="wai1.{cap[4:]}"'
def mime_cap(m):
mt = re.fullmatch(r'application/wai; codecs="wai1\.([^"]*)"', m or "")
if not mt:
return None
c = "wai." + mt.group(1)
return c if cap_ok(c) else None
# ------------------------------------------------------------------ §4 writing glTF
def jw(v):
"""Write the JSON of §4.1: dicts in insertion order, F(...) numbers per §3.2."""
if isinstance(v, dict):
return "{" + ",".join(f"{jquote(k)}:{jw(x)}" for k, x in v.items()) + "}"
if isinstance(v, list):
return "[" + ",".join(jw(x) for x in v) + "]"
if isinstance(v, bool):
return "true" if v else "false"
if isinstance(v, int):
return str(v)
if isinstance(v, F):
return json_number(v.v)
return jquote(v)
class F:
def __init__(self, v):
self.v = v
def f32(q):
return float(to_f32(q))
def vec3(p):
return [F(f32(fx_frac(x))) for x in p]
def export_gltf(b):
w, a = check_bundle(b)
nodes, used = [], []
if w:
for nid, st in w["nodes"]:
n = {"name": "node-" + nid.hex()}
p = position(w["sim"], st)
if p is not None:
n["translation"] = vec3(p)
nodes.append(n)
items = []
first = {}
for m in b["media"]:
first.setdefault(m["asset"], m)
if a:
for src, _ in a["objects"]:
if all(i[1] != src for i in items):
m = first.get(src)
items.append((m["name"] if m else "source-" + src.hex()[:16], src, m["capability"] if m else None, None))
nsrc = len(items)
for m in b["media"]:
if all(i[1] != m["asset"] for i in items):
items.append((m["name"], m["asset"], m["capability"], (m["width"], m["height"]) if m["width"] is not None else None))
anim = bytearray()
views, accs, samplers, channels, cams, plan, objnodes = [], [], [], [], [], [], []
if a:
cams.append({"type": "perspective", "perspective": {"yfov": F(1.0), "znear": F(0.25)}})
nodes.append({"name": "listener", "camera": 0, "extensions": {"MPEG_audio_spatial": {"listener": {"id": 0}}}})
used.append("MPEG_audio_spatial")
for i, (src, keys) in enumerate(a["objects"]):
if any(keys[j + 1][0] <= keys[j][0] for j in range(len(keys) - 1)):
raise Refuse("not_presentable", "order")
times = [to_f32(Fraction(s, a["rate"])) for s, _ in keys]
if any(times[j + 1] <= times[j] for j in range(len(times) - 1)):
raise Refuse("not_presentable", "times")
objnodes.append(len(nodes))
if len(keys) >= 2:
plan.append((len(nodes), times, [p for _, p in keys]))
nodes.append(None)
buffers = [None] if plan else []
for node, times, poss in plan:
ti = len(anim)
for t in times:
anim += struct.pack("<f", float(t))
to = len(anim)
for p in poss:
for x in p:
anim += struct.pack("<f", f32(fx_frac(x)))
vi = len(views)
views.append({"buffer": 0, "byteOffset": ti, "byteLength": 4 * len(times)})
views.append({"buffer": 0, "byteOffset": to, "byteLength": 12 * len(times)})
ai = len(accs)
accs.append({"bufferView": vi, "componentType": 5126, "count": len(times), "type": "SCALAR", "min": [F(float(times[0]))], "max": [F(float(times[-1]))]})
accs.append({"bufferView": vi + 1, "componentType": 5126, "count": len(times), "type": "VEC3"})
si = len(samplers)
samplers.append({"input": ai, "interpolation": "LINEAR", "output": ai + 1})
channels.append({"sampler": si, "target": {"node": node, "path": "translation"}})
if plan:
buffers[0] = {"byteLength": len(anim), "uri": "data:application/octet-stream;base64," + base64.b64encode(bytes(anim)).decode()}
item_acc = [None] * len(items)
textures = []
for k, (_, _, _, size) in enumerate(items):
if k >= nsrc and size is None:
continue
if size:
cnt = size[0] * size[1]
nb, acc = 3 * cnt, {"componentType": 5121, "normalized": True, "count": cnt, "type": "VEC3"}
else:
nb, acc = 4096, {"componentType": 5126, "count": 1024, "type": "SCALAR"}
bi = len(buffers)
buffers.append({"byteLength": nb, "extensions": {"MPEG_buffer_circular": {"media": k}}})
vi = len(views)
views.append({"buffer": bi, "byteLength": nb})
full = {"bufferView": vi}
full.update(acc)
full["extensions"] = {"MPEG_accessor_timed": {"immutable": True}}
item_acc[k] = len(accs)
accs.append(full)
if size:
textures.append({"extensions": {"MPEG_texture_video": {"accessor": item_acc[k], "width": size[0], "height": size[1], "format": "RGB"}}})
if a:
for i, (src, keys) in enumerate(a["objects"]):
k = next(j for j, it in enumerate(items) if it[1] == src)
n = {"name": f"object-{i}"}
if keys:
n["translation"] = vec3(keys[0][1])
n["extensions"] = {"MPEG_audio_spatial": {"sources": [{"id": i, "type": "Object", "attenuation": "custom", "accessors": [item_acc[k]]}]}}
nodes[objnodes[i]] = n
if items:
used.append("MPEG_media")
if any("extensions" in x for x in buffers):
used += ["MPEG_buffer_circular", "MPEG_accessor_timed"]
if textures:
used.append("MPEG_texture_video")
used.sort()
media = [{"name": nm, "autoplay": True, "alternatives": [{"uri": URN + h.hex(), "mimeType": mime(c)}]} for nm, h, c, _ in items]
doc = {"asset": {"version": "2.0", "generator": PROFILE}}
if used:
doc["extensionsUsed"] = used
if media:
doc["extensions"] = {"MPEG_media": {"media": media}}
doc["scene"] = 0
doc["scenes"] = [{"nodes": list(range(len(nodes)))} if nodes else {}]
for k, v in (("nodes", nodes), ("cameras", cams), ("buffers", buffers), ("bufferViews", views), ("accessors", accs)):
if v:
doc[k] = v
if channels:
doc["animations"] = [{"name": "audio", "channels": channels, "samplers": samplers}]
if textures:
doc["textures"] = textures
wai = {"profile": PROFILE}
if b["world"] is not None:
wai["world"] = b["world"].hex()
if b["audio"] is not None:
wai["audio"] = b["audio"].hex()
if b["media"]:
wai["media"] = media_text(b["media"])
doc["extras"] = {"wai": wai}
return (jw(doc) + "\n").encode()
# ------------------------------------------------------------------ §5 reading glTF
def DM(why):
return Refuse("document_malformed", why)
def arr(o, k):
v = o.get(k) if isinstance(o, Obj) else None
return v if isinstance(v, list) else []
def index(v, n, what):
i = uint(v)
if i is None or i >= n:
raise DM(what)
return i
def f32_of(v):
if not isinstance(v, Num):
return None
return to_f32(Fraction(v))
def mint(actor, i):
return blake3(b"wai:world-node\x01" + actor + struct.pack("<Q", i))[:16]
def kin_state(p):
return struct.pack("<3q4q", *p, 0, 0, 0, 0)
def container(magic, secs):
out = bytearray(magic + struct.pack("<H", len(secs)))
off = 0
for k, body in secs:
out += struct.pack("<BII", k, off, len(body))
off += -(-len(body) // 8) * 8
for _, body in secs:
out += body + b"\0" * ((-len(body)) % 8)
return bytes(out)
def asset_table(assets):
o = bytearray(struct.pack("<I", len(assets)))
for h, r, n in assets:
nb = n.encode()
o += h + bytes([r]) + struct.pack("<I", len(nb)) + nb
return bytes(o)
def world_bytes(contract, nodes, assets):
nodes = sorted(nodes)
g = bytearray(struct.pack("<I", len(nodes)))
for nid, st in nodes:
g += nid + struct.pack("<I", len(st)) + st
secs = [(1, contract), (2, bytes(g))]
if assets:
secs.append((3, asset_table(assets)))
secs.append((4, b""))
return container(b"WWLD", secs)
def audio_bytes(contract, objs, assets):
t = bytearray(struct.pack("<I", len(objs)))
for src, keys in objs:
t += src + struct.pack("<qH", 1 << 32, len(keys))
for s, p in keys:
t += struct.pack("<Q3q", s, *p)
secs = [(1, contract), (2, bytes(t))]
if assets:
secs.append((3, asset_table(assets)))
return container(b"WSCN", secs)
def contract_json(c, code="contract_absent"):
if c is None:
raise Refuse(code, "no contract")
try:
return strict_json(c)
except Refuse:
raise Refuse(code, "not JSON")
def audio_rate(params):
j = contract_json(params.get("audio_contract"))
r = uint(j.get("sample_rate")) if isinstance(j, Obj) else None
if r is None or not 1 <= r <= 0xFFFFFFFF:
raise Refuse("contract_absent", "sample_rate")
return r
def unhex(s):
if not isinstance(s, str) or len(s) % 2 or not re.fullmatch(r"[0-9a-f]*", s):
return None
return bytes.fromhex(s)
def import_gltf(b, params):
doc = strict_json(b)
asset = doc.get("asset") if isinstance(doc, Obj) else None
if not isinstance(doc, Obj) or not isinstance(asset, Obj) or asset.get("version") != "2.0":
raise DM("not glTF 2.0")
extras = doc.get("extras")
if isinstance(extras, Obj) and extras.has("wai"):
bundle = carried_gltf(extras.get("wai"))
try:
again = export_gltf(bundle)
except Refuse as r:
raise Refuse("carried_malformed", r.code)
if again != b:
raise Refuse("presentation_mismatch")
return bundle, []
return foreign_gltf(doc, params)
def carried_gltf(w):
if not isinstance(w, Obj):
raise Refuse("carried_malformed", "not an object")
b = {"world": None, "audio": None, "media": []}
for k, v in w.items:
if k == "profile" and v == PROFILE:
continue
if k in ("world", "audio"):
x = unhex(v)
if x is None:
raise Refuse("carried_malformed", k)
b[k] = x
elif k == "media":
if not isinstance(v, str):
raise Refuse("carried_malformed", "media")
try:
j = strict_json(v.encode())
except Refuse:
raise Refuse("carried_malformed", "media")
m = media_from(j)
if m is None:
raise Refuse("carried_malformed", "media")
b["media"] = m
else:
raise Refuse("carried_malformed", k)
if w.get("profile") != PROFILE:
raise Refuse("carried_malformed", "profile")
return b
def foreign_gltf(doc, params):
nodes, scenes = arr(doc, "nodes"), arr(doc, "scenes")
# Step 1
roots = []
if scenes:
s = index(doc.get("scene"), len(scenes), "scene") if doc.has("scene") else 0
roots = [index(n, len(nodes), "node") for n in arr(scenes[s], "nodes")]
order, seen, parent = [], set(), {}
stack = [(r, None) for r in reversed(roots)]
while stack:
n, par = stack.pop()
if n in seen:
raise DM("twice")
seen.add(n)
parent[n] = par
order.append(n)
ch = [index(c, len(nodes), "child") for c in arr(nodes[n], "children")]
for c in reversed(ch):
stack.append((c, n))
# Step 2
pos = {}
for n in order:
nd = nodes[n]
if not isinstance(nd, Obj):
raise DM("node")
if nd.has("matrix"):
raise Refuse("transform_not_carried", "matrix")
def floats(k, ln):
if not nd.has(k):
return None
v = nd.get(k)
if not isinstance(v, list) or len(v) != ln:
raise DM(k)
out = [f32_of(x) for x in v]
if any(x is None for x in out):
raise DM(k)
return out
r = floats("rotation", 4)
if r is not None and r != [0, 0, 0, 1]:
raise Refuse("transform_not_carried", "rotation")
sc = floats("scale", 3)
if sc is not None and sc != [1, 1, 1]:
raise Refuse("transform_not_carried", "scale")
t = floats("translation", 3) or [0, 0, 0]
tf = [to_fx(x) for x in t]
if any(x is None for x in tf):
raise Refuse("position_not_exact", "translation")
base = pos[parent[n]] if parent[n] is not None else [0, 0, 0]
p = [base[i] + tf[i] for i in range(3)]
if any(not -(1 << 63) <= x < (1 << 63) for x in p):
raise Refuse("position_not_exact", "range")
pos[n] = p
# Step 3
def spatial(n):
e = nodes[n].get("extensions")
return e.get("MPEG_audio_spatial") if isinstance(e, Obj) else None
audio_nodes, world_nodes, dropped = [], [], []
for n in order:
sp = spatial(n)
if isinstance(sp, Obj) and sp.has("sources"):
audio_nodes.append(n)
elif nodes[n].has("camera") or sp is not None:
continue
else:
if nodes[n].has("mesh"):
dropped.append(f"node {n}: mesh")
world_nodes.append(pos[n])
# Step 4
anims = arr(doc, "animations")
anim_of = {}
for ai, an in enumerate(anims):
smp = arr(an, "samplers")
for c in arr(an, "channels"):
si = index(c.get("sampler") if isinstance(c, Obj) else None, len(smp), "sampler")
tg = c.get("target")
if not isinstance(tg, Obj):
raise DM("target")
if not tg.has("node"):
continue
node = index(tg.get("node"), len(nodes), "node")
path = tg.get("path") if isinstance(tg.get("path"), str) else ""
it = smp[si].get("interpolation") if isinstance(smp[si], Obj) else None
interp = it if isinstance(it, str) else "LINEAR"
if node not in audio_nodes or path != "translation" or interp != "LINEAR" or node in anim_of:
raise Refuse("animation_not_carried", f"{node} {path} {interp}")
anim_of[node] = (ai, si)
# Step 5
ext = doc.get("extensions")
mm = ext.get("MPEG_media") if isinstance(ext, Obj) else None
items = mm.get("media") if isinstance(mm, Obj) else None
items = items if isinstance(items, list) else []
accs, views, bufs = arr(doc, "accessors"), arr(doc, "bufferViews"), arr(doc, "buffers")
def media_of(a):
v = index(accs[a].get("bufferView"), len(views), "view")
bb = index(views[v].get("buffer"), len(bufs), "buffer")
e = bufs[bb].get("extensions")
cb = e.get("MPEG_buffer_circular") if isinstance(e, Obj) else None
return index(cb.get("media") if isinstance(cb, Obj) else None, len(items), "media")
def wai_alt(k):
for alt in arr(items[k], "alternatives"):
if not isinstance(alt, Obj):
continue
u = alt.get("uri")
if isinstance(u, str) and u.startswith(URN) and re.fullmatch(r"[0-9a-f]{64}", u[len(URN):]):
mt = alt.get("mimeType")
return bytes.fromhex(u[len(URN):]), mime_cap(mt) if isinstance(mt, str) else None
return None
def floats_of(a, ty):
acc = accs[a]
if uint(acc.get("componentType")) != 5126 or acc.get("type") != ty or acc.has("sparse"):
raise DM("accessor type")
cnt = uint(acc.get("count"))
if cnt is None:
raise DM("count")
comps = 3 if ty == "VEC3" else 1
v = index(acc.get("bufferView"), len(views), "view")
view = views[v]
if view.has("byteStride"):
raise DM("stride")
bb = index(view.get("buffer"), len(bufs), "buffer")
uri = bufs[bb].get("uri")
pre = "data:application/octet-stream;base64,"
if not isinstance(uri, str) or not uri.startswith(pre):
raise DM("buffer")
try:
data = base64.b64decode(uri[len(pre):], validate=True)
except (binascii.Error, ValueError):
raise DM("base64")
if base64.b64encode(data).decode() != uri[len(pre):]:
raise DM("base64")
def opt(o, k):
if not o.has(k):
return 0
x = uint(o.get(k))
if x is None:
raise DM(k)
return x
off = opt(view, "byteOffset") + opt(acc, "byteOffset")
ln = uint(view.get("byteLength"))
if ln is None:
raise DM("byteLength")
need = cnt * 4 * comps
vstart = uint(view.get("byteOffset")) or 0
if off + need > len(data) or off + need > vstart + ln:
raise DM("past the view")
return [struct.unpack_from("<f", data, off + 4 * i)[0] for i in range(cnt * comps)]
objects, src_items, src_names = [], [], []
for n in audio_nodes:
for s in arr(spatial(n), "sources"):
if not isinstance(s, Obj) or s.get("type") != "Object":
raise Refuse("audio_not_object")
if s.has("pregain") and f32_of(s.get("pregain")) != 0:
raise Refuse("gain_not_exact")
a0s = arr(s, "accessors")
a0 = index(a0s[0] if a0s else None, len(accs), "accessor")
k = media_of(a0)
alt = wai_alt(k)
if alt is None:
raise Refuse("media_not_wai")
h = alt[0]
if k not in src_items:
src_items.append(k)
if all(x[0] != h for x in src_names):
nm = items[k].get("name")
src_names.append((h, nm if isinstance(nm, str) else ""))
if n not in anim_of:
keys = [(0, pos[n])]
else:
rate = audio_rate(params)
base = pos[parent[n]] if parent[n] is not None else [0, 0, 0]
ai, si = anim_of[n]
smp = arr(anims[ai], "samplers")[si]
ti = index(smp.get("input"), len(accs), "input")
to = index(smp.get("output"), len(accs), "output")
times, vals = floats_of(ti, "SCALAR"), floats_of(to, "VEC3")
if len(vals) != 3 * len(times):
raise DM("counts")
keys = []
for j, t in enumerate(times):
if t != t or t in (float("inf"), float("-inf")) or t < 0:
raise Refuse("time_not_exact")
q = Fraction(t) * rate
if q.denominator != 1 or q >= 1 << 64:
raise Refuse("time_not_exact")
p = []
for c in range(3):
x = to_fx(vals[3 * j + c])
if x is None:
raise Refuse("position_not_exact")
p.append(base[c] + x)
if any(not -(1 << 63) <= x < (1 << 63) for x in p):
raise Refuse("position_not_exact")
keys.append((q.numerator, p))
objects.append((h, keys))
# Step 6
texs = arr(doc, "textures")
media = []
for k in range(len(items)):
if k in src_items:
continue
alt = wai_alt(k)
if alt is None or alt[1] is None:
raise Refuse("media_not_wai")
nm = items[k].get("name") if isinstance(items[k], Obj) else None
m = {"name": nm if isinstance(nm, str) else "", "asset": alt[0], "capability": alt[1], "width": None, "height": None}
if picture(alt[1]):
for t in texs:
e = t.get("extensions") if isinstance(t, Obj) else None
tv = e.get("MPEG_texture_video") if isinstance(e, Obj) else None
if not isinstance(tv, Obj):
continue
a = index(tv.get("accessor"), len(accs), "texture accessor")
if media_of(a) == k:
w, h = uint(tv.get("width")), uint(tv.get("height"))
if w is None or h is None:
raise DM("texture size")
m["width"], m["height"] = (w if w < 1 << 32 else 0), (h if h < 1 << 32 else 0)
break
check_media(m, "media_not_wai")
media.append(m)
# Step 7
bundle = {"world": None, "audio": None, "media": media}
if world_nodes:
j = contract_json(params.get("world_contract"))
if not isinstance(j, Obj) or j.get("sim") != KIN:
raise Refuse("sim_not_importable")
ns = [(mint(params["actor"], i + 1), kin_state(p)) for i, p in enumerate(world_nodes)]
bundle["world"] = world_bytes(params["world_contract"], ns, [])
if objects:
audio_rate(params)
bundle["audio"] = audio_bytes(params["audio_contract"], objects, [(h, 0, n) for h, n in src_names])
return bundle, dropped
# ------------------------------------------------------------------ §6 writing USD
def usd_char_ok(c):
u = ord(c)
return (u <= 0x7F or 0xA0 <= u <= 0xD7FF or 0xF900 <= u <= 0xFDCF or 0xFDF0 <= u <= 0xFFEF
or (0x10000 <= u <= 0xEFFFD and (u & 0xFFFE) != 0xFFFE))
def usd_string(s):
o = '"'
for c in s:
if not usd_char_ok(c):
raise Refuse("not_presentable", "character")
o += {'"': '\\"', "\\": "\\\\", "\n": "\\n", "\r": "\\r", "\t": "\\t"}.get(c) or (f"\\x{ord(c):02X}" if ord(c) < 0x20 or ord(c) == 0x7F else c)
return o + '"'
def tuple3(p):
return "(" + ", ".join(usd_number(float(fx_frac(x))) for x in p) + ")"
def export_usda(b):
w, a = check_bundle(b)
L = ["#usda 1.0", "(", " customLayerData = {"]
if b["audio"] is not None:
L.append(f' string "wai:audio" = "{b["audio"].hex()}"')
if b["media"]:
L.append(f' string "wai:media" = {usd_string(media_text(b["media"]))}')
L.append(f' string "wai:profile" = "{PROFILE}"')
if b["world"] is not None:
L.append(f' string "wai:world" = "{b["world"].hex()}"')
L += [" }", ' defaultPrim = "World"']
if a:
if a["duration"] > 1 << 53 or a["rate"] > 1 << 53:
raise Refuse("not_presentable", "duration")
L += [f" endTimeCode = {a['duration']}", " startTimeCode = 0", f" timeCodesPerSecond = {a['rate']}"]
L += [")", "", 'def Xform "World"', "{"]
blocks = []
if w:
for nid, st in w["nodes"]:
p = position(w["sim"], st)
blk = [f' def Xform "node_{nid.hex()}"', " {"]
if p is not None:
blk += [f" double3 xformOp:translate = {tuple3(p)}", ' uniform token[] xformOpOrder = ["xformOp:translate"]']
blocks.append(blk + [" }"])
if w["assets"]:
blk = [' def Scope "Assets"', " {"]
for i, (h, role, name) in enumerate(w["assets"]):
if i:
blk.append("")
blk += [f' def "asset_{i}" (', f" prepend references = @{URN}{h.hex()}@", " )", " {",
f" custom string wai:name = {usd_string(name)}", f" custom int wai:role = {role}", " }"]
blocks.append(blk + [" }"])
if a and a["objects"]:
blk = [' def Scope "Audio"', " {"]
for i, (src, keys) in enumerate(a["objects"]):
if i:
blk.append("")
if any(keys[j + 1][0] <= keys[j][0] for j in range(len(keys) - 1)):
raise Refuse("not_presentable", "order")
if any(s > 1 << 53 for s, _ in keys):
raise Refuse("not_presentable", "time code")
blk += [f' def Xform "object_{i}"', " {", f' custom string wai:source = "{src.hex()}"']
if keys:
blk.append(f" double3 xformOp:translate = {tuple3(keys[0][1])}")
if len(keys) >= 2:
blk.append(" double3 xformOp:translate.timeSamples = {")
blk += [f" {s}: {tuple3(p)}," for s, p in keys]
blk.append(" }")
blk.append(' uniform token[] xformOpOrder = ["xformOp:translate"]')
blk.append(" }")
blocks.append(blk + [" }"])
if b["media"]:
blk = [' def Scope "Media"', " {"]
for i, m in enumerate(b["media"]):
if i:
blk.append("")
blk += [f' def "media_{i}" (', f" prepend references = @{URN}{m['asset'].hex()}@", " )", " {",
f" custom string wai:capability = {usd_string(m['capability'])}"]
if m["width"] is not None:
blk.append(f" custom int wai:height = {m['height']}")
blk.append(f" custom string wai:name = {usd_string(m['name'])}")
if m["width"] is not None:
blk.append(f" custom int wai:width = {m['width']}")
blk.append(" }")
blocks.append(blk + [" }"])
for i, blk in enumerate(blocks):
if i:
L.append("")
L += blk
L.append("}")
return ("\n".join(L) + "\n").encode()
# ------------------------------------------------------------------ §7 reading USD
def NR(why):
return Refuse("usd_not_read", why)
def lex(t):
out, i, n = [], 0, len(t)
while i < n:
c = t[i]
if c in " \t\r\n":
i += 1
elif c == "#" or t.startswith("//", i):
while i < n and t[i] not in "\r\n":
i += 1
elif t.startswith("/*", i):
j = t.find("*/", i + 2)
if j < 0:
raise DM("comment")
i = j + 2
elif c == "-" or c.isdigit() and c.isascii() or (c == "." and i + 1 < n and t[i + 1].isdigit() and t[i + 1].isascii()):
s0 = i
if c == "-":
i += 1
if t.startswith("inf", i):
i += 3
else:
while i < n and ((t[i].isascii() and t[i].isdigit()) or t[i] in ".eE" or (t[i] in "+-" and t[i - 1] in "eE")):
i += 1
out.append(("num", t[s0:i]))
elif c in "()[]{}=:,;.":
out.append(("p", c))
i += 1
elif c in "\"'":
if t.startswith(c * 3, i):
raise NR("triple quotes")
i += 1
s = ""
while True:
if i >= n:
raise DM("string")
x = t[i]
i += 1
if x == c:
break
if x in "\r\n":
raise DM("line break")
if x != "\\":
s += x
continue
if i >= n:
raise DM("escape")
e = t[i]
i += 1
simple = {"n": "\n", "r": "\r", "t": "\t", "a": "\a", "b": "\b", "f": "\f", "v": "\v", "\\": "\\", "'": "'", '"': '"'}
if e in simple:
s += simple[e]
elif e == "x":
v = d = 0
while d < 2 and i < n and t[i] in "0123456789ABCDEF":
v = v * 16 + int(t[i], 16)
i += 1
d += 1
if d == 0:
raise DM("empty \\x")
if v >= 0x80:
raise NR("\\x past 0x7F")
s += chr(v)
else:
raise NR("escape")
out.append(("str", s))
elif c == "@":
if t.startswith("@@", i + 1):
raise NR("@@@")
if t.startswith("@", i + 1):
raise NR("@@@")
i += 1
s = ""
while True:
if i >= n:
raise DM("asset path")
x = t[i]
i += 1
if x == "@":
break
if x in "\r\n":
raise DM("line break")
s += x
out.append(("asset", s))
elif c == "<":
j = t.find(">", i + 1)
if j < 0:
raise DM("path")
out.append(("path", t[i + 1:j]))
i = j + 1
elif (c.isascii() and c.isalpha()) or c == "_":
s0 = i
while i < n and ((t[i].isascii() and (t[i].isalnum() or t[i] == "_")) or (t[i] == ":" and i + 1 < n and ((t[i + 1].isascii() and t[i + 1].isalpha()) or t[i + 1] == "_"))):
i += 1
out.append(("id", t[s0:i]))
else:
raise NR(f"character {c!r}")
return out
class UP:
def __init__(self, toks):
self.t, self.i = toks, 0
def peek(self):
return self.t[self.i] if self.i < len(self.t) else None
def next(self):
if self.i >= len(self.t):
raise DM("ends early")
self.i += 1
return self.t[self.i - 1]
def is_(self, c):
return self.peek() == ("p", c)
def punct(self, c):
if self.next() != ("p", c):
raise DM(f"{c} due")
def ident(self):
k, v = self.next()
if k != "id":
raise DM("name due")
return v
def skip(self):
while self.is_(";"):
self.i += 1
def value(self):
k, v = self.next()
if k in ("num", "str", "id", "asset", "path"):
return (k, v)
if (k, v) == ("p", "("):
items = []
while not self.is_(")"):
items.append(self.value())
if not self.is_(")"):
self.punct(",")
self.i += 1
return ("tuple", items)
if (k, v) == ("p", "["):
items = []
while not self.is_("]"):
items.append(self.value())
if not self.is_("]"):
self.punct(",")
self.i += 1
return ("list", items)
if (k, v) == ("p", "{"):
return self.dict_body()
raise DM("value due")
def dict_body(self):
items = []
while True:
self.skip()
if self.is_("}"):
self.i += 1
return ("dict", items)
ty = self.ident()
ty2 = ty
if self.is_("["):
self.i += 1
self.punct("]")
ty2 += "[]"
k, key = self.next()
if k not in ("str", "id"):
raise DM("key")
self.punct("=")
if ty == "dictionary":
self.punct("{")
v = self.dict_body()
else:
v = self.value()
items.append((ty2, key, v))
def samples(self):
self.punct("{")
v = []
while not self.is_("}"):
k, tcode = self.next()
if k != "num":
raise DM("time code")
self.punct(":")
v.append((tcode, self.value()))
if not self.is_("}"):
self.punct(",")
self.i += 1
return ("samples", v)
def prim(self):
s = self.ident()
if s in ("over", "class"):
raise NR(s)
if s != "def":
raise DM("prim due")
p = {"ty": "", "name": None, "refs": [], "props": [], "children": []}
if self.peek() and self.peek()[0] == "id":
p["ty"] = self.next()[1]
k, name = self.next()
if k != "str":
raise DM("prim name")
p["name"] = name
if self.is_("("):
self.i += 1
while True:
self.skip()
if self.is_(")"):
self.i += 1
break
if self.peek() and self.peek()[0] == "str":
self.i += 1
continue
key = self.ident()
op = None
if key in ("prepend", "append", "delete", "add", "reorder"):
op, key = key, self.ident()
if key == "references":
if op not in (None, "prepend", "append"):
raise NR("references op")
self.punct("=")
v = self.value()
its = v[1] if v[0] == "list" else ([] if v == ("id", "None") else [v])
for it in its:
if it[0] != "asset":
raise NR("not an asset path")
if (self.peek() and self.peek()[0] == "path") or self.is_("("):
raise NR("prim path or parameters")
p["refs"].append(it[1])
elif key in ("payload", "inherits", "specializes", "variants", "variantSets", "relocates"):
raise NR(key)
else:
if op is not None:
raise NR("list op")
self.punct("=")
self.value()
self.punct("{")
while True:
self.skip()
if self.is_("}"):
self.i += 1
break
pk = self.peek()
if pk and pk[0] == "id" and pk[1] in ("def", "over", "class"):
c = self.prim()
if any(x["name"] == c["name"] for x in p["children"]):
raise DM("prim twice")
p["children"].append(c)
elif pk and pk[0] == "id" and pk[1] in ("rel", "variantSet", "reorder", "prepend", "append", "delete", "add"):
raise NR(pk[1])
else:
name, ty, default, smp, special = self.prop()
e = next((x for x in p["props"] if x[0] == name), None)
if e is None:
e = [name, "", None, None]
p["props"].append(e)
if e[1] and e[1] != ty:
raise DM("two types")
e[1] = ty
if special:
if e[3] is not None:
raise DM("samples twice")
e[3] = smp
else:
if e[2] is not None:
raise DM("default twice")
e[2] = default
return p
def prop(self):
t = self.ident()
if t == "custom":
t = self.ident()
if t in ("uniform", "varying"):
t = self.ident()
ty = t
if self.is_("["):
self.i += 1
self.punct("]")
ty += "[]"
name = self.ident()
default = smp = None
special = False
if self.is_("."):
self.i += 1
if self.ident() != "timeSamples":
raise NR("not timeSamples")
self.punct("=")
smp = self.samples()
special = True
elif self.is_("="):
self.i += 1
default = self.value()
if self.is_("("):
self.i += 1
while True:
self.skip()
if self.is_(")"):
self.i += 1
break
if self.peek() and self.peek()[0] == "str":
self.i += 1
continue
self.ident()
self.punct("=")
self.value()
return name, ty, default, smp, special
def parse_usda(b):
try:
text = b.decode("utf-8")
except UnicodeDecodeError:
raise DM("not UTF-8")
if not text.startswith("#usda"):
raise DM("header")
rest = text[5:]
m = re.search(r"[\r\n]", rest)
le = m.start() if m else len(rest)
if rest[:le].strip(" \t") != "1.0":
raise NR("version")
p = UP(lex(rest[le:]))
custom, default_prim, prims = [], None, []
if p.is_("("):
p.i += 1
while True:
p.skip()
if p.is_(")"):
p.i += 1
break
if p.peek() and p.peek()[0] == "str":
p.i += 1
continue
key = p.ident()
if key in ("subLayers", "relocates", "prepend", "append", "delete"):
raise NR(key)
p.punct("=")
v = p.value()
if key == "customLayerData":
if v[0] != "dict":
raise DM("customLayerData")
custom += [(k, x) for ty, k, x in v[1] if ty == "string"]
elif key == "defaultPrim":
if v[0] != "str":
raise DM("defaultPrim")
default_prim = v[1]
while p.peek() is not None:
c = p.prim()
if any(x["name"] == c["name"] for x in prims):
raise DM("prim twice")
prims.append(c)
return custom, default_prim, prims
def import_usda(b, params):
custom, default_prim, prims = parse_usda(b)
wai = [(k, v) for k, v in custom if k.startswith("wai:")]
if wai:
bundle = {"world": None, "audio": None, "media": []}
prof = False
for k, v in wai:
if v[0] != "str":
raise Refuse("carried_malformed", "not a string")
s = v[1]
if k == "wai:profile" and s == PROFILE:
prof = True
elif k in ("wai:world", "wai:audio"):
x = unhex(s)
if x is None:
raise Refuse("carried_malformed", k)
bundle[k[4:]] = x
elif k == "wai:media":
try:
m = media_from(strict_json(s.encode()))
except Refuse:
m = None
if m is None:
raise Refuse("carried_malformed", "media")
bundle["media"] = m
else:
raise Refuse("carried_malformed", k)
if not prof:
raise Refuse("carried_malformed", "profile")
try:
again = export_usda(bundle)
except Refuse as r:
raise Refuse("carried_malformed", r.code)
if again != b:
raise Refuse("presentation_mismatch")
return bundle, []
# §7.3
if default_prim is not None:
root = next((x for x in prims if x["name"] == default_prim), None)
if root is None:
raise DM("defaultPrim")
else:
if not prims:
raise DM("no prim")
root = prims[0]
nodes, assets, dropped = [], [], []
stack = [(root, [0, 0, 0], "/" + root["name"])]
while stack:
pr, base, path = stack.pop()
off = [0, 0, 0]
for name, ty, default, smp in pr["props"]:
if name == "xformOpOrder":
if not (default is not None and default[0] == "list" and default[1] == [("str", "xformOp:translate")]) or smp is not None:
raise Refuse("transform_not_carried", "order")
elif name == "xformOp:translate":
if smp is not None or default is None or default[0] != "tuple":
raise Refuse("transform_not_carried", "translate")
if len(default[1]) != 3:
raise DM("three values")
for i, x in enumerate(default[1]):
if x[0] != "num":
raise DM("numbers")
try:
f = float(x[1])
except ValueError:
raise DM("number")
if f != f or f in (float("inf"), float("-inf")):
raise Refuse("position_not_exact")
q = to_fx(f)
if q is None:
raise Refuse("position_not_exact")
off[i] = q
elif name.startswith("xformOp:"):
raise Refuse("transform_not_carried", name)
at = [base[i] + off[i] for i in range(3)]
if any(not -(1 << 63) <= x < (1 << 63) for x in at):
raise Refuse("position_not_exact")
if pr["refs"]:
for r in pr["refs"]:
if not (r.startswith(URN) and re.fullmatch(r"[0-9a-f]{64}", r[len(URN):])):
raise Refuse("asset_not_wai", r)
assets.append((bytes.fromhex(r[len(URN):]), 0, pr["name"]))
elif pr["ty"] == "Xform" and any(x[0] == "xformOp:translate" for x in pr["props"]):
nodes.append(at)
elif pr["ty"] not in ("", "Xform", "Scope"):
dropped.append(f"{path}: {pr['ty']}")
for c in reversed(pr["children"]):
stack.append((c, at, path + "/" + c["name"]))
bundle = {"world": None, "audio": None, "media": []}
if nodes or assets:
j = contract_json(params.get("world_contract"))
if not isinstance(j, Obj) or j.get("sim") != KIN:
raise Refuse("sim_not_importable")
ns = [(mint(params["actor"], i + 1), kin_state(p)) for i, p in enumerate(nodes)]
bundle["world"] = world_bytes(params["world_contract"], ns, assets)
return bundle, dropped
# ------------------------------------------------------------------ the corpus
def run(corpus):
doc = json.load(open(os.path.join(corpus, "vectors.json"), encoding="utf-8"))
known = codes()
fails = []
for inp in doc["inputs"]:
b = open(os.path.join(corpus, inp["path"]), "rb").read()
if hashlib.sha256(b).hexdigest() != inp["sha256"]:
fails.append(f"input {inp['path']} does not match its pin")
files = {}
for f in doc["files"]:
b = open(os.path.join(corpus, f["file"]), "rb").read()
if hashlib.sha256(b).hexdigest() != f["sha256"]:
fails.append(f"{f['file']} does not match its digest")
files[f["file"]] = b
listed = set(files) | {"vectors.json", "README.md", ".gitattributes"}
for root, _, fs in os.walk(corpus):
for x in fs:
rel = os.path.relpath(os.path.join(root, x), corpus).replace(os.sep, "/")
if rel not in listed:
fails.append(f"{rel} is not listed")
def load(path):
return files[path] if path in files else open(os.path.join(corpus, path), "rb").read()
def bundle_of(bv):
media = []
for m in bv["media"]:
media.append({"name": m["name"], "asset": bytes.fromhex(m["asset"]), "capability": m["capability"], "width": m.get("width"), "height": m.get("height")})
return {"world": load(bv["world"]) if bv["world"] else None, "audio": load(bv["audio"]) if bv["audio"] else None, "media": media}
exported = []
for v in doc["vectors"]:
name, op, fmt = v["name"], v["op"], v["format"]
if v["outcome"] == "refuse" and v["code"] not in known:
fails.append(f"{name}: {v['code']} is not in §8")
try:
if op == "export":
got = (export_gltf if fmt == "gltf" else export_usda)(bundle_of(v["bundle"]))
else:
pv = v["params"] or {}
params = {"actor": bytes.fromhex(pv["actor"]) if pv.get("actor") else bytes(32),
"world_contract": pv["world_contract"].encode() if pv.get("world_contract") is not None else None,
"audio_contract": pv["audio_contract"].encode() if pv.get("audio_contract") is not None else None}
got = (import_gltf if fmt == "gltf" else import_usda)(files[v["file"]], params)
outcome = ("accept", got)
except Refuse as r:
outcome = ("refuse", r.code)
if v["outcome"] == "refuse":
if outcome != ("refuse", v["code"]):
fails.append(f"{name}: wanted {v['code']}, got {outcome[0]} {outcome[1] if outcome[0] == 'refuse' else ''}")
continue
if outcome[0] != "accept":
fails.append(f"{name}: wanted accept, got {outcome[1]}")
continue
if op == "export":
if outcome[1] != files[v["file"]]:
fails.append(f"{name}: the document differs from {v['file']}")
else:
exported.append((fmt, files[v["file"]], bundle_of(v["bundle"])))
else:
want = bundle_of(v["bundle"])
b, dropped = outcome[1]
if (b["world"], b["audio"]) != (want["world"], want["audio"]) or b["media"] != want["media"]:
fails.append(f"{name}: read to another bundle")
if dropped != v["dropped"]:
fails.append(f"{name}: reported {dropped}, wanted {v['dropped']}")
missing = set(known) - {v.get("code") for v in doc["vectors"]}
if missing:
fails.append(f"codes no vector gives: {sorted(missing)}")
caught = 0
for fmt, b, want in exported:
for at in sorted({0, len(b) // 4, len(b) // 2, 3 * len(b) // 4, len(b) - 2}):
m = bytearray(b)
m[at] ^= 0x04
try:
got, _ = (import_gltf if fmt == "gltf" else import_usda)(bytes(m), {"actor": bytes(32), "world_contract": None, "audio_contract": None})
ok = got != want
except Refuse:
ok = True
if ok:
caught += 1
else:
fails.append(f"a flip at byte {at} of a {fmt} export read to the same bundle")
return fails, len(doc["vectors"]), caught
def main():
if len(sys.argv) != 2:
print(__doc__)
sys.exit(2)
fails, n, caught = run(sys.argv[1])
for f in fails:
print("FAIL", f)
if fails:
sys.exit(1)
print(f"scene: every vector verifies ({n}), and every mutated copy is caught ({caught})")
if __name__ == "__main__":
main()