Predator/IR vision: reverse-engineered from the original firmware and
confirmed by the build team -- it is the Division board's TEXTURE-VALUE RAMP
mode (a "check your texture maps" diagnostic the devs hijacked), NOT a
grayscale squash or a false-colour palette. Located in VREND.MNG (effect
handler @0xe6c0, wire action 0x1b, type -1 ON / -2 OFF); ramp colours from
VR_DRAW.C. Renderer reworked to match: vrview_gl now does the 4-ramp
lerp(color0,color1,luminance(texel)) in the mesh pass (grayscale+defog
removed). Live-rendered on a new night-clear arena egg; crew A/B verdict
pending.
Firmware-decomp toolchain (emulator/firmware-decomp/), all built from the
project's own artifacts and validated:
- coff860.py i860 COFF reader (symbols/sections), names match AS860 source
- derive860.py derives the i860 opcode map from matched .S<->.O pairs
- dis860.py i860 disassembler (98% on clean ground truth; proven on
VREND.MNG -- velocirender_statistics decodes correctly)
- sigmatch860.py reloc-invariant signature matcher onto the stripped image
- i860-encoding.md / FIRMWARE-SYMBOLS.txt / README.md
PVISION-IMPLEMENTATION-GUIDE.md: self-contained hand-off for the BT411 team.
HARDWARE-ARCHITECTURE.md + hardware-photos/ (15 board shots): the Division
VelociRender card is a 2-board stack driving a 3-processor pipeline --
INMOS IMS T425-J25S (comms/control, runs vrendmon.btl) + Intel i860 XP-50 (FP
geometry, runs vrender.mng) + Division PXPL IGC 5.2 ASIC with ~48x PXPL EMC
5.1 (UNC Pixel-Planes-5 SIMD array; "EMC" = the firmware's configEMCs) +
Analog Devices ADV7150 RAMDAC + NTSC. Plus the VWE Video Distribution Board
(P/N 1404: AMD MACH130 + 3x Brooktree Bt477) for the 3-VGA-head cockpit split.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8.2 KiB
Implementing IR / "Predator" Vision in a BattleTech (VelociRender) renderer
Audience: the BT411 renderer + whoever's Claude is implementing it.
Status: reverse-engineered from the original VelociRender i860 firmware
(VREND.MNG) and the DPL3/VRENDER board source, and confirmed by original-team
recollection. This document is self-contained — the key code is quoted inline so
you don't need our tree.
TL;DR
Predator/IR vision is not a grayscale squash, not a palette swap, and not a scene-luminance heat map. It is the Division board's texture-value ramp mode — a diagnostic the board provided for checking texture maps that the game developers hijacked as the in-game IR effect.
Each textured surface is drawn as:
out_rgb = lerp(color0, color1, luminance(texel))
where luminance(texel) is the brightness of the sampled texel (the texture
map's value, not the lit/shaded pixel), and (color0, color1) are the two RGB
endpoints of the ramp assigned to that surface's material. There are four
ramps (gray / red / green / blue → white). Because different materials use
different ramps, the scene comes out multicolor.
1. How the game turns it on (host side)
The game's renderer object (DPLRenderer, shared by BattleTech and Red Planet)
has a toggle. From the original source (MUNGA_L4/L4VIDEO.CPP,
DPLRenderer::DPLTogglePVision):
static Logical pvision_on = 0;
dpl_EXPLOSION_EFFECT_INFO sfx_info;
sfx_info.x = sfx_info.y = sfx_info.z = 0;
if ((pvision_on ^= 1) != 0) sfx_info.type = -1; // pvision ON
else sfx_info.type = -2; // pvision OFF
dpl_Effect(dpl_effect_type_explosion, NULL, &sfx_info);
Key point: it sends no colour. It fires a bogus "explosion" effect at the
origin whose type field is a magic flag: -1 = ON, -2 = OFF. The
dpl_EXPLOSION_EFFECT_INFO struct is {float x,y,z; int32 type; dpl_TEXTURE*}
— there is no colour to carry. So the palette is entirely board-side; the
host only flips a switch.
On the VelociRender wire this arrives as the effect action (in our decode,
action 0x1b), payload first word = type. If you drive a board/emulator, the
handler is: type >= 0 → normal explosion; type == -1/-2 → set/clear the
pvision mode flag.
2. What the board does (firmware)
The board firmware selects a texture display mode per frame
(dvpx_eoftexmode, from the end-of-frame code VRENDER/PXPL5SUP/EOF.C):
| mode | meaning |
|---|---|
0 |
texture RAMP — texel value → interpolated colour ramp (this is IR vision) |
0x4 |
8-bit monochrome |
0x6 |
8-bit 3-3-2 full colour |
0x7 |
12-bit 4-4-4 full colour |
Normal rendering uses the full-colour modes. pvision ON flips the whole scene
into mode 0 (ramp). The end-of-frame code literally linear-interpolates each
channel across the texel value:
/* now access the texture colour map table (8 32-bit entries) */
/* now linterp from r0 to r1 */ // Red across texel value
/* now linterp from g0 to g1 */ // Green
/* now linterp from b0 to b1 */ // Blue
The "texel value" is a 6-bit luminance of the 24-bit texel
(VRENDER/DNC.C::luminize — "returns a 6-bit luminance value from a 24-bit
texel"). Think of it as value = luminance(texel), value ∈ [0,1].
3. The ramps
A ramp is two RGB endpoints (dpl_RAMP { float32 color0[3]; float32 color1[3]; }).
color0 = the colour for dark texels (value 0), color1 = the colour for
bright texels (value 1). Four ramps are installed once at render init
(VRENDER/VR_DRAW.C, setRampEntry(ramp, index, r0,g0,b0, r1,g1,b1)):
| ramp | color0 (dark) | color1 (bright) | look |
|---|---|---|---|
| 0 | 0.0, 0.0, 0.0 |
1.0, 1.0, 1.0 |
black → white (grayscale) |
| 1 | 0.3, 0.0, 0.0 |
1.0, 1.0, 0.9 |
dark red → warm white |
| 2 | 0.0, 0.5, 0.0 |
1.0, 1.0, 1.0 |
green → white |
| 3 | 0.0, 0.0, 0.4 |
0.9, 0.9, 1.0 |
blue → cool white |
Each material references one of these by an integer ramp_entry (0–3), so a
mech built from several materials shows several colour families at once — that's
the multicolor IR look, and it's why the tool was useful for checking which
material/ramp a surface used.
Provenance caveat: those RGBs are the source defaults. The shipped 1996
VREND.MNGbuild tweaked the exact floats (the constant0.9is absent from the binary — the bright endpoints were likely rounded toward pure white). The structure (four gradients from a dark saturated colour to ~white, indexed by texel value) is unchanged. Use the table above; nudge the two0.9s to1.0if you want to match the shipped build more closely.
4. How to implement it in your renderer
The recolour must happen at texturing time in the scene pass, on the sampled texel — not in a post/present pass on the final pixel, because you need the raw texel value, not the lit result.
Per textured fragment, when pvision is active:
// u_pv : 1 when IR/predator vision is on
// u_ramp0/1 : the two RGB endpoints of THIS surface's ramp (from ramp_entry)
if (u_pv == 1) {
float value = clamp(dot(texel.rgb, vec3(0.299, 0.587, 0.114)), 0.0, 1.0);
frag_rgb = mix(u_ramp0, u_ramp1, value);
}
// else: normal shading (frag_rgb = texel * lighting, etc.)
Wiring notes:
- Ramp selection = the material's
ramp_entry(0–3). Setu_ramp0/u_ramp1fromPV_RAMP[ramp_entry]per draw call. If your pipeline doesn't yet trackramp_entry, pick a stable ramp per surface (e.g.hash(material_id) & 3) so different objects get different families — this reproduces the multicolor look until you can plumb the real per-material index. - Apply to all textured world geometry — mechs, terrain, buildings, sky.
- Untextured surfaces: use the shaded base colour's luminance as the value (there's no texel), or leave them as-is; they're a minor case.
- HUD / MFDs are separate and must not be ramped — they're a different display path (the cockpit's mono/color panels), not the out-the-window scene.
- Fog is orthogonal. The ramp mode is a texture-colour stage; it does not touch the fog/haze stage. Do not disable fog for pvision unless you get specific evidence otherwise. (We initially guessed "see through fog" and it was wrong — the mode is purely about colour.)
- DAC gamma (the board applies ~
pow(c, 1/1.25)at scan-out) is unrelated to pvision; keep whatever gamma you already do.
Host trigger
If you emulate/interpret the wire, treat the effect action carrying type == -1
as "pvision on" and type == -2 as "off"; type >= 0 is a real explosion.
If you drive rendering directly from game state, hook DPLTogglePVision.
5. What NOT to do (things we tried that are wrong)
- ❌ Grayscale squash of the frame.
- ❌ False-colour heat/green/amber palette applied to scene luminance.
- ❌ Recolouring in a present/post pass using final-pixel luminance.
- ❌ Disabling fog as part of the effect.
- ❌ Expecting the game to send colours — it sends only the on/off flag.
The correct model is the four-ramp, per-material, texel-value mapping above.
6. How to verify against the originals
Everything here is checkable in the VelociRender materials:
- Host trigger:
MUNGA_L4/L4VIDEO.CPPDPLTogglePVision(BT and RP both link the sameDPLRenderer). - Ramp mechanism & modes:
DPL3/VRENDER/PXPL5SUP/EOF.C("lerp texture ramp",dvpx_eoftexmode),DPL3/VRENDER/DNC.C::luminize,DPL3/VRENDER/DPLTYPES.H(dpl_RAMP,dpl_draw_luminance 0x04). - Ramp colours:
DPL3/VRENDER/VR_DRAW.C(the foursetRampEntrycalls at render init). - Firmware (if you want ground truth from the shipped board image): in
RPLIVE/VREND.MNG/BTLIVE/VREND.MNGthe effect handler is reached via the wire-action jump table; it readsmsg[0]astype, tests sign, and branchestype==-1/type==-2. Code loads at0xf0400000, globals low (0x10000). (Reversing that binary needs an i860 disassembler; the source above is easier.)
If your Claude has the shared dump, those paths are under sda4/DPL3/VRENDER/.
The Tesla preservation project (this repo) is the authoritative source for these
findings.