Files
BT411/context/bgf-format.md
T
arcattackandClaude Opus 5 35c750dc7c Gitea #44: the reticle TARGET NAME PLATE (PNAME1-8) -- the target's callsign under the crosshair
Reconstructed the last deferred piece of the 1995 reticle, decoded from the
Execute disassembly @004cdcf0 [T1]:
 - selection: target_mech+0x190 (owning BTPlayer) -> player+0x1e0
   (playerBitmapIndex), 1-based into the mesh table at this+0x2e8
 - placement: re-placed every frame the aim moves -- scale 0.12 uniform,
   translate (K*retPos.x, K*retPos.y - 0.08, -1.0), K = 1/2.8 (the x87 long
   double @0x4cee64) -- so the label TRACKS THE AIM POINT, not screen centre
 - visibility: the LOCK attribute (this+0x184, cached +0x188) AND an owning
   player; reticle-off / simple-X also hide it
 - art: the plate quad UV-addresses a shared 128x64 bitslice texmap whose
   texels the pod OVERWROTE each mission with the egg's callsign rasters
   (original source commented out at L4VIDEO.cpp:5682-5710) -- the baked
   'PLAYER n' art in BMAP.BSL is only the shipped fallback.  Material colour
   is a neutral (0.5,0.5,0.5): grey-white, not phosphor green.

Port: same geometry in reticle units (0.224 below the aim point, 0.336x0.084)
drawn as the target's egg callsign texture through the reticle's own MapX/MapY
mapping, so it follows the world view in every layout and BT_SHOT captures it.
New: dpl2d_DrawTexturedRect, BTReticleTargetPlate, BTGetPlayerNameTexture.

Also: kRetCaret corrected 0.02f -> 0.025f (_DAT_004cd7f4 read from the binary;
the old value was a T3 guess, 20% small).

KB: gauges-hud + open-questions corrected (the chain was filed as deferred and
mis-attributed to 'the 3D marker'); TWO PNAME consumers now distinguished (this
plate, and CameraShipHUDRenderable's ranking window which shipped 2026-07-18);
new bgf-format section on the plate atlas.

Rig-verified: 2-node relay, BT_GOTO=enemy -> '[hud] name plate: bmp=2 tex=1'
and 'Boreas' rendered under the crosshair on the locked target.  Solo clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166KTsC7ADm7VXEi1HF1jNg
2026-07-24 18:36:02 -05:00

8.4 KiB
Raw Blame History

id, title, status, source_sections, related_topics, key_terms, open_questions
id title status source_sections related_topics key_terms open_questions
bgf-format BGF Model Format (DIV-BIZ2 geometry) + the render gotchas established docs/BGF_FORMAT.md (full spec); CLAUDE.md §5, §10 render-fidelity notes
asset-formats
rendering
decomp-reference
BGF
DIV-BIZ2
PMESH
CONN
PCONN
LOD
material-ramp
SV_SPECIAL
Replacement-LOD selection for NON-additive multi-LOD models (hot-spot/reference semantics)

BGF Model Format

.BGF = DIV-BIZ2 little-endian nested Tag-Length-Value geometry. Full byte-level parsing spec (verified against all 1275 content .BGF files) lives in docs/BGF_FORMAT.md — this topic captures the structure + the hard-won gotchas that cost real debugging. Loader of record: engine/MUNGA_L4/bgfload.cpp (game-callable via LoadBgfFile); standalone reference reader DivLoader/VGCDivLoader.cpp (archive-only; in-repo reimplementation: engine/MUNGA_L4/image.cpp). Tag names: dsys/PFBIZTAG.H.

Structure

OBJECT → LOD → PATCH (geogroup) → PMESH. Interleaved float32 verts; QUAD faces (must triangulate [a b c][a c d]); materials referenced by library:material name (resolved against the map's .BMF libs). Damage/articulation are name-driven via SV_SPECIAL (0x2037) dz_* tokens, not stored geometry. [T1]

Indices — the CONN/PCONN story (two corrections)

A PMESH carries faces in CONNECTION_LIST (0x0047) and/or PCONN_LIST:

  • CONNECTION_LIST is a FLAT TRIANGLE LIST (3 indices/face) — NOT "one polygon, fan-triangulate" (the original doc was wrong; fan-triangulating turned buttec's 115 tris into 341 fan-garbage). Corpus-verified: ALL 3488 CONN chunks have n%3==0. [T2]
  • CONN and PCONN COEXIST — a pmesh can carry quads/hexes in PCONN PLUS loose triangles in CONN. 370 of 841 GEO models had mixed pmeshes silently dropping their CONN triangles until both were processed always (the "turret base missing panels" bug). ppf=6 hexagons exist. [T2]

LODs — the √3 decode

Each LOD chunk carries a 0x2046 header = [near..far) viewing band. The board selected the LOD whose band contains the camera distance.

  • √3 range decode: every stored band value is the authored euclidean distance ÷ √3 (92% of the corpus is exactly nice-number/√3; hand-conversion typos prove it). Multiply parsed bands by √3 (kLodRangeScale=1.7320508). [T2]
  • Metric = the instance-transformed OBJECT ORIGIN distance, ONE shared d for all LODs of an object (a bounding-sphere-SURFACE metric desyncs sibling bands — wrong). [T2]
  • ADDITIVE_LODS (object-level SV_SPECIAL token, 135 pod BGFs — all arena structures): at eye distance d, EVERY LOD with d < OutDist draws (near detail ADDS onto coarser massing). Non-additive keep first-LOD-only (the 2007 engine's own behavior). [T2]
  • PUNCH (patch-level SV_SPECIAL, dpl_Punchize = FUN_00490308 → VPX board cmd 0x20, tokens {0x80000003,0x80000017,0x80000004} — a CONSTANT geogroup-modifier opcode, no color key; the sibling dpl_Damagize FUN_004902b0 uses the same cmd with a different triple). Two cases: [T1]
    • Textured punch (arena walls/catwalks): black texels = HOLES (alpha-test in the opaque pass). Coplanar shells inside one LOD z-fight → depth-bias by submission ordinal. [T2]
    • FINAL mechanism (i860 VREND.MNG firmware, decoded 2026-07-11): a punch geogroup is a THREE-CHUNK STENCIL-CUT KIT -- in file order: aperture MASK (invisible per-pixel cutout), visible HULL (the only colour-drawn chunk, drawn where not masked), byte-identical hull TWIN (the damage-reset record, never visible). Visible result = hull-minus-apertures. Applies to cockpit canopies, arena punch (AR02 etc.) and outside-torso punch (VUX_TOR) alike -- the port's arena "black texels = holes" alpha-test path is a visual approximation that predates this decode (works; revisit for exactness). Port: bgfload punch-kit roles + the L4D3D stencil cut (D24S8). See cockpit-view + decomp-reference for firmware addresses. [T1 mechanism / T2 render-verified all 8 cockpits]

Shading model (from libDPL DPLTYPES.H)

Shading is selected PER-GEOMETRY by vertex type:

  • No-normal geometry (XYZ_UV / XYZ_RGBA_UV: mesas/ground/sky/buildings/mech, WHITE baked verts) is UNLIT, colorized by the material's 2-endpoint RAMP (see material-ramp).
  • Normal-bearing geometry (XYZ_N_UV: ~150 vehicle/missile files) is LIT by the map light.
  • Applying the ramp to lit geometry = the "dusty white blobs" bug — gate on hasNormals. [T2]

Skeleton assembly

A mech is not one BGF — it's a .SKL skeleton (a DCS tree of joints) whose each joint's Object=part.bgf is loaded + posed, then animated by .ANI clips. See asset-formats / locomotion for skeleton + animation. [T1]

The PNAME/PLACE name-plate atlas (the only TEXT-bearing BGF geometry) [T1/T2]

PNAME1-8.BGF + PLACE1-8.BGF (content/VIDEO/GEO/, 235/238 bytes) are the MP scoreboard's 16 billboard plates — each ONE flat quad (4 verts, XYZ_UV, PCONN ppf=4, no LOD chunk = 1 implicit LOD, no normals, no SV_SPECIAL). PNAME = 1.0×0.25 m, PLACE = 0.5×0.25 m, planar Z=0. Consumers: FUN_00454a70 (the scoreboard, resource type 0x46) and BTReticleRenderable @004cc40c (PNAME only). See open-questions.

They are the only geometry in the corpus whose texture is TEXT, which makes them the sole asset that can prove BSL row order and slice mapping — and they CONFIRM the port's pair-swapped nibble decode (image.cpp:122-125): slices 0→3 come out as "PLAYER 1/2", "3/4", "5/6", "7/8" in exact index order. [T2 — decoded + read visually]

One shared 128×64 texmap: bmap:VIDEO/MAT/BMAP.BMF → 6 TEXTURE records bmap1_tex..bmap6_tex, all TEXTURE_MAP=bmap with BITSLICE 0..5 → VIDEO/TEX/BMAP.BSL (DIV-BSL2, 128×64, depth 4, texels @0x70). Cell map (UVs read straight from each BGF):

material slice plates → UV sub-rect
name12_mtlbmap1_tex 0 PNAME1 v[.5,1], PNAME2 v[0,.5] (full U; 128×32 cells)
name34_mtlbmap2_tex 1 PNAME3 v[.5,1], PNAME4 v[0,.5]
name56_mtlbmap3_tex 2 PNAME5 v[.5,1], PNAME6 v[0,.5]
name78_mtlbmap4_tex 3 PNAME7 v[.5,1], PNAME8 v[0,.5]
place1234_mtlbmap5_tex 4 PLACE1-4 = 2×2 grid of 64×32 cells (u[0,.5]/[.5,1] × v[.5,1]/[0,.5])
place5678_mtlbmap6_tex 5 PLACE5-8 = same 2×2 grid

The BSL is a RUNTIME COMPOSITING TARGET, not final art [T0 engine source]. DPLRenderer::MakeBitSliceStorage (L4VIDEO.cpp:9888) allocates uint32[128*64]; LoadBitSliceTexture merges a 1bpp egg BitMap into it; FlushBitSliceTexture (L4VIDEO.cpp:5682, stubbed in 2007) did dpl_LookupTexture("bmap:bmap1_tex") + dpl_TexmapTexels2D(texmap, buf, 128, 64, 4) — one upload rewrites ALL SIX slices. So the 1995 engine painted the egg's callsign + ordinal bitmaps onto these plates every mission; the baked "PLAYER n" text is only the shipped DEFAULT. Two stacked 128×32 large-name bitmaps per slice = exactly the PNAME v-halves; two stacked 128×32 ordinal bitmaps ("1st|2nd") = exactly the PLACE 2×2 grid. Corollaries:

  • The shipped BMAP.BSL has only 4 distinct planes — slice4 aliases slice0 and slice5 aliases slice3 (nibble3==nibble7, nibble4==nibble6; nibbles 0/1 zero). The ordinal art was never baked, so a naive PLACE draw shows "PLAY"/"ER 1"/… fragments. [T1 measured]
  • The placeholder cells are stored scanline-reversed vs the compositor's top-down order (LoadBitSliceTexture writes y=0..31 top-down), so a naive PNAME draw gives the RIGHT number upside-down. Don't chase it — implement the compositor instead. [T1 / T3 fix choice]
  • These 6 materials carry no DIFFUSE/AMBIENT — only NAME, MATERIAL_TEXTURE and an unhandled tag 0x0027 (12 B = {0.5,0.5,0.5}; only 41 uses in 1618 BMFs, likely SPECULAR [T4]). bgfload therefore falls back to the gray placeholder 0xFFB0B0B8; force WHITE for these unlit text plates. And decodeBSL emits alpha=255 for mono slices, so the plate draws as an OPAQUE black rectangle — a billboard needs black→alpha-0 keying (which is exactly what the 2D path's A4R4G4B4 0xFFFF/0x0000 upload does).

Key Relationships