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>
@@ -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
|
||||
@@ -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` (0–3), so a
|
||||
mech built from several materials shows several colour families at once — that's
|
||||
the multicolor IR look, and it's why the tool was useful for *checking* which
|
||||
material/ramp a surface used.
|
||||
|
||||
> **Provenance caveat:** those RGBs are the source defaults. The shipped 1996
|
||||
> `VREND.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` (0–3).** 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.
|
||||
@@ -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 (1994–95)** — same era as the C/asm source we have,
|
||||
so they *predate the pvision feature*. Their value: (1) they validate the
|
||||
toolchain/format, (2) they name the board-side machinery, and (3) they are the
|
||||
**signature library** for naming functions in the stripped, newer `VREND.MNG`.
|
||||
|
||||
## pvision — where the answer is, and the anchors we have
|
||||
|
||||
The game only flips a switch (`L4VIDEO.CPP::DPLTogglePVision` sends a special
|
||||
"explosion" effect at the origin, `type = -1` ON / `-2` OFF — no colour). So
|
||||
the behaviour is board-side. Operator's working model: **grayscale squash +
|
||||
fog OFF**, not a false-colour palette.
|
||||
|
||||
Confirmed anchors from the symbol map, both in `EOF.O` (end-of-frame / DAC):
|
||||
|
||||
- Fog machinery lives in board globals: `_eof_doFOG`, `_eof_FOG_rval/gval/bval`,
|
||||
`_eof_FOG_near/far`, `_eof_backR/G/B`, `_back_colour`. A "fog off" in pvision
|
||||
= clearing `_eof_doFOG`.
|
||||
- Colour/DAC path: `_tblcpy`, `_configEMCs`, `_send_em`, and the "texture
|
||||
colour map table (8×32-bit)" hacked per-frame in `EOF.C`.
|
||||
- `DNC.C::luminize` exists but is a **texture-MIP** helper (24-bit texel →
|
||||
6-bit luminance), *not* the screen grayscale — a near-miss, noted so nobody
|
||||
re-chases it.
|
||||
|
||||
The effect dispatch is `VR_REMOT.O::_remote_velocirender` → the SFX handlers
|
||||
(`SFX.O`, `_PAZsfx`). In our source vintage `PAZsfx` only knows explosion codes
|
||||
0/1/2 (no −1/−2), confirming pvision is newer code — i.e. **only in
|
||||
`VREND.MNG`**, which has no symbols.
|
||||
|
||||
## Progress: the i860 disassembler (scope = FULL annotated, operator-chosen)
|
||||
|
||||
No objdump/Ghidra i860 support exists, so we build the decoder ourselves,
|
||||
validated against the AS860 `.S`↔`.O` pairs (exact ground truth).
|
||||
|
||||
- [x] **Word format cracked & CONFIRMED** — REG/CTRL layout, control-reg
|
||||
numbers, from `CONTROL.O` (see `i860-encoding.md`).
|
||||
- [x] **Primary opcode map validated** — `derive860.py` aligns `.S`↔`.O` by
|
||||
proving every label offset against the COFF symbol table, then harvests
|
||||
`op6→mnemonic`. Clean data matches the published i860 map. Table in
|
||||
`i860-encoding.md`.
|
||||
- [x] **Decoder built & validated** — `dis860.py`; 98% base-mnemonic match on
|
||||
the clean pair (XFLGHTPR 220/224). Residual misses are `.S`/`.O` drift in
|
||||
TRISTRIP, not decode errors, plus 4 cosmetic FP graphics-op variant names.
|
||||
- [x] **Disassembler proven on real firmware** — `dis860.py --list VREND.MNG
|
||||
0xe940` decodes `_velocirender_statistics` as `adds 0x1,r0,r29 … bri r1`
|
||||
(loads 1, returns) — matching what our virtual board replies for that wire
|
||||
command (`vrboard do_statistics → 1`). Independent end-to-end confirmation.
|
||||
- [~] **Name functions in `VREND.MNG`** — `sigmatch860.py` (reloc-invariant,
|
||||
tolerant). Anchors landed (velocirender_statistics, reg_dump,
|
||||
tri_zb_d_s_texm, dpl_init_light…) but the only objects we have are 1994
|
||||
DPL3/VRENDER vs the 1996 `.MNG`, so C code has drifted and dense naming
|
||||
isn't possible from signatures alone. The stable hand-asm rasterizers/math
|
||||
match; navigate the rest from these anchors + the wire dispatch.
|
||||
- **Load format (reversed):** header = 3 LE size words at offset 0
|
||||
(`.text`=0x39ec0, `.data`=0x1e940, `.bss`=0x4b40); raw `.text` begins at
|
||||
file offset **0x0c** (`0xf0400000` = entry veneer); `.data` follows;
|
||||
a tiny section-name stub (`.text`/`.data`/`.bss`) trails — **no symbol
|
||||
table** (stripped image, so signatures are required).
|
||||
- Signatures must be **relocation-invariant**: mask the disp/immediate
|
||||
fields of call/br/ld/st-with-address before matching `.O` code against
|
||||
the linked `.text`.
|
||||
- [ ] **Annotate** — whole-image reference; then walk the effect path
|
||||
(`_remote_velocirender` → SFX) to the `type == -1` branch and read what
|
||||
it does to `_eof_doFOG` and the DAC/colour map, settling pvision.
|
||||
|
||||
## The endgame: preservation, not interpretation
|
||||
|
||||
dpl3-revive (the GL bridge) *interprets* the wire — a modern reconstruction of
|
||||
what the protocol means. Valuable for a playable pod, but it is our reading of
|
||||
the hardware. The archival-correct path is to **run the original `VREND.MNG` in
|
||||
an i860 emulator**: the board's own code builds the display lists, runs the real
|
||||
`SCANLINE.SS` rasterizer, programs the DAC, and writes a framebuffer we capture
|
||||
— **bit-exact to the hardware**. Every fidelity question (pvision, point
|
||||
sampling, fog, gamma, filtering) then stops being "did we interpret this right?"
|
||||
and becomes "the board's code did this."
|
||||
|
||||
This disassembler is the front half of that emulator (same decode; add execution
|
||||
semantics). Two tiers:
|
||||
1. **Functional-accurate** — i860 core + FP + the board's memory-mapped
|
||||
peripherals (link/DMA, the `_configEMCs` units, DAC, video timing, reversed
|
||||
from the firmware + AS860 source). Yields bit-exact images. The big prize.
|
||||
2. **Cycle-accurate** — model the i860 dual-issue pipeline / FP latency / delayed
|
||||
branches. Adds timing fidelity (frame rate, the long-mission render decay).
|
||||
Hardest; last.
|
||||
|
||||
Keep the GL bridge as the fast/interactive path; the i860 emulator becomes the
|
||||
high-fidelity **reference** — and the oracle that says whether the bridge is
|
||||
right.
|
||||
|
||||
## Live disassembly findings (RPLIVE/VREND.MNG)
|
||||
|
||||
The wire-command **dispatch table** is decoded and mapped (per-action case block
|
||||
`[mov r16,r4; call <handler>; mov r8,r16; br exit]`; the dispatcher indexes by
|
||||
action code). Confirmed against our virtual board:
|
||||
|
||||
| action | handler | | action | handler |
|
||||
|--|--|--|--|--|
|
||||
| 9 draw_scene | 0xe340 | | 16 readpixels | 0xeb78 |
|
||||
| 10 draw_scene_complete | 0xe4c0 | | 17 hspcode | 0xec80 |
|
||||
| 11 list_add | 0xe6c0 | | 18 code860 | 0xece0 |
|
||||
| 12 list_remove | 0xe780 | | 19 data860 | 0xed60 |
|
||||
| 13 morph | 0xe810 | | 20 bss860 | 0xed90 |
|
||||
| 14 version | 0xe888 | | 21 args860 | 0x56a0 |
|
||||
| **15 statistics** | **0xe940** (returns 1 ✓) | | 22 set_geom_verts | 0x5710 |
|
||||
| | | | 23 set_texmap_texels | 0x5738 |
|
||||
|
||||
Higher actions (the effect/psfx family, incl. pvision `type -1/-2`): handlers at
|
||||
**0xedd8, 0xee14**; action-27 case is the default/error path (loads a string ptr,
|
||||
calls the log util 0x2c330). **`0xee14` = the effect handler** — prologue reads
|
||||
`[msg+0]` into r29 (the effect **type**), `[msg+4]` into r30, logs via 0x4408,
|
||||
then branches (`btne` @0xee4c → 0xee7c; calls 0xb688, 0x2c330).
|
||||
|
||||
### NEXT (pvision, definitive): walk 0xee14's branches to the `type == -1/-2`
|
||||
case and read what it writes — the board-side fog disable (`_eof_doFOG`) and any
|
||||
DAC/colour-map change. That is the ground-truth answer to grayscale-squash+defog
|
||||
vs a palette. (Anchor utils seen: 0x2c330 = most-called util/log, 0x4408 =
|
||||
logger, 0xb120/0xb688 = effect helpers.)
|
||||
@@ -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} ===")
|
||||
@@ -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))
|
||||
@@ -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>")
|
||||
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 931 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 975 KiB |
|
After Width: | Height: | Size: 968 KiB |
|
After Width: | Height: | Size: 447 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 810 KiB |
@@ -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 (0x00–0x07);
|
||||
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.
|
||||
@@ -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
|
||||
|
||||