Files
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

7.3 KiB
Raw Permalink Blame History

.SCN scene format

Reverse-engineered from DPL3/EXAMPLES/FLYK.C (the flythrough viewer) and DPL3/MATRIX.C. .SCN is plain text, one command per line, whitespace- delimited. Comments start with // or #. It assembles a world by referencing models (.B2Z) and textures (.SVT) by name — it embeds no geometry itself.

Core commands (the FLYK subset)

These cover the overwhelming majority of every scene on the disk (STATIC alone is ~5,000 of ~9,000 command lines):

Command Arguments Meaning
FOG z0 z1 r g b linear fog: start/end distance + colour
BACKGND r g b background / clear colour
AMBIENT r g b ambient light
LIGHT r g b ax ay az directional light: colour + orientation angles (deg)
STATIC name scale x y z ax ay az place a model instance
DYNAMIC name scale splinefile t0 dt model that follows a camera-path spline
SSTATIC (as STATIC) static variant
SCROLL texname u0 v0 du dv UV drift on a TEXTURE entity (see below)

Colours are floats in 0..1. Distances/positions are world units. Angles are in degrees. name is resolved to a geometry file by search path (see Resolution).

Instance transform

From FLYK.C + MATRIX.C. DPL uses a row-vector convention — a point is a row and transforms as p' = p · M, with translation in the bottom row of the matrix and each op post-multiplied (M = M · op). STATIC builds:

M = Rz(az) · Rx(ax) · Ry(ay) · Scale(s) · Translate(x, y, z)
p_world = [x y z 1] · M

so a point is rotated (Z, then X, then Y), scaled, then translated into place. Rotation matrices (row-vector form, c=cos, s=sin):

Rz = [ c  s  0 0]   Rx = [1  0  0 0]   Ry = [c  0 -s 0]
     [-s  c  0 0]        [0  c  s 0]        [0  1  0 0]
     [ 0  0  1 0]        [0 -s  c 0]        [s  0  c 0]
     [ 0  0  0 1]        [0  0  0 1]        [0  0  0 1]

LIGHT's three angles orient a directional light the same way (Rz·Rx·Ry).

Asset resolution

  • Models: name<name>.B2Z (also .BIZ/.V2Z), found on a search path (originally the GEOMETRY env var; here: index of the archive by basename).

  • Materials & textures (authoritative): a geogroup carries a material name (e.g. redrock_mtl). The real definition lives in the model's .V2Z source (the verbose text twin of the binary .B2Z — magic DIV-VIZ2), which inlines its material library:

    TEXTURE(NAME=redrock_tex) { MAP {"rocknoiz"} ... }
    MATERIAL(NAME=redrock_mtl) { TEXTURE {redrock_tex}  DIFFUSE {0.743, 0.216, 0.202} }
    

    So a material = a texture (→ <map>.SVT) modulated by a DIFFUSE colour. The final surface colour is texel × diffuse × lighting. Material names are scoped per model (the same name can differ between themes), so each model's own .V2Z is the source of truth — parser/bundle parse it per model.

    • Two kinds of texture, handled differently:
      • Full-colour painted skins (vehicle textures like VTVCAB) are shown as-is (tint = white); their colour lives in the texels.
      • Greyscale / intensity "noise" maps (CANAL, ROCKNOIZ) are tinted by the material diffusetechy (white) × CANAL = grey concrete, rednoise (red) × CANAL = red pipes. Some (e.g. CANAL, VTVALL) have a dead colour channel and are rebuilt as luminance first. The loader auto-classifies by saturation.
    • Prefer the material's per-part skin <stem>.SVT (e.g. vtvcab_mtlVTVCAB.SVT) over any atlas the material names (vtvall) — the per-part skins match the model UVs; the atlas tinted by diffuse does not.
    • Pair geometry and materials from the same source directory. The same asset exists in several theme folders (CONVERT/THOR/, CONVERT/CANALS/, …) and the same material name is defined with different colours in each (THOR rednoise = vivid red 0.83,0.006,0.006; CANALS = salmon 0.69,0.41,0.40). Loading .B2Z from one folder but .V2Z from another mixes them and yields the wrong colour. bundle.v2z_for() resolves the sibling .V2Z first.
    • Fallback: only when a model has no .V2Z do we fall back to the old stem/keyword heuristics.

Output gamma (the Division DAC)

Material/diffuse values are linear. The Division i860/pxpl5 card output through a 10-bit DAC with a gamma-1.7 encode — from DPL3/GAMMA.C, which builds a 256→1023 table out = 1023 · (i/255)^(1/1.7). This brightens midtones and gives the characteristic bright/pastel look. The viewer reproduces it by applying colour^(1/1.7) as the final fragment step (and to the background clear). Without it, linear output on an sRGB display looks too dark and over-saturated.

Surface merging (a bug worth noting)

When baking a scene, geometry is merged into surfaces to cut draw calls. Surfaces must be keyed on (texture, diffuse-colour), not texture alone — otherwise two materials that share a texture but differ in tint (grey columns vs red pipes, both on CANAL.SVT) merge into one and the second colour is lost. This was the cause of "the pipes render grey".

Scrolling textures (SCROLL)

SCROLL <texname> u0 v0 du dv        e.g.  SCROLL btfx:firesmoke1_scr_tex 0 0 0.05 -0.331

Animates a texture's UV coordinates — flowing water, drifting smoke, rising fire, laser-beam flow (287 uses across the disk). texname addresses a TEXTURE entity (not a bitmap): either lib:name in a named material library (.VMF/.BMF) or a plain name from the model's own .V2Z/.VGF source. u0 v0 is the initial offset, du dv the drift rate — the viewer plays it as UV/second (rate units are inferred; the engine source for this path is not on the disk).

The scene command is actually an override: the same data is baked into the texture entity itself as a SPECIAL hook in the material source —

TEXTURE(NAME="firesmoke1_scr_tex";SPECIAL=" SCROLL  0 0 0.05 -0.331 \0x00")
{ MAP {"bintA"} }

so a texture scrolls even in scenes/maps with no SCROLL line, and several entities can share one bitmap (bintA) at different rates. In the binary .BMF form the SPECIAL string is texture sub-block tag 0x037 (see B2Z_FORMAT §7). Scroll is a per-texture-entity property, so the baker keys animated surfaces on the entity, not the bitmap.

Extended commands (game-tool scenes)

Larger scenes authored by the game tools use more keywords. The current loader recognises and skips these; they are documented here for completeness:

MORPH (animated geometry) · SPECIALFX (particle/effect hooks) · TEXTURE / GEOMETRY / MATERIAL / RAMP (explicit asset declarations) · ZONE / START / END (zone blocks) · CLIP (near/far clip) · VIEWANGLE (fov) · TRACKER / RETRACE / KEYRATE / GEOMETRIZE / IMMUNE / DRAWLAST / NOVIEWMATRIX / BILLBOARD.

Validation

parser/scn.py parses CANAL.SCN → fog, background, ambient, 1 light, 47 instances (5 unique models, all resolved). viewer/bundle.py assembles it into world space and renders it: a Red Planet canal — 22 red-rock cliff segments lining a channel, 22 struts, a VTV "mule" vehicle, rock and tech-junk — with reddish fog. See viewer/preview_scene.png.