refs: survey the sibling project and recalibrate the hero facade

Tools/sibling_asset_audit.py reads the sibling security-game project's prop
library straight from the GLB headers: 20 street props, 11.8k triangles, every
one with a Blender source. The 50 .blend sources are copied to
Art/props/blender_src/ with a manifest; nothing is imported into Unreal yet. Its
building meshes are deliberately not taken - they are Godot GLBs built around a
29.6 m facade that this project measured at 25.75 m.

The facade proportions were wrong against the photograph: 5.7 m of blank wall
above the capitals. Entablature is now 3.4 m, parapet 2.2 m, with architrave and
cornice bands and square pedestals, so the elevation reads as layered stone.
This commit is contained in:
zwitschi committed 2026-11-07 12:20:41 +01:00
1 parent 52a736bc9f
commit 5e5e5ae9b4
67 files changed
+591 -27

No files matched your search

+107
View File
@@ -0,0 +1,107 @@
# Audit the sibling Gig Line project for assets this project can reuse.
#
# python3 Tools/sibling_asset_audit.py # summary
# python3 Tools/sibling_asset_audit.py --manifest # write Art/props/reuse-manifest.json
#
# The sibling project (…/06-games/security-game) is a Godot 4 security game by
# the same author that researched this block first. Its street props are small,
# generic and far cheaper to reuse than to remodel; its building meshes are not
# reusable because they are Godot GLBs built to a different contract.
#
# What this tool does NOT do: copy anything, decide licence, or assume a prop
# matches this project's proportions. It reports name, size, format and whether
# a Blender source exists, so the copy list can be chosen deliberately.
import argparse
import json
import os
import struct
import sys
SIBLING = "/run/media/system/DATA/Documents/06-games/security-game"
PROPS_GLB = os.path.join(SIBLING, "assets/models/blender/props")
SRC_BLEND = os.path.join(SIBLING, "assets/models/blender_src")
OUT = "Art/props/reuse-manifest.json"
def glb_stats(path):
"""Triangle count and bounds from a GLB, without any dependency."""
with open(path, "rb") as f:
data = f.read()
if data[:4] != b"glTF":
return None
json_len = struct.unpack("<I", data[12:16])[0]
doc = json.loads(data[20 : 20 + json_len].decode("utf-8"))
tris = 0
for mesh in doc.get("meshes", []):
for prim in mesh.get("primitives", []):
idx = prim.get("indices")
if idx is not None:
tris += doc["accessors"][idx]["count"] // 3
# bounds across all positions
mins = [1e9] * 3
maxs = [-1e9] * 3
for mesh in doc.get("meshes", []):
for prim in mesh.get("primitives", []):
acc = prim.get("attributes", {}).get("POSITION")
if acc is None:
continue
a = doc["accessors"][acc]
if "min" in a and "max" in a:
mins = [min(m, v) for m, v in zip(mins, a["min"])]
maxs = [max(m, v) for m, v in zip(maxs, a["max"])]
size = [round(maxs[i] - mins[i], 3) for i in range(3)] if mins[0] < 1e8 else None
return {"triangles": tris, "size_gltf": size, "materials": len(doc.get("materials", []))}
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--manifest", action="store_true")
args = ap.parse_args()
if not os.path.isdir(PROPS_GLB):
sys.exit(f"missing {PROPS_GLB}")
rows = []
for name in sorted(os.listdir(PROPS_GLB)):
if not name.endswith(".glb"):
continue
stem = name[:-4]
path = os.path.join(PROPS_GLB, name)
stats = glb_stats(path) or {}
source = os.path.join(SRC_BLEND, stem + ".blend")
rows.append({
"prop": stem,
"glb": name,
"bytes": os.path.getsize(path),
"blend_source": os.path.basename(source) if os.path.exists(source) else None,
"blend_bytes": os.path.getsize(source) if os.path.exists(source) else None,
**stats,
})
for r in rows:
src = "blend" if r["blend_source"] else "glb-only"
size = r.get("size_gltf")
dims = "x".join(f"{v:.2f}" for v in size) if size else "?"
print(f"{r['prop']:18} {r['triangles']:5} tris {dims:>18} glTF m "
f"mats {r.get('materials', 0)} {src}")
print(f"\n{len(rows)} props, {sum(r['bytes'] for r in rows)//1024} KB of GLB, "
f"{sum(r['triangles'] for r in rows)} triangles total")
print("glTF is Y-up and Blender is Z-up: the exporter swaps Y/Z, so `size_gltf` "
"lists X, height, depth — not X, Y, Z as authored.")
if args.manifest:
os.makedirs(os.path.dirname(OUT), exist_ok=True)
with open(OUT, "w") as f:
json.dump({
"source_project": SIBLING,
"scanned": "assets/models/blender/props + assets/models/blender_src",
"licence": "project-owned (same author); confirm before shipping",
"note": "audit only — nothing has been copied",
"props": rows,
}, f, indent=2)
print(f"wrote {OUT}")
if __name__ == "__main__":
main()