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>
158 lines
8.5 KiB
Markdown
158 lines
8.5 KiB
Markdown
# 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 by `I8602MNG.EXE` from the
|
||
linked COFF. **This is where the renderer logic lives — including pvision.**
|
||
The `.BTL` is 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 (magic `0x014d`) 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 in `EOF.C`.
|
||
- `DNC.C::luminize` exists 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).
|
||
|
||
- [x] **Word format cracked & CONFIRMED** — REG/CTRL layout, control-reg
|
||
numbers, from `CONTROL.O` (see `i860-encoding.md`).
|
||
- [x] **Primary opcode map validated** — `derive860.py` aligns `.S`↔`.O` by
|
||
proving every label offset against the COFF symbol table, then harvests
|
||
`op6→mnemonic`. Clean data matches the published i860 map. Table in
|
||
`i860-encoding.md`.
|
||
- [x] **Decoder built & validated** — `dis860.py`; 98% base-mnemonic match on
|
||
the clean pair (XFLGHTPR 220/224). Residual misses are `.S`/`.O` drift in
|
||
TRISTRIP, not decode errors, plus 4 cosmetic FP graphics-op variant names.
|
||
- [x] **Disassembler proven on real firmware** — `dis860.py --list VREND.MNG
|
||
0xe940` decodes `_velocirender_statistics` as `adds 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 `.text` begins at
|
||
file offset **0x0c** (`0xf0400000` = entry veneer); `.data` follows;
|
||
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 `.O` code against
|
||
the linked `.text`.
|
||
- [ ] **Annotate** — whole-image reference; then walk the effect path
|
||
(`_remote_velocirender` → SFX) to the `type == -1` branch and read what
|
||
it does to `_eof_doFOG` and 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:
|
||
1. **Functional-accurate** — i860 core + FP + the board's memory-mapped
|
||
peripherals (link/DMA, the `_configEMCs` units, DAC, video timing, reversed
|
||
from the firmware + AS860 source). Yields bit-exact images. The big prize.
|
||
2. **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.)
|