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.5 KiB
VelociRender i860 firmware — decompilation reference
Purpose: make this project the authoritative answer for how the Division / VelociRender board firmware behaves (e.g. what IR/"predator" vision actually does). Started 2026-07-14.
Boot architecture (what gets loaded, and how)
The i860 render board is booted transputer-style (boot-from-link) every
cold start — see sda4/DPL3/TESTVR.BAT:
test /tranny vrendmon.btl /i860 vrender.mng /link_A 3 /device 0x150 /video NTSC /n_860s 1
/tranny vrendmon.btl— the bootstrap monitor (~85 KB). Loaded first over the link while the i860 is held in boot-from-link./i860 vrender.mng— the actual renderer application (~380 KB;RPLIVE/VREND.MNG,BTLIVE/VREND.MNG). Produced byI8602MNG.EXEfrom the linked COFF. This is where the renderer logic lives — including pvision. The.BTLis only the loader.
Load is blind, every boot, no version check — the board is reset (\x00RSET
strobe on port 0x160) and the host streams the image as wire actions
code860/data860/bss860/args860/hspcode; a reset board can't answer a
presence/version query, so the push is unconditional. dpl_VERSION's
firmware_*_version fields are read after boot, for reporting only. (Same
model as the sound cards.) ~465 KB/boot; contributes to boot time, worse in
emulation (trapped link I/O + the bridge's fixed 0.5 s idle handshake).
What's here
coff860.py— validated i860 COFF (magic0x014d) reader: file header, sections, symbol table. Names verified against the AS860/PGI source (_SemWait,_nameToAddress,_createBang, …). Usage:python coff860.py "…/VRENDER/*.O".FIRMWARE-SYMBOLS.txt— full symbol/section map of the DPL3 VRENDER objects (32 modules, 633 defined symbols). The function inventory of the renderer, straight from the object files' symbol tables.
The .O objects live in the git-ignored dump (sda4/DPL3/VRENDER/*.O); they
are the DPL3 SDK vintage (1994–95) — same era as the C/asm source we have,
so they predate the pvision feature. Their value: (1) they validate the
toolchain/format, (2) they name the board-side machinery, and (3) they are the
signature library for naming functions in the stripped, newer VREND.MNG.
pvision — where the answer is, and the anchors we have
The game only flips a switch (L4VIDEO.CPP::DPLTogglePVision sends a special
"explosion" effect at the origin, type = -1 ON / -2 OFF — no colour). So
the behaviour is board-side. Operator's working model: grayscale squash +
fog OFF, not a false-colour palette.
Confirmed anchors from the symbol map, both in EOF.O (end-of-frame / DAC):
- Fog machinery lives in board globals:
_eof_doFOG,_eof_FOG_rval/gval/bval,_eof_FOG_near/far,_eof_backR/G/B,_back_colour. A "fog off" in pvision = clearing_eof_doFOG. - Colour/DAC path:
_tblcpy,_configEMCs,_send_em, and the "texture colour map table (8×32-bit)" hacked per-frame inEOF.C. DNC.C::luminizeexists but is a texture-MIP helper (24-bit texel → 6-bit luminance), not the screen grayscale — a near-miss, noted so nobody re-chases it.
The effect dispatch is VR_REMOT.O::_remote_velocirender → the SFX handlers
(SFX.O, _PAZsfx). In our source vintage PAZsfx only knows explosion codes
0/1/2 (no −1/−2), confirming pvision is newer code — i.e. only in
VREND.MNG, which has no symbols.
Progress: the i860 disassembler (scope = FULL annotated, operator-chosen)
No objdump/Ghidra i860 support exists, so we build the decoder ourselves,
validated against the AS860 .S↔.O pairs (exact ground truth).
- Word format cracked & CONFIRMED — REG/CTRL layout, control-reg
numbers, from
CONTROL.O(seei860-encoding.md). - Primary opcode map validated —
derive860.pyaligns.S↔.Oby proving every label offset against the COFF symbol table, then harvestsop6→mnemonic. Clean data matches the published i860 map. Table ini860-encoding.md. - Decoder built & validated —
dis860.py; 98% base-mnemonic match on the clean pair (XFLGHTPR 220/224). Residual misses are.S/.Odrift in TRISTRIP, not decode errors, plus 4 cosmetic FP graphics-op variant names. - Disassembler proven on real firmware —
dis860.py --list VREND.MNG 0xe940decodes_velocirender_statisticsasadds 0x1,r0,r29 … bri r1(loads 1, returns) — matching what our virtual board replies for that wire command (vrboard do_statistics → 1). Independent end-to-end confirmation. - [~] Name functions in
VREND.MNG—sigmatch860.py(reloc-invariant, tolerant). Anchors landed (velocirender_statistics, reg_dump, tri_zb_d_s_texm, dpl_init_light…) but the only objects we have are 1994 DPL3/VRENDER vs the 1996.MNG, so C code has drifted and dense naming isn't possible from signatures alone. The stable hand-asm rasterizers/math match; navigate the rest from these anchors + the wire dispatch. - Load format (reversed): header = 3 LE size words at offset 0 (.text=0x39ec0,.data=0x1e940,.bss=0x4b40); raw.textbegins at file offset 0x0c (0xf0400000= entry veneer);.datafollows; a tiny section-name stub (.text/.data/.bss) trails — no symbol table (stripped image, so signatures are required). - Signatures must be relocation-invariant: mask the disp/immediate fields of call/br/ld/st-with-address before matching.Ocode against the linked.text. - Annotate — whole-image reference; then walk the effect path
(
_remote_velocirender→ SFX) to thetype == -1branch and read what it does to_eof_doFOGand the DAC/colour map, settling pvision.
The endgame: preservation, not interpretation
dpl3-revive (the GL bridge) interprets the wire — a modern reconstruction of
what the protocol means. Valuable for a playable pod, but it is our reading of
the hardware. The archival-correct path is to run the original VREND.MNG in
an i860 emulator: the board's own code builds the display lists, runs the real
SCANLINE.SS rasterizer, programs the DAC, and writes a framebuffer we capture
— bit-exact to the hardware. Every fidelity question (pvision, point
sampling, fog, gamma, filtering) then stops being "did we interpret this right?"
and becomes "the board's code did this."
This disassembler is the front half of that emulator (same decode; add execution semantics). Two tiers:
- Functional-accurate — i860 core + FP + the board's memory-mapped
peripherals (link/DMA, the
_configEMCsunits, DAC, video timing, reversed from the firmware + AS860 source). Yields bit-exact images. The big prize. - Cycle-accurate — model the i860 dual-issue pipeline / FP latency / delayed branches. Adds timing fidelity (frame rate, the long-mission render decay). Hardest; last.
Keep the GL bridge as the fast/interactive path; the i860 emulator becomes the high-fidelity reference — and the oracle that says whether the bridge is right.
Live disassembly findings (RPLIVE/VREND.MNG)
The wire-command dispatch table is decoded and mapped (per-action case block
[mov r16,r4; call <handler>; mov r8,r16; br exit]; the dispatcher indexes by
action code). Confirmed against our virtual board:
| action | handler | action | handler | |
|---|---|---|---|---|
| 9 draw_scene | 0xe340 | 16 readpixels | 0xeb78 | |
| 10 draw_scene_complete | 0xe4c0 | 17 hspcode | 0xec80 | |
| 11 list_add | 0xe6c0 | 18 code860 | 0xece0 | |
| 12 list_remove | 0xe780 | 19 data860 | 0xed60 | |
| 13 morph | 0xe810 | 20 bss860 | 0xed90 | |
| 14 version | 0xe888 | 21 args860 | 0x56a0 | |
| 15 statistics | 0xe940 (returns 1 ✓) | 22 set_geom_verts | 0x5710 | |
| 23 set_texmap_texels | 0x5738 |
Higher actions (the effect/psfx family, incl. pvision type -1/-2): handlers at
0xedd8, 0xee14; action-27 case is the default/error path (loads a string ptr,
calls the log util 0x2c330). 0xee14 = the effect handler — prologue reads
[msg+0] into r29 (the effect type), [msg+4] into r30, logs via 0x4408,
then branches (btne @0xee4c → 0xee7c; calls 0xb688, 0x2c330).
NEXT (pvision, definitive): walk 0xee14's branches to the type == -1/-2
case and read what it writes — the board-side fog disable (_eof_doFOG) and any
DAC/colour-map change. That is the ground-truth answer to grayscale-squash+defog
vs a palette. (Anchor utils seen: 0x2c330 = most-called util/log, 0x4408 =
logger, 0xb120/0xb688 = effect helpers.)