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>
588 lines
34 KiB
Markdown
588 lines
34 KiB
Markdown
# Cockpit cage, glances, and the "black rear view" (2026-07-11)
|
|
|
|
> **SUPERSEDED IN PART -- see "2026-07-12: the BT411 cross-check" at the
|
|
> bottom.** The geometry forensics below stand; the identities and the fix
|
|
> plan were revised after reading the BT411 port's reconstruction.
|
|
|
|
Operator report: hat-down (rear glance) = black display; sliding the seat cam
|
|
back showed "a large black cube-ish". Full forensics from a live-session
|
|
fifodump snapshot (offline renders + wire scans). Resolution shipped in
|
|
`live_bridge.py` (cage twist + unframed glances).
|
|
|
|
## What the geometry actually is
|
|
|
|
Two cockpit fixtures ride the player mech (own-chain, `gated`):
|
|
|
|
| inst | dcs | mount (wire) | shape |
|
|
|---|---|---|---|
|
|
| `9fd` **cockpit CAGE** | `9fe` | `dcs_link` DIRECTLY under the vehicle root (`7 9fc 9fe`) | 45 verts / 40 tris, 21x21x42, **all faces inward** -- open framed panes forward, SOLID textured wall aft; invisible from outside (backface-culled) |
|
|
| `a11` canopy trim | `a10` | linked into the head rig (`7 a12 a10`, `7 a10 a3e`) | 5 geoms, r=3.4, at head height; the near-cull (<10 units) hides it in first person |
|
|
|
|
Both DCS create-records carry **zone `9fb`** (type-2 node, created at mission
|
|
staging alongside the two boot zones). The three ER-laser beam instances are
|
|
own-chain + gated too (r=2000; the fixture filter excludes them by radius).
|
|
|
|
- **The black rear view = the cage's solid rear wall**, verified by rendering
|
|
the snapshot offline: forward through the panes is a perfect canopy view,
|
|
rear is wall-to-wall black. Seat-cam slid back = the same shell seen from
|
|
its edge (plus the canopy un-culling past 10 units).
|
|
- The mech has NO real model instance of itself in the scene -- from outside
|
|
(chase cam) the player mech IS a small black box. Other mechs are full
|
|
models; your own is cage + canopy + beams only.
|
|
|
|
## What the wire does NOT do
|
|
|
|
Scanned the whole session dump (~43k records):
|
|
|
|
- The game **never updates** the cage/canopy nodes after staging -- no
|
|
arm/disarm, no DCS re-pose, nothing per-glance.
|
|
- **No view switching** during glances: ONE type-3 view node (handle 3); its
|
|
36 flushes are projection/fog only (incl. the mission-start fog sweep and
|
|
the screen dims -- the game itself programs **832x512**, w12/w13).
|
|
- Zone masks (`ffffffff` -> `0fffffff`) are set once at boot/staging for all
|
|
zones uniformly -- not per-glance.
|
|
- Texture alpha-cutout is NOT the rear-wall mechanism: zero of the 15 texmap
|
|
nodes carry the alpha flag, and the wall texels (17,17,17 sum=51) wouldn't
|
|
pass the <=24 discard threshold anyway.
|
|
- Glances arrive as ordinary head-DCS chain motion (nothing else flushes).
|
|
|
|
## Ground truth (operator, ran the real pods)
|
|
|
|
Tank-turret model: **torso twist = turning the turret; the cockpit cage
|
|
tracks the twist.** The hat moves the PILOT'S HEAD on top of that (twist 90
|
|
right + hat right = looking back down your path). And **glance views are
|
|
UNFRAMED** -- the real pods showed a clean world view with no cockpit framing
|
|
on hat glances. (So hat-rear was never "through the cage's rear wall" -- the
|
|
real views simply didn't render the cockpit fixtures.)
|
|
|
|
## Fix shipped (live_bridge.py -- bridge policy, Dave's core untouched)
|
|
|
|
1. **Cage twist**: `hook_chain_matrix` wraps `Renderer.chain_matrix`; chains
|
|
rooted at the cage DCS get the camera's composed torso-twist yaw
|
|
pre-multiplied in model space (`v @ Y @ M`; cage chain is [cage_dcs, root]
|
|
with identity local, so Y lands exactly as a yaw about the mech axis).
|
|
`CAGE_TWIST_SIGN` flips (default follows the camera's twist).
|
|
2. **Unframed glances**: `fp_cam` now reports `.glance` = look direction
|
|
deviating > `GLANCE_DEG` (45) HORIZONTALLY from the twisted hull heading
|
|
(stick-Y pitch can't trip it; TORSO.SUB vertical limits are +10/-30).
|
|
While glancing, `render()` draws with the cockpit fixtures filtered out.
|
|
`GLANCE_HIDE=0` disables.
|
|
3. Fixture identification (`cockpit_refresh`, after every SceneCache
|
|
rebuild): own-chain + gated + not billboard + radius < 100; the cage =
|
|
the one hanging directly under the root (chain length 2, radius > 5).
|
|
Verified on the snapshot: picks exactly {9fd, a11}, cage dcs 9fe.
|
|
|
|
## Open / related
|
|
|
|
- **Laser aim vs torso** (operator): beams render/aim along the hull heading,
|
|
missiles track the torso. The beam chains ride the DCS rig, and the rig's
|
|
flushed values EXCLUDE joint angles (the torso joint arrives only as 0x1f
|
|
(sin,cos) entries) -- composing joint rotations into instance chains at the
|
|
right chain position is the proper fix (would move the beams' origin AND
|
|
direction with the twist). The pick ray (CAM backchannel) already follows
|
|
the twisted look -- likely why missiles track.
|
|
- Canopy trim (`a11`) twist: same joint-composition gap; currently masked by
|
|
the near-cull in first person.
|
|
- Whether the ORIGINAL board treated zone-9fb instances specially
|
|
(camera-space / per-view zone enables) is still undetermined -- our fix
|
|
implements the operator-specified behavior at the bridge level instead.
|
|
- The pod crashed at session end with a guest "Illegal Unhandled Interrupt 6"
|
|
storm (game-side; unrelated to the bridge -- device untouched today).
|
|
|
|
## 2026-07-12: the BT411 cross-check (C:\VWE\BT411, fresh pull) -- REVISED PICTURE
|
|
|
|
The concurrent BT411 port hit the SAME black-enclosure cockpit problem, and
|
|
their reconstruction settles the identities and the authentic mechanism:
|
|
|
|
1. **Instance `a11` = `MAX_COP.BGF`, the REAL cockpit shell** -- byte-proven:
|
|
272 verts / extent 2.5x2.3x3.3 / Y 0.4..2.7 matches a11's five geoms
|
|
exactly (every mech has one: `*_COP.BGF`, measured with
|
|
scratchpad cop_bounds.py). Per BT411 (open-questions.md): it is **the
|
|
TORSO segment's inside-skeleton mesh** -- so it rotates with the torso on
|
|
the real rig (operator's turret model confirmed) -- and **its window panes
|
|
are PUNCH texels**: "with punch live the black pane texels become holes
|
|
and the world shows through the frame". THE PUNCH CUTOUT is why real
|
|
glances read as (nearly) unframed world -- the shell is never hidden. The
|
|
operator's memory of seeing "the cage or the mech's arm" on left/right
|
|
glances fits: thin frame lines + arm through the punched side panes.
|
|
(BT411's interim policy is ours too: they hide `_cop` by name filter
|
|
until their punch/skeleton branch works, `BT_INSIDE_COCKPIT=1` reveals.)
|
|
2. **Instance `9fd` (the 21x21x42 inward box, root-linked, hidden-until-
|
|
armed) = the mission fade shroud** (`BTPOVStartEndRenderable`, armed for
|
|
MissionStartingState=3 / MissionEndingState=4 with dplMainZone+
|
|
dplDeathZone). Session 1's "black rear wall" happened because THAT
|
|
mission had ENDED (egg timer expired) and the shroud was armed; session 2
|
|
(fresh mission) never armed it -- which is why the twist hook "did
|
|
nothing": the visible frame was a11 all along.
|
|
3. **Glances = `EyepointRotation`** (mech attribute; mapper look states
|
|
LookLeft/Right/Behind/Down; LookBehind = yaw pi + pitch), composed into
|
|
the view every frame by DPLEyeRenderable. The look commit ALSO walks the
|
|
weapon roster setting the weapon+0x3e0 gate per look state -- **glancing
|
|
inhibits FIRE, it does NOT hide cockpit fixtures** (my "camera children"
|
|
read was wrong; those offsets are the weapon fields from the fire-gate
|
|
disasm). BT411 has the whole commit block deferred (offset conflicts) --
|
|
their port has no glances yet either.
|
|
4. **On our wire, glances/twist ride the 0x1f articulation batches only**
|
|
(cam-chain DCS bodies flush exactly once at creation -- verified across
|
|
the whole session dump). The "hats click but nothing renders" at session
|
|
end: the final 10% of the dump has ZERO 0x1f batches -- the game had
|
|
stopped articulating entirely (wedged/ended state, pre-crash), not a
|
|
bridge regression.
|
|
|
|
### GLANCES SOLVED + LIVE-CONFIRMED 2026-07-14 -- it was a KeyError crash
|
|
|
|
**Operator confirmed hat glances now render correctly.** The whole saga
|
|
resolved to a one-line bug of my own making, found via the operator's key
|
|
symptom: "when the hat is held the screen STOPS rendering and snaps to the
|
|
current view on release." That = render() was THROWING during glances.
|
|
|
|
Root cause: render()'s glance-hide guard referenced `_ckpt['fix']`, but I had
|
|
renamed that key to `'fixh'` days earlier (cockpit_refresh) and never updated
|
|
this line -> **KeyError EVERY frame where fp_cam.glance was true** (i.e. every
|
|
glance frame). The exception was swallowed by render()'s try/except, so NO
|
|
frame drew during a hold (screen froze), and on release fp_cam.glance went
|
|
false, the guard short-circuited before the bad key, and the draw resumed
|
|
("snaps to current"). fp_cam was computing the glance camera correctly the
|
|
WHOLE time (out_yaw provably swung with the hat) -- the crash was downstream
|
|
of it, in the draw dispatch.
|
|
|
|
Fix: REMOVED the glance-hide block entirely (always `r.draw(board)`). This
|
|
both kills the crash AND implements the already-decided descope (pilots
|
|
confirm the canopy stays visible during glances -- a11/MAX_COP has no rear
|
|
geometry so rear reads unframed naturally). The glance rides the camera via
|
|
fp_cam, which reads the eye-DCS 0x1f absolute pose -- exactly the authentic
|
|
game->board wire op the operator insisted it was.
|
|
|
|
**The operator was right at every turn:** it was a wire operation (action
|
|
0x1f eye-DCS reflush), NOT input/transport/binding; do NOT tee from vRIO. My
|
|
successive "game-side / fix_degenerate / not-emitted" theories were all
|
|
wrong; the persistence on "find where we drop it in the data path" was
|
|
correct -- we were crashing on it.
|
|
|
|
### REAR VIEW (hat-down) 2026-07-14 -- canopy drop, LIVE-CONFIRMED
|
|
|
|
Right after glances started rendering, the rear glance (hat-down / LookBehind)
|
|
came up BLACK, and sliding the seat back showed a black box. Chased it:
|
|
- NOT the 9fd shroud: in gameplay 9fd is inst_visible=0 (unarmed, not drawn);
|
|
hiding it was a no-op. The live probe's "own drawn" list showed the ONLY
|
|
own-mech instance drawn is a11 (the canopy, MAX_COP, r3).
|
|
- The canopy is a shell around the head: open front, dark solid BACK. Looking
|
|
forward = through the open front (world + strut framing); looking behind =
|
|
the canopy's dark interior back = black. GL render confirmed: rear WITH a11
|
|
= black, rear WITHOUT a11 = full arena (geometry is all there behind).
|
|
|
|
OPERATOR (ran the real pods): **hat-down on original hardware gave a CLEAR
|
|
rear view, NO canopy framing** (left/right glances DO keep the canopy). So the
|
|
authentic rule is direction-dependent. Fix: fp_cam computes glance_dev (look
|
|
deviation from twisted hull heading); render() drops the canopy fixtures when
|
|
glance_dev > REAR_DEG (110 deg default -- left/right ~50 deg keep it, rear
|
|
~180 deg drops it). Operator confirmed "acceptable behaviour."
|
|
|
|
COCKPIT CAMERA COMPLETE + ALL LIVE-CONFIRMED: twist (turret-true, zero
|
|
parallax) + lasers follow torso + hat glances left/right (canopy framed) +
|
|
hat-down clean rear view + the pipe transport (vRIO/vPLASMA). Remaining is
|
|
cosmetic (IR/thermal palette on action 0x1b, texture interpolation, per-mech
|
|
seat trim). Diagnostic scaffolding (glance_probe, fp_cam.out_yaw telemetry,
|
|
own-drawn list, BRIDGE_AUTOSAVE) left in, harmless; trim in a cleanup pass.
|
|
|
|
### GLANCE: THE forensics trail 2026-07-14 (era A/B -- superseded by the fix above)
|
|
|
|
**There is no glance regression -- the wire NEVER carried a hat glance, in
|
|
any era.** Prompted by the operator's challenge ("engine unchanged, only the
|
|
transport changed com0com->pipe -- so why did it break?"), diffed a 7/07
|
|
working-era capture vs 7/13:
|
|
- Cam-chain DCSs flush ONCE at construction in BOTH eras; no per-glance
|
|
reflush ever (era_diff.py).
|
|
- `fix_degenerate` provably changes the look yaw by ZERO (bisect_glance.py:
|
|
raw == fixd at every checkpoint) -- EXONERATED as the culprit.
|
|
- Decomposing the look into hull + twist + residual (find_glance.py): the
|
|
residual is FLAT across both 7/07 captures (only the +-180 chain-
|
|
construction artifact). No independent glance rotation in the wire.
|
|
|
|
**What actually happened (from our own memory + these forensics):** the game
|
|
never emitted a chain-independent glance through our HLE device. The single
|
|
time hat glances "worked" (2026-07-07) was a BRIDGE fp_cam hack (recover the
|
|
glance from the degenerate chain, swap yaw<->pitch) that the operator
|
|
confirmed "momentarily" -- and it was REVERTED the same day because it
|
|
corrupted stick-Y vertical aim into yaw. The "source fix" that replaced it
|
|
(`fix_degenerate`) is inert for glances (proven above). So there is NOTHING
|
|
to bisect: glances were a reverted experiment, not a lost feature; the
|
|
com0com->pipe transport change coincided with "stopped working" purely by
|
|
timing. This RETIRES the prior "game-side binding / keyboard-dead" and
|
|
"fix_degenerate ate it" theories -- both wrong.
|
|
|
|
**Path forward (operator principle 2026-07-14: DO NOT tee from vRIO -- the
|
|
glance is an authentic game->board WIRE op; find where we drop/misdecode
|
|
it).** This is CORRECT and cracked open the key protocol fact:
|
|
|
|
**RUNTIME DCS UPDATES TRANSMIT AS ACTION 0x1f, NOT 3.** BT411 FUN_0048ffa4
|
|
(flush dirty-DCS list) + FUN_0048e440 -> FUN_00492580(**0x1f**, ...): a
|
|
runtime DCS matrix update -- INCLUDING the eyepoint reflush -- packs per
|
|
DCS either 12 floats (type-0, full matrix) or 2 words (type 1-3) and sends
|
|
on **action 0x1f**, the same action as the articulation batch. So every
|
|
earlier "no reflush" scan (which looked at action 3) was blind to it. The
|
|
glance lives in the 0x1f stream.
|
|
|
|
**Where our render stands vs that:** the eye DCS = the node the game
|
|
list-adds to the view (6df in glance_snap); it IS a 12-float 0x1f node and
|
|
IS in our cam_chain -> chain_matrix already applies it, so a real glance
|
|
reflush WOULD render for free. BUT: our fp_cam only composes 0x1f *joints*
|
|
(2-float sin/cos) for cam_chain members (the twist sum); a glance carried
|
|
as a 2-word entry on a DCS OUTSIDE our heuristic chain would be parsed and
|
|
DISCARDED -- a concrete renderer gap if that's the form.
|
|
|
|
**What the captures show (glance_snap, plateau.py):** NO 0x1f handle shows a
|
|
sustained hold-then-return matching the 5s hat holds -- the eye DCS (6df)
|
|
moves only with hull/drive, walk joints just oscillate (runs 15-45), static
|
|
mounts sit at 180. So in OUR broken-setup capture the game isn't emitting a
|
|
glance reflush. But this capture is from the NON-working setup, so it can't
|
|
distinguish "renderer drops it" from "game doesn't emit it" -- need a
|
|
capture that contains a real glance.
|
|
|
|
**DECISIVE next step (probe rewritten -- glance_probe now watches the RIGHT
|
|
signal: eye/leaf 0x1f yaw + out-of-chain 0x1f movers, per 4s report):** a
|
|
NARRATED 5s hold next session settles it -- leaf yaw plateaus with the hold
|
|
= game emits, we must render it (bridge application bug, find it);
|
|
out-of-chain mover plateaus = we parse-and-drop it (apply that handle);
|
|
both flat = game not emitting (then it's upstream, the hat->look-state
|
|
binding). NO vRIO tee.
|
|
|
|
### GLANCE MECHANISM DECODED 2026-07-14 (disasm) + eyepoint pipeline (reference)
|
|
|
|
Chased the hat glance through the shipped BTL4OPT binary (BT411 decomp).
|
|
The camera path, end to end:
|
|
- The piloted mech builds as **insideEntity** (btl4vid.cpp:300-304) -> the
|
|
**eyepoint camera is a board DCS** created by **DPLEyeRenderable**
|
|
(ctor @004579a8): eye DCS handle at [this+0x10], and a pointer to the
|
|
mech's **EyepointRotation** attribute at [this+0x48].
|
|
- Per frame, **DPLEyeRenderable update @00457b48**: if EyepointRotation
|
|
changed (vs cached [this+0x4c]), recompose the eye DCS matrix from the
|
|
euler and mark it dirty (@0045fbf4 appends to the renderer's dirty-DCS
|
|
list @+0x2c8) -> the eye DCS gets **reflushed to the board**.
|
|
- EyepointRotation (mech+0x360) is written by the **mapper look-commit**
|
|
(mechmppr.cpp KeypressMessageHandler / InterpretControls, part_013.c:418):
|
|
when look-state (0x198, derived from lookLeft/Right/Behind/Down at
|
|
param+0x134..0x140) changes, it writes the euler (LookBehind = yaw pi =
|
|
0x40490fdb, pitch from torso+0x570) and re-slaves the camera children.
|
|
|
|
**So a glance = an eye-DCS reflush, and that DCS is IN our cam chain --
|
|
we'd render it for FREE if it happened.** It doesn't: the glance-probe
|
|
premise (validated on glance_snap: all 11 cam-DCS reflushes are at staging
|
|
22.6%, ZERO during the 80-98% holds) proves the game never moves the eye
|
|
during our glances.
|
|
|
|
**Root cause (narrowed):** EyepointRotation never changes => the mapper
|
|
look-commit never fires => the look-state fields never change. Those fields
|
|
are set by the ARROW keys via KeypressMessageHandler (keyboard is DEAD in
|
|
the pod, see above) -- and the HAT button reaches an audio-mapped action
|
|
(hence the glance SOUND) but, in this build/config, does not set the
|
|
look-state fields. Both glance routes are therefore blocked in the current
|
|
-egg trim; "worked on 7/07" was a different session config. Overturns two
|
|
earlier claims: NOT a view/zone re-parent (the only view list-ops are at
|
|
staging) and NOT a wire-parse blind spot (zero 0x1f parse failures).
|
|
|
|
**Decisive next step (telemetry SHIPPED, live_bridge glance_probe):** a
|
|
NARRATED hat hold ("holding left NOW") vs the `GLANCE-PROBE: cam DCS ..
|
|
REFLUSHED` line settles it: a reflush during a hold = camera IS moving
|
|
(bridge bug, unlikely per forensics); silence = game-side gate confirmed,
|
|
and the fix moves to the input binding (make the hat set the look-state
|
|
fields -- CTL binding / the same fields the dead arrow keys drive), OR a
|
|
bridge-synthetic glance (compose glance yaw in fp_cam like twist -- but the
|
|
bridge needs a hat-state feed it doesn't currently have; the game's
|
|
per-hat instance-visibility blip on inst-701-family + zone is the only
|
|
wire-observable candidate and needs the narrated capture to decode).
|
|
The in-game keyboard being dead is now ON the glance critical path -- fixing
|
|
it (or binding the hat to look) may be the whole fix.
|
|
|
|
### PUNCH-CUTOUT DESCOPED 2026-07-13 (pilot testimony)
|
|
|
|
Operators who flew the pods report **seeing canopy geometry when hat-glancing
|
|
left/right** -- the shell structure was VISIBLE in glance views. This matches
|
|
a11/MAX_COP's measured geometry exactly: all five geoms sit FORWARD of the
|
|
head (bbox z -3.4..-0.7, NO rear geometry), so rear glance = naturally
|
|
unframed world and side glances = sweeping past the struts. NO punch cutout
|
|
is needed for glance correctness; the 7/12 "black rear wall" was the 9fd
|
|
fade shroud (mission-end state), not the canopy. Consequences:
|
|
- The punch-cutout work item is DROPPED for the canopy (BT411's D3D port
|
|
still needs it for their blx_cop texture; and the punch texel semantics
|
|
may still matter for OTHER textures someday -- footnote only).
|
|
- **The GLANCE-HIDE policy must RETIRE once glances render**: authentic
|
|
glances show the canopy sweeping past, so hiding fixtures during glances
|
|
is anti-authentic. Keep GLANCE_HIDE only until the eyepoint pipeline
|
|
works, then default it OFF.
|
|
|
|
### Revised fix plan
|
|
|
|
- **The authentic fix for the shell = implement the PUNCH texel cutout** for
|
|
`_cop` textures in the renderer (their windows render as holes). Our wire
|
|
carries NO texmap alpha-flag words (all 15 = 0), and the shell's dark
|
|
texels (17,17,17, sum 51) exceed vrview_gl's alpha-cut threshold (<=24) --
|
|
so BT punch is a DIFFERENT flag/semantic than the FLYK-era cutout: find
|
|
where the punch attribute travels on the VPX wire (texture upload header?
|
|
b2z tag?) or key it on the `_cop` texture content. With punch live, the
|
|
glance-hide policy for a11 RETIRES (authentic = shell always on, windows
|
|
see through); keep hiding 9fd behavior as-is (it is a fade shroud -- its
|
|
blackness is the point).
|
|
- **Twist**: a11 is torso-mounted per BT411, and the operator watched it
|
|
stay chassis-locked -- so our chains genuinely lack the torso joint
|
|
rotation (confirmed joint-composition gap). Bridge yaw hook targets
|
|
fixture DCS a10; first live test DOUBLED (sign inverted in model space);
|
|
`CAGE_TWIST_SIGN=-1` is loaded but UNTESTED (sim was retired for the
|
|
night). The proper engine-level fix stays: compose the 0x1f joint
|
|
(sin,cos) rotations into instance chains at the joint DCS -- BT411's
|
|
gyro/eye-joint reconstruction (IntegrateEyeJoint @004b2ec0, currently a
|
|
NaN-stub for them) is the reference for WHERE the joints sit.
|
|
- **Lasers vs missiles**: BT411 confirms the crosshair = torso boresight
|
|
(stick twists the torso to aim; no free-aim on twist mechs). Their
|
|
open item (b): "on a TWIST-CAPABLE mech the crosshair should deflect with
|
|
the torso twist AND the torso should visibly lead the legs". Our beam
|
|
render + aim ride the joint-less chain = hull heading; the pick ray (CAM
|
|
backchannel) is twist-aware = missiles track. Same joint-composition fix
|
|
covers the beams (origin AND direction).
|
|
- 9fd arming = mission state; a mission-review/production run will show the
|
|
start blackout + end blackout arming on the wire (watch instance body
|
|
word4 flushes on 9fd).
|
|
|
|
## 2026-07-13: twist SOLVED (root-pivot + mirrored basis); glances = wire-silent
|
|
|
|
First full pipe-transport session (namedpipe vRIO/vPLASMA: controls + plasma
|
|
confirmed good). Offline forensics on the glance/twist capture
|
|
(scratchpad glance_snap.fifodump, torso held to +68deg at 92.2%):
|
|
|
|
**LIVE-CONFIRMED 2026-07-13 night (operator): the canopy tracks the torso
|
|
twist (turret-true), and the LASER BEAMS follow the torso too.** Final
|
|
recipe: root-pivot yaw conjugation (M inv(Mr) Y Mr) at CAGE_TWIST_SIGN=+1
|
|
applied to ALL own gated instances (canopy + shroud + the three gun-mount
|
|
beam instances -- twist_dcs; beams excluded from the glance-hide set), plus
|
|
the camera EYE swung along the same turret arc in fp_cam (operator: the
|
|
head is rigid with the torso -- zero shell/view parallax; the eye-position
|
|
arc is the authentic world-view motion). Closes both "cage locked to
|
|
chassis" and "lasers fire where the chassis faces".
|
|
|
|
ALSO FOUND 2026-07-13 night: **in-game PC keyboard input is DEAD in the
|
|
pod** ('l' searchlight dev key, '+'/'-' zoom, arrows: nothing; DOS-prompt
|
|
typing fine). Never historically validated -- may have been broken forever.
|
|
Suspects for a cold test: the shipped binary's L4CONTROLS parse
|
|
(setenv echoes RIO,KEYBOARD so policy is right), or the VPX device's extra
|
|
SDL head windows eating keystrokes. Consequence: the arrow-key glance
|
|
experiment is VOID -- keyboard nulls prove nothing about the look pipeline.
|
|
Glance regression suspect narrowed to vRIO hold semantics (old COM build
|
|
glanced fine yesterday; glances are the hat's only HOLD-driven function;
|
|
ask vRIO's log: 88 on deflect, 89 only on release?).
|
|
|
|
**Twist sign CORRECTED 2026-07-13 late (rendered A/B, ab2_*.png): default
|
|
CAGE_TWIST_SIGN=+1.** The -1 default came from a numeric calibration that
|
|
mixed yaw-sign conventions; the rendered A/B at the held +68deg twist shows
|
|
+1 brings the shell around WITH the twist (turret behavior; the small
|
|
parallax = head-off-axis, authentic) while -1 rotates it OUT of view (the
|
|
"canopy does not rotate" live report -- it was leaving the frustum).
|
|
Also learned: handle namespaces alternate between missions (9fc/a10 vs
|
|
69c/6b0 rigs) -- never hardcode; the fixture detection handles it.
|
|
One live confirm pending.
|
|
|
|
**BT411 new-pull digest (2026-07-13 evening, renderer-relevant):**
|
|
- FOG restored (task #63): authored per-map/time/WEATHER in BTDPL.INI
|
|
(weather = an EGG field: clear/fog/soup); the arcade fog =
|
|
dpl_fog_type_pixel_lin (per-pixel linear) -- Dave's GL shader is already
|
|
per-pixel linear, so our fog is authentic; useful for checking per-egg
|
|
fog VALUES.
|
|
- Gait (task #59): piloted mech plays INTERIOR 'i'-suffix clips -- NO hip
|
|
lean (level cockpit) + jointshakey cockpit rattle; exterior mechs lean
|
|
-8/-11 deg into walk/run. Our wire carries whatever the game plays =>
|
|
the bridge inherits level+rattle natively; other mechs SHOULD visibly
|
|
lean (check live).
|
|
- Torso twist live (task #57/58) w/ centered boresight crosshair --
|
|
reference for our laser-aim/joint-composition item.
|
|
- Missile splash/damage-economy fixes + DAFC muzzle flash (task #60-62)
|
|
and the i860 specialfx forensics (065c114) -- board-side effect engine
|
|
decode, groundwork for rendering detonations/impact FX in the bridge.
|
|
|
|
**Twist -- measured and closed.** Yaw ledger: the camera = hull yaw (chain
|
|
Z-row, contains NO twist) + JOINTS twist sum; the canopy is HULL-LOCKED
|
|
natively (its world yaw tracks the hull exactly through twists). The three
|
|
confusing live observations all came from ONE bug: the hook's model-space
|
|
pre-multiply yawed the canopy about its own local origin, swinging it
|
|
sideways in an arc. Fix: compose the yaw ABOUT THE VEHICLE-ROOT AXIS
|
|
(M' = M inv(Mr) Y Mr, Mr = root pose from anim_abs) -- distance-from-axis
|
|
preserved exactly (pivot_check.py). One subtlety: the rig's root basis is
|
|
MIRRORED (det -1, same family as FP_RIGHT_SIGN/fliplr), which inverts the
|
|
yaw sense under conjugation -- hence default CAGE_TWIST_SIGN=-1, calibrated
|
|
offline (canopy lands exactly on native+twist). AWAITING one live confirm.
|
|
|
|
**Glances -- the game never puts them on the wire (in the test egg).**
|
|
Around-the-clock 5s holds captured; exhaustive scans of the tail: NO pose
|
|
node rotates, NO joint pulses match holds (the 12-joint flurry = the legs
|
|
WALKING; the +-64deg sweeps = the twist test itself; the zero-translation
|
|
anim nodes = STATIC rear-facing mounts at yaw 180 always -- NOT eyepoints;
|
|
my 6b3 identification was wrong). Meanwhile the game DOES react internally
|
|
(range-bar jumps on hats). Best hypothesis, matching BT411: the glance
|
|
renders via the GYROSCOPE's eye-joint integrators (IntegrateEyeJoint
|
|
@004b2ec0 -- incomplete/NaN-stubbed in their port too), and that subsystem
|
|
never emits in our -egg trim -- OR earlier "working glances" (7/07) came
|
|
through a path that today's state doesn't drive. The 7/07 sessions DID
|
|
render glances, so the signal exists under some conditions -- find what
|
|
differs (egg? mission state? control mode BAS/MID/ADV? gyro power?).
|
|
|
|
## 2026-07-13: vision modes (IR/thermal) + the shipped keyboard map
|
|
|
|
Looked up the rumored CTRL-W / CTRL-T vision keys in BTL4OPT.EXE
|
|
(BT411 decomp + reconstruction). Verdict: **no such bindings exist in the
|
|
shipped binary.** Plain `w`/`t` are pilot-target select (slots 0/3). The
|
|
RP-era engine SOURCE had ALT-W = wireframe and ALT-V = "predator vision"
|
|
debug keys (L4APP.cpp), but in the shipped BT binary the wireframe toggle
|
|
(@0045fdd8, prints "wireframe ON/OFF") is DEAD CODE -- no caller, no
|
|
pointer-table reference -- and no app-level ALT-key switch exists at all.
|
|
The tip most likely drifted from ALT-W/ALT-V or a different build.
|
|
|
|
**The REAL IR/thermal mode -- and how to render it:**
|
|
- `ThermalSight` subsystem (BT411 thermalsight.hpp, classID 0x0BDE, a
|
|
PowerWatcher): driven by the cockpit IR BUTTON (`requestedOn`), gated by
|
|
generator power + heat + being the locally-viewed mech.
|
|
- Its sim calls the global pvision toggle @0045fe44 (prints "pvision
|
|
ON"/"pvision OFF") which issues **`dpl_Effect`(type 0, handle 0,
|
|
{c,c,c}, mode) with mode = -1 (ON) / -2 (OFF)** -- a special view-effect
|
|
= the Division board's thermal palette flip. THAT is the wire signature
|
|
our renderer must honor: watch for the effect/fog-mode carrying -1/-2 on
|
|
the view and swap the output palette (the "IR button = thermal render
|
|
mode, nothing to render it" note from 7/06 closes here).
|
|
- dpl_Effect type map (from callers): 0 = view color effect/fog-family
|
|
(color + mode), 1 = per-handle effect, 2 = clear/reset. The pvision call
|
|
is type 0 on handle 0 (the main view).
|
|
- **Wire encoding (host stub @0048e168): type-0 effects transmit ACTION
|
|
0x1b, payload = [mode:u32][node:u32][0x40-byte color struct]** -- fog uses
|
|
the same action with ordinary modes (0x1b already appears in our session
|
|
histograms); **pvision = mode 0xFFFFFFFF (ON) / 0xFFFFFFFE (OFF)**. Bridge
|
|
implementation = watch action 0x1b word0 for -1/-2, flip a thermal
|
|
palette in the present pass. (Type-1 effects = action 0x1d, 0x108 bytes;
|
|
type-2 = action 0x23, 8 bytes.)
|
|
|
|
**"Some mechs use IR, some use another vision mode" (operator) -- tracked
|
|
down 2026-07-13, game-side verdict: there is exactly ONE vision mode.**
|
|
Every mech roster in BTL4.RES carries the SAME ThermalSight subsystem
|
|
(BT411 weapsub audit, all chains), it has no per-mech model fields, the IR
|
|
button gate at record+0x25c is the electrical-short event flag (not a
|
|
capability switch), and the pvision toggle takes no parameters. Candidate
|
|
explanations for the memory, for the old pilots to arbitrate:
|
|
(a) the OTHER vision aid on every roster is the SEARCHLIGHT (night
|
|
illumination cone) -- "IR mech vs searchlight mech" habits; (b) ThermalSight
|
|
SHUTS OFF at heat alarm level >= 2 -- hot energy-boat mechs lose IR
|
|
constantly, reading as "this mech doesn't have IR"; (c) a different build
|
|
('96 BTLIVE vs 4.10) or the sensor-MFD display modes. Ask: WHICH mechs had
|
|
which mode, and what did it look like (white-hot vs green)?
|
|
Archaeology bonus: sda4/DPL3/VRENDER/VWE.DOC = Division musing on a
|
|
Pixel-Planes 5 / DEC Alpha board for VWE (the PXPL5SUP dir) -- pvision's
|
|
official name in the engine is "predator vision".
|
|
|
|
**Shipped keyboard map (PC keyboard, KeypressMessageHandler @004d1bf0)**,
|
|
useful for dev testing from the DOSBox window:
|
|
`+`/`-` target-range zoom; `1-5` + `a b c d f g s v x z` weapon/preset
|
|
modes; `w e r t y u i o` pilot-target select; `j`/`l` fake LRM10 /
|
|
Searchlight buttons; F1/F2 joystick align begin/end; 0x13d/0x13e cycle
|
|
control/display mode; **ARROW KEYS = the eyepoint look keys** (UP=forward,
|
|
LEFT/RIGHT=glance left/right, DOWN=look BEHIND, CTRL-UP=look down);
|
|
Ctrl-F1..F10 = fake subsystem buttons (GeneratorA-D, Condenser1, LRM15
|
|
fire/ammo/load, Avionics, Hud); `\` = mech message 0x19; `&` = stop
|
|
mission. NOTE for the glance hunt: the arrow keys drive the SAME
|
|
lookLeft/lookRight/lookBehind/lookDown fields as the hat -- a keyboard
|
|
glance test needs no vRIO and isolates the eyepoint->board question.
|
|
|
|
**Renderer backlog (operator, 2026-07-13): the Division card did TEXTURE
|
|
INTERPOLATION (bilinear filtering)** -- check vrview_gl's texture sampler
|
|
(likely NEAREST) and implement/verify linear filtering for authenticity.
|
|
|
|
**Bridge state:** the eyepoint camera compose is EXPLICIT OPT-IN only
|
|
(GLANCE_DCS=<hex>) -- auto-detection would have latched the static 180
|
|
mounts and flipped the camera permanently. New telemetry for the narrated
|
|
hunt: the 4s report prints `wire: NEW anim/joints=[...]` whenever handles
|
|
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`).
|