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:
@@ -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))
|
||||
Reference in New Issue
Block a user