Lane C measured the bookshelf's transmission halving interior draws (opshop/hall 191->104, book/hall
101->51, dept/hall 116->116 control). The cause, with vendored line numbers: three.module.js:16209
enters renderTransmissionPass(opaqueObjects, ...) whenever currentRenderList.transmissive is
non-empty, and :16433 re-renders the WHOLE OPAQUE LIST into a render target — every opaque draw
issued twice. Trigger is material.transmission > 0 (:6890), set from the glTF at GLTFLoader:1202.
And at 1.0 it is not 'a bit glassy': :494 does totalDiffuse = mix(totalDiffuse, transmitted.rgb,
material.transmission) — the diffuse term is REPLACED by the backdrop.
I SCANNED ALL 103 GLBs. Three carried it:
procity_fit_bookshelf_01 mtl_10218_Bookshelves_v1 shipped and hurting (C's)
procity_street_longbench_01 lambert3SG NOT WIRED — the 5.86 m arcade bench
the v9 charter names, on my R38
recommend list for Lane B
procity_street_streetlight_01 Streetlight_HighResSG3 NOT WIRED — one per ~18 m of street
The two unwired ones are the worse finding: the street budget is 292/300 with EIGHT draws of margin,
and a pre-pass there costs another ~292, not eight. Armed, not fired.
FIXED SURGICALLY. pipeline/strip_transmission.py rewrites only the JSON chunk and copies BIN through,
printing sha1(BIN) before and after: BIN IDENTICAL on all three, tris/dims/mtl/img/tex unchanged.
KHR_materials_ior deliberately left (ior does not trigger the pre-pass).
PUBLISHED + VERIFIED under John's R22 standing authorization via the passwordless tailnet ingress:
6/6 HTTP 200 off the PUBLIC depot, byte counts exact, sha1 identical, depot copies re-scanned clean.
_published.json 56 -> 59.
GATED, which matters more than the fix: validate_manifest.py now HARD FAILS on transmissionFactor>0
with the three.js line numbers and the fix command in the message; glb_stat.py grew a transmission
column and a loud footer. Nobody looked for three epochs because a bookshelf is obviously not glass.
ALSO: the arcade's three meshes, published and catalogued the same round they were generated —
aframe_board 366 tris (<=16 inst), blade_sign 299 (17 inst), keycutter_sign 498 (1 inst). manifest
furniture 13->16, GLBs 36->39, validate 0 errors 0 warnings. blade_sign ships FLAGGED: its back face
carries TRELLIS-hallucinated art where the concept had no data, which is one more reason the
+0-draw perpendicular-quad answer is the recommendation and this GLB is the fallback.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
108 lines
4.5 KiB
Python
108 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""PROCITY Lane E — strip_transmission.py (R39, the transmission pre-pass)
|
|
|
|
Remove `KHR_materials_transmission` from a GLB **without touching one byte of geometry**.
|
|
|
|
WHY THIS IS NOT A COSMETIC FIX. three.js renders a **transmission pre-pass** — a second full render
|
|
of the opaque scene into a render target — as soon as ANY material in the scene has
|
|
`transmission > 0` (`WebGLRenderer`'s transmissive-object list). Every opaque draw call in that
|
|
scene is therefore issued **twice**. Lane C measured it in R39 by zeroing the bookshelf's:
|
|
|
|
opshop/hall 191 -> 104 draws (-46%) · opshop/wide 142 -> 80
|
|
book/hall 101 -> 51 draws (-50%) · dept/hall 116 -> 116 (control, no bookshelf)
|
|
|
|
Three PROCITY assets carry `transmissionFactor: 1`, and only one of them has ever been wired:
|
|
|
|
procity_fit_bookshelf_01.glb mtl_10218_Bookshelves_v1 SHIPPED — the one C measured
|
|
procity_street_longbench_01.glb lambert3SG + ior 1 NOT WIRED — the 5.86 m arcade bench
|
|
procity_street_streetlight_01.glb Streetlight_HighResSG3 + ior 1 NOT WIRED — one per ~18 m of street
|
|
|
|
The two unwired ones are the worse finding. They are STREET assets, LANE_E_NOTES R38 recommends both
|
|
to Lane B, and the street budget is 292/300 with **eight draws of margin** — a transmission pre-pass
|
|
there does not cost eight draws, it costs another ~292. Nobody looked because a bookshelf, a bench
|
|
and a streetlight are not glass; this is an exporter default that rode in with the source meshes.
|
|
|
|
python3 pipeline/strip_transmission.py FILE.glb [FILE ...] [--in-place] [--dry-run]
|
|
|
|
The JSON chunk is rewritten and the BIN chunk is copied through untouched — the tool prints the
|
|
sha1 of the BIN chunk before and after so "geometry unchanged" is a checked claim, not a promise.
|
|
`KHR_materials_ior` is deliberately LEFT ALONE: ior alone does not trigger the pre-pass, and
|
|
removing more than the defect widens the diff for no measured gain.
|
|
"""
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import struct
|
|
import sys
|
|
|
|
EXT = "KHR_materials_transmission"
|
|
|
|
|
|
def read(path):
|
|
d = open(path, "rb").read()
|
|
assert d[:4] == b"glTF", f"{path}: not a GLB"
|
|
off, gltf, binc = 12, None, b""
|
|
while off < len(d):
|
|
clen, ctype = struct.unpack("<II", d[off:off + 8])
|
|
chunk = d[off + 8: off + 8 + clen]
|
|
if ctype == 0x4E4F534A:
|
|
gltf = json.loads(chunk.decode("utf-8"))
|
|
elif ctype == 0x004E4942:
|
|
binc = chunk
|
|
off += 8 + clen
|
|
return gltf, binc
|
|
|
|
|
|
def write(path, gltf, binc):
|
|
j = json.dumps(gltf, separators=(",", ":")).encode("utf-8")
|
|
j += b" " * ((4 - len(j) % 4) % 4)
|
|
b = binc + b"\0" * ((4 - len(binc) % 4) % 4)
|
|
total = 12 + 8 + len(j) + (8 + len(b) if b else 0)
|
|
out = bytearray()
|
|
out += b"glTF" + struct.pack("<II", 2, total)
|
|
out += struct.pack("<II", len(j), 0x4E4F534A) + j
|
|
if b:
|
|
out += struct.pack("<II", len(b), 0x004E4942) + b
|
|
open(path, "wb").write(bytes(out))
|
|
|
|
|
|
def main():
|
|
files = [a for a in sys.argv[1:] if not a.startswith("--")]
|
|
dry = "--dry-run" in sys.argv
|
|
for f in files:
|
|
gltf, binc = read(f)
|
|
before = hashlib.sha1(binc).hexdigest()
|
|
hits = []
|
|
for i, m in enumerate(gltf.get("materials", [])):
|
|
ex = m.get("extensions") or {}
|
|
if EXT in ex:
|
|
hits.append((i, m.get("name"), ex[EXT]))
|
|
del ex[EXT]
|
|
if not ex:
|
|
m.pop("extensions", None)
|
|
if not hits:
|
|
print(f"{os.path.basename(f)}: clean, nothing to do")
|
|
continue
|
|
# prune extensionsUsed/Required only if no material still declares it
|
|
still = any(EXT in (m.get("extensions") or {}) for m in gltf.get("materials", []))
|
|
for key in ("extensionsUsed", "extensionsRequired"):
|
|
if not still and EXT in (gltf.get(key) or []):
|
|
gltf[key] = [e for e in gltf[key] if e != EXT]
|
|
if not gltf[key]:
|
|
del gltf[key]
|
|
for i, name, val in hits:
|
|
print(f"{os.path.basename(f)}: material[{i}] '{name}' — removed {EXT} {val}")
|
|
if dry:
|
|
print(" (--dry-run, not written)")
|
|
continue
|
|
write(f, gltf, binc)
|
|
_, binc2 = read(f)
|
|
after = hashlib.sha1(binc2).hexdigest()
|
|
ok = "BIN IDENTICAL" if after == before else "*** BIN CHANGED ***"
|
|
print(f" wrote {f} {os.path.getsize(f)} B {ok} sha1(BIN) {after[:12]}")
|
|
if after != before:
|
|
sys.exit(2)
|
|
|
|
|
|
main()
|