Files
BT411/phases/phase-02-dpl2d-reticle.md
arcattackandClaude Fable 5 02cdfd6576 Torso: the TWIST goes LIVE -- electrical watchdog chain, centered crosshair, coherent controls (task #57/#58)
The MadCat torso twists, the view turns with it, and targeting follows.
Three reconstruction fronts closed:

THE ELECTRICAL WATCHDOG CHAIN (why the torso never powered up):
- PowerWatcher::UpdateWatch reconstructed (@004b181c, the REAL registered
  Performance -- PTR @0050f5fc; Ghidra missed the fn start): the watchdog
  MIRRORS the watched subsystem's electrical level (+0x278), brownout
  downgrade when gen output <= minVoltage% x rated.  @004b1804 relabeled
  ResetToInitialState (slot 10) -- the old "Simulation" tag was wrong.
- The factory watcher-CONNECT pass reconstructed (vtable slot +0x38,
  @004aee2c/@004b1a40 byte-identical, recovered from raw exe bytes):
  watchedLink.Add(roster[watchedSubsystem]) on the master node.  Was the
  SubProxy::Start() no-op -- every watchdog sat at 0 forever.
- MinVoltageScale = 0.01 (a 10-byte x87 literal @0x4b1924; was 1.0f =
  permanent brownout) and PowerWatcher's Derivation chains its REAL base
  HeatWatcher (the HeatableSubsystem stand-in broke IsDerivedFrom for the
  whole Torso/Searchlight/ThermalSight family).
- KB correction swept: derivation tag 0x50e604 = HEATWATCHER (not
  "HeatSink"); the btl4gaug heat-widget gate now tests it via the
  BTIsHeatWatcher bridge.

THE CROSSHAIR (task #58 forensics, 6-agent workflow + live probes):
- The VIEW is TORSO-MOUNTED: jointtorso -> jointeye -> siteeyepoint in
  every twist-capable .SKL; the camera + canopy ride the same hinge
  subtree through HingeRenderable's live matrix-stack compose -- ALREADY
  WORKING in the port.  The crosshair stays screen-centered (center IS
  the boresight); the twist reads on the tape carets/compass/radar.
- The real bug was the port's gBTAimX = tan(twist) slew (the falsified
  "body-mounted view" model): the camera already carried the twist, so
  the crosshair counter-slid to hull-forward and the fire ray with it.
  Deleted; the pick ray inherits the twist from the yawing eye basis.
- Two instrumentation traps documented (chase-eye-as-default-camera,
  BT_FORCE_TORSO clobbering real joints -> the hook now only fills
  unresolved ones); an over-correcting explicit eye compose was added on
  those false readings and retired the same day.

CONTROLS + REPLICATION:
- Q/E spring-center on release (the axis is a twist-RATE demand; the old
  hold-deflection model drifted forever); X also zeroes the axis and
  pulses the authentic torso Recenter (@004b6918).  M cycles control
  mode via the real CycleControlMode body.
- Torso update-record DIRECTION fixed: engine truth is Write=serialize /
  Read=apply; @004b6a78 is the READ (was mislabeled Write) and the
  missing WRITE @004b6a1c recovered from raw disasm (recordLength 0x1C,
  twist/vel/rate at +0x10/14/18) -- kills the replicant's 0xCDCDCDCD
  -140-degree ghost twist.
- Marching-ghost desync: 4 Standing-case guards zero stale reverse
  cycleSpeed (negative cadence passed the <= ZeroSpeed stop gate).
- Kill credit rerouted to the OBSERVED killer (lastInflictingID ->
  killer's player link) -- kills count, target K/D populates.

KB: subsystems.md (watcher chain), multiplayer.md (record direction),
combat-damage.md + gauges-hud.md + cockpit-view.md (torso-mounted view
re-correction), decomp-reference.md (new addresses + tag fix),
open-questions.md (dead capability-roster loops 2-4, snapshot CD read).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 13:27:49 -05:00

5.5 KiB

Phase 02 — dpl2d API recovery + BTReticleRenderable reconstruction (task #35)

Goal: the main-view HUD (targeting reticle + up to 10 per-weapon pips). Status: COMPLETE (steps 1-5 live, verified 2026-07-08). dpl2d opcode model + CallList inline-include semantics in game/reconstructed/dpl2d.cpp; ctor glyph program + FUN_004cd938 tick-ladder helper transcribed in btl4vid.cpp; per-weapon registration via the binary's real gate IsDerivedFrom(0x511830 = MechWeapon::ClassDerivations) (part_014.c:5386) — 7 pips on the BLH (3 lasers + 2 PPCs + 2 MissileLaunchers), AddWeapon @004cdac0 store map verified (part_014.c:4827-4837; both state attrs are named "SimulationState", strings @51d526/51d577). Draw hook = BTDrawReticle(device) after the 3D scene, cockpit view only. The binary Execute @004cdcf0 is an UN-EXPORTED gap → Draw dynamics are best-effort [T3] (caret slide from live target range; pip A lit on TargetWithinRange else ring B). [SUPERSEDED: Execute @004cdcf0 was RECOVERED task #37 (capstone disasm → reference/decomp/reticle_execute_004cdcf0.disasm.txt) — Draw dynamics are now [T1]; pip lighting is FIRE-CYCLE state, not TargetWithinRange; crosshair positioning re-corrected task #58 — see context/gauges-hud.md.] Step 6 (blx_cop canopy shell) and the PNAME1-8.bgf 3D pip meshes remain DEFERRED — tracked in context/open-questions.md.

The recovered dpl2d recorder API (libDPL 2D display lists) [T1]

Every recorder self-identifies via its FUN_0049a300(1, s_dpl2d_...) debug-name string (part_010.c). Opcodes are the first word written into the list's command buffer.

FUN @ dpl2d name opcode args
00487f34 NewDisplayList → list handle
00487fbc OpenDisplayList (Begin) (list, mode)
00488030 CloseDisplayList (End) (list)
00488054 FlushDisplayList (Compile) (list)
004880e8 AddOpenPolyline 4 closed-loop polyline begin
0048814c AddClosePolyline (loop end) 5
00488278 AddOpenLines 6 open line-strip begin
004882dc AddClosePolyline (strip end) 7
00488340 AddOpenPolypoint 2 point-set begin
004883a4 AddClosePolypoint 3
00488408 AddPoint 8 (x, y) — vertex of the open primitive
0048847c AddCircle 9 (x, y, r, fill)
00488500 AddSetLineWidth 0x15 (w) — 1.0/2.0/3.0 used
0048856c AddPushState 0x12
004885cc AddPopState 0x13
00488630 AddSetColor 0xf (r, g, b) floats
004886ac AddSetColor (packed) 0x16 (argb?)
00488718 AddSetMatrix 0x10 6 floats (2x3 affine)
004887b0 AddConcatMatrix 0x11 6 floats + flag
00488850 AddCallDisplayList (nested) (list) — glyph reuse
004888c0 AddFullScreenClipRegion
00488b80 / 00488bac 2x3 matrix build: identity / set-translate(x,y) local helpers feeding AddSetMatrix

⚠ CORRECTION to the earlier AddWeapon recon comments: the "PushMatrix/MoveTo/PopMatrix" triple is actually AddOpenPolypoint / AddPoint / AddClosePolypoint = "draw a POINT" — game/reconstructed/dpl2d.cpp's current Push/Move/Pop interpretation must be reworked to this opcode model (points, open/closed polylines, circles, width, color, push/pop state, set/concat matrix, nested list calls) and its dpl2d_ExecuteList renderer extended to draw them (XYZRHW screen-space lines/points/fans; state save/restore already present).

BTReticleRenderable — what the ctor builds (@004cc40c, part_014.c:4362) [T1 read]

  • Calibration from ctor params/templates: pip origin/bias/scale (+0x1fc..0x234 in class terms), min/max range from templates, rangeScale = (clamped - lo) / (hi - lo).
  • Loads PNAME1-8.bgf (all ship in VIDEO/GEO) — pip/number meshes — plus a 3D renderable chain (FUN_00489708/489724/48ab0c/491cc4... — the dpl 3D overlay object at 0.12 scale) — the reticle has BOTH a 3D marker component and the 2D display lists.
  • Builds ~15 display lists: the master list (param_1[0x98]) composed of nested glyph lists via AddCallDisplayList: crosshair dot cluster (AddPoint xy at ±0.04/±0.1 = the dotted cross), heavier ±0.1..0.16 tick cross at width 3, two tick-LADDERS via FUN_004cd938 (count 0xd=13 / 0x15=21 ticks, spacing/geometry from the calibration constants at [0x7f..0x88]: 0.35/0.25/0.5/±0.008/0.016...), side arrow polylines (the 4-vertex closed arrows at the ladder ends), an arc segment (AddCircle partial at 0.03?), and the heading/aim sub-lists ([0x9a..0xa1, 0xb6..0xba]) positioned by AddSetMatrix translates.
  • FUN_004cd938 = the tick-ladder builder (count, flag, scale, x, y, step, minor/major tick half-lengths) — transcribe next.

Remaining steps

  1. Rework dpl2d.cpp to the opcode model + extend ExecuteList (lines/points/polys/matrix/call).
  2. Transcribe the ctor's glyph program into BTReticleRenderable::BTReticleRenderable (+ FUN_004cd938 helper) with the authentic constants.
  3. Read + transcribe Execute (vtable PTR_LAB_0051db44 — find the Execute slot; per-frame: withinRange attr reads → pip list visibility/colors, aim/heading matrix updates).
  4. Read the 1996 caller wiring (FUN_004cef28's inside path: the ctor's 15 args + the AddWeapon per-weapon argument sourcing) and reproduce in MakeMechRenderables.
  5. Draw hook: the engine already calls mReticle->Execute() per frame (L4VIDEO ~6562); or execute from the BT renderer after the 3D scene; dpl2d_ExecuteList(device).
  6. The cockpit canopy shell (blx_cop, punch cutout) black-enclosure fix (BT_INSIDE_COCKPIT=1).