Files
BT412/docs/BGF_FORMAT.md
arcattackandClaude Opus 4.8 7b7d465e5e Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.

Layout:
  engine/   MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
            work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
            models) + image codec; the minimal rp/ headers the audio HAL needs
  game/     reconstructed BT logic + surviving-original BT source + fwd shims
            + WinMain launcher
  content/  full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
  docs/     format specs + reconstruction ledgers
  reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
  tools/    MP console emulator + map/resource scanners

One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:03:40 -05:00

111 lines
6.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.