# DIV-BIZ2 (`.B2Z` / `.V2Z`) binary geometry format > **The format family is wider than its extensions.** The game-side pipeline uses > the same two encodings under different names: > `.BGF` = binary geometry (magic `DIV-BIZ2`), `.VGF` = its text twin (`DIV-VIZ2`), > `.BMF` = binary **material library** (a BIZ2 file with only material/texture/ramp > blocks, no geometry), `.VMF` = its text twin. Everything below applies to all of > them. Game-side material references are **namespaced**: `stships:eblu4_mtl` means > material `eblu4_mtl` in library `STSHIPS.VMF`/`.BMF`. > > **Texture sub-block `0x018` (1996+): BITSLICE** — u8 plane index into a `.BSL` > bit-slice texture pack (see `SVT_FORMAT.md`). Absent = slice 0. Reverse-engineered from `DPL3/BIZREAD.C` (© DIVISION Ltd 1994, author PJA) and validated against real models on drive SDA4. This is the object/geometry format consumed by the DPL3 renderer used in Virtual World Entertainment's BattleTech / Red Planet pods. `.B2Z` and `.V2Z` share this format. All multi-byte values are **little-endian**. Floats are IEEE-754 single precision (the header rejects double-precision files). There is no compression — the stream is a flat sequence of length-delimited, tagged blocks. ## 1. File layout ``` "DIV-BIZ2" 8-byte ASCII magic * sequence of top-level blocks ``` Parsing stops at the **trailer** block (type `0x005`). ## 2. Block header Every block begins with a 16-bit header, followed by a length field whose **width is encoded in the top nibble of the header**: ``` hdr = u16 sel = (hdr >> 12) & 0xC sel == 0x0 -> length = u8 (1 byte) top nibble 0,1,2,3 sel == 0x4 -> length = u16 (2 bytes) top nibble 4,5,6,7 sel == 0x8 -> length = u32 (4 bytes) top nibble 8,9,A,B ``` `length` counts the block **body** only (everything after the length field). The record **type** is `hdr & 0xFFF`; the top nibble is purely the length-encoding flag. That is why the source lists each type three times (`0x0NNN` / `0x4NNN` / `0x8NNN`) — same record, three body sizes. ## 3. Top-level block types (`hdr & 0xFFF`) | Type | Meaning | Body | |-------|----------|------| | 0x003 | Header | sub-blocks (§4) | | 0x004 | Comment | opaque, skip | | 0x005 | Trailer | end of file | | 0x010 | Texture | sub-blocks (§7) | | 0x020 | Material | sub-blocks (§7) | | 0x030 | Ramp | sub-blocks (§7) | | 0x040 | Object | sub-blocks (§5) | ## 4. Header sub-blocks | Type | Name | Body | |-------|-----------|------| | 0x002 | version | skip | | 0x003 | date | skip | | 0x004 | time | skip | | 0x005 | scale | f32 | | 0x006 | precision | u8 (must be 0 = single) | | 0x007 | filetype | skip | | 0x009 | unit | u8: 0 = model units, 1 = inches (scale by 1/25.4) | ## 5. Object sub-blocks | Type | Name | Body | |-------|----------------|------| | 0x008 | object name | NUL-terminated string | | 0x030 | front material | material-ref (§6) | | 0x031 | back material | material-ref (§6) | | 0x036 | vertex format | u8 draw_mode (inherited by patches) | | 0x041 | LOD | payload skipped by original loader | | 0x042 | patch | sub-blocks (§8) — one geogroup | | 0x032/33/34/35/37 | plane / draw-mode / decal / facet / voodoo | skip | | 0x000/001 (+comment tags) | comment | skip | ## 6. Material reference (used by 0x030 / 0x031) ``` type = u8 0 -> none (NULL) 1 -> named: NUL-terminated material name follows 2 -> DEFAULT material 3 -> (back only) same as front ``` ## 7. Material / Texture / Ramp sub-blocks **Material** (top-level 0x020): | Type | Name | Body | |-------|----------|------| | 0x008 | name | string | | 0x00a | scope | u8 | | 0x021 | texture | u8 mode; if mode==2, NUL-terminated texture name | | 0x023 | ambient | 3×f32 | | 0x024 | diffuse | 3×f32 | | 0x025 | specular | 4×f32 | | 0x026 | emissive | 3×f32 | | 0x027 | opacity | 3×f32 | | 0x028 | ramp | NUL-terminated ramp name | **Texture** (top-level 0x010): 0x008 name, 0x00a scope, 0x011 map filename (string), 0x012 minify, 0x013 magnify, 0x014 alpha, 0x015 wrap_u, 0x016 wrap_v, 0x017 detail — the mode fields are each a u8. The bitmap itself lives in an external `.SVT` file named by 0x011. 0x037 is the SPECIAL hook, a NUL-terminated free-text string mirroring the text source's `SPECIAL="..."` header field — in practice `" SCROLL u0 v0 du dv "` UV-drift rates (see SCN_FORMAT, Scrolling textures). **Ramp** (top-level 0x030): 0x008 name, 0x00a scope, 0x031 ramp data (color0 = 3×f32, color1 = 3×f32). ## 8. Patch (geogroup) sub-blocks | Type | Name | Body | |-------|----------------|------| | 0x008 | patch name | string | | 0x030 | front material | material-ref (§6) | | 0x031 | back material | material-ref (§6) | | 0x036 | vertex format | u8 draw_mode | | 0x043 | polygon | vertex-list (§9), type = polystrip | | 0x044 | tristrip | vertex-list (§9), type = tristrip | | 0x045 | polystrip | vertex-list (§9), type = polystrip | | 0x046 | pmesh | vertex-list (§9), type = pmesh | | 0x048/04a/04b | sphere / line / text | not implemented in original; skip | | 0x032/33/34/35/37 | plane / draw-mode / decal / facet / voodoo | skip | `draw_mode` bits (from `dpltypes.h`): 0x01 smooth, 0x02 colored, 0x04 luminance, 0x08 textured, 0x10 3d-textured, 0x200 front, 0x400 back. ## 9. Vertex-list sub-blocks A primitive's body is itself a sequence of blocks: zero or more **connectivity** blocks (pmesh only) plus exactly one **vertex** block. A vertex block's `length` gives `n_floats = length / 4`; `n_vertices = n_floats / floats_per_vertex`. Each vertex is `floats_per_vertex` consecutive f32s; components live at fixed offsets by format: | Type | f/vert | position | normal | rgba | lum(la) | uv(2) | uvw(3) | |-------|:-----:|:--------:|:------:|:----:|:-------:|:-----:|:------:| | 0x080 | 3 | 0 | – | – | – | – | – | | 0x081 | 6 | 0 | 3 | – | – | – | – | | 0x082 | 7 | 0 | – | 3 | – | – | – | | 0x083 | 10 | 0 | 3 | 6 | – | – | – | | 0x084 | 5 | 0 | – | – | 3 | – | – | | 0x085 | 8 | 0 | 3 | – | 6 | – | – | | 0x088 | 5 | 0 | – | – | – | 3 | – | | 0x089 | 8 | 0 | 3 | – | – | 6 | – | | 0x08a | 9 | 0 | – | 3 | – | 7 | – | | 0x08c | 7 | 0 | – | – | 3 | 5 | – | | 0x090 | 6 | 0 | – | – | – | – | 3 | | 0x091 | 9 | 0 | 3 | – | – | – | 6 | | 0x092 | 10 | 0 | – | 3 | – | – | 7 | | 0x094 | 8 | 0 | – | – | 3 | – | 5 | **Connectivity** (pmesh only): - `0x047` triangle connectivity — `length/12` triangles, each = 3× i32 vertex indices. - `0x04d` polygon connectivity — u8 `verts_per_poly`, then `(length-1)/(vpp*4)` polygons, each = `vpp`× i32 indices. **Triangulation for export:** - `tristrip`: triangles `(i, i+1, i+2)`, flipping winding on odd `i`. - `pmesh`: fan each face `(f0, fk, fk+1)`. - `polygon` / `polystrip`: current parser fans the block's vertices — adequate for simple patches; a faithful polystrip walk is a known refinement (see README). ## 10. Validation `parser/b2z.py` decodes the samples with no unrecognized blocks and clean trailers. Semantic checks that confirm correctness: | File | Result | |------|--------| | PT_RED.B2Z | material diffuse = (1,0,0) → red; no geometry (a colored point) | | PT_WHITE.B2Z | material diffuse = (1,1,1) → white | | BALL.B2Z | 50 verts / 40 tris, bounds ≈ unit sphere at origin, has normals | | TESTBOX.B2Z | 5 pmesh parts (blue/red/black/green/yellow), 202 tris, boxy bounds | | CANDA_S1.B2Z | Red Planet terrain tile: redrock/water materials, inch units, UV coords |