# BGF (`DIV-BIZ2`) Model Format — Parsing Spec Reverse-engineered for the BattleTech pod port (replacing the closed `libDPL`/Division-IG renderer). Verified by parsing **all 1275 `.BGF` files** in `CONTENT/` with a prototype parser — every file closes to the exact byte. Header authority cited inline; source headers under `…/CODE/RP/MUNGA_L4/libDPL/`. ## 1. Header & global encoding - **Magic:** 8 bytes ASCII `DIV-BIZ2` (`__PFILE.H:60-61` `dpfB2ZIDstr`). Siblings: `DIV-BMF2` (binary material), `DIV-VIZ2`/`DIV-VMF2` (ascii). - **Endian:** little-endian (`FILELIB.H:55`). All ints/tags + IEEE-754 **float32** (not double, `PFILE.H:127-130`). - **No directory.** After the magic, a flat-to-walk **nested chunk (TLV) tree**. First chunk is always `HEADER (0x0003)`, last always `BIZ_DONE (0x0005)`. - Content written by format v2.8 (lib 2.07). ## 2. Chunk grammar (TLV tree) ``` Chunk { uint16 tagword; (uint8|uint16) len; byte payload[len]; } ``` - **Tag id = `tagword & 0x2fff`.** Namespaces: `0x00xx` = structural/DEF, `0x20xx` = attribute/CON (all leaves). Validity macro `dpfValidTag` (`PFBIZTAG.H:134-137`). - **Length width flag = `tagword >> 14`:** `0` → 1-byte len; `1` (`0x4000`) → 2-byte LE len; `0x8000` (4-byte) reserved, unused in this content. (Census: 1-byte 38890, 2-byte 8525, 4-byte 0.) - **Containers** (payload = child chunks): `HEADER, BOUND, OBJECT, LOD, PATCH, PMESH, SPHERE_LIST` (+ spec'd MATERIAL/TEXTURE/RAMP/POLYGON/TRISTRIP/POLYSTRIP/LINE/TEXT). Everything else is a leaf. ### Tag IDs actually used by this game (full table in `PFBIZTAG.H`) Structural `0x00xx`: `0003 HEADER`, `0005 BIZ_DONE`, `0040 OBJECT`, `0041 LOD`, `0042 PATCH(=GEOGROUP)`, `0046 PMESH` (**only mesh type used**), `0047 CONNECTION_LIST`, `004d PCONN_LIST`, `0048/0049 SPHERE_LIST/SPHERE`, `0070/0071/0072 BOUND/BBOX/BSPHERE`, vertex blocks `0080 XYZ`, `0081 XYZ_N`, `0082 XYZ_RGBA`, `0088 XYZ_UV`, `0089 XYZ_N_UV`, `008A XYZ_RGBA_UV`. Attribute `0x20xx`: `2002 VERSION`, `2003 DATE`, `2004 TIME`, `2005 SCALE`, `2007 FILETYPE`(0=geom/1=mtl), `2008 *_NAME`, `2009 UNIT`(1=metre), `2030/2031 SV_F/B_MATERIAL`, `2034 SV_DECAL`, `2035 SV_FACETED`, `2036 SV_VERTEX`, `2037 SV_SPECIAL` (articulation/part names — see §6), `2046 LOD_DISTANCE`(2×f32 in,out), `2048 LOD_TRANSITION`. ## 3. Geometry **Vertex blocks** — interleaved packed float32, count = `len / stride`: | tag | fields | stride | |---|---|---| |0080 XYZ|pos[3]|12| |0081 XYZ_N|pos[3],n[3]|24| |0082 XYZ_RGBA|pos[3],rgba[4]|28| |0088 XYZ_UV|pos[3],uv[2]|20 ← dominant (2595 blocks)| |0089 XYZ_N_UV|pos[3],n[3],uv[2]|32| |008A XYZ_RGBA_UV|pos[3],rgba[4],uv[2]|36| Positions = 3 floats, units per `UNIT` (metres). Flags map 1:1 to `dpl_VERTEX_TYPE` (`DPLTYPES.H:189-199`). **Indices** (inside a `PMESH`, after its vertex block): - **`PCONN_LIST` (0x004d)** — `uint8 pointsPerFace` (4 = quads is typical; **6 = hexagons occur**, e.g. CALPB) then `int32 index[]` (0-based into this geometry's vertex block). `nFaces = ((len-1)/4)/ppf`; fan-triangulate each face. - **`CONNECTION_LIST` (0x0047)** — a **flat triangle list** (3 indices per face, no leading byte; corpus: all 3488 CONN chunks have `n%3==0`). - ⚠ **CONN and PCONN are NOT alternatives — a single PMESH can carry BOTH** (quads/hexes in PCONN *plus* plain triangles in CONN): 370 of 841 pod GEO models mix them in one pmesh. A loader that prefers PCONN and skips CONN silently drops those triangles (found via the calliope turret base `calpb`: 78 PCONN tris + 24 CONN tris — the missing base panels). Process both, always. > **D3D9 note:** faces are **quads** → triangulate each quad `[a b c d]` → `[a b c][a c d]` at load. ## 4. Materials & textures (NOT in the BGF) - Geometry refs material via parent `PATCH`'s `SV_F_MATERIAL`/`SV_B_MATERIAL` leaf: `uint8 mattype` (`PFILE.H:169-170`; 1=named) + NUL-terminated `"library:material"` string, e.g. `basev:shadow_mtl`. - The part before `:` = a **`.BMF` material-library** file (`basev` → `BASEV.BMF`). BMF uses the **identical chunk grammar** (magic `DIV-BMF2`/`DIV-BIZ2`, `FILETYPE=1`, `MATERIAL`/`TEXTURE` chunks) — so the same parser reads it. - Texture **pixels** live in further files named by the BMF's `TEXTURE_MAP` (loaders `dpl_vtxRead/sgiRead/tgaRead`, `DPLUTILS.H:210-223`). **Load chain: BGF → BMF → image.** (Open: this archive has 2222 `.BMF` but few `.vtx/.sgi/.tga` — confirm where pixels actually live.) ## 5. Hierarchy → dpl model ``` HEADER BOUND { BSPHERE, BBOX } OBJECT → dpl_OBJECT SV_VERTEX LOD → dpl_LOD (LOD_DISTANCE in/out; may be absent = 1 implicit LOD) PATCH → dpl_GEOGROUP (SV_F/B_MATERIAL, SV_FACETED, SV_DECAL, SV_SPECIAL) PMESH → dpl_GEOMETRY VERTEX_xxx PCONN_LIST / CONNECTION_LIST BIZ_DONE ``` `PATCH == GEOGROUP` (`VCELTYPE.H:481-492`). Multi-LOD objects (1947 LODs/1275 files) are distance-switched; blend per `LOD_TRANSITION`/runtime `dpl_MORPH_MODE`. ## 6. Articulation / damage — name-driven, not geometric No morph-target/joint chunk exists. Articulation & damage key off **`SV_SPECIAL` (0x2037)** string tokens on geogroups, e.g. `dz_hip`, `dz_utorso`, `dz_larm`, `dz_rgun`, `dz_lfoot`, `dz_searchlight`, and behaviour tokens `PUNCH`, `BLINK`, `WIREFRAME`, `ADDITIVE_LODS`, `GEOMETRIZE 0x8000001a`. `dpl_FlushDCSArticulations`/`dpl_MorphObject`/`dpl_Damagize` match these names to attach DCS joints / morph / hide-swap on damage. **Loader must expose each geogroup's special string verbatim.** ## 7. Open items (need a known-good render or more samples) 1. ~~`PCONN_LIST` vs `CONNECTION_LIST` semantics~~ **RESOLVED (twice-corrected — see §"Indices"):** `PCONN_LIST` = many faces with a leading points-per-face byte (4 or 6); `CONNECTION_LIST` = a FLAT TRIANGLE LIST (the earlier "ONE polygon per chunk" reading was wrong — task #20), and the two **coexist in one PMESH** (the earlier "prefer PCONN" reading dropped CONN's triangles — the turret-base missing-panels bug). The `*_LP` "light-point/FX" objects (materials `btfx:*`) carry only `SPHERE_LIST`/`POINT_LIST` geometry — no triangle mesh — and render as sprites later, not solids. Loader result on this corpus: **621/663 triangle meshes load; 42 are point/sphere FX.** 2. Winding order (CW/CCW) + `SV_FACETED` ↔ backface culling — confirm visually. 3. Full `SV_SPECIAL` token grammar (multi-word commands). 4. `SPHERE`/`SPHERE_LIST` per-sphere radius source. 5. 4-byte length variant (`0x8000`) — unused here, handle defensively.