Files
TeslaRel410/dpl3-revive/parser/svt.py
T
CydandClaude Fable 5 afc3fd839e Vendor dpl3-revive: the Division/DPL3 renderer, now ours
Bring the graphics-dev collaborator's dpl3-revive into the repo as first-class
project code (they've handed it off; it's ours now). This is the proven
Division renderer that our in-process rt_draw has been trying to be.

What's here:
- parser/  B2Z/V2Z/SVT/SCN/SPL/BGF/BMF/BSL decoders (pure Python).
- spec/    reverse-engineered format + the definitive VelociRender wire
           protocol (from the original DIVISION source, matches our live
           VPX node/action tables exactly).
- source-ref/  read-only copies of the original DIVISION C (BIZREAD.C,
           DPLTYPES.H, DPL.H) that define the formats.
- patha/   the "virtual VelociRender board": vrboard.py (24-action protocol
           server), vrview.py (numpy software rasterizer, the reference),
           vrview_gl.py (moderngl GPU backend, 832x512@60Hz), plus the
           run/replay/regress tooling and evidence renders. Drives FLYK/BLADE/
           Star Trek demos AND our btl4opt/rpl4opt game binaries.
- viewer/  WebGL archive generators (.py); prebuilt HTML/data regeneratable.
- samples/ small test models/textures.
- bt*.raw.bin  real BTL4OPT arena wire captures (kept for offline renderer
           testing/regression against OUR game).

.gitignore keeps the multi-hundred-MB demo capture dumps + debug logs +
regeneratable viewer bundles out of history (they stay on disk).

Phase 0 of the integration is validated: their board decodes our bt8 capture
with zero errors (3748 nodes, 507 instances, 4 mechs) and renders our arena
(terrain/dome/sky, correct Division DAC gamma). Plan + status in memory;
integration continues in emulator/RENDERER-COLLAB.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 22:06:25 -05:00

107 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""
svt.py -- reader for DIVISION / VWE ".SVT" textures used by the DPL3 renderer.
Format (from DPL3/DPL_LOAD.C : load_svt): there is NO header. The file is a raw
array of 32-bit texels, always square. The edge length is inferred from the file
size alone:
16384 bytes -> 64 x 64
65536 bytes -> 128 x 128
262144 bytes -> 256 x 256
Each texel is 4 bytes. The channel order within the 32-bit word is not stated in
the loader (the hardware consumed it directly), so this tool lets you pick it and
writes a standard PNG for viewing. Determined empirically: see spec/SVT_FORMAT.md.
Usage:
python svt.py info <file.svt>
python svt.py png <file.svt> [out.png] [order] # order default = rgba
"""
import struct
import sys
import zlib
SIZE_TO_EDGE = {16384: 64, 65536: 128, 262144: 256}
def load(path):
with open(path, "rb") as fp:
data = fp.read()
edge = SIZE_TO_EDGE.get(len(data))
if edge is None:
raise ValueError("Bad texture file size %d (not 64/128/256 square RGBA32)" % len(data))
return data, edge
def _png_chunk(tag, payload):
return (struct.pack(">I", len(payload)) + tag + payload +
struct.pack(">I", zlib.crc32(tag + payload) & 0xffffffff))
def write_png(path, width, height, rgba: bytes):
"""rgba = width*height*4 bytes, row-major, top-to-bottom."""
raw = bytearray()
stride = width * 4
for y in range(height):
raw.append(0) # filter: none
raw += rgba[y * stride:(y + 1) * stride]
out = b"\x89PNG\r\n\x1a\n"
out += _png_chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0))
out += _png_chunk(b"IDAT", zlib.compress(bytes(raw), 9))
out += _png_chunk(b"IEND", b"")
with open(path, "wb") as fp:
fp.write(out)
def to_rgba(data: bytes, order: str) -> bytes:
"""Reorder 4-byte texels (file byte positions) into R,G,B,A for PNG.
`order` names which file byte maps to each channel, e.g. 'rgba','bgra'.
Use 'x' for an unused/pad byte; alpha is then forced opaque (255).
"""
idx = {c: i for i, c in enumerate(order)}
r, g, b = idx["r"], idx["g"], idx["b"]
a = idx.get("a") # may be None when a pad byte ('x') is present
mv = memoryview(data)
out = bytearray(len(data))
for p in range(0, len(data), 4):
out[p + 0] = mv[p + r]
out[p + 1] = mv[p + g]
out[p + 2] = mv[p + b]
out[p + 3] = mv[p + a] if a is not None else 255
return bytes(out)
def cmd_info(path):
data, edge = load(path)
print("file :", path)
print("size :", len(data), "bytes")
print("dims : %d x %d, 32bpp (%d texels)" % (edge, edge, edge * edge))
# quick stats on the raw bytes to hint at channel usage
b = memoryview(data)
for ch in range(4):
vals = b[ch::4]
lo = min(vals); hi = max(vals)
print(" byte[%d]: min=%3d max=%3d" % (ch, lo, hi))
def cmd_png(path, out=None, order="rgba"):
data, edge = load(path)
out = out or (path.rsplit(".", 1)[0] + ".png")
write_png(out, edge, edge, to_rgba(data, order))
print("wrote %s : %dx%d order=%s" % (out, edge, edge, order))
if __name__ == "__main__":
if len(sys.argv) < 3:
print(__doc__); sys.exit(1)
cmd = sys.argv[1]
if cmd == "info":
cmd_info(sys.argv[2])
elif cmd == "png":
cmd_png(sys.argv[2],
sys.argv[3] if len(sys.argv) > 3 else None,
sys.argv[4] if len(sys.argv) > 4 else "xbgr")
else:
print("unknown command", cmd); sys.exit(1)