#!/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 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(" len(b): raise Refuse(code, "table") out = {} for i in range(n): k, off, ln = struct.unpack_from(" 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(" len(a): raise Refuse(code, "assets") h, role, nl = a[at:at + 32], a[at + 32], struct.unpack_from(" 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(" len(g): raise Refuse("world_malformed", "graph") nid, ln = g[at:at + 16], struct.unpack_from(" 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(" 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("= 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(" len(data) or off + need > vstart + ln: raise DM("past the view") return [struct.unpack_from("= 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()