pvision solved (texture-value ramp) + i860/hardware reverse-engineering

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>
This commit is contained in:
Cyd
2026-07-14 22:29:53 -05:00
co-authored by Claude Fable 5
parent 5593d57d48
commit 1323397a50
28 changed files with 2436 additions and 27 deletions
+229
View File
@@ -0,0 +1,229 @@
[mission]
adventure=BattleTech
map=arena1
scenario=freeforall
time=night
weather=clear
temperature=27
length=600
[ordinals]
bitmap=Ordinal::BitMap::1
bitmap=Ordinal::BitMap::2
bitmap=Ordinal::BitMap::3
bitmap=Ordinal::BitMap::4
[Ordinal::BitMap::1]
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=000038000003C000001FF00000000F00
bitmap=0000F8000003C000003FF80000000F00
bitmap=0001F8000003C00000707C0000000F00
bitmap=0001F8000003C00000603C0000000F00
bitmap=00007801FC0FF00000003C3DF807FF00
bitmap=00007803FE0FF00000003C3FFC0FFF00
bitmap=00007803C703C00000003C3E3C1F0F00
bitmap=000078078303C0000000783C1E1E0F00
bitmap=000078078003C0000000783C1E1E0F00
bitmap=00007807C003C0000000F03C1E1E0F00
bitmap=00007807F003C0000001E03C1E1E0F00
bitmap=00007803FC03C0000003C03C1E1E0F00
bitmap=00007801FE03C0000007803C1E1E0F00
bitmap=000078007F03C000000F003C1E1E0F00
bitmap=000078001F03C000001E003C1E1E0F00
bitmap=000078000F03C000003C003C1E1E0F00
bitmap=000078060F03C0000078003C1E1E0F00
bitmap=000078071E03E0000078003C1E1F1F00
bitmap=00007803FE01F000007FFC3C1E0FFF00
bitmap=00007801FC00F000007FFC3C1E07EF00
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
x=128
y=32
width=8
[Ordinal::BitMap::2]
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=001FFF0000003C0000003C03C0780000
bitmap=001FFF0000003C0000007C03C0780000
bitmap=00000E0000003C000000FC03C0780000
bitmap=00003C0000003C000001BC03C0780000
bitmap=0000700F3E1FFC000003BC0FF07BF000
bitmap=0001E00F7E3FFC0000073C0FF07FF800
bitmap=0003800FFE7C3C0000063C03C07C7800
bitmap=0007F80FFE783C00000C3C03C0783C00
bitmap=0007FE0F80783C0000183C03C0783C00
bitmap=00001E0F00783C0000383C03C0783C00
bitmap=00000F0F00783C0000703C03C0783C00
bitmap=00000F0F00783C00007FFF03C0783C00
bitmap=00000F0F00783C00007FFF03C0783C00
bitmap=00000F0F00783C0000003C03C0783C00
bitmap=00000F0F00783C0000003C03C0783C00
bitmap=00000F0F00783C0000003C03C0783C00
bitmap=00180F0F00783C0000003C03C0783C00
bitmap=001C1F0F007C7C0000003C03E0783C00
bitmap=000FFE0F003FFC0000003C01F0783C00
bitmap=0007FC0F001FBC0000003C00F0783C00
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
x=128
y=32
width=8
[Ordinal::BitMap::3]
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=001FFF03C07800000000FC03C0780000
bitmap=001FFF03C07800000003FC03C0780000
bitmap=001E0003C07800000007C003C0780000
bitmap=001E0003C0780000000F0003C0780000
bitmap=001E000FF07BF000000F000FF07BF000
bitmap=001E000FF07FF800001E000FF07FF800
bitmap=001E0003C07C7800001E0003C07C7800
bitmap=001FFC03C0783C00001EFC03C0783C00
bitmap=001FFE03C0783C00001FFE03C0783C00
bitmap=00001F03C0783C00001F1F03C0783C00
bitmap=00000F03C0783C00001E0F03C0783C00
bitmap=00000F03C0783C00001E0F03C0783C00
bitmap=00000F03C0783C00001E0F03C0783C00
bitmap=00000F03C0783C00001E0F03C0783C00
bitmap=00000F03C0783C00001E0F03C0783C00
bitmap=00000F03C0783C00001E0F03C0783C00
bitmap=00180F03C0783C00001E0F03C0783C00
bitmap=001C1F03E0783C00001F1F03E0783C00
bitmap=000FFE01F0783C00000FFE01F0783C00
bitmap=0007FC00F0783C000007FC00F0783C00
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
x=128
y=32
width=8
[Ordinal::BitMap::4]
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=001FFF03C07800000007FC03C0780000
bitmap=001FFF03C0780000000FFE03C0780000
bitmap=00000F03C0780000001F1F03C0780000
bitmap=00000F03C0780000001E0F03C0780000
bitmap=00000F0FF07BF000001E0F0FF07BF000
bitmap=00001F0FF07FF800001E0F0FF07FF800
bitmap=00001E03C07C7800001E0F03C07C7800
bitmap=00003E03C0783C00001E0F03C0783C00
bitmap=00003C03C0783C00000F1E03C0783C00
bitmap=00003C03C0783C000007FC03C0783C00
bitmap=00007803C0783C000007FC03C0783C00
bitmap=00007803C0783C00000F1E03C0783C00
bitmap=00007803C0783C00001E0F03C0783C00
bitmap=0000F003C0783C00001E0F03C0783C00
bitmap=0000F003C0783C00001E0F03C0783C00
bitmap=0000F003C0783C00001E0F03C0783C00
bitmap=0000F003C0783C00001E0F03C0783C00
bitmap=0000F003E0783C00000F1E03E0783C00
bitmap=0000F001F0783C00000FFE01F0783C00
bitmap=0000F000F0783C000007FC00F0783C00
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
x=128
y=32
width=8
[pilots]
pilot=200.0.0.113
[200.0.0.113]
hostType=0
advancedDamage=1
loadzones=1
name=cyd
bitmapindex=1
experience=veteran
badge=VGL
patch=Yellow
role=Role::Default
dropzone=one
vehicle=madcat
vehicleValue=0
color=White
[largebitmap]
bitmap=BitMap::Large::cyd
[BitMap::Large::cyd]
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=000000000000000000003C0000000000
bitmap=000000000000000000003C0000000000
bitmap=000000000000000000003C0000000000
bitmap=000000000000000000003C0000000000
bitmap=000000000000FCF00E0FBC0000000000
bitmap=000000000003FE781C1FFC0000000000
bitmap=000000000007FE781C3C7C0000000000
bitmap=00000000000F82783C3C3C0000000000
bitmap=00000000001F003C38783C0000000000
bitmap=00000000001E003C78783C0000000000
bitmap=00000000001E003C70783C0000000000
bitmap=00000000001E003E70783C0000000000
bitmap=00000000001E001EE0783C0000000000
bitmap=00000000001E001EE0783C0000000000
bitmap=00000000001F001FE0783C0000000000
bitmap=00000000000F020FC07C7C0000000000
bitmap=000000000007FE0FC03FFC0000000000
bitmap=000000000003FE0F803FFC0000000000
bitmap=000000000000FC07800F3C0000000000
bitmap=00000000000000070000000000000000
bitmap=00000000000000070000000000000000
bitmap=000000000000000F0000000000000000
bitmap=000000000000000E0000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
x=128
y=32
width=8
[smallbitmap]
bitmap=BitMap::Small::cyd
[BitMap::Small::cyd]
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000600000
bitmap=00000000006000000000000000600000
bitmap=000001EC33E000000000030666600000
bitmap=000003066660000000000303C6600000
bitmap=00000303C66000000000030186600000
bitmap=000001E183E000000000000300000000
bitmap=00000003000000000000000000000000
x=64
y=16
width=4
[Role::Default]
model=dfltrole
[Role::NoReturn]
model=noretun
+44 -27
View File
@@ -103,6 +103,9 @@ uniform float u_opacity; // DITHER n -> screen-door
uniform vec3 u_fogcol;
uniform int u_fog_on;
uniform vec2 u_fogrange; // near, far
uniform int u_pv; // IR / predator vision: texture-value ramp mode
uniform vec3 u_pvcol0; // ramp dark endpoint (low texel value)
uniform vec3 u_pvcol1; // ramp bright endpoint (high texel value)
in vec3 v_base;
in vec2 v_uv;
in float v_fog;
@@ -115,14 +118,20 @@ void main() {
if (bay > u_opacity) discard;
}
vec3 rgb;
vec3 t = v_base; // texel value; defaults to shaded base if untextured
if (u_has_tex == 1) {
vec3 t = texture(u_tex, v_uv + u_uvoff).rgb;
t = texture(u_tex, v_uv + u_uvoff).rgb;
if (u_alpha_cut == 1 && (t.r + t.g + t.b) <= 24.0 / 255.0)
discard; // near-black texel = transparent
rgb = t * (v_base * 1.275); // software: texel * (base/200), base 0..255
} else {
rgb = v_base;
}
if (u_pv == 1) {
// Division texture-value ramp: texel brightness -> colour gradient
float val = clamp(dot(t, vec3(0.299, 0.587, 0.114)), 0.0, 1.0);
rgb = mix(u_pvcol0, u_pvcol1, val);
}
// alpha only takes effect in the deferred blended pass (opaque pass
// renders with BLEND disabled)
float fogf = (u_fog_on == 1)
@@ -189,36 +198,36 @@ void main() { // fullscreen triangle from gl_VertexID
PRESENT_FS = """
#version 330
uniform sampler2D u_tex;
uniform int u_pv; // IR / predator-vision on (board.pvision)
uniform int u_pvmode; // 0 mono, 1 green, 2 amber, 3 heat-ramp
uniform int u_pv; // retained (set 0); pvision is now in the mesh pass
uniform int u_pvmode;
in vec2 v_uv;
out vec4 f_color;
vec3 heat(float t) { // black -> red -> yellow -> white heat ramp
vec3 c = mix(vec3(0.0), vec3(1.0, 0.0, 0.0), clamp(t / 0.40, 0.0, 1.0));
c = mix(c, vec3(1.0, 1.0, 0.0), clamp((t - 0.40) / 0.40, 0.0, 1.0));
c = mix(c, vec3(1.0), clamp((t - 0.80) / 0.20, 0.0, 1.0));
return c;
}
void main() {
vec3 c = texture(u_tex, v_uv).rgb;
if (u_pv == 1) { // thermal false-colour from scene luminance
float lum = dot(c, vec3(0.299, 0.587, 0.114));
if (u_pvmode == 0) c = vec3(lum);
else if (u_pvmode == 1) c = vec3(lum * 0.15, lum, lum * 0.15);
else if (u_pvmode == 2) c = vec3(lum, lum * 0.72, lum * 0.18);
else c = heat(lum);
}
f_color = vec4(pow(c, vec3(1.0 / 1.25)), 1.0); // Division DAC gamma
}
"""
# IR / "predator vision" thermal view. The game toggles it via dpl_Effect on
# action 0x1b (mode -1 ON / -2 OFF) -> board.pvision; VRVIEW_PVISION=1 or the
# bridge 'v' key forces it on for evaluation without pressing the cockpit IR
# button. Palette: VRVIEW_PVISION_PALETTE = mono|green|amber|heat (default
# heat/Predator-style, operator's interim pick 2026-07-14; exact original TBD).
# IR / "predator vision". The game toggles it via dpl_Effect on action 0x1b
# (mode -1 ON / -2 OFF) -> board.pvision; VRVIEW_PVISION=1 or the bridge 'v' key
# forces it on for evaluation without the cockpit IR button.
#
# ANSWER (firmware-decompiled + confirmed by the original team, 2026-07-14): it
# is the Division board's TEXTURE-VALUE RAMP mode -- a "check your texture maps"
# diagnostic the devs hijacked. Each textured surface renders as
# lerp(color0, color1, luminance(texel)): the texel's *value* drives a colour
# ramp. Multicolour because each material picks one of FOUR ramps (VR_DRAW.C
# 704-707). ramp_entry isn't plumbed through vrboard yet, so we assign a ramp
# per geometry (stable hash) to reproduce the multicolour look; swap to the real
# per-material index when material-ramp parsing lands. See
# emulator/firmware-decomp/README.md.
PVISION = [os.environ.get('VRVIEW_PVISION', '0') != '0']
_PV_MODES = {'mono': 0, 'green': 1, 'amber': 2, 'heat': 3}
# color0 = dark endpoint (low texel value), color1 = bright endpoint (high).
PV_RAMP0 = ((0.0, 0.0, 0.0), (0.3, 0.0, 0.0), (0.0, 0.5, 0.0), (0.0, 0.0, 0.4))
PV_RAMP1 = ((1.0, 1.0, 1.0), (1.0, 1.0, 0.9), (1.0, 1.0, 1.0), (0.9, 0.9, 1.0))
# VRVIEW_PVISION_RAMP=0..3 forces a single ramp for all surfaces (A/B testing);
# unset = per-geometry ramp (multicolour, the authentic look).
_PV_FORCE = os.environ.get('VRVIEW_PVISION_RAMP')
HUD_VS = """
@@ -578,11 +587,10 @@ class GLRenderer(vrview.Renderer):
ctx.disable(mgl.DEPTH_TEST)
self._fbo_tex.use(0)
self._set(self._present_prog, 'u_tex', 0)
self._set(self._present_prog, 'u_pv',
1 if (PVISION[0] or getattr(board, 'pvision', False)) else 0)
self._set(self._present_prog, 'u_pvmode',
_PV_MODES.get(os.environ.get('VRVIEW_PVISION_PALETTE',
'heat'), 3))
# pvision moved to the mesh pass (texture-value ramp); present is a
# plain gamma present now.
self._set(self._present_prog, 'u_pv', 0)
self._set(self._present_prog, 'u_pvmode', 0)
self._present_vao.render(mgl.TRIANGLES, vertices=3)
pg.display.flip()
cam = self.cam_matrix(board)
@@ -661,6 +669,15 @@ class GLRenderer(vrview.Renderer):
self._set(prog, 'u_has_nrm', 1 if mesh['nrm'] is not None else 0)
self._set(prog, 'u_has_col', 1 if mesh['col'] is not None else 0)
# IR / predator vision: texture-value ramp. ramp_entry isn't plumbed, so
# pick a stable ramp per geometry (or a forced one) for the multicolour look.
pv_on = 1 if (PVISION[0] or getattr(board, 'pvision', False)) else 0
self._set(prog, 'u_pv', pv_on)
if pv_on:
ri = int(_PV_FORCE) & 3 if _PV_FORCE is not None else hash(gh) & 3
self._set(prog, 'u_pvcol0', PV_RAMP0[ri])
self._set(prog, 'u_pvcol1', PV_RAMP1[ri])
tex = c.geom_tex.get(gh)
has_tex = tex is not None and mesh['uv'] is not None
self._set(prog, 'u_has_tex', 1 if has_tex else 0)
@@ -0,0 +1,825 @@
=== CONTROL.O magic=0x014d nscns=3 nsyms=8 ===
sect .text vaddr=0x00000000 size=0x000010 reloc=0
sect .data vaddr=0x00000010 size=0x000000 reloc=0
sect .bss vaddr=0x00000010 size=0x000000 reloc=0
-- 2 defined EXT/STAT symbols:
0x00000000 EXT _getFsr
0x00000008 EXT _setFsr
=== OPTFLOAT.O magic=0x014d nscns=3 nsyms=51 ===
sect .text vaddr=0x00000000 size=0x000d20 reloc=24
sect .data vaddr=0x00000d20 size=0x000060 reloc=0
sect .bss vaddr=0x00000d80 size=0x000080 reloc=0
-- 31 defined EXT/STAT symbols:
0x00000000 EXT _fn_bbox_plane
0x00000238 EXT _p_memcpy
0x00000270 EXT _matrix_copy
0x000002d8 EXT _fn_cache_mtx
0x00000320 EXT _fn_cache_opt_mtx
0x00000358 EXT _fn_cache_mtxd
0x00000380 EXT _fn_cache_opt_mtxd
0x000003a8 EXT _fn_xform
0x00000458 EXT _fn_optxform
0x000004c8 EXT _fn_xform_bound
0x00000540 EXT _fn_xform_boundtroid
0x000005c0 EXT _fn_concatenate
0x00000660 EXT _quick_renorm
0x00000698 EXT _quick_radius
0x000006b8 EXT _quick_root
0x000006c8 EXT _quick_recip
0x000006d0 EXT _copy_spoint
0x00000700 EXT _smintersect
0x000007d8 EXT _dnc_Wait_count
0x000007dc EXT dnc_Wait_count1
0x00000818 EXT _bla
0x00000824 EXT bla_bla
0x00000838 EXT _xform_and_project_fn
0x00000b48 EXT _solve_fn
0x00000c30 EXT _wait_locked_nonzero
0x00000c34 EXT wait_locked_nonzero1
0x00000c70 EXT _i860_locked_read
0x00000c88 EXT _manual_flush
0x00000cdc EXT RESTORE_DIRBASE
0x00000d00 EXT D_FLUSH
0x00000d0c EXT D_FLUSH_LOOP
=== TRISTRIP.O magic=0x014d nscns=3 nsyms=12 ===
sect .text vaddr=0x00000000 size=0x000590 reloc=2
sect .data vaddr=0x00000590 size=0x000000 reloc=0
sect .bss vaddr=0x00000590 size=0x000000 reloc=0
-- 4 defined EXT/STAT symbols:
0x00000000 EXT _reg_dump
0x00000118 EXT _trapezoid
0x000001b8 EXT _trapezoom
0x00000230 EXT _astristrip
=== XFLGHTPR.O magic=0x014d nscns=3 nsyms=31 ===
sect .text vaddr=0x00000000 size=0x0003d0 reloc=24
sect .data vaddr=0x000003d0 size=0x000110 reloc=0
sect .bss vaddr=0x000004e0 size=0x000000 reloc=0
-- 8 defined EXT/STAT symbols:
0x00000000 EXT _fn_xform_light_project
0x00000000 EXT _ambientR
0x00000004 EXT _ambientG
0x00000008 EXT _ambientB
0x00000010 EXT _quad_f_matrix
0x00000090 EXT _quad_b_matrix
0x00000360 EXT _fn_inv
0x00000398 EXT _sub_pxl_correct
=== ZBUF32.O magic=0x014d nscns=3 nsyms=24 ===
sect .text vaddr=0x00000000 size=0x0007b0 reloc=0
sect .data vaddr=0x000007b0 size=0x000080 reloc=0
sect .bss vaddr=0x00000830 size=0x000000 reloc=0
-- 17 defined EXT/STAT symbols:
0x00000000 EXT slope_table
0x00000000 EXT _scaninit
0x00000040 EXT _texinit
0x000000a8 EXT _ascaninit
0x000000e8 EXT _atexinit
0x00000128 EXT _texinit256
0x00000178 EXT _scanline
0x000002d0 EXT _flatline
0x000003e0 EXT _ftexline
0x000005a0 EXT _ftexline256
0x00000710 EXT _setpixelsize
0x0000072c EXT _getpixelsize
0x0000073c EXT _setdirectmode
0x00000754 EXT _clear_store
0x00000774 STAT init_loop
0x00000784 EXT _thrash
0x00000794 STAT thrash_loop
=== BILLBOAR.O magic=0x014d nscns=3 nsyms=22 ===
sect .text vaddr=0x00000000 size=0x0001d0 reloc=31
sect .data vaddr=0x000001d0 size=0x000020 reloc=0
sect .bss vaddr=0x000001f0 size=0x000000 reloc=0
-- 4 defined EXT/STAT symbols:
0x00000000 STAT __atan2deg
0x0000001c EXT _billboard_rotY
0x00000090 EXT _billboardize
0x00000120 EXT _billboardstuff
=== DNC.O magic=0x014d nscns=3 nsyms=60 ===
sect .text vaddr=0x00000000 size=0x001cd0 reloc=494
sect .data vaddr=0x00001cd0 size=0x000010 reloc=0
sect .bss vaddr=0x00001ce0 size=0x000020 reloc=0
-- 36 defined EXT/STAT symbols:
0x00000000 EXT _dN_send
0x00000000 EXT _uncached_input_block
0x00000004 EXT _uncached_output_block
0x00000008 EXT _uncached_general_block
0x0000000c STAT _peek_addr
0x000000f8 EXT _dN_receive
0x00000228 EXT _dN_mynode
0x000002d8 EXT _dN_nodes
0x00000388 EXT _dN_poll
0x00000438 EXT _dN_timer
0x000004f0 EXT _dN_wait
0x00000580 EXT _dN_wait_ack
0x000005c8 EXT _dN_ld_run
0x00000668 EXT _dN_ld_out_noflush
0x00000740 EXT _dN_ld_out
0x00000818 EXT _dN_ld_ccb_out
0x00000920 EXT _dN_ld_ccb_in
0x000009f8 EXT _dN_ld_in
0x00000ad0 EXT _dN_ld_out_2D
0x00000bc8 EXT _dN_ld_in_2D
0x00000cc0 EXT _dN_ld_stat
0x00000d70 STAT _stat
0x00000da0 EXT _spin
0x00000df0 EXT _dN_pxpl5_readpixels
0x00000ee8 EXT _dN_pxpl5_init
0x00000fc0 EXT _dN_pxpl5_dma_done
0x00001010 EXT _dN_pxpl5_dma
0x00001150 EXT _dN_pxpl5_texels
0x000012f0 EXT _dN_pxpl5_texture24
0x000013e8 EXT _dN_pxpl5_fx_texels
0x00001588 EXT _dN_pxpl5_fx_texture24
0x00001658 STAT _pxpl5_texels_8
0x00001750 STAT _luminize
0x000017e0 EXT _dN_pxpl5_texture8
0x00001ab8 EXT _dN_pxpl5_microwords
0x00001ba8 EXT _dN_pxpl5_microcode
=== I860SEM.O magic=0x014d nscns=3 nsyms=18 ===
sect .text vaddr=0x00000000 size=0x0001c0 reloc=15
sect .data vaddr=0x000001c0 size=0x000030 reloc=0
sect .bss vaddr=0x000001f0 size=0x000000 reloc=0
-- 6 defined EXT/STAT symbols:
0x00000000 EXT _SemWait
0x00000020 STAT ___freeSem
0x00000024 STAT ___sems_free
0x000000d0 EXT _SemSignal
0x000000e8 EXT _InitSem
0x00000118 EXT _nextSem
=== MATERIAL.O magic=0x014d nscns=3 nsyms=35 ===
sect .text vaddr=0x00000000 size=0x0004c0 reloc=85
sect .data vaddr=0x000004c0 size=0x000070 reloc=0
sect .bss vaddr=0x00000530 size=0x000000 reloc=0
-- 11 defined EXT/STAT symbols:
0x00000000 EXT _add_material
0x00000040 EXT _create_material
0x00000054 STAT _material_head
0x00000058 STAT _material_tail
0x0000005c STAT _texture_head
0x00000060 STAT _texture_tail
0x00000064 EXT _scene_resolved
0x000000e8 EXT _delete_material
0x00000180 EXT _add_texture
0x000001c0 EXT _trace_materials
0x00000278 EXT _resolve_scene
=== NAMES.O magic=0x014d nscns=3 nsyms=34 ===
sect .text vaddr=0x00000000 size=0x000570 reloc=85
sect .data vaddr=0x00000570 size=0x000110 reloc=0
sect .bss vaddr=0x00000680 size=0x000800 reloc=0
-- 10 defined EXT/STAT symbols:
0x00000000 EXT _checkNull
0x00000048 EXT _initNames
0x00000090 STAT _addName
0x00000100 STAT _init
0x00000104 EXT _allocatedNames
0x000001a0 EXT _newName
0x00000208 EXT _deleteName
0x00000350 EXT _nameToAddress
0x00000468 EXT _addressToName
0x00000540 EXT _check_name
=== PAZPL5.O magic=0x014d nscns=3 nsyms=30 ===
sect .text vaddr=0x00000000 size=0x0001a0 reloc=70
sect .data vaddr=0x000001a0 size=0x0000b0 reloc=0
sect .bss vaddr=0x00000250 size=0x000000 reloc=0
-- 1 defined EXT/STAT symbols:
0x00000000 EXT _main
=== PAZREAD.O magic=0x014d nscns=3 nsyms=86 ===
sect .text vaddr=0x00000000 size=0x001a90 reloc=369
sect .data vaddr=0x00001a90 size=0x000330 reloc=3
sect .bss vaddr=0x00001dc0 size=0x0000e0 reloc=0
-- 18 defined EXT/STAT symbols:
0x00000000 STAT _mexico_68
0x00000108 STAT _set_surface
0x00000160 STAT _planar
0x00000270 EXT _matrix_dump
0x00000314 EXT _open_fn
0x00000318 EXT _gets_fn
0x0000031c EXT _close_fn
0x00000320 STAT _patches
0x00000324 STAT _tesselations
0x00000328 STAT _triangles
0x0000032c STAT _points
0x00000330 EXT _point_dump
0x00000380 EXT _bound_dump
0x000003e8 EXT _point_dump4
0x00000440 STAT _fix_vstrip_texture_coords
0x00000450 EXT __consolidate
0x000005a0 EXT _reconnectLODs
0x00000798 EXT _bin_read
=== PAZSTORE.O magic=0x014d nscns=3 nsyms=67 ===
sect .text vaddr=0x00000000 size=0x0010d0 reloc=94
sect .data vaddr=0x000010d0 size=0x000120 reloc=0
sect .bss vaddr=0x000011f0 size=0x000050 reloc=0
-- 33 defined EXT/STAT symbols:
0x00000000 EXT _checkNull
0x00000048 STAT _vertex_max
0x00000098 STAT _vertex_min
0x000000e8 EXT _build_bound
0x000000f8 EXT _left
0x000000fc EXT _right
0x00000100 EXT _render_strip
0x00000104 EXT _output_device
0x00000108 EXT _link_A
0x0000010c EXT _link_B
0x00000110 EXT _raster
0x00000114 EXT _flip
0x00000258 EXT _bound_patch
0x00000410 STAT _sphere_max
0x00000470 STAT _sphere_min
0x000004d0 EXT _bound_spatch
0x00000680 EXT _reboundLOD
0x00000808 EXT __BoundLOD
0x000009a0 EXT _reboundObject
0x00000ae0 EXT __BoundObject
0x00000c38 EXT __InitVertex
0x00000c78 EXT __InitVstrip
0x00000cc0 EXT __InitPatch
0x00000d10 EXT __InitObject
0x00000d58 EXT __InitLOD
0x00000da0 EXT __InitLight
0x00000de0 EXT __InitView
0x00000e58 EXT __InitInstance
0x00000ee0 EXT __InitScene
0x00000f28 EXT __LinkVertices
0x00000f68 EXT _findPAZitem
0x00000fa0 EXT _addPAZitem
0x00001038 EXT _removePAZitem
=== EOF.O magic=0x014d nscns=3 nsyms=64 ===
sect .text vaddr=0x00000000 size=0x003ac0 reloc=149
sect .data vaddr=0x00003ac0 size=0x0000f0 reloc=1
sect .bss vaddr=0x00003bb0 size=0x0000c0 reloc=0
-- 35 defined EXT/STAT symbols:
0x00000000 EXT _tblcpy
0x00000050 EXT _configEMCs_hi
0x000000ac EXT _pxpl5_ticks
0x000000b0 EXT _random_index
0x000000b4 EXT _texture_table_iptr
0x000000b8 STAT _send_pass
0x000000bc EXT _eof_backR
0x000000c0 EXT _eof_backG
0x000000c4 EXT _eof_backB
0x000000c8 EXT _eof_farZ
0x000000cc EXT _eof_doFOG
0x000000d0 EXT _eof_FOG_rval
0x000000d4 EXT _eof_FOG_gval
0x000000d8 EXT _eof_FOG_bval
0x000000dc EXT _eof_FOG_near
0x000000e0 EXT _eof_FOG_far
0x000000e4 EXT _eof_Z_near
0x000000e8 EXT _eof_Z_far
0x00000250 EXT _configEMCs
0x00000378 EXT _init_screenbin
0x000007a0 STAT _linterp
0x00000b78 STAT _multuu_unc
0x00001070 STAT _send_em
0x00001190 STAT _texdivide
0x00001360 EXT _perspective_divides
0x00001420 EXT _transpective_divides
0x000014e0 EXT _explode4
0x000015a8 STAT _end_of_texture
0x00002340 STAT _short_end_of_frame
0x00002e50 EXT _short_end_of_frame_DMA
0x00003048 EXT _end_of_textr_DMA
0x000030f8 STAT _end_of_transptex
0x000033b0 STAT _end_of_transframe
0x00003840 EXT _end_of_transframe_DMA
0x00003a10 EXT _end_of_transp_DMA
=== PSTART.O magic=0x014d nscns=3 nsyms=13 ===
sect .text vaddr=0x00000000 size=0x0000c0 reloc=7
sect .data vaddr=0x000000c0 size=0x000000 reloc=0
sect .bss vaddr=0x000000c0 size=0x000000 reloc=0
-- 2 defined EXT/STAT symbols:
0x00000000 EXT _start
0x00000000 EXT pstart
=== PXPL5OPT.O magic=0x014d nscns=3 nsyms=89 ===
sect .text vaddr=0x00000000 size=0x0010a0 reloc=11
sect .data vaddr=0x000010a0 size=0x000160 reloc=0
sect .bss vaddr=0x00001200 size=0x000000 reloc=0
-- 62 defined EXT/STAT symbols:
0x00000000 EXT _preplanarize_fn
0x0000002c EXT _fx2gtfx1
0x00000038 EXT _fx1gtfx2
0x00000040 EXT _checkx3
0x00000054 EXT _fx3min
0x00000060 EXT _fx3max
0x00000064 EXT _minimaxy
0x00000068 EXT _minimaxydb
0x0000006c EXT _fy2gtfy1
0x00000078 EXT _fy1gtfy2
0x00000080 EXT _checky3
0x0000009c EXT _fy3min
0x000000a8 EXT _fy3max
0x000000ac EXT _endminimax
0x000000b0 EXT _endminimaxdb
0x000000f8 EXT _preplanarize_fn_p
0x0000011c EXT _fx2gtfx1_p
0x00000128 EXT _fx1gtfx2_p
0x00000130 EXT _checkx3_p
0x00000144 EXT _fx3min_p
0x00000150 EXT _fx3max_p
0x00000154 EXT _minimaxy_p
0x00000158 EXT _minimaxydb_p
0x00000160 EXT _fy2gtfy1_p
0x0000016c EXT _fy1gtfy2_p
0x00000174 EXT _checky3_p
0x00000194 EXT _fy3min_p
0x000001a0 EXT _fy3max_p
0x000001a4 EXT _endminimax_p
0x000001a8 EXT _endminimaxdb_p
0x000001f0 EXT _edgize_tri_fn
0x00000278 EXT _edgize_tri_fn_p
0x00000318 EXT _edgize_quad_fn
0x00000320 EXT _zbuffer_fn
0x00000348 EXT _zbuffer_fn_p
0x00000370 EXT _planarize_fn
0x00000420 EXT _planarize_fn_p
0x000004e8 EXT _binitize_fn
0x000004f0 EXT _safe_binitize_fn
0x00000560 EXT _y_loop
0x00000564 EXT _y_loopdb
0x00000568 EXT _x_loop
0x0000056c EXT _x_loopdb
0x0000057c EXT _bin_full
0x0000062c EXT _bin_not_full
0x00000640 EXT _bump_x
0x00000650 EXT _bump_y
0x00000664 STAT _exit_binitize
0x00000670 EXT _tex_scalefac
0x00000698 EXT i2_gt_i1
0x000006ac EXT igotmax
0x000006c0 EXT _trunc_test
0x00000750 EXT _tri_d
0x00000860 EXT _tri_zb
0x00000990 EXT _tri_zb_d
0x00000ab8 EXT _tri_zb_d_s
0x00000bf0 EXT _tri_zb_d_s_tex
0x00000d58 EXT _tri_zb_f_tex
0x00000e80 EXT _tri_zb_d_s_texm
0x00000e88 EXT _getFsr
0x00000e90 EXT _setFsr
0x00000e98 EXT _reg_dump
=== PXPL5SUP.O magic=0x014d nscns=3 nsyms=99 ===
sect .text vaddr=0x00000000 size=0x0014a0 reloc=276
sect .data vaddr=0x000014a0 size=0x000450 reloc=16
sect .bss vaddr=0x000018f0 size=0x000000 reloc=0
-- 34 defined EXT/STAT symbols:
0x00000000 STAT _devirtualize
0x00000040 STAT _newPages
0x00000098 EXT _newBytes
0x000000e0 EXT _preplanarize
0x000001c8 EXT _planarize
0x00000378 EXT _tex_fixz
0x000003d8 STAT _lastGrab
0x000003dc EXT _free_binchunks
0x000003e0 EXT _screen0bins
0x000003e4 EXT _screen1bins
0x000003e8 EXT _screenbins
0x000003ec EXT _trans_screen0bins
0x000003f0 EXT _trans_screen1bins
0x000003f4 EXT _trans_screenbins
0x000003f8 EXT _DMAscreen
0x000003fc EXT _writeScreen
0x00000400 EXT _last_coeffchunk
0x00000404 EXT _last_coeffchunk0
0x00000408 EXT _last_coeffchunk1
0x0000040c EXT _back_offs
0x000004e0 EXT _edgeize
0x00000530 STAT _grab_binchunks
0x000005e8 EXT _next_binchunk
0x00000648 EXT _next_coeffchunk
0x00000710 STAT _initbins
0x00000868 EXT _create_screenbins
0x00000b38 EXT _liberate_screenbins
0x00000c60 EXT _binitize
0x00000eb0 EXT _safe_binitize
0x00001088 EXT _trace_regs
0x000011b0 STAT _macro_name
0x00001270 EXT _dump_bins
0x00001428 EXT _tracepixelmap
0x00001430 EXT _binitize_fail
=== PXPL5TRI.O magic=0x014d nscns=3 nsyms=16 ===
sect .text vaddr=0x00000000 size=0x000140 reloc=15
sect .data vaddr=0x00000140 size=0x000000 reloc=0
sect .bss vaddr=0x00000140 size=0x000000 reloc=0
-- 3 defined EXT/STAT symbols:
0x00000000 EXT _tri_zb_d_s
0x00000080 EXT _tri_zb_d_s_tex
0x00000128 EXT _tri_zb_d_s_texm
=== PXPL5TST.O magic=0x014d nscns=3 nsyms=66 ===
sect .text vaddr=0x00000000 size=0x000620 reloc=188
sect .data vaddr=0x00000620 size=0x0002a0 reloc=0
sect .bss vaddr=0x000008c0 size=0x000000 reloc=0
-- 2 defined EXT/STAT symbols:
0x00000000 STAT _set_vertex
0x00000048 EXT _main
=== PXPL5YUC.O magic=0x014d nscns=3 nsyms=34 ===
sect .text vaddr=0x00000000 size=0x000a60 reloc=60
sect .data vaddr=0x00000a60 size=0x0000d0 reloc=0
sect .bss vaddr=0x00000b30 size=0x000040 reloc=0
-- 16 defined EXT/STAT symbols:
0x00000000 EXT _edgeize
0x00000050 EXT _preplanarize
0x000000b8 EXT _free_binchunks
0x000000bc EXT _screen0bins
0x000000c0 EXT _screen1bins
0x000000c4 EXT _screenbins
0x000000c8 EXT _DMAscreen
0x000000cc EXT _writeScreen
0x000000e8 EXT _planarize
0x000001f0 EXT _planarizip
0x00000310 EXT _grab_binchunks
0x000003b0 EXT _next_binchunk
0x00000408 EXT _create_screenbins
0x000005c8 EXT _liberate_screenbins
0x000006e0 EXT _binitize
0x000008b0 EXT _safe_binitize
=== TEST.O magic=0x014d nscns=3 nsyms=15 ===
sect .text vaddr=0x00000000 size=0x000100 reloc=15
sect .data vaddr=0x00000100 size=0x000010 reloc=0
sect .bss vaddr=0x00000110 size=0x000020 reloc=0
-- 5 defined EXT/STAT symbols:
0x00000000 EXT _intintint
0x00000008 STAT _statint
0x0000000c STAT _statfloat
0x00000040 EXT _floatfloatfloat
0x00000078 EXT _floatintfloat
=== WALLTIME.O magic=0x014d nscns=3 nsyms=14 ===
sect .text vaddr=0x00000000 size=0x0000a0 reloc=16
sect .data vaddr=0x000000a0 size=0x000020 reloc=0
sect .bss vaddr=0x000000c0 size=0x000000 reloc=0
-- 2 defined EXT/STAT symbols:
0x00000000 EXT _second_
0x00000018 STAT _lastTime
=== PXPL5TRI.O magic=0x014d nscns=3 nsyms=99 ===
sect .text vaddr=0x00000000 size=0x001570 reloc=66
sect .data vaddr=0x00001570 size=0x0000d0 reloc=0
sect .bss vaddr=0x00001640 size=0x000000 reloc=0
-- 58 defined EXT/STAT symbols:
0x00000000 EXT edgize_fn
0x00000000 EXT _Cdelta_u
0x00000004 EXT _Cdelta_v
0x00000008 EXT _Cmax_x
0x0000000c EXT _Cmax_y
0x00000030 STAT _.Cturn_z_to_tex
0x00000034 STAT _.Cturn_z_to_tex_by_4
0x00000038 STAT _.Cturn_z_to_tex_by_8
0x00000080 EXT edgize_poly_fn_entry
0x00000088 EXT edgize_poly_fn
0x000000e0 EXT carry_on_branching
0x00000138 EXT edgize_last_poly_vert
0x00000190 EXT _preplanarize_fn_p
0x000001d0 EXT vertloop
0x000001d4 EXT vertloop_db
0x000001e8 EXT xmax_ok
0x000001f4 EXT xmin_ok
0x000001f4 EXT ycheck
0x00000204 EXT ymax_ok
0x00000210 EXT ymin_ok
0x00000210 EXT zcheck
0x00000228 EXT texscale_ok
0x00000234 EXT zmin_ok
0x00000348 EXT _planarize_fn_p
0x00000410 EXT _texture_rescale
0x00000510 EXT bin_was_full
0x000005c0 EXT _safe_binitize_fn
0x00000648 EXT _y_loop
0x0000064c EXT _y_loopdb
0x00000650 EXT _x_loop
0x00000654 EXT _x_loopdb
0x00000664 EXT _bin_full
0x0000067c EXT _bin_not_full
0x00000688 EXT _bump_x
0x00000698 EXT _bump_y
0x000006ac STAT _exit_binitize
0x000006b8 EXT chunk_fn
0x00000788 EXT _tri_zb_f
0x000007e4 EXT back_000
0x00000814 EXT label000
0x00000900 EXT label001
0x00000968 EXT _tri_zb_rgb
0x000009c4 EXT back_001
0x000009f4 EXT label002
0x00000adc EXT label003
0x00000b40 EXT _tri_zb_f_t
0x00000b9c EXT back_002
0x00000bcc EXT label004
0x00000d90 EXT label005
0x00000df8 EXT _tri_zb_rgb_t
0x00000e54 EXT back_003
0x00000e84 EXT label006
0x00001048 EXT label007
0x000010b0 EXT _tri_zb_rgb_o_t
0x0000110c EXT back_004
0x0000113c EXT label008
0x000012f8 EXT label009
0x00001360 EXT _reg_dump
=== RPAZLIB.O magic=0x014d nscns=3 nsyms=201 ===
sect .text vaddr=0x00000000 size=0x0038b0 reloc=675
sect .data vaddr=0x000038b0 size=0x0003b0 reloc=0
sect .bss vaddr=0x00003c60 size=0x000810 reloc=0
-- 75 defined EXT/STAT symbols:
0x00000000 STAT _reorderViews
0x00000040 STAT _vrtlist
0x00000170 EXT _PAZidMatrix
0x000001a0 EXT _PAZtranslate
0x000001d0 EXT _PAZrotate
0x00000200 EXT _PAZscale
0x00000230 EXT _PAZtranslatePair
0x00000260 EXT _PAZrotatePair
0x00000290 EXT _PAZscalePair
0x000002c0 EXT _PAZconcat
0x00000308 EXT _PAZcreateBinObjects
0x00000318 EXT _PAZcreateBinObject
0x00000370 EXT _PAZdeleteObject
0x00000380 EXT _currentScene
0x00000384 EXT _vrthead
0x00000388 STAT _sect_pixel_req
0x0000038c STAT _sectinst
0x00000390 EXT _total_grab_patch
0x00000394 EXT _total_grab_light
0x00000398 STAT _f_mtl_override
0x0000039c STAT _b_mtl_override
0x000003a0 STAT _f_tex_override
0x000003a4 STAT _b_tex_override
0x000003a8 STAT _inited
0x000003ac STAT _reordered
0x00000580 EXT _PAZreadBound
0x000005c0 EXT _PAZcreateInstance
0x00000638 EXT _PAZdeleteInstance
0x00000778 EXT _PAZreadInstance
0x00000780 EXT _PAZwriteInstance
0x00000788 STAT _distance
0x00000800 STAT _planesect
0x00000970 STAT _box_sect
0x00000d80 STAT _pix_sect_obj
0x00000f78 STAT _pix_sect_inst
0x00001138 EXT _PAZsectPixel
0x00001388 STAT _vect_sect_obj
0x00001518 STAT _vect_sect_inst
0x000016b8 EXT _PAZsectVector
0x00001818 EXT _PAZcreateLight
0x00001890 EXT _PAZdeleteLight
0x00001970 EXT _PAZreadLight
0x00001978 EXT _PAZwriteLight
0x00001980 EXT _PAZinitLight
0x000019a8 EXT _PAZcreateView
0x00001a20 EXT _PAZdeleteView
0x00001b18 EXT _PAZreadView
0x00001b20 EXT _PAZwriteView
0x00001b60 EXT _PAZinitView
0x00001c68 EXT _PAZcreateScene
0x00001ca8 EXT _PAZdeleteScene
0x00001db8 EXT _PAZsetScene
0x00001df8 EXT _PAZinit
0x00001ea0 STAT _replace_strip
0x00001f80 STAT _new_patch_rec
0x00002110 STAT _new_light_rec
0x00002228 STAT _add_renderPatch
0x00002370 STAT _scale_from_matrix
0x000023c8 STAT _whichLOD
0x00002610 STAT _visibility
0x00002858 STAT __renderObj
0x00002dc0 STAT _render_inst
0x00003038 STAT _build_displaylist
0x000030f8 EXT _PAZrenderScene
0x00003300 EXT _PAZsetBackGND
0x000034a8 EXT _PAZlink
0x000034d0 EXT _PAZnest
0x000034f8 EXT _PAZunlink
0x00003520 EXT _PAZunnest
0x00003548 EXT _PAZdeleteTree
0x000035c0 EXT _PAZlinkName
0x00003640 EXT _PAZnestName
0x000036c0 EXT _PAZunlinkName
0x00003748 EXT _PAZunnestName
0x000037d0 EXT _PAZdeleteTreeName
=== SFX.O magic=0x014d nscns=3 nsyms=110 ===
sect .text vaddr=0x00000000 size=0x001940 reloc=400
sect .data vaddr=0x00001940 size=0x000240 reloc=0
sect .bss vaddr=0x00001b80 size=0x000000 reloc=0
-- 16 defined EXT/STAT symbols:
0x00000000 STAT _float_0to1
0x00000040 STAT _makeBangMtls
0x00000118 STAT _sfx_random_numbers
0x00000138 STAT _vertex_block
0x000001f0 STAT _initBang
0x00000224 STAT _sfx_random_ix
0x00000228 STAT _sfx_random_base
0x0000022c EXT _paz_explosions_active
0x00000230 STAT _first_mtl
0x00000234 EXT _sfx_inited
0x00000238 STAT _active_fx
0x00000888 STAT _createBang
0x00000c18 STAT _step_bang
0x00001090 EXT _sfx_init
0x000012c8 EXT _PAZsfx
0x000018a8 EXT _step_fx
=== TEST.O magic=0x014d nscns=3 nsyms=9 ===
sect .text vaddr=0x00000000 size=0x000020 reloc=0
sect .data vaddr=0x00000020 size=0x000000 reloc=0
sect .bss vaddr=0x00000020 size=0x000000 reloc=0
-- 1 defined EXT/STAT symbols:
0x00000000 EXT _test
=== TEXTURE.O magic=0x014d nscns=3 nsyms=21 ===
sect .text vaddr=0x00000000 size=0x000550 reloc=38
sect .data vaddr=0x00000550 size=0x000030 reloc=0
sect .bss vaddr=0x00000580 size=0x000010 reloc=0
-- 8 defined EXT/STAT symbols:
0x00000000 STAT _newCell
0x00000028 STAT _texturesInited
0x00000068 STAT _cell_hwareSize
0x000000a8 EXT _texInit
0x00000180 EXT _allocateHSPtexture
0x00000350 STAT _paletfix
0x00000378 EXT _codeWordFromRamp
0x000004f8 EXT _setRampEntry
=== VREND.O magic=0x014d nscns=3 nsyms=32 ===
sect .text vaddr=0x00000000 size=0x0001a0 reloc=70
sect .data vaddr=0x000001a0 size=0x0000e0 reloc=0
sect .bss vaddr=0x00000280 size=0x000000 reloc=0
-- 1 defined EXT/STAT symbols:
0x00000000 EXT _main
=== VR_CULL.O magic=0x014d nscns=3 nsyms=99 ===
sect .text vaddr=0x00000000 size=0x001740 reloc=321
sect .data vaddr=0x00001740 size=0x000510 reloc=0
sect .bss vaddr=0x00001c50 size=0x0001c0 reloc=0
-- 22 defined EXT/STAT symbols:
0x00000000 STAT _dbl_plane_term
0x00000050 EXT _plane_eqn
0x000001d8 STAT _make_cullview
0x00000408 STAT _new_draw_item
0x000004f8 STAT _current_frame_count
0x000004fc STAT _chunk_head
0x00000500 STAT _chunk_active
0x00000504 STAT _chunk_free_ptr
0x00000508 STAT _chunk_usage
0x00000528 STAT _bbox_plane
0x00000688 STAT _scale_from_matrix
0x000006f8 STAT _visibility
0x000009f0 STAT _build_bound
0x00000b78 STAT _cullize_material
0x00000be0 STAT _whichLOD
0x00000c00 STAT _cull_lmodel
0x00000d50 STAT _cull_geogroup
0x00000e78 STAT _printf_matrix
0x00000f60 STAT _cull_object
0x000012e8 STAT _cull_node
0x00001460 STAT _cull_tree
0x00001598 EXT _veloci_cull
=== VR_DRAW.O magic=0x014d nscns=3 nsyms=41 ===
sect .text vaddr=0x00000000 size=0x000f00 reloc=100
sect .data vaddr=0x00000f00 size=0x000100 reloc=0
sect .bss vaddr=0x00001000 size=0x000060 reloc=0
-- 19 defined EXT/STAT symbols:
0x00000000 EXT _setpxpl5VideoMode
0x00000080 EXT _light_upto2_vertices
0x000000f4 EXT _back_colour
0x000000f8 STAT _first_framebuf_y
0x000000fc STAT _framebuf_flip
0x000001f8 EXT _light_upto2_facets
0x00000370 EXT _light_vertices
0x00000518 EXT _light_facets
0x000006c0 STAT _clear_vertices
0x00000700 STAT _clear_facets
0x00000740 STAT _solve
0x000008a0 STAT _xform_and_project
0x000009a0 STAT _draw_geometry
0x000009a8 STAT _draw_geogroup
0x00000c78 STAT _eval_lmodel
0x00000ca8 STAT _veloci_draw
0x00000db8 STAT _veloci_rasterize
0x00000df0 STAT _veloci_swap_buffers
0x00000e28 EXT _vr_draw_scene
=== VR_REMOT.O magic=0x014d nscns=3 nsyms=221 ===
sect .text vaddr=0x00000000 size=0x003170 reloc=718
sect .data vaddr=0x00003170 size=0x000930 reloc=97
sect .bss vaddr=0x00003aa0 size=0x0002b0 reloc=0
-- 89 defined EXT/STAT symbols:
0x00000000 EXT _reply
0x00000050 EXT _dN_reply
0x00000090 STAT _protocol_error
0x00000138 STAT _do_init
0x00000228 STAT _parse_argv
0x000002b0 EXT _type_to_string
0x00000420 STAT _init_list
0x00000480 STAT _init_superlist
0x000004e0 STAT _new_list
0x00000538 STAT _get_first
0x000005a0 STAT _get_next
0x00000618 STAT _add_item
0x000006f0 STAT _find_item
0x00000768 EXT _replying
0x0000076c EXT _view_id
0x00000770 EXT __dN_transactions
0x00000774 STAT _receive_bytes
0x00000778 EXT _size
0x0000077c EXT _init
0x00000780 STAT _dN_data
0x00000784 EXT _recompute_normals
0x00000788 EXT _smooth
0x0000078c EXT _cooked
0x00000790 EXT _textured
0x00000794 EXT _current_fn
0x00000798 STAT _unlink_superlist_item
0x00000798 EXT _mess_start
0x0000079c STAT _init_pending
0x000007a0 EXT _dealt_with
0x000007a4 EXT ___trianglesRendered
0x000007a8 EXT ___frameTime
0x000007ac STAT _useful_index
0x000007b0 STAT _dpl_err
0x000007f8 STAT _remove_item
0x000008b0 STAT _new_node
0x00000900 EXT _dpl_init_scene
0x00000950 EXT _dpl_init_zone
0x00000968 EXT _dpl_init_view
0x000009a0 EXT _dpl_init_lmodel
0x000009d0 EXT _dpl_init_light
0x000009e8 EXT _dpl_init_material
0x00000a38 EXT _dpl_init_ramp
0x00000a68 EXT _dpl_init_texmap
0x00000a88 EXT _dpl_init_texture
0x00000a98 EXT _dpl_init_instance
0x00000ad0 EXT _dpl_init_object
0x00000b00 EXT _dpl_init_lod
0x00000b40 EXT _dpl_init_geogroup
0x00000b78 EXT _dpl_init_geometry
0x00000b98 EXT _dpl_init_dcs
0x00000be0 STAT _velocirender_create
0x00000ef0 STAT _velocirender_delete
0x00001058 STAT _flush_dpl_type_zone
0x000010d0 STAT _flush_dpl_type_view
0x00001300 STAT _flush_dpl_type_instance
0x00001408 STAT _flush_dpl_type_dcs
0x00001478 STAT _flush_dpl_type_lmodel
0x00001480 STAT _flush_dpl_type_light
0x000014f0 STAT _flush_dpl_type_object
0x000014f8 STAT _flush_dpl_type_lod
0x00001578 STAT _flush_dpl_type_geogroup
0x00001620 STAT _flush_dpl_type_geometry
0x00001678 STAT _flush_dpl_type_material
0x00001758 STAT _flush_dpl_type_texmap
0x00001788 STAT _flush_dpl_type_texture
0x00001838 STAT _flush_dpl_type_ramp
0x00001878 STAT _velocirender_flush
0x00001a80 STAT _velocirender_add_list_item
0x00001d40 STAT _velocirender_remove_list_item
0x00002008 STAT _velocirender_nest_dcs
0x00002070 STAT _velocirender_link_dcs
0x000020d8 STAT _velocirender_prune_dcs
0x00002170 STAT _velocirender_readpixels
0x00002180 STAT _velocirender_sectvector
0x00002190 STAT _velocirender_sectpixel
0x000021a0 STAT _velocirender_init
0x000021f0 STAT _velocirender_draw_scene
0x00002230 STAT _velocirender_morph_object
0x00002240 STAT _velocirender_statistics
0x00002258 STAT _NewVertices
0x000023e8 STAT _NewConnections
0x00002568 STAT _replace_vertices
0x00002650 STAT _replace_connections
0x000026e0 STAT _vertex_from_index
0x00002750 STAT _resolve_connections
0x00002930 STAT _fix_up_geometry
0x00002b40 STAT _velocirender_set_geom_verts
0x00002d38 STAT _velocirender_set_texmap_texels
0x00002f30 EXT _remote_velocirender
=== WALLTIME.O magic=0x014d nscns=3 nsyms=14 ===
sect .text vaddr=0x00000000 size=0x0000a0 reloc=16
sect .data vaddr=0x000000a0 size=0x000020 reloc=0
sect .bss vaddr=0x000000c0 size=0x000000 reloc=0
-- 2 defined EXT/STAT symbols:
0x00000000 EXT _second_
0x00000018 STAT _lastTime
@@ -0,0 +1,226 @@
# Division VelociRender card — hardware architecture (for emulation)
Reconstructed from the firmware (`VREND.MNG`), the DPL3/VRENDER board source, and
Division's own design docs (`VWE.DOC`, `PXPL5001.DOC`, `CM200IO.H`, `DMA.TXT`).
**Physical component tables at the bottom are placeholders to fill from the
photos.** This documents what the software *proves* must be on the board, so the
photos only need to pin part numbers and quantities.
## Form factor
Full-length, full-height, **double-stacked EISA** card pair (operator-described):
- **Bottom board:** the i860 front-end processor + its DRAM + the host/link
interface.
- **Top board:** the **Division PXPL IGC 5.2** rasterizer + supporting logic
(VRAM, RAMDAC, video out).
## The pipeline (three stages)
```
Host PC (EISA) Card, bottom board Card, top board
┌───────────────┐ link/CCB ┌────────────────────┐ FIFO/ ┌──────────────────┐
│ BTL4OPT.EXE │ ─────────► │ i860 front-end │ coeff │ PXPL IGC 5.2 │
│ VelociRender │ (transputer│ (VREND.MNG): │ stream │ (Pixel-Planes 5) │
│ wire: draw_ │ link, │ traverse, TRANSFORM│ ─────► │ tiled SIMD │
│ scene, DCS, │ C011-style│ LIGHT, planarize, │ "down │ rasteriser: │
│ materials …) │ 0x150) │ binitize, emit │ the │ edge/plane eval, │
└───────────────┘ │ pxpl5 coefficients │ wire" │ Z-buffer,texture │
└────────────────────┘ │ → VRAM tiles │
└───────┬──────────┘
VRAM→FrameStore│→ RAMDAC → 3 VGA heads
```
1. **Host (EISA PC)** runs the game; sends the VelociRender command wire (scene
graph: create/DCS/draw_scene/materials/effects — *not* transformed geometry).
2. **i860 front-end** (bottom board, runs `VREND.MNG`): scene traversal,
per-frame **transform / lighting / planarization / "binitize"**, and
generation of the per-primitive **coefficient words** the rasterizer consumes.
Feeds them to the back-end via a FIFO/DMA. (`VWE.DOC`: "push a ton of
triangles down the wire into the pxpl5 back-end"; a z-buffered flat-shaded
textured quad = **48 32-bit words**, a triangle ~24.)
3. **PXPL IGC 5.2 back-end** (top board): Division's implementation of **UNC
Chapel Hill Pixel-Planes 5** — a *tiled SIMD pixel-processor array*. Per tile
it evaluates edges/planes, does Z-buffering and texturing, into VRAM tile
buffers, then VRAM→FrameStore (`DMA.TXT`: tile loop, "wait for 2 interrupts
from DMA engine (scan-converter + end-of-texture)", `VRAMwritetoFrameStore`).
4. **Video out:** FrameStore → RAMDAC → **3 VGA heads** (the octopus cable; 5
mono MFDs + colour radar packed in one framebuffer — see the VDB decode note),
NTSC timing (`TESTVR.BAT /video NTSC`).
## Register / command interfaces (what an emulator must model)
**Host ↔ i860 — transputer link + CCB** (`CM200IO.H`, Division project "BLASTER",
CM100/CM200 lineage):
- "860 ↔ transputer io control register", `INPUT_CCB 0xFFFFF000` / `OUTPUT_CCB
0xFFFFF300` (256-byte Command Control Blocks), `CLEAR_INT`/`INT_CLEARED`.
- IO opcodes: `IO_LINK_OUT`, `IO_LINK_RAST_OUT`, `IO_DIVNET_IN/OUT/NODE`
(DIVNET = Division's transputer-link network — this is also the pod-to-pod
mesh path), `IO_BLOCK_IN/OUT`, `IO_MATHADV_REQ`, `IO_TIMER`.
- The host side pumps the link via a **C011/C012-style link adapter at EISA I/O
0x150** (`outveryfast` REP OUTSW); boot resets via port 0x160.
**i860 → PXPL IGC — coefficient stream** (`DMA.TXT`, `EOF.C` `IGC_*` macros):
- `IGC_SETENABS`, `IGC_MEMeqSCA_S1`, `IGC_MEMeqZERO`, `IGC_MEMgeSCA_S1`,
`IGC_CLEAR`, `IGC_CPY`, `send_em(&DMAptr,&coeffptr,…)` — the ops that build the
per-tile coefficient blocks the IGC executes.
- IGC per-primitive/per-pixel registers (`dvpx_*`): `dvpx_zbuf`, `dvpx_texid`,
`dvpx_texsize`, **`dvpx_texmode`**, **`dvpx_texrampsel`** (the material's ramp
index at the hardware level), `dvpx_diffuse`, `dvpx_specular`, `dvpx_scalar`.
- **Texture display modes** (`dvpx_eoftexmode`, `EOF.C`): **0 = texture RAMP**
(texel value → colour ramp — this is where IR/"predator" vision lives; see
PVISION-IMPLEMENTATION-GUIDE.md), 0x4 = 8-bit mono, 0x6/0x7 = full colour
(3-3-2 / 4-4-4). The 4 ramps (`texture_ramps[MAX_TEX_RAMPS=4]`) live in IGC
memory, selected per primitive by `dvpx_texrampsel`.
## Boot (both processors, every cold start, blind — see board-boot note)
- **`/tranny vrendmon.btl`** = the **transputer** boot image (INMOS boot-from-
link; ~85 KB). Loaded first over the link while the transputer is held reset.
- **`/i860 vrender.mng`** = the **i860** application (~380 KB; disassembles
cleanly as i860 — confirmed). The renderer logic (incl. pvision) is here.
- No version handshake possible pre-boot; both are streamed unconditionally.
## Lineage & external references (for the emulator author)
- **Division Ltd** (UK), project "BLASTER"; chip generations CM100 → CM200 →
**PXPL IGC 5.2** (the VWE board).
- **Rasterizer = UNC Pixel-Planes 5** (Fuchs, Poulton et al., UNC Chapel Hill,
~1989-92): tiled, SIMD pixel-processor array, "renderer" bin architecture,
quadratic expression evaluation. Well-documented in the graphics literature —
the authoritative model for the back-end.
- **Intel i860 (XR/XP)** front-end — documented ISA; our `dis860.py` disassembler
reads `VREND.MNG` directly.
- **INMOS transputer** (T-series) + **C011/C012 link adapters** — documented; the
host-comms and DIVNET/mesh layer.
## Emulation implications
- It's a **3-processor system**: transputer (comms/link/DIVNET), i860
(geometry/coefficient generation — already disassemblable), PXPL IGC (SIMD
rasterizer). A bit-exact renderer = run `VREND.MNG` on an i860 core + model the
IGC's coefficient interpreter (edge/plane eval, Z, texture modes incl. the
ramp) + the CCB/link glue. The transputer layer can likely be *stubbed* at the
CCB boundary (as our GL bridge already stubs the link) unless DIVNET/mesh
behaviour is needed.
- The IGC is the hard part (custom SIMD ASIC) — but its *programming model* is
fully visible in `EOF.C`/`PP5REND.C`/the `dvpx_*` set + the PXPL5 docs, and the
Pixel-Planes 5 literature describes the microarchitecture.
- Priorities: (1) i860 core + coefficient emit is enough to *drive* a
functional-accurate rasterizer we write to spec; (2) modeling the IGC exactly
buys true bit-exactness; (3) transputer only if mesh/DIVNET timing matters.
---
## Physical component inventory — IDENTIFIED FROM PHOTOS (2026-07-14, 13 photos)
Photos in `hardware-photos/` (`01`-`08` close-ups, `09`-`13` the assembly + module
shots). **Corrected topology (operator + edge-on/separated shots): it is a
TWO-BOARD stack**, each board covered front and back — the several `DBxxxx`
silkscreens (all "(C) Division Ltd. 1994") are functional areas/sub-modules of
the two boards, not separate cards.
### Board 1 — i860 geometry board (bottom)
| area | key silicon (as marked) | photos |
|---|---|---|
| CPU | **Intel `A80860XP-50`** — i860 **XP, 50 MHz** (heatsinked) | 04, 10-left |
| coeff FIFO path | **IDT `72510`** FIFOs · grid of Cypress `CY7C451-20` | 04 |
| working RAM | **2× RAM SIMM** = `DBIO151/03`, **Hitachi `HM624256AJP-25`** DRAM ×6/SIMM | 12-left, 09 |
| soldered memory | **16× Ramtron `DM2202J-15` EDRAM** | 03, 10-left |
| I/O / data-mux (other side) | `DBIO152/03` (SN 630254): large `QS74FCT153/158` mux array · Lattice ispLSI 1032/1016 · IDT `16Z646` · `QS338450` bus switches | 01, 11-left |
| host bus | **gold EISA edge** · IDT `74FCT16292/16841` transceivers ("pma") | 02 |
| glue/clock | Lattice ispLSI · GAL22V10 · Cypress `CY7B991` RoboClocks | all |
### Board 2 — PXPL rasterizer board (top)
| area | key silicon (as marked) | photos |
|---|---|---|
| rasterizer control | **`DIVISION PXPL IGC 5.2`** custom ASIC (gold ceramic PGA, date 9447) | 07, 08, 10-right |
| scan-conversion | **3× Xilinx `XC3090-100`** FPGAs (+ a 4th on the video side) | 07, 08, 10-right |
| **SIMD pixel array** | **`DBIO153/02` daughter cards: 4 sets of 3 = 12 cards, EACH with 4× `DIVISION PXPL EMC 5.1`** custom chips ⇒ **~48 EMC chips**. (These "look like 12 RAM SIMMs" but are the pixel-plane modules.) Back side: `QS74FCT2821` regd transceivers + `74F08`. | 12-mid/right, 13, 07 |
| VRAM | ~10-16× Micron `MT42C8256` VRAM | 08, 10-right |
| RAMDAC | **Analog Devices `ADV7150`** (programmable gamma/palette) | 08 |
| clocks | ICS `2572` synth · Cypress `CY7B991` | 08 |
| video out (other side) | `DBE0151 Pixel Planes rev/02` (SN 120227): VGA heads · **Maxim `MAX440/442`** amps · **`LM1882` + `74ACT715` + 14.318 MHz** (NTSC) · Lattice ispLSI 1048 ×2 | 05, 06, 11-right |
### The big reconciliation: PXPL IGC + PXPL EMC = the rasterizer
The daughter cards carry **`DIVISION PXPL EMC 5.1`** chips — and **`EMC` is exactly
the `_configEMCs` / `configEMCs_hi` the firmware programs** (`EOF.C`). So:
- **`PXPL IGC 5.2`** (1 ASIC on Board 2) = the **I**mage **G**eneration
**C**ontroller — the master rasterizer that runs the coefficient stream and
drives the array.
- **`PXPL EMC 5.1`** (~48 chips on the 12 daughter cards) = the **E**nhanced
**M**emory **C**ontroller — the **UNC Pixel-Planes-5 SIMD pixel-processor/memory
array** ("the planes"). Organized 4 banks × 3 cards × 4 chips.
Together, IGC + EMC array = the complete Division Pixel-Planes 5 rasterizer. The
texture-mode/ramp work (incl. pvision) is IGC-programmed and executed across the
EMC array.
### What the photos pin for emulation
- **i860 = XP @ 50 MHz** — set the emulated core exactly.
- **Rasterizer = 1× `PXPL IGC 5.2` + ~48× `PXPL EMC 5.1`** (SIMD array), no public
datasheet — model from the `dvpx_*`/`IGC_*`/`configEMCs` firmware interface +
the Pixel-Planes 5 literature. This is the hard part but the programming model
is fully in-source.
- **RAMDAC = ADV7150** (documented) — pins the colour/gamma back-end (supersedes
the guessed `pow(1/1.25)`; read its real config from the firmware).
- **Video = NTSC** (`LM1882`+`74ACT715`+14.318 MHz, `MAX440/442` amps → VGA heads).
- **Memory:** i860 = 2× Hitachi-DRAM SIMM + 16× Ramtron `DM2202` EDRAM;
rasterizer = Micron `MT42C8256` VRAM + the EMC on-chip pixel memory.
### The transputer — CONFIRMED: INMOS IMS T425-J25S (2026-07-14)
Firmware proved it first: **`VRENDMON.BTL` is transputer code, not i860** (as i860
it's garbage; it opens `0xF0`=240 = INMOS **boot-from-link** length byte, then the
textbook prologue `ajw 4; stl 1; stl 1; stl 1` + coherent `ldl/stnl/ldc/opr`).
Operator then located the chip on the i860 board (dust-covered, lower-centre-right
PLCC):
- **`INMOS IMS T425-J25S`, date 9335 (wk35 1993):** the **32-bit integer
transputer** — 4 KB on-chip SRAM, 4 serial links, **no FPU** — `J` = 84-pin
PLCC, `25` = **25 MHz**.
- The **no-FPU** point confirms the labour split: **T425 = comms/control/boot**
(runs `vrendmon.btl`; host link + the `CM200IO.H` CCB to the i860 + DIVNET
pod-mesh over its 4 links, no FP needed); **i860 XP = FP geometry** (runs
`vrender.mng`). Exactly the 1990 CM200 "860 ↔ transputer" pairing carried into
the 1994 board.
- **Emulation impact:** the system is **T425 + i860 XP + IGC/EMC**. `VREND.MNG`
(i860, disassembles clean via `dis860.py`) and `VRENDMON.BTL` (T425 bytecode)
are DIFFERENT ISAs — don't run `dis860` on the `.BTL`. For a functional renderer
the T425 can be stubbed at the CCB/link boundary (as the GL bridge does); for
bit-exact/timing it must be modelled (INMOS T4-series ISA is well documented —
the boot image already decodes cleanly).
## Downstream: the VWE Video Distribution Board (VDB) — photos 14/15
A **separate board, VWE's own** (not Division): silkscreen **"VIDEO DISTRIBUTION
BOARD, (C) 1994 V.W.E. Inc., Design by A.G./M.C., P/N 1404 REV.1"**. ISA form
factor; receives the rendered framebuffer from the Division card and fans it out
to the cockpit displays — the physical realization of the **3-VGA-head decode**
(5 mono MFDs + colour radar packed in one 16bpp framebuffer → 3 heads → octopus).
- **AMD `MACH130-18JC` CPLD** — the pixel routing / region-decode / timing (splits
the packed framebuffer into per-display regions).
- **3× Brooktree `Bt477KPJ80`** — 256×24 palette **RAMDACs**, one per VGA head
(the heads' final DAC is here, distinct from the Division card's `ADV7150`).
- Support: `74F245`/`74LS125`/`SN74LS541` buffers, `MAD1103` delay lines, an HP
part, fuse + analog output driver/filter array → D-sub outputs on the bracket.
- Back = solder-side only, one hand-added **bodge wire** (a Rev.1 fix).
- **Emulation note:** a display-fanout stage; render-accuracy needs only the
framebuffer→region mapping (the bridge already models the 3-head split). The
`Bt477` palettes / `MACH130` matter only for exact per-head colour/timing.
- **MACH130 "firmware":** no `.JED`/PAL source in the software dump (checked), and
its FUNCTION is already RE'd in `VDB-NOTES.md` from the MUNGA_L4 driver, so a
chip dump isn't needed for emulation. It IS **socketed** (84-pin PLCC — easy to
pull), so a hardware read is possible with a universal programmer that lists
MACH130 — the only catch is the MACH1xx **security bit** (if set, typical for
production, readback is blocked). Low priority.
### Still open (for the hardware owner)
2. **Exact EMC-array geometry** — confirm 4×3×4 = 48 `PXPL EMC 5.1`, and how the
4 banks map to screen tiles / the Pixel-Planes bin structure.
3. **VRAM/EDRAM totals** (count × size) for the frame/Z/texture split.
4. **`ADV7150` exact variant** + any genlock on the video side.
@@ -0,0 +1,193 @@
# 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`):
```c
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` (03), 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.MNG` build tweaked the exact floats (the constant `0.9` is 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 two `0.9`s to `1.0`
> if 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:
```glsl
// 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:
1. **Ramp selection = the material's `ramp_entry` (03).** Set `u_ramp0`/`u_ramp1`
from `PV_RAMP[ramp_entry]` per draw call. If your pipeline doesn't yet track
`ramp_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.
2. **Apply to all textured world geometry** — mechs, terrain, buildings, sky.
3. **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.
4. **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.
5. **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.)
6. **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.CPP` `DPLTogglePVision` (BT and RP both link
the same `DPLRenderer`).
- 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 four `setRampEntry` calls at
render init).
- Firmware (if you want ground truth from the shipped board image): in
`RPLIVE/VREND.MNG` / `BTLIVE/VREND.MNG` the effect handler is reached via the
wire-action jump table; it reads `msg[0]` as `type`, tests sign, and branches
`type==-1`/`type==-2`. Code loads at ~`0xf0400000`, 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.
+157
View File
@@ -0,0 +1,157 @@
# 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 (199495)** — 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.)
+64
View File
@@ -0,0 +1,64 @@
"""Minimal i860 COFF (magic 0x014d) reader: dump file header, sections, and the
symbol table (function/global names + section + value). Deterministic -- no
instruction decoding -- so it is a trustworthy reference map of the firmware
objects. Validates the format by checking the symbol names look like the known
AS860/PGI source (e.g. _createBang, _PAZsfx)."""
import sys, struct, glob, os
def rd(b, off, fmt): return struct.unpack_from(fmt, b, off)
def parse(path):
d = open(path, 'rb').read()
magic, nscns, timdat, symptr, nsyms, opthdr, flags = rd(d, 0, '<HHIIIHH')
out = {'path': path, 'magic': magic, 'nscns': nscns, 'nsyms': nsyms,
'symptr': symptr, 'opthdr': opthdr, 'flags': flags,
'sections': [], 'syms': []}
# section headers
base = 20 + opthdr
for i in range(nscns):
o = base + i * 40
name = d[o:o+8].split(b'\0')[0].decode('latin1')
paddr, vaddr, size, scnptr, relptr, lnnoptr, nreloc, nlnno, sflags = \
rd(d, o + 8, '<IIIIIIHHI')
out['sections'].append((name, vaddr, size, scnptr, nreloc, sflags))
# symbol table (18-byte SYMENT) + string table right after
strbase = symptr + nsyms * 18
def symname(o):
z, offs = rd(d, o, '<II')
if z == 0: # name is an offset into the string table
e = d.index(b'\0', strbase + offs)
return d[strbase + offs:e].decode('latin1')
return d[o:o+8].split(b'\0')[0].decode('latin1')
i = 0
while i < nsyms:
o = symptr + i * 18
nm = symname(o)
value, scnum, typ, sclass, naux = rd(d, o + 8, '<iHHBB')
out['syms'].append((nm, value, scnum, typ, sclass, naux))
i += 1 + naux
return out
SCLASS = {2: 'EXT', 3: 'STAT', 6: 'LABEL', 100: '.bb', 101: '.file'}
def report(o, want_funcs=True):
print(f"\n=== {os.path.basename(o['path'])} magic={o['magic']:#06x} "
f"nscns={o['nscns']} nsyms={o['nsyms']} ===")
for (name, vaddr, size, scnptr, nreloc, sf) in o['sections']:
print(f" sect {name:<10} vaddr={vaddr:#010x} size={size:#08x} "
f"reloc={nreloc}")
# external/static functions (scnum>0, class EXT/STAT), text-ish
funcs = [(nm, v, sc) for (nm, v, sn, ty, sc, na) in o['syms']
if sn > 0 and sc in (2, 3) and not nm.startswith('.')]
print(f" -- {len(funcs)} defined EXT/STAT symbols:")
for nm, v, sc in sorted(funcs, key=lambda x: x[1]):
print(f" {v:#010x} {SCLASS.get(sc,sc):<5} {nm}")
if __name__ == '__main__':
pats = sys.argv[1:] or [r'C:\VWE\TeslaRel410\sda4\DPL3\VRENDER\*.O']
files = []
for p in pats: files += glob.glob(p)
for f in sorted(files):
try:
report(parse(f))
except Exception as e:
print(f"\n=== {f}: PARSE FAILED: {e} ===")
+100
View File
@@ -0,0 +1,100 @@
"""Empirically derive the i860 opcode table from matched .S (PGI/AS860 assembly)
and .O (COFF machine code) pairs. Byte-accounting is proven correct by
cross-checking every .S label against its .O symbol offset; only then is the
op6->mnemonic harvest trusted."""
import sys, struct, re, glob, os
sys.path.insert(0, r'C:\VWE\TeslaRel410\emulator\firmware-decomp')
from coff860 import parse
DATASZ = {'.long':4, '.word':4, '.int':4, '.float':4, '.single':4,
'.double':8, '.short':2, '.half':2, '.byte':1, '.string':None}
def parse_S(path):
"""Return (labels{name:off}, instrs[(off,mnem,line)]) for the .text section."""
labels, instrs = {}, []
off = 0; sect = '.text'; in_text = True
for raw in open(path, encoding='latin1'):
line = raw.split('//', 1)[0].rstrip()
s = line.strip()
if not s:
continue
# label(s): possibly "name:" / "name::" possibly followed by an instr
while True:
m = re.match(r'^([A-Za-z_.$][\w.$]*)::?\s*(.*)$', s)
if m and (m.group(2) == '' or not m.group(2).startswith('.')) and ':' in s[:m.end(1)+2]:
# confirm it's really a label (colon right after the name)
after = s[m.end(1):]
if after.startswith('::') or after.startswith(':'):
if in_text: labels[m.group(1)] = off
s = after.lstrip(':').strip()
if not s: break
continue
break
if not s:
continue
if s.startswith('.'):
d = s.split()[0].lower()
if d in ('.text',): sect='.text'; in_text=True
elif d in ('.data','.bss','.section'): sect=d; in_text=False
elif d == '.align' and in_text:
n = int(s.split()[1]); off = (off + n - 1) & ~(n - 1)
elif d in DATASZ and in_text:
sz = DATASZ[d]
if sz is None: # .string -> count bytes incl NUL
txt = s[s.index('"')+1:s.rindex('"')]
off += len(txt.encode('latin1').decode('unicode_escape')) + 1
else:
vals = s[len(d):].split(',')
off += sz * len([v for v in vals if v.strip()])
# other directives: no size
continue
if in_text:
mnem = s.split()[0]
instrs.append((off, mnem, s))
off += 4
return labels, instrs
def load_text(opath):
o = parse(opath); d = open(opath, 'rb').read()
text = syms = None
for (name, vaddr, size, scnptr, nreloc, sf) in o['sections']:
if name == '.text': text = d[scnptr:scnptr+size]
syms = {nm: v for (nm, v, sn, ty, sc, na) in o['syms'] if sn == 1 and sc in (2,3)}
return text, syms
def derive(spath, opath, table):
labels, instrs = parse_S(spath)
text, syms = load_text(opath)
# cross-check label offsets vs symbol table
common = [k for k in labels if k in syms]
good = [k for k in common if labels[k] == syms[k]]
bad = [(k, labels[k], syms[k]) for k in common if labels[k] != syms[k]]
tag = os.path.basename(opath)
print(f"[{tag}] instrs={len(instrs)} textwords={len(text)//4} "
f"labels_ok={len(good)}/{len(common)}", end='')
if bad:
print(f" MISALIGNED e.g. {bad[:3]}")
return False
print(" ALIGNED")
for (off, mnem, line) in instrs:
if off + 4 > len(text): break
w = struct.unpack_from('<I', text, off)[0]
op6 = (w >> 26) & 0x3f
table.setdefault(op6, {}).setdefault(mnem, 0)
table[op6][mnem] += 1
return True
if __name__ == '__main__':
base = r'C:\VWE\TeslaRel410\sda4\DPL3\VRENDER\AS860'
pairs = []
for s in glob.glob(os.path.join(base, '*.S')):
o = s[:-2] + '.O'
if os.path.exists(o): pairs.append((s, o))
table = {}
for s, o in sorted(pairs):
try: derive(s, o, table)
except Exception as e: print(f"[{os.path.basename(o)}] ERROR {e}")
print("\n=== derived op6 -> mnemonic(count) ===")
for op6 in sorted(table):
items = sorted(table[op6].items(), key=lambda x: -x[1])
print(f" {op6:#04x} ({op6:06b}): " + ", ".join(f"{m}×{c}" for m,c in items))
+219
View File
@@ -0,0 +1,219 @@
"""Intel i860 disassembler for the VelociRender firmware.
Opcode/field assignments are those VALIDATED against our own AS860 .S<->.O pairs
(see i860-encoding.md / derive860.py); unknowns decode as `.word`. This is also
the front-end for an eventual i860 *emulator* that runs VREND.MNG directly.
Run `python dis860.py --validate` to check the decoder against the matching
source/object pairs (per-instruction base-mnemonic agreement)."""
import sys, struct, os, glob
REG = [f"r{i}" for i in range(32)]
FREG = [f"f{i}" for i in range(32)]
CTRL = {0: "fir", 1: "psr", 2: "dirbase", 3: "db", 4: "fsr", 5: "epsr"}
def s16(v): return v - 0x10000 if v & 0x8000 else v
def s26(v): return v - 0x4000000 if v & 0x2000000 else v
# ---- FP escape (op 0x12) sub-opcode[6:0] -> base name (validated where marked) ----
FP_SUB = {
0x00: "r2p1", 0x01: "r2pt", 0x02: "r2ap1", 0x04: "i2p1", 0x05: "i2pt",
0x06: "i2ap1", 0x07: "i2apt", 0x08: "rat1p2", 0x09: "m12apm",
0x0a: "ra1p2", 0x0b: "m12ttpa", 0x0c: "iat1p2", 0x0d: "m12tpm",
0x0e: "ia1p2", 0x0f: "m12tpa", 0x1c: "mimt1s2",
0x20: "fmul", 0x21: "fmlow", 0x22: "frcp", 0x23: "frsqr",
0x24: "fmul3", 0x28: "pfmam", 0x2c: "pfmsm",
0x30: "fadd", 0x31: "fsub", 0x32: "fix", 0x34: "fgt", 0x35: "feq",
0x3a: "ftrunc", 0x40: "fxfr", 0x49: "fiadd", 0x4d: "fisub",
0x50: "faddp", 0x51: "faddz", 0x57: "fzchk", 0x5a: "form",
0x5b: "fzchkl", 0x5f: "fnop",
}
# precision suffix from result/source-precision bits (bit8=R result-prec, bit7=S src-prec)
def fp_suffix(w):
s = 'd' if (w >> 7) & 1 else 's'
r = 'd' if (w >> 8) & 1 else 's'
return f".{s}{r}"
def dec_fp(w):
sub = w & 0x7f
p = (w >> 10) & 1 # P: pipelined
src2 = (w >> 21) & 0x1f; dest = (w >> 16) & 0x1f; src1 = (w >> 11) & 0x1f
base = FP_SUB.get(sub)
if base is None:
return (".fp?", f"{sub:#04x} {FREG[src1]},{FREG[src2]},{FREG[dest]}")
if base == "fxfr": # FP->int reg move
return ("fxfr", f"{FREG[src1]},{REG[dest]}")
if base == "fnop":
return ("fnop", "")
name = ("pf" if p else "f") + base[1:] if base[0] == 'f' else (("pf" if p else "") + base)
# graphics dual-ops (r2p1 etc.) keep their name; only f* get the pf/f prefix
if base[0] != 'f':
name = base
return (name + fp_suffix(w), f"{FREG[src1]},{FREG[src2]},{FREG[dest]}")
# ---- primary decode ----
# immediate-form rule: for load/store (0x00-0x0b) and arith/logic (0x20-0x3f),
# the ODD opcode of each pair is the 16-bit immediate/const form.
MEM = {0x00: "ld.s", 0x01: "ld.s", 0x04: "ld.l", 0x05: "ld.l",
0x06: "st.l", 0x07: "st.l", 0x08: "fld.l", 0x09: "fld.l",
0x0a: "fst.l", 0x0b: "fst.l"}
ARITH = {0x20: "addu", 0x22: "subu", 0x24: "adds", 0x26: "subs",
0x28: "shl", 0x2a: "shr", 0x2c: "shrd", 0x2e: "shra",
0x30: "and", 0x32: "andh", 0x34: "andnot", 0x36: "andnoth",
0x38: "or", 0x3a: "orh", 0x3c: "xor", 0x3e: "xorh"}
CTRLB = {0x1a: "br", 0x1b: "call", 0x1c: "bc", 0x1d: "bc.t",
0x1e: "bnc", 0x1f: "bnc.t"}
def decode(w, addr):
op = (w >> 26) & 0x3f
src2 = (w >> 21) & 0x1f; dest = (w >> 16) & 0x1f; src1 = (w >> 11) & 0x1f
imm = w & 0xffff
# loads/stores
if op in MEM:
m = MEM[op]
off = s16(imm & 0xfffc) if 'l' in m else s16(imm)
if m.startswith('f'):
rd = FREG[dest]
else:
rd = REG[dest]
if m.startswith('st') or m.startswith('fst'):
return m, f"{rd},{off:#x}({REG[src2]})"
return m, f"{off:#x}({REG[src2]}),{rd}"
if op == 0x02:
return "ixfr", f"{REG[src1]},{FREG[dest]}"
if op == 0x0c:
return "ld.c", f"{CTRL.get(src2,src2)},{REG[dest]}"
if op == 0x0e:
return "st.c", f"{REG[src1]},{CTRL.get(src2,src2)}"
if op == 0x10:
return "bri", f"{REG[src1]}"
if op == 0x11:
return "trap", f"{REG[src1]},{REG[src2]},{REG[dest]}"
if op == 0x12:
return dec_fp(w)
if op == 0x13:
# core escape: sub-op in low bits; calli common
if (w & 0x7ff) == 0 or True:
return "calli", f"{REG[src1]}"
if op in (0x14, 0x15, 0x16, 0x17):
base = "btne" if op < 0x16 else "bte"
broff = s16(((dest << 11) | (w & 0x7ff)) & 0xffff)
tgt = addr + 4 + broff * 4
# odd opcode = 5-bit-const form (i860 rule; e.g. `btne 0x0,r19` = 0x15)
s1 = f"{src1:#x}" if op & 1 else f"{REG[src1]}"
return base, f"{s1},{REG[src2]},{tgt:#010x}"
if op in CTRLB:
tgt = addr + 4 + s26(w & 0x03ffffff) * 4
if CTRLB[op] in ("br", "call"):
return CTRLB[op], f"{tgt:#010x}"
return CTRLB[op], f"{tgt:#010x}"
if op in (0x18, 0x19):
off = s16(imm & 0xfff8)
return "pfld.d", f"{off:#x}({REG[src2]}),{FREG[dest]}"
if (op & 0x3e) in ARITH:
m = ARITH[op & 0x3e]
if op & 1: # immediate form
v = s16(imm)
return m, f"{v:#x},{REG[src2]},{REG[dest]}"
# register form; recognise pseudo-ops (mov/nop = shl r0)
if m == "shl" and src1 == 0:
if src2 == 0 and dest == 0:
return "nop", ""
return "mov", f"{REG[src2]},{REG[dest]}"
if m == "or" and src1 == 0 and src2 == 0:
return "mov", f"r0,{REG[dest]}"
return m, f"{REG[src1]},{REG[src2]},{REG[dest]}"
return ".word", f"{w:#010x}"
# ---------------- validation ----------------
def _norm_ops(s):
"""normalize an operand string for comparison: strip spaces, lower, and
canonicalize hex/dec immediates."""
import re
s = s.lower().replace(' ', '')
def canon(m):
v = int(m.group(0), 0)
return str(v)
return re.sub(r'-?0x[0-9a-f]+|-?\b\d+\b', canon, s)
def validate():
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import derive860 as D
base = r'C:\VWE\TeslaRel410\sda4\DPL3\VRENDER\AS860'
# operand check only for ops whose operands are NOT link-time relocated
OPCHECK = ('ld.', 'st.', 'fld.', 'fst.', 'adds', 'addu', 'subs', 'subu',
'and', 'or', 'xor', 'shl', 'shr', 'shra', 'mov', 'ixfr', 'bri')
for s in sorted(glob.glob(os.path.join(base, '*.S'))):
o = s[:-2] + '.O'
if not os.path.exists(o):
continue
labels, instrs = D.parse_S(s)
text, syms = D.load_text(o)
common = [k for k in labels if k in syms]
aligned = not any(labels[k] != syms[k] for k in common)
ok = tot = 0; oper_ok = oper_tot = 0; misses = []; omiss = []
for (off, mnem, line) in instrs:
if off + 4 > len(text):
break
w = struct.unpack_from('<I', text, off)[0]
dm, dops = decode(w, off)
tot += 1
if dm.split('.')[0] == mnem.split('.')[0] or \
dm.split('.')[0].lstrip('p') == mnem.split('.')[0] or \
(mnem.startswith('pf') and dm.startswith('pf')):
ok += 1
# operand-level check where the source operands are literal
if mnem.startswith(OPCHECK) and dm == mnem:
src_ops = line[len(mnem):].strip()
if not any(c.isalpha() and c not in 'rxf' for c in
src_ops.replace('r', '').replace('f', '')
.replace('x', '')):
oper_tot += 1
if _norm_ops(src_ops) == _norm_ops(dops):
oper_ok += 1
elif len(omiss) < 8:
omiss.append(f"@{off:#x} {mnem} src[{src_ops}] "
f"dec[{dops}] w={w:#010x}")
else:
if len(misses) < 8:
misses.append(f"@{off:#x} src={mnem} dec={dm} w={w:#010x}")
tag = os.path.basename(o)
flag = "aligned" if aligned else "MISALIGNED"
print(f"[{tag:14}] {flag:10} mnemonic {ok}/{tot}="
f"{100*ok//max(tot,1)}% operands {oper_ok}/{oper_tot}="
f"{100*oper_ok//max(oper_tot,1)}%")
if aligned:
for m in misses:
print(" mn-miss ", m)
for m in omiss:
print(" op-miss ", m)
def disasm_range(text, start, count, base=0x0c):
"""Disassemble `count` words of `text` from byte offset `start`.
`base` = file offset where .text begins (VREND.MNG = 0x0c), so printed
addresses are file/memory offsets."""
out = []
for i in range(count):
off = start + i*4
if off + 4 > len(text): break
w = struct.unpack_from('<I', text, off)[0]
# decode with the PRINTED address so branch targets match the listing
m, ops = decode(w, base+off)
out.append(f" {base+off:#08x}: {w:08x} {m:<10} {ops}")
return "\n".join(out)
def load_mng(path):
d = open(path, 'rb').read()
tsize = struct.unpack_from('<I', d, 0)[0]
return d[0x0c:0x0c+tsize]
if __name__ == '__main__':
if '--validate' in sys.argv:
validate()
elif '--list' in sys.argv:
# dis860.py --list <mng> <hexoffset> <count>
a = [x for x in sys.argv if not x.startswith('--')][1:]
mng = a[0]; start = int(a[1], 0); count = int(a[2]) if len(a) > 2 else 32
print(disasm_range(load_mng(mng), start, count))
else:
print("usage: dis860.py --validate | --list <VREND.MNG> <off> <count>")
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 975 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 KiB

+83
View File
@@ -0,0 +1,83 @@
# i860 instruction encoding — validated against our own object files
Every fact here is cross-checked against the AS860 `*.S` (PGI/AS860 assembly) ↔
`*.O` (COFF machine code) pairs in `sda4/DPL3/VRENDER/AS860/`, using
`derive860.py`. Method: reproduce every `.text` byte offset from the assembly and
confirm each computed label offset equals its COFF symbol value; only then is the
`op6 → mnemonic` harvest trusted. 3 pairs align perfectly (CONTROL 2/2, TRISTRIP
4/4, XFLGHTPR 20/20 labels).
## Word format (little-endian, 32-bit) — CONFIRMED
REG-format:
```
31 26 25 21 20 16 15 11 10 0
+--------+-------+-------+-------+-----------+
| opcode | src2 | dest | src1 | (unused) | register src1
+--------+-------+-------+-------+-----------+
| opcode | src2 | dest | imm16 | 16-bit immediate
+--------+-------+-------+-------------------+
```
CTRL-format (br/call/bc/bnc/bt/bnt): `opcode[31:26] | disp26` (sign-extended,
word-scaled, PC-relative to the *next* instruction; i860 has one delay slot).
Verified from `CONTROL.O` (4 instrs, exact):
- `bri r1` = `0x40000800` → op `0x10`, src1(15:11)=1 ✓
- `ld.c fsr,r16` = `0x30900000` → op `0x0c`, src2(25:21)=4=fsr, dest(20:16)=16 ✓
- `st.c r16,fsr` = `0x38808000` → op `0x0e`, src2=4=fsr, src1(15:11)=16 ✓
Control registers: `fir=0 psr=1 dirbase=2 db=3 fsr=4 epsr=5` (fsr=4 confirmed).
## Primary opcode map (bits 31:26)
CONFIRMED = dominant mnemonic seen in cleanly-aligned ground-truth data;
otherwise from the published i860 ISA and consistent with what we saw.
| op6 | mnemonic | src | notes |
|----|----|----|----|
| 0x02 | ixfr | | integer→FP reg transfer (CONFIRMED) |
| 0x08/09 | fld.{l,d,q} | | FP load (+auto-inc); 0x09 CONFIRMED |
| 0x0a/0b | fst / pst | | FP store; 0x0b fst CONFIRMED |
| 0x0c | ld.c | | load control reg (CONFIRMED) |
| 0x0e | st.c | | store control reg (CONFIRMED) |
| 0x10 | bri | | branch indirect (CONFIRMED) |
| 0x11 | trap | | |
| 0x12 | **FP escape** | | fadd/fmul/fsub/pf*/fxfr/frcp/… sub-op in low bits (CONFIRMED) |
| 0x13 | core escape | | calli, etc. (calli CONFIRMED) |
| 0x14/15 | btne | imm/reg | 0x15 CONFIRMED |
| 0x16/17 | bte | imm/reg | 0x17 CONFIRMED |
| 0x18/19 | pfld.{l,d,q} | | pipelined FP load |
| 0x1a | br | | CTRL (CONFIRMED) |
| 0x1b | call | | CTRL (CONFIRMED) |
| 0x1c | bc | | branch on CC (CONFIRMED) |
| 0x1d | bc.t | | + taken hint (CONFIRMED) |
| 0x1e | bnc | | branch on !CC (CONFIRMED) |
| 0x1f | bnc.t | | + taken hint (CONFIRMED) |
| 0x20/21 | addu | imm/reg | |
| 0x22/23 | subu | imm/reg | |
| 0x24/25 | adds | reg/imm | CONFIRMED |
| 0x26/27 | subs | reg/imm | CONFIRMED |
| 0x28/29 | shl | | shift left |
| 0x2a/2b | shr | | shift right |
| 0x2c | shrd | | |
| 0x2d | bla | | |
| 0x2e/2f | shra | | 0x2f CONFIRMED |
| 0x30/31 | and | reg/imm | |
| 0x32/33 | andh | | high half |
| 0x34/35 | andnot | | |
| 0x36/37 | andnoth | | |
| 0x38/39 | or | | |
| 0x3a/3b | orh | | 0x3b CONFIRMED |
| 0x3c/3d | xor | | 0x3c CONFIRMED |
| 0x3e/3f | xorh | | |
Loads/stores `ld.{b,s,l}` / `st.{b,s,l}` sit in the low opcodes (0x000x07);
exact split (size/sign bits) to be pinned when the full decoder byte-exactly
reproduces the aligned pairs. Pseudo-ops seen in source: `mov` (= `or`/`shl`
with r0), `nop`, `fnop`.
## Tools
- `coff860.py` — COFF reader (symbols/sections).
- `derive860.py` — the .S↔.O aligner that produced/validates the above.
+131
View File
@@ -0,0 +1,131 @@
"""Name functions in the stripped VREND.MNG by matching relocation-invariant
signatures harvested from the DPL3 VRENDER .O object files.
For each .O .text function we mask (wildcard) every 32-bit word that a COFF
relocation touches -- those hold call targets / data addresses that the linker
rewrites -- and keep the fixed opcode+register skeleton. A function that carried
over into VREND.MNG (even relocated to a new address) still matches its
skeleton. Word granularity throughout (i860 is fixed 4-byte, word-aligned)."""
import sys, struct, os, glob
def parse_coff(path):
d = open(path, 'rb').read()
magic, nscns, timdat, symptr, nsyms, opthdr, flags = struct.unpack_from('<HHIIIHH', d, 0)
secs = []
base = 20 + opthdr
for i in range(nscns):
o = base + i*40
name = d[o:o+8].split(b'\0')[0].decode('latin1')
paddr, vaddr, size, scnptr, relptr, lnnoptr, nreloc, nlnno, sflags = \
struct.unpack_from('<IIIIIIHHI', d, o+8)
secs.append(dict(name=name, vaddr=vaddr, size=size, scnptr=scnptr,
relptr=relptr, nreloc=nreloc))
# symbols
strbase = symptr + nsyms*18
def symname(o):
z, offs = struct.unpack_from('<II', d, o)
if z == 0:
e = d.index(b'\0', strbase+offs); return d[strbase+offs:e].decode('latin1')
return d[o:o+8].split(b'\0')[0].decode('latin1')
syms = []; i = 0
while i < nsyms:
o = symptr + i*18
nm = symname(o)
value, scnum, typ, sclass, naux = struct.unpack_from('<iHHBB', d, o+8)
syms.append((nm, value, scnum, sclass)); i += 1 + naux
return d, secs, syms
def text_section(secs):
for s in secs:
if s['name'] == '.text': return s
return None
def sig_for_functions(path, min_words=6):
"""yield (name, [word|None ...]) skeletons for each .text function."""
d, secs, syms = parse_coff(path)
t = text_section(secs)
if not t or t['size'] == 0: return
text = d[t['scnptr']:t['scnptr']+t['size']]
nwords = t['size'] // 4
reloc_word = set()
for r in range(t['nreloc']):
rva = struct.unpack_from('<I', d, t['relptr'] + r*10)[0]
reloc_word.add(rva // 4) # wildcard the whole word
# function boundaries = .text EXT/STAT symbols, sorted
fsyms = sorted([(v, nm) for (nm, v, sn, sc) in syms
if sn == 1 and sc in (2, 3)], key=lambda x: x[0])
for i, (v, nm) in enumerate(fsyms):
end = fsyms[i+1][0] if i+1 < len(fsyms) else t['size']
w0, w1 = v // 4, end // 4
if w1 - w0 < min_words: continue
skel = []
for w in range(w0, w1):
if w in reloc_word: skel.append(None)
else: skel.append(struct.unpack_from('<I', text, w*4)[0])
if sum(1 for x in skel if x is not None) < min_words: continue
yield nm, skel
def load_mng_text(path):
d = open(path, 'rb').read()
tsize, dsize, bsize = struct.unpack_from('<III', d, 0)
text = d[0x0c:0x0c+tsize]
return text, tsize
def build_index(text):
idx = {}
for p in range(len(text)//4):
w = struct.unpack_from('<I', text, p*4)[0]
idx.setdefault(w, []).append(p)
return idx
def match(skel, text, idx, thresh=0.75):
"""Anchor on the first concrete word, then SCORE the concrete words.
Returns (best_addr, best_frac, unique) so drifted functions still match on
their stable prologue/skeleton."""
k = next((i for i, x in enumerate(skel) if x is not None), None)
if k is None: return None
concrete = [i for i, x in enumerate(skel) if x is not None]
best = (None, 0.0); second = 0.0
for p in idx.get(skel[k], []):
start = p - k
if start < 0 or (start + len(skel))*4 > len(text): continue
good = 0
for i in concrete:
if struct.unpack_from('<I', text, (start+i)*4)[0] == skel[i]:
good += 1
frac = good / len(concrete)
if frac > best[1]:
second = best[1]; best = (start*4, frac)
elif frac > second:
second = frac
if best[0] is not None and best[1] >= thresh:
return (best[0], best[1], best[1] > second + 0.05)
return None
def main():
mng = sys.argv[1] if len(sys.argv) > 1 else r'C:\VWE\TeslaRel410\sda4\RPLIVE\VREND.MNG'
odir = r'C:\VWE\TeslaRel410\sda4\DPL3\VRENDER'
text, tsize = load_mng_text(mng)
idx = build_index(text)
print(f"VREND.MNG .text = {tsize:#x} bytes ({tsize//4} words); {os.path.basename(mng)}")
named = {} # addr -> (name, frac)
nsig = 0
for opath in sorted(glob.glob(os.path.join(odir, '*.O')) +
glob.glob(os.path.join(odir, 'PXPL5SUP', '*.O'))):
for nm, skel in sig_for_functions(opath):
nsig += 1
r = match(skel, text, idx)
if r is None: continue
addr, frac, uniq = r
if not uniq: nm = nm + "?"
if addr not in named or frac > named[addr][1]:
named[addr] = (nm, frac)
strong = sum(1 for (n, f) in named.values() if f >= 0.95)
print(f"signatures tried={nsig}, named={len(named)} "
f"(exact/near-exact >=0.95: {strong})")
for addr in sorted(named):
nm, frac = named[addr]
print(f" text+{addr:#08x} (mem {0x0c+addr:#08x}) {frac:0.2f} {nm}")
if __name__ == '__main__':
main()
@@ -498,3 +498,90 @@ first appear in the 0x1f stream -- next session, have the pilot narrate
("holding hat-left NOW") and read the log; if nothing appears during holds,
compare against a 7/07-style run (gauge_arena_sound.conf, no net stack) to
isolate what enables the eyepoint emission.
---
## 2026-07-14: Where the IR / thermal palette actually lives (source hunt)
Operator question: does the game tell the Division card *which* palette to use
for IR/predator vision, or is the card's firmware around to decompile? Answer:
**the game specifies no palette at all -- it just flips a switch; the palette
is baked into the i860 board firmware.**
Chain of evidence (all from THIS project's primary sources -- OUR dump is the
authority; BT411 is a downstream reconstruction *of* this and is NOT cited):
1. **Our own game source: `CODE/RP/MUNGA_L4/L4VIDEO.CPP:5535`,
`DPLRenderer::DPLTogglePVision()`** -- the real, shipped, non-stubbed
implementation (the engine `DPLRenderer` is shared by BT and RP). Comment
reads `toggles the state of dpl "predator" vision`. Verbatim:
```cpp
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);
```
Pvision is sent as a **special "explosion" effect at the origin with
`type = -1 / -2`**. The struct (`CODE/RP/MUNGA_L4/libDPL/dpl/DPLTYPES.H:278`,
`{float x,y,z; int32 type; dpl_TEXTURE *tex;}`) has **no colour field** --
so the game hands the board zero palette information. (This also corrects an
earlier disasm misread that thought an RGB triple + matrix was being sent;
there is none.)
2. **DPL3 SDK source (`sda4/DPL3`, `sda4/DPL3RLS`).** The board effect
dispatcher `PAZsfx()` (`VRENDER/SFX.C:751`) only handles explosion codes
`0/1/2`; there is **no `-1/-2` branch and no DAC/palette flip anywhere in
this source**. This VRENDER source is the 1994-95 vintage (DPL3RLS build
dirs `VR_40826`..`VR_50329`) -- it **predates the pvision feature**.
3. **The exe itself.** Same as (1): the on/off flag is all that crosses the
wire. Nothing selects a colour.
So the palette is the **board's built-in response to explosion type -1/-2**,
i.e. it lives in the i860 firmware, not in the game, the SDK, or the exe.
### Is the firmware around / decompilable? Yes.
The board-load images are in the dump as `VRENDMON.BTL` (plus the `VREND2..9`
overlays per game). Dated build tree `sda4/VREND/` runs **Oct 1995 -> Jun 1996**
(`51019` ... `60623EDO`), newer than the source we have; the production images
are `sda4/BTLIVE/` and `sda4/RPLIVE/`. The newest (`VREND/60623EDO/
VRENDMON.BTL`, 13 Jun 1996, 85 KB) is **raw i860 machine code** -- opens
`f0 b4 d1 d1 ...`, no COFF/ELF wrapper, no symbol/string table (grep for
vision/pred/therm/heat/palette = nothing; strings are stripped/packed).
Bottom line for the crew:
- The **exact** IR palette can only come from **reversing the i860
`VRENDMON.BTL`** (Ghidra has an i860 module; same workflow the BT411 team
already runs). Target: the colour/DAC transform gated by the effect
`type == -1`. It's a real RE effort (stripped code) but the binary is here.
- Until then our `heat` palette is an educated interim; the IR-vision-options
sheet (heat/green/amber/mono) is the fast path if a crew member simply
*recognises* the original look.
- Empirical cross-check: hitting the cockpit IR button in the live sim already
drives `board.pvision` through action `0x1b` mode `-1/-2` -- exactly the
explosion-type encoding above -- so the mechanism is confirmed end to end;
only the colour LUT is unknown.
### Revised model (operator, 2026-07-14): grayscale squash + fog off, not a palette
Operator's read: predator vision is probably **not** a false-colour palette
swap -- it's a **per-pixel squash to grayscale plus fog turned OFF** (so
targets read through night/weather). This fits everything: the game sends no
colour, and "see through the murk" is the same family of trick as the
searchlight (which turned out to be a view-fog push-back, not a light cone).
`heat`/`green`/`amber` were only ever an interim guess at the *name*
"predator".
Renderer changed to match (`dpl3-revive/patha/vrview_gl.py`):
- default `VRVIEW_PVISION_PALETTE` flipped `heat` -> `mono` (grayscale squash).
- when pvision is active, the **scene pass forces `fog_on = 0`** (a present-
pass recolour alone cannot undo fog already baked into the pixels).
- green/amber/heat ramps retained purely as opt-in crew A/B (`VRVIEW_
PVISION_PALETTE=green|amber|heat`).
Still a hypothesis pending a live look + crew memory; the ground-truth remains
recoverable by reversing the i860 `VRENDMON.BTL` (colour/DAC transform gated by
effect `type == -1`).
@@ -0,0 +1,78 @@
[sdl]
output=opengl
# higher,higher not highest: HIGH_PRIORITY_CLASS starved the host desktop;
# with the retry patches a rare dropout self-recovers (see gauge_rio.conf).
priority=higher,higher
[dosbox]
memsize=32
machine=svga_s3
[cpu]
core=dynamic
cputype=pentium
cycles=max
[sblaster]
sbtype=sb16
sbbase=220
irq=5
dma=1
hdma=5
[mixer]
# match the EMU8000s' native rate (no resample) and buffer ~60ms so brief
# emulation-thread stalls (RIO retry recovery) don't audibly chop
rate=44100
blocksize=1024
prebuffer=60
[ne2000]
# real pods loaded the ODI stack in AUTOEXEC before ANY game launch --
# weapons-fire test: bare -egg run WITH the packet stack resident (the game's
# WATTCP identity is 200.0.0.113 from REL410\BT\WATTCP.CFG). nicbase 340:
# 300 clashes with the VDB and blanks heads. backend=slirp: THIS build lacks
# pcap ("Backend not supported"); slirp = user-mode NAT, no LAN peers, but
# the packet driver functions and accepts sends -- enough for a solo fire
# test. For pod<->console runs use a pcap-enabled build (net_full.conf).
ne2000=true
nicbase=340
nicirq=3
backend=slirp
[serial]
# RIO on COM1 with the low-latency options (rxpollus/rxburst) so the board's
# few-ms ACK deadline is met; plasma display on COM2 (real pod has both).
serial1=namedpipe pipe:vrio rxpollus:100 rxburst:16
serial2=namedpipe pipe:vplasma
[autoexec]
mount c "C:\VWE\TeslaRel410\ALPHA_1"
mount d "C:\VWE\TeslaRel410\emulator\net-boot"
d:
echo === loading NE2000 packet-driver stack (as production AUTOEXEC did) ===
d:\lsl
d:\ne2000
d:\odipkt
c:
cd \REL410\BT
set VIDEOFORMAT=svga
rem production pod card init (PARAMETR.BAT:181-186): DIAGNOSE + AWEUTIL per
rem card -- AWEUTIL /S does the EMU8000 bring-up and DRAM detect the HMI SOS
rem driver relies on; skipping it left the cards uninitialized (silent).
rem aweutil /s SKIPPED for now: it verifies the AWE32 GM ROM, which the
rem emulated cards lack (hangs in a retry loop) -- restore once the ROM is
rem dumped from a real card. diagnose /s kept (passes, sets mixer config).
set BLASTER=A220 I5 D1 H5 P330 T6
c:\sb16\diagnose /s
set BLASTER=A240 I7 D3 H6 P300 T6
c:\sb16\diagnose /s
set BLASTER=A220 I5 D1 H5 P330 T6
set TEMP=c:\
rem arena1 city mission (TESTARN.EGG: map=arena1, time=day), RIO attached,
rem bare -egg launch (no netnub) = the user's real-world test-egg setup.
set HEAPSIZE=15000000
set L4GAUGE=640x480x16
call setenv.bat r f s p
32rtm.exe -x
rem stdout -> log: this optimized build still emits DEBUG_STREAM lines (the
rem RIO retry spam proved it) -- capture the weapon fire-refusal reason.
rem DOS flushes in 4KB chunks; the tail lands on clean exit (mission timer).
btl4opt.exe -egg testnclr.egg > c:\weaponlog.txt
32rtm.exe -u
echo ALPHA1-RUN-DONE
pause