From 24ea7f13af0a93db4ddb427fecb2306dcc3bcca8 Mon Sep 17 00:00:00 2001 From: arcattack Date: Wed, 8 Jul 2026 23:12:58 -0500 Subject: [PATCH] HUD ironing (task #38): authentic Lock producer + simple-X mode - LOCK rules transcribed from the exported HudSimulation (part_013.c:5619-5634): lock requires a target AND your own HUD host zone damage < 0.75 (_DAT_004b7ec4 -- a shot-up targeting computer drops lock) AND the targeted zone damage < 1.0 (_DAT_004b7ec8; whole-mech target checks zone 0 -- a wreck's dead zone can't re-lock). Box and ring are now separate signals like the binary: gBTHudLockState 1 = target held (hotbox draws), 2 = LOCKED (+ the spinning ring). - SIMPLE-X mode: the ctor's [0x99] minimal-reticle list transcribed (@4689-4705: green +-0.02..0.08 cross on the aim translate); Draw switches master <-> simple X on the PrimaryHudOn element bit (0x20), completing the recovered Execute's state-list logic. - Canopy diagnosis sharpened (open-questions): blx_cop is the torso segment's inside-skeleton mesh enclosing the eye; the black box = the skeleton render branch missing its texture + PUNCH cutout (the canopy windows are punch texels). A rendering-branch task; stays hidden by default. Co-Authored-By: Claude Fable 5 --- context/gauges-hud.md | 11 +++++++++-- context/open-questions.md | 20 +++++++++++--------- game/reconstructed/btl4vid.cpp | 34 ++++++++++++++++++++++++++++++++-- game/reconstructed/btl4vid.hpp | 1 + game/reconstructed/mech4.cpp | 30 +++++++++++++++++++++++++++++- 5 files changed, 82 insertions(+), 14 deletions(-) diff --git a/context/gauges-hud.md b/context/gauges-hud.md index 3365ae2..8c1511a 100644 --- a/context/gauges-hud.md +++ b/context/gauges-hud.md @@ -113,8 +113,15 @@ and every instrument is now live [T2]:** (the weapon-GROUP bits Front/Rear/Left/Right). **Range plays NO part** — Execute never reads the stored TargetWithinRange slots. - **Lock ring** = subB9 (ring+cross) at frame centre, **SPINNING 4°/frame** while the Lock attr - (0xA) is up (port: while a target is locked); the binary also hangs the PNAMEx.bgf player-name - mesh on the 3D marker here (3D chain still deferred). + (0xA) is up. The Lock PRODUCER is the authentic HudSimulation rule (part_013.c:5619-5634 [T1], + wired task #38): lock requires a target AND your own HUD's host zone damage < **0.75** (a + shot-up targeting computer loses lock) AND the targeted zone's damage < **1.0** (whole-mech + target checks zone 0 — so a wreck's dead zone can't re-lock). The hotbox stays visible without + lock (box = HotBoxVector, ring = Lock — separate signals). The binary also hangs the PNAMEx.bgf + player-name mesh on the 3D marker here (3D chain still deferred). +- **Simple-X mode** (PrimaryHudOn off, mask 0x20): the minimal reticle — a small green cross + (±0.02..0.08 arms) riding the aim translate (ctor @4689-4705 [T1]) — swapped for the full HUD + by Draw's state switch. - **Target HOTBOX** (attr 0xB HotBoxVector) = a rectangle hugging the projected extents — x±4 around the hotbox point, +1/−11.5 vertical (K=2.8145 baked projection; the port uses the live per-axis projection) — switching to the left/right edge ARROW past ±1.6 or behind. diff --git a/context/open-questions.md b/context/open-questions.md index 1744fbf..a9863bd 100644 --- a/context/open-questions.md +++ b/context/open-questions.md @@ -60,16 +60,18 @@ authentic path scoped. by the mouse — wire the real channel when pod hardware lands; (c) pre-burial, the pick still tests the sinking wreck's collision box at its parked position (pre-existing wreck-targeting behavior). -- **Cockpit HUD leftovers (tasks #35-#37 residue, 2026-07-08)** — the reticle + 7 weapon pips + +- **Cockpit HUD leftovers (tasks #35-#38 residue, 2026-07-08)** — the reticle + 7 weapon pips + ALL the instrument dynamics are LIVE (the Execute @004cdcf0 gap was RECOVERED via capstone - disasm, task #37 — see [[gauges-hud]] for the full instrument map). Remaining: (a) the - **blx_cop canopy shell** renders BLACK (untextured/unlit in our inside view) so it's hidden by a - name filter — `BT_INSIDE_COCKPIT=1` shows it; the authentic look needs its material/lighting - path; (b) the **3D marker chain** ([0x31c]/[0x320] dpl objects + the PNAME1-8.bgf player-name - meshes the Execute hangs over the locked target); (c) the "simple X" list [0x264] - (PrimaryHudOn off — the minimal reticle mode) is built empty in the port; (d) the HUD Lock - attr's (0xA) own HudSimulation producer (port analog: locked == designated) and the compass - rotation SIGN are [T3]. + disasm, task #37; the authentic Lock producer + the simple-X mode landed in #38 — see + [[gauges-hud]] for the full instrument map). Remaining: (a) the **blx_cop canopy shell**: it is + the torso segment's SkeletonType_A mesh and fully encloses the eye; it renders as a black box + because the skeleton-branch render path isn't giving it its texture + the PUNCH cutout (the + canopy WINDOWS are punch texels — with punch live the black pane texels become holes and the + world shows through the frame). Fix lives in the [[rendering]] skeleton/punch branch, not the + HUD; hidden by name filter until then (`BT_INSIDE_COCKPIT=1` shows it). (b) the **3D marker + chain** ([0x31c]/[0x320] dpl objects + the PNAME1-8.bgf player-name meshes over the locked + target — matters for MP). (c) the compass rotation SIGN is derived-not-verified [T3] (turn + left → the stem should swing right; flip the sin sign in Draw if footage disagrees). - **StatusMessagePool (NULL stub, btstubs.cpp:62)** — the per-player status-message queue. BTPlayer+0x1dc is never populated → MessageBoard is empty (authentic for bring-up). What resolves it: wire the pool + decode `AddStatusMessage @0042e580` + the `Player__StatusMessage` diff --git a/game/reconstructed/btl4vid.cpp b/game/reconstructed/btl4vid.cpp index 69872e5..dc73ce2 100644 --- a/game/reconstructed/btl4vid.cpp +++ b/game/reconstructed/btl4vid.cpp @@ -1049,6 +1049,7 @@ BTReticleRenderable::BTReticleRenderable(Entity *entity, Scalar *range_attr) lockSpinDeg= 0.0f; masterList = dpl2d_NewDisplayList(); + simpleXList = dpl2d_NewDisplayList(); aimDotList = dpl2d_NewDisplayList(); rangeCaretR = dpl2d_NewDisplayList(); rangeCaretB = dpl2d_NewDisplayList(); @@ -1207,6 +1208,30 @@ BTReticleRenderable::BTReticleRenderable(Entity *entity, Scalar *range_attr) dpl2d_SetLineWidth(rightArrow, 1.0f); dpl2d_End(rightArrow); dpl2d_Compile(rightArrow); + // + // The SIMPLE X [0x99] (ctor @4689-4705 [T1]): the minimal reticle used + // when PrimaryHudOn is OFF -- a small green cross (arms +-0.02..0.08) + // riding the same aim translate. Draw switches master <-> this on the + // element-mask bit (the recovered Execute's state-list logic @4cdd9d). + // + dpl2d_Begin(simpleXList, 1); + dpl2d_SetLineWidth(simpleXList, 1.0f); + dpl2d_FullScreenClip(simpleXList); + dpl2d_SetColor(simpleXList, 0.0f, 0.75f, 0.0f); + dpl2d_CallList(simpleXList, aimDotList); // slews with the crosshair + dpl2d_OpenLines(simpleXList); + dpl2d_AddPoint(simpleXList, -0.08f, 0.0f); + dpl2d_AddPoint(simpleXList, -0.02f, 0.0f); + dpl2d_AddPoint(simpleXList, 0.02f, 0.0f); + dpl2d_AddPoint(simpleXList, 0.08f, 0.0f); + dpl2d_AddPoint(simpleXList, 0.0f, -0.08f); + dpl2d_AddPoint(simpleXList, 0.0f, -0.02f); + dpl2d_AddPoint(simpleXList, 0.0f, 0.02f); + dpl2d_AddPoint(simpleXList, 0.0f, 0.08f); + dpl2d_CloseLines(simpleXList); + dpl2d_End(simpleXList); + dpl2d_Compile(simpleXList); + // empty placeholders (filled per frame / on lock) dpl2d_Begin(crossList, 1); dpl2d_End(crossList); dpl2d_Compile(crossList); dpl2d_Begin(aimDotList, 1); dpl2d_End(aimDotList); dpl2d_Compile(aimDotList); @@ -1393,7 +1418,7 @@ void // -- the 3D marker chain remains deferred.) { extern int gBTHudLockState; - const int locked = (gBTHudLockState != 0); + const int locked = (gBTHudLockState == 2); // the Lock attr rule, not just a target if (locked != lockShown) { lockShown = locked; @@ -1449,7 +1474,12 @@ void dpl2d_Compile(crossList); } - dpl2d_ExecuteList(masterList, device); + // the STATE-list switch (Execute @4cdd9d [T1]): PrimaryHudOn (element mask + // 0x20) selects the full HUD; off = just the simple aim cross. + { + extern int gBTHudPrimary; + dpl2d_ExecuteList(gBTHudPrimary ? masterList : simpleXList, device); + } } // diff --git a/game/reconstructed/btl4vid.hpp b/game/reconstructed/btl4vid.hpp index a4c1487..bde45a6 100644 --- a/game/reconstructed/btl4vid.hpp +++ b/game/reconstructed/btl4vid.hpp @@ -547,6 +547,7 @@ class BTReticleRenderable: // Scalar *rangeAttr2; // [0x89] the target-range source dpl2d_DISPLAY *masterList; // [0x98] the composed reticle + dpl2d_DISPLAY *simpleXList; // [0x99] the minimal cross (PrimaryHudOn off) dpl2d_DISPLAY *aimDotList; // [0x9a] centre aim dot dpl2d_DISPLAY *rangeCaretR; // [0x9b] right-ladder caret dpl2d_DISPLAY *rangeCaretB; // [0x9c] bottom-ladder carets diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index ee1ead2..05f392a 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -563,6 +563,7 @@ float gBTHudHeading = 0.0f; float gBTHudTwist = 0.0f; float gBTHudTwistLimit = 0.0f; int gBTHudGroupMask = 0xF; +int gBTHudPrimary = 1; // PrimaryHudOn (element mask 0x20): full HUD vs simple X // BT_GOTO beeline harness outputs (consumed by the mapper bridge, mechmppr.cpp) int gBTGotoActive = 0; float gBTGotoTurn = 0.0f; @@ -2383,10 +2384,36 @@ void float hddx = dp.x - localOrigin.linearPosition.x; float hddz = dp.z - localOrigin.linearPosition.z; BTSetHudTargetRange((Scalar)sqrtf(hddx*hddx + hddz*hddz)); - gBTHudLockState = 2; // locked == crosshair on it gBTHudLockWorld[0] = dp.x; // the HOTBOX point: top-centre gBTHudLockWorld[1] = dp.y + (float)dm->CylinderReferenceHeight(); gBTHudLockWorld[2] = dp.z; + + // AUTHENTIC LOCK (HudSimulation, part_013.c:5619-5634 [T1]): + // the fire-control LOCK needs (a) a working targeting computer + // -- your own HUD's host zone below 75% damage -- and (b) a + // live targeted zone -- its damage below 1.0 (a whole-mech + // target checks zone 0). So a shot-up cockpit loses the lock + // light, and a destroyed wreck's dead zone can't be re-locked. + int lock = 1; + { + MechSubsystem *hud = (MechSubsystem *)GetHudSubsystem(); + if (hud != 0 && hud->GetDamageZoneProxy() != 0) + { + Mech__DamageZone *hz = + (Mech__DamageZone *)hud->GetDamageZoneProxy(); + if (hz->damageLevel >= 0.75f) // _DAT_004b7ec4 + lock = 0; + } + int tz = MECH_TARGET_SUBIDX(this); + if (tz < 0) tz = 0; // whole-mech -> zone 0 + if (lock && tz < dm->damageZoneCount && dm->Zone(tz) != 0 + && dm->Zone(tz)->damageLevel >= 1.0f) // _DAT_004b7ec8 + lock = 0; + } + // 1 = target held (hotbox draws); 2 = LOCKED (box + spin ring). + // The binary keeps these separate: the box follows HotBoxVector, + // the ring follows the Lock attr. + gBTHudLockState = lock ? 2 : 1; } else { @@ -2398,6 +2425,7 @@ void gBTHudTwist = (float)TorsoHeading(); // RotationOfTorsoHorizontal (attr 4) gBTHudTwistLimit = (float)GetHorizontalFiringReach();// HorizontalTorsoLimit (attrs 5/6) gBTHudGroupMask = (int)targetReticle.reticleElementMask & 0xF; + gBTHudPrimary = ((int)targetReticle.reticleElementMask & 0x20) != 0; } if (gEnemyMech != 0)