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

Predator/IR vision: reverse-engineered from the original firmware and
confirmed by the build team -- it is the Division board's TEXTURE-VALUE RAMP
mode (a "check your texture maps" diagnostic the devs hijacked), NOT a
grayscale squash or a false-colour palette. Located in VREND.MNG (effect
handler @0xe6c0, wire action 0x1b, type -1 ON / -2 OFF); ramp colours from
VR_DRAW.C. Renderer reworked to match: vrview_gl now does the 4-ramp
lerp(color0,color1,luminance(texel)) in the mesh pass (grayscale+defog
removed). Live-rendered on a new night-clear arena egg; crew A/B verdict
pending.

Firmware-decomp toolchain (emulator/firmware-decomp/), all built from the
project's own artifacts and validated:
- coff860.py    i860 COFF reader (symbols/sections), names match AS860 source
- derive860.py  derives the i860 opcode map from matched .S<->.O pairs
- dis860.py     i860 disassembler (98% on clean ground truth; proven on
                VREND.MNG -- velocirender_statistics decodes correctly)
- sigmatch860.py reloc-invariant signature matcher onto the stripped image
- i860-encoding.md / FIRMWARE-SYMBOLS.txt / README.md

PVISION-IMPLEMENTATION-GUIDE.md: self-contained hand-off for the BT411 team.

HARDWARE-ARCHITECTURE.md + hardware-photos/ (15 board shots): the Division
VelociRender card is a 2-board stack driving a 3-processor pipeline --
INMOS IMS T425-J25S (comms/control, runs vrendmon.btl) + Intel i860 XP-50 (FP
geometry, runs vrender.mng) + Division PXPL IGC 5.2 ASIC with ~48x PXPL EMC
5.1 (UNC Pixel-Planes-5 SIMD array; "EMC" = the firmware's configEMCs) +
Analog Devices ADV7150 RAMDAC + NTSC. Plus the VWE Video Distribution Board
(P/N 1404: AMD MACH130 + 3x Brooktree Bt477) for the 3-VGA-head cockpit split.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-14 22:29:53 -05:00
co-authored by Claude Fable 5
parent 5593d57d48
commit 1323397a50
28 changed files with 2436 additions and 27 deletions
+100
View File
@@ -0,0 +1,100 @@
"""Empirically derive the i860 opcode table from matched .S (PGI/AS860 assembly)
and .O (COFF machine code) pairs. Byte-accounting is proven correct by
cross-checking every .S label against its .O symbol offset; only then is the
op6->mnemonic harvest trusted."""
import sys, struct, re, glob, os
sys.path.insert(0, r'C:\VWE\TeslaRel410\emulator\firmware-decomp')
from coff860 import parse
DATASZ = {'.long':4, '.word':4, '.int':4, '.float':4, '.single':4,
'.double':8, '.short':2, '.half':2, '.byte':1, '.string':None}
def parse_S(path):
"""Return (labels{name:off}, instrs[(off,mnem,line)]) for the .text section."""
labels, instrs = {}, []
off = 0; sect = '.text'; in_text = True
for raw in open(path, encoding='latin1'):
line = raw.split('//', 1)[0].rstrip()
s = line.strip()
if not s:
continue
# label(s): possibly "name:" / "name::" possibly followed by an instr
while True:
m = re.match(r'^([A-Za-z_.$][\w.$]*)::?\s*(.*)$', s)
if m and (m.group(2) == '' or not m.group(2).startswith('.')) and ':' in s[:m.end(1)+2]:
# confirm it's really a label (colon right after the name)
after = s[m.end(1):]
if after.startswith('::') or after.startswith(':'):
if in_text: labels[m.group(1)] = off
s = after.lstrip(':').strip()
if not s: break
continue
break
if not s:
continue
if s.startswith('.'):
d = s.split()[0].lower()
if d in ('.text',): sect='.text'; in_text=True
elif d in ('.data','.bss','.section'): sect=d; in_text=False
elif d == '.align' and in_text:
n = int(s.split()[1]); off = (off + n - 1) & ~(n - 1)
elif d in DATASZ and in_text:
sz = DATASZ[d]
if sz is None: # .string -> count bytes incl NUL
txt = s[s.index('"')+1:s.rindex('"')]
off += len(txt.encode('latin1').decode('unicode_escape')) + 1
else:
vals = s[len(d):].split(',')
off += sz * len([v for v in vals if v.strip()])
# other directives: no size
continue
if in_text:
mnem = s.split()[0]
instrs.append((off, mnem, s))
off += 4
return labels, instrs
def load_text(opath):
o = parse(opath); d = open(opath, 'rb').read()
text = syms = None
for (name, vaddr, size, scnptr, nreloc, sf) in o['sections']:
if name == '.text': text = d[scnptr:scnptr+size]
syms = {nm: v for (nm, v, sn, ty, sc, na) in o['syms'] if sn == 1 and sc in (2,3)}
return text, syms
def derive(spath, opath, table):
labels, instrs = parse_S(spath)
text, syms = load_text(opath)
# cross-check label offsets vs symbol table
common = [k for k in labels if k in syms]
good = [k for k in common if labels[k] == syms[k]]
bad = [(k, labels[k], syms[k]) for k in common if labels[k] != syms[k]]
tag = os.path.basename(opath)
print(f"[{tag}] instrs={len(instrs)} textwords={len(text)//4} "
f"labels_ok={len(good)}/{len(common)}", end='')
if bad:
print(f" MISALIGNED e.g. {bad[:3]}")
return False
print(" ALIGNED")
for (off, mnem, line) in instrs:
if off + 4 > len(text): break
w = struct.unpack_from('<I', text, off)[0]
op6 = (w >> 26) & 0x3f
table.setdefault(op6, {}).setdefault(mnem, 0)
table[op6][mnem] += 1
return True
if __name__ == '__main__':
base = r'C:\VWE\TeslaRel410\sda4\DPL3\VRENDER\AS860'
pairs = []
for s in glob.glob(os.path.join(base, '*.S')):
o = s[:-2] + '.O'
if os.path.exists(o): pairs.append((s, o))
table = {}
for s, o in sorted(pairs):
try: derive(s, o, table)
except Exception as e: print(f"[{os.path.basename(o)}] ERROR {e}")
print("\n=== derived op6 -> mnemonic(count) ===")
for op6 in sorted(table):
items = sorted(table[op6].items(), key=lambda x: -x[1])
print(f" {op6:#04x} ({op6:06b}): " + ", ".join(f"{m}×{c}" for m,c in items))