Files
TeslaRel410/emulator/firmware-decomp/IG-SHADING-MODEL.md
T
CydandClaude Opus 4.8 84ea322a18 Document the weapon-fire/pick-range investigation, paused per user
Records tonight's finding before stopping this thread: the user correctly
pushed back on an oversimplification (the renderer isn't a purely passive
wire tap -- the protocol has a real synchronous pick-query shape, sect_pixel/
sect_vector, where the board runs its own Moller-Trumbore ray-triangle test
and replies with range+identity). Checked directly against the REAL firmware
(not just the reference software renderer's notes):

- sect_pixel/sect_vector never appear in this capture -- unused by this build.
- Action 0x23 (the notes' fire/pick candidate) gets no reply from the real
  firmware either (checked via the h_reply hook, which fires on ANY
  firmware-issued reply) -- but its payload doesn't match the documented
  FLYK format at all (no screen coords; a counter-tagged discharge-event
  shape instead), and its wire context (list_remove + zone-color + a
  PSFX-shaped 0x1d) reads as a weapon-discharge notification with visual
  consequences, not a pick query.

Net: open question, not resolved. BT4.10/MUNGA's real pick/range mechanism
(if board-side at all) is not yet identified. Paused per the user rather
than continue an open-ended search without a specific pointer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 15:58:48 -05:00

305 lines
18 KiB
Markdown
Raw 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.
# The IG-board shading model (ground truth from libDPL)
Authoritative reference for the software rasterizer, distilled from the **shipped
Division library headers** in the reconstructed game source:
- `C:\VWE\BT411\engine\MUNGA_L4\libDPL\dpl\dpltypes.h` — the public `dpl_*` enums
- `C:\VWE\BT411\engine\MUNGA_L4\libDPL\dpl\dpl.h` — the `dpl_Set*` API
- `C:\VWE\RP411\DivLoader\{VGCDivLoader.h,tagIdents.h}` — the `.biz`/BGF asset
format the same model is authored in (DivVertex, BIZ_IDENTITIES)
- `C:\VWE\BT411\context\rendering.md` / `docs\BGF_FORMAT.md` — how the modern
D3D9 port reproduces this same model (the ramp/BSL/effect-card fidelity work)
These are the host-side commands the game issued to the VelociRender board; our
firmware-decomp emulates the board that consumes them. Where our renderer differs
from this model, this file is the reference.
---
## 1. Vertex type mask — the per-geometry shading selector
`dpl_VERTEX_TYPE` (dpltypes.h:189) is a bit mask; it is exactly the `DivVertex.mask`
field in the asset format (VGCDivLoader.h:65):
| bit | token | meaning |
|------|-------------------------|--------------------------------------------|
| 0x01 | `dpl_vertex_coord` | has x,y,z (always) |
| 0x02 | `dpl_vertex_normal` | has Nx,Ny,Nz → **LIT** by the map light |
| 0x04 | `dpl_vertex_rgba` | per-vertex RGBA (effect cards) |
| 0x08 | `dpl_vertex_luminance` | per-vertex luminance `l` |
| 0x10 | `dpl_vertex_texture2D` | has u,v |
| 0x20 | `dpl_vertex_texture3D` | has u,v,w (perspective texture) |
| 0x40 | `dpl_vertex_radius` | sphere/point radius |
**The key rule (rendering.md §"IG-board shading model"):** shading is chosen PER
GEOMETRY by the vertex type:
- **No-normal geometry** (terrain / mesas / sky / buildings / mech — the WHITE
baked verts) is **UNLIT**, and colorized by the material's 2-endpoint RAMP.
- **Normal-bearing geometry** (~150 vehicle/missile files) is **LIT** by the map
light; the ramp is NOT applied (applying it = the "dusty white blobs" bug —
gate ramp on `!hasNormals`).
- Exception: cockpit-frame (`*_cop`) batches are pure-emissive constant colour.
---
## 2. Material ramp — how the board colours grayscale texels
`dpl_SetMaterialRamp` + `dpl_SetRampComponents(r0,g0,b0, r1,g1,b1)` (dpl.h:326,578):
a material carries a **2-endpoint colour ramp**. The (grayscale / bit-sliced)
texture's **luminance L∈[0,1] indexes the low→high gradient**:
```
out_rgb = rampLo + L * (rampHi - rampLo) # per channel
```
This is how the IG board coloured terrain from gray bit-slice art (rendering.md /
bgfload.cpp:341): rock `0.25,0.21,0.16 → 0.8,0.5,0.4` (warm tan); grass
`0.13,0.13,0.07 → 0.38,0.33,0.23`. Materials with no diffuse relied ENTIRELY on
the ramp.
> **Applicability to the wire-capture renderer:** the `0x1a` texmaps in the
> `netdeath` capture arrive as *already-colourised RGB* (HUD labels, emblems,
> panel/terrain art — 012% grayscale), i.e. the ramp is pre-baked on this path.
> So `render_final` must **not** re-apply a ramp (it would double-colour). The
> ramp model matters for a raw grayscale/BSL texmap path, not for these uploads.
---
## 3. Texture value model — wrap and alpha (implemented: `dpl_sampler.py`)
`dpl_SetTextureProperty(tex, prop, value)` (dpl.h:344) sets `dpl_TEX_PROP` fields
to `dpl_TEX_VALUE` tokens (dpltypes.h:103,121). The two that change rasterizer
*output*:
**WRAP** (`dpl_tex_prop_wrap`/`wrapu`/`wrapv`):
- `dpl_tex_value_repeat` — texel index wraps mod size (tiling terrain) — the board default
- `dpl_tex_value_clamp` — texel index clamps to the edge (a one-shot decal / HUD
label / emblem — tiling a "PLAYER 1" label is the wrong-wrap tell)
**ALPHA / cutout** (`dpl_tex_prop_alpha`):
There is **no stored alpha channel** — the texel word is `[pad, B, G, R]` (see §3a).
The board realises `dpl_tex_value_cut` (**PUNCH**: the cockpit/effect-card cutout
that retires the "opaque rectangle" reading — the wreck flame that read as a
"twisted drill bit", rendering.md §effect-cards) by **near-black keying** at draw
time: a texel whose `R+G+B <= ~24` is a HOLE (poly not drawn there, no z claim).
Ground truth: `dpl3-revive/patha/vrview_gl.py` ("alpha cutout (texel sum <= 24/255)").
Filtering (`point`/`bilinear`/`trilinear`/`mipmap_*`) is a quality axis; pvision
runs the board in texmode 0 = **point (nearest)**, which is what we model.
`dpl_sampler.py` implements wrap + near-black cutout (numpy-vectorized, with a
synthetic conformance self-test). Cutout is a per-MATERIAL property the wire
capture does not yet expose per drawn surface, so `render_final` defaults it OFF
(opaque) and leaves the hook for when a `SetTextureProperty` stream is decoded.
## 3a. Texel format — SVT `[pad, B, G, R]` (the ex-CYAN bug)
The `0x1a` upload's texel word is **`[pad, B, G, R]`** ("SVT xbgr") — ground truth
from the real renderer's own decode (`dpl3-revive/patha/vrboard.py:448` `do_texels`,
`stage_assets.py:43`). **Byte 0 is a PAD (0 in every texel across the capture);
the colour is bytes 1,2,3 = B,G,R, so RGB = bytes (3,2,1).** `mode` 1 is the
board's internal 8-bit storage path — the WIRE upload is always 4 bytes/texel.
`texstore.py` had been reading RGB as bytes (0,1,2) = `(pad, B, G)` — zero red —
which rendered the whole arena **CYAN**. Fixed to (3,2,1): the arena reads as its
true grayscale metal panels + coloured decals. This, not the texid binding (§5),
was the cyan.
---
## 4. Fog — the arcade model (not yet in the software renderer)
`dpl_FOG_TYPE` (dpltypes.h:238): the arcade uses **`dpl_fog_type_pixel_lin`** —
per-PIXEL linear fog on perspective W (rendering.md §fog: table fog on the
z-buffer collapses because the z-buffer is perspective-nonlinear; the board fogs
on W). Authored per map/time/weather as `fog = <near> <far> <r> <g> <b>` in the
DPL env INI (e.g. cavern/night/clear: near 90, far 1100, dark blue 0.1,0.1,0.12).
Night fog is dark BY DESIGN. Our renderer has the per-pixel Z/W planes to do this
faithfully; the near/far/colour are map-specific (not applied blind).
---
## 5. The remaining blocker — texid → texmap-handle binding
`render_final` still binds each drawn quad's 6-bit texid to an uploaded handle by
`texlist[tid % len]` — a placeholder. Investigated 2026-07-19/20: the draw's
texture-select word (op `0xf7`, ad 141/142) is **not** a handle — it is the
board's *resolved texture-descriptor reference* (per-texmap: high byte differs,
`0xf26a`/`0xf081`/`0xf1ea`/`0xcafa`…), computed at geometry-CREATE time from the
texmap (traced: the select-word value is written to scratch ~`0xbfc98` during the
`create` cmds, not the opaque texel store), and `battle_prog.pkl` is the
firmware's already-compiled program, so the game's material/texture *names* are
gone by then. Cracking it needs the deferred **board texture-RAM model**: RE the
firmware texmap→descriptor allocation and map the select word to an upload handle.
**Note (2026-07-20):** the monochromatic frame was NOT this binding — it was the
texel-format misread (§3a). With SVT decode fixed, most arena surfaces are
grayscale metal, so the frame reads correctly *despite* the placeholder binding;
the binding now only mis-tints the handful of coloured decals. Much lower priority.
**Escalation (2026-07-20, live-pod session):** with the reconstructed renderer
running live behind the actual game (not just replaying a static capture), this
binding gap became visually severe on **vehicles** ("trucks look like they were
painted with skittles" — user report). Vehicles are exactly the ~150
normal-bearing (LIT) BGF files that carry authored PAINT/decal materials
(rendering.md §"Per-pilot mech PAINT" — warning-stripe/badge/patch layers), so a
wrong-texture assignment here is far more visible than on a flat metal wall.
**No longer low priority — this is the top open item for texture correctness.**
Path forward (not yet done): live-instrument the firmware (we run the actual
i860 code via emu860c, so we can WATCH memory writes during `set_texmap_texels`
processing rather than guess from wire order) to find the real texid→handle
table the firmware builds, instead of the `tid % len(texlist)` placeholder.
---
## 6. Purpose of this pipeline — hardware ground truth for BT411 (2026-07-20)
This renderer's job is **not** to look good — it is to give the BT411
modernization-port developers a hardware-grounded, independently-verified
answer wherever the software renderer (`dpl3-revive`, the D3D9 port) has open
or hard-to-fix questions. Where this doc and BT411's `context/rendering.md`
overlap, treat agreement as cross-validation (two independent
reconstructions — firmware-level here, decomp-level there — agreeing) and
disagreement as a lead worth chasing on BOTH sides.
**Cross-reference index (BT411 `context/`):**
- `rendering.md` — the authoritative D3D9-port model: vertex-type shading
select (§1 above), material ramp (§2), fog (per-map/time/weather via
`BTDPL.INI`, per-pixel linear on W — NOT yet implemented in this renderer),
weapon beams (`ermlaser.bgf` TUBE geometry, muzzle-anchored, NOT a special
ray-trace primitive), mech skins (BSL bit-slice), per-pilot PAINT.
- `open-questions.md` — living list of everything BT411 has NOT yet resolved;
check before re-deriving something they've already nailed down (or vice
versa: this firmware pipeline may resolve one of THEIR opens).
- `combat-damage.md` — target acquisition (torso-boresight pick ray, auto-lock)
is RECONSTRUCTED and working in BT411's port; if our live session shows "no
weapon fire," suspect input/session state (fire button not reaching the
emulated RIO, or simply not fired yet this session) before suspecting a
missing render mechanism — the port's evidence is that firing itself works.
### 6a. The lit-color plane (r24/g24/b24) — REAL, mechanism confirmed, exact use UNRESOLVED
Verified live from the actual wire + firmware source (`sda4/DPL3/VRENDER/PXPL5SUP/`):
- `DIVPXMAP.H` defines `dvpx_r24/g24/b24` (addr 117/125/133, 8 bits each) as
scan-conversion-time fields; `EOF.C` (lines ~1456, 1742) copies them
straight into `dvpx_eofr/g/b` (184/192/200 — the final screen color) via a
24-bit `IGC_CPY` in the simple (unfogged) case.
- Confirmed present in the live per-draw coefficient program: `TREEclmpintoMEM`
(op 0x5a) at addr 118/126/134 (one bit off from the header's 117/125/133 —
this bytecode's own addressing, taken empirically), a 3-word instruction
whose 3rd word is a clamped float — REAL per-polygon values observed
(~49..395, not the same everywhere, roughly B > R > G — a genuine signal,
not noise).
- **What's still open:** applying this as a flat-poly color (§ current
gpu_raster.py) is a reasonable, low-risk use since flat polys had no real
data before (a placeholder gray). Applying it as a TEXTURE TINT is WRONG
(§5 caveat: textures arrive pre-colourised) — tried live 2026-07-20,
reverted same session (commit 0527022e) after the user reported "more
colors but neither correct." The exact relationship between this
BOARD-INTERNAL scan-conversion value and BT411's GAME-LEVEL ramp/lighting
model (§1-2) is NOT yet established — they may be the SAME quantity
(the board-side result of the ramp/lighting the game already computed and
is now shipping per-polygon) or a DIFFERENT one (e.g. a fog term). Next
step: cross-check a KNOWN lit vs unlit polygon (from BT411's vertex-type
rule) against its r24/g24/b24 value to see if unlit polys show the
material ramp's actual endpoint colors.
### 6b. Fog — IMPLEMENTED (2026-07-20), depth formula source-derived
BT411 (`rendering.md` §Fog) has the complete, verified model: per-map/time/
weather leaf in `content/BTDPL.INI` (`fog=<near> <far> <r> <g> <b>`), per-pixel
LINEAR fog on distance. For THIS mission (testarn.egg = arena1/day/clear) the
leaf is `[ardayclear]`: `clip=0.25 1300.0`, `fog=200.0 1250.0 0.32 0.3 0.5`
verified against the live game tree, exact match.
**The depth term was initially attempted with `tz` (texz) directly, which is
WRONG** — measured live (a vertical scan up a receding ceiling, row 0 to row
160): `tz` DECREASES with distance (324 -> 121), i.e. it behaves as an
inverse-depth quantity, not the "linear on W" BT411 describes. Re-derived from
actual firmware SOURCE instead of guessing again: `sda4/DPL3/VRENDER/AS860/
XFPROJ.SS`'s own comment on the projection code says `invz=eye_d/rz` and
`pz=wz*invz (where pz=1 at near clip, 0 at infinity)` — i.e. the z-buffer value
(`dvpx_zbuf`, 20-bit) is a NORMALIZED `hither/distance` quantity. So:
```
distance = hither * 2^20 / raw_zbuf_value
```
Implemented in `gpu_raster.py` (`FOG_HITHER/FOG_ZSCALE/FOG_NEAR/FOG_FAR/FOG_RGB`
+ the shader's new 10th output channel carrying the raw z-buffer float). Sanity
check (frame 11, netdeath capture, a receding ceiling): distance climbs
394 -> 1051 smoothly from the near edge to the horizon vanishing point,
landing squarely inside the authored 200-1250 fog range with no re-guessing
needed after the tz correction. Visually: the horizon now shows the authored
blue-purple haze (0.32,0.3,0.5), fading correctly toward the near floor.
**Not yet independently cross-validated:** the exact 2^20 scale (vs. 2^20-1 or
a slightly different fixed-point convention) and treating `hither` as a fixed
per-mission constant (0.25) rather than reading it per-view from the wire's
view-flush payload (the OTHER renderer already parses `hither +44, yon +48`
see [[dpl3-revive-integration]] memory) are reasonable, source-grounded
choices, not exact proof. Good enough to trust qualitatively; treat exact
near/far crossover distances as approximate until cross-checked against a
reference screenshot or the view-flush hither/yon.
### 6c. HUD — not rendering at all, cause NOT YET INVESTIGATED
No wire-level investigation done yet. BT411's `gauges-hud.md` /
`cockpit-view.md` should be checked first for the mechanism (a separate
draw pass? a different coordinate space/orthographic projection? a distinct
wire action type?) before searching the firmware blind.
### 6d. Weapon-fire rendering / board-side pick-range mechanism — PARTIALLY INVESTIGATED, PAUSED
Per BT411 `rendering.md` §"Weapon beams": beams are ordinary TUBE geometry
(`ermlaser.bgf`), muzzle-anchored every frame — i.e. on the wire this should
look like ordinary create/list_add/draw_scene traffic for a thin, elongated
object, NOT a special ray-trace primitive. Per `combat-damage.md`, the
original game's fire/target-lock mechanism is fully reconstructed and working
in BT411's port, so a live session showing no fire is more likely an input/
session-state gap (RIO fire button not reaching this DOSBox session) than a
missing render path.
**User's counter-point (2026-07-21), correcting an oversimplification above:**
the renderer is NOT a purely passive tap in the strict protocol sense — there
IS a real query/response shape in the wire protocol (VELOCIRENDER_PROTOCOL.md
`vr_sect_pixel`(4)/`vr_sect_vector`(5): synchronous, blocking, the board runs a
REAL MöllerTrumbore ray-triangle test over its own live scene and replies
with an instance handle — "the exact range to the pixel in the center of the
target and what that pixel is a part of"). Whether the SHIPPED BT4.10/MUNGA
build actually uses this path (vs. computing it host-side, as BT411's own
`HudSimulation`/pick-ray reconstruction does) was the open question.
**Investigated, using this pipeline's unique capability (running the REAL
firmware, not a reimplementation):**
- `sect_pixel`(4)/`sect_vector`(5) never appear on this capture's wire at all
(0 occurrences) — this build doesn't use the 1994-enum pick-query actions.
- Action `0x23` (the notes' "fire/pick" candidate) DOES appear (23 times), and
checked directly against the real firmware's OWN reply mechanism (`h_reply`
hook, which fires on ANY firmware-issued reply regardless of action): NO
reply follows `0x23` in this build either — consistent with the old
fire-and-forget finding, now confirmed against the actual firmware code
rather than just the software reimplementation.
- BUT the `0x23` payload itself does NOT match the documented FLYK format
(`[u:f][v:f][host ptr][-1]`, screen coords) — this build's payload is
`[1][incrementing event ID | 0x30000000][0]`, no screen coordinates. Wire
context around it (`0x23`×2 → `list_remove`×2 → `0x1b`×2 → a 264-byte
`0x1d`, matching the documented PSFX/particle-burst branch ≥160B) reads
like a WEAPON-DISCHARGE NOTIFICATION with visual consequences (a projectile
resolving + effect spawn), NOT a board-side "what's under the crosshair"
query. So `0x23` in BT4.10/MUNGA is very likely NOT the same mechanism the
1994-era FLYK notes describe under the same action number.
**Net conclusion: open.** Either this build's real pick/range mechanism lives
under a still-unidentified action, or BT4.10/MUNGA moved this computation
entirely host-side (matching BT411's own architecture) and `0x23` (whatever
it really is) is unrelated to targeting. Paused here per the user
(2026-07-21) rather than open a broader search without a specific pointer —
resume by identifying what wire traffic accompanies a KNOWN weapon fire in a
fresh, targeted capture (fire once, deliberately, with minimal other action
noise) rather than mining an existing capture for a guessed action number.
### 6e. Cockpit cage — intermittent, NOT YET CROSS-CHECKED
See `emulator/render-bridge/COCKPIT-CAGE-NOTES.md` (the OTHER renderer's
findings: PUNCH-texel windows, CAGE_TWIST_SIGN calibration, GLANCE_DCS
opt-in) and BT411's `cockpit-view.md` (the canopy stencil-cut kit, solved
there). Not yet cross-checked against what this firmware pipeline receives
on the wire for cockpit-frame geometry.