Files
TeslaRel410/emulator/firmware-decomp/IG-SHADING-MODEL.md
T
CydandClaude Opus 4.8 bf234842cb Commit the texel-format fix + shared dpl_sampler module (was uncommitted)
These changes were made and verified earlier this session (the all-cyan bug
fix is already recorded as SOLVED in project memory) but never got committed --
render_final.py has referenced dpl_sampler as a module in its docstring since
ac4142e7, yet dpl_sampler.py itself was never added, leaving the repo unable
to run the M4b/M4c/M4d renderer scripts that all import it.

texstore.py: fix the texel word format. SVT texels are [pad,B,G,R] (ground
truth: dpl3-revive patha/vrboard.py do_texels + stage_assets.py); reading RGB
as bytes (0,1,2)=(pad,B,G) zeroed red, producing the all-cyan render. Correct
read is bytes (3,2,1).

dpl_sampler.py: the authentic IG-board texture-value model (wrap repeat/clamp,
near-black CUT keying) factored into its own tested module, distilled from the
shipped libDPL headers -- see IG-SHADING-MODEL.md (also added, referenced by
render_final.py's docstring but likewise never committed).

igc_exec.py: OP48/OP49 candidate opcodes (pixel global-X/Y seed into mem) added
to the CPU golden-model executor alongside the existing SCMEMA seed handling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 10:07:16 -05:00

140 lines
7.3 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.