Commit Graph
28 Commits
Author SHA1 Message Date
arcattackandClaude Opus 4.8 129b27499d Cockpit surround: composite the gauges around the 3D view (default desktop layout)
Under BT_DEV_GAUGES the DEFAULT is now the pod-faithful cockpit surround: the 3D
world CENTERED with the six gauge surfaces composited AROUND it in the single main
window at 1/2 native scale, plus clickable RIO button lamps. Iterated as a visual
reference with the user, then landed in the engine.

- L4VB16.cpp/.h: BTCockpitLayout + BTCockpitComputeLayout (layout single source of
  truth, computed from the backbuffer canvas); BTDrawCockpitPanels (band fill ->
  button lamp quads -> 9-entry surface loop, phosphor-green mono / amber palette
  sec, Eng-sibling slot map -> GDI-atlas flight labels; D3DSBT_ALL state-block
  isolation = the floating-rocks trap); BTCockpitMouseDown/Up (client->bb hit-test
  + glass press/release/right-latch contract); BTApplyWorldViewport + DevGaugeDocked
  cockpit branches; shared BTLampBrightnessOf inline. Hooked at the top of
  BTDrawGaugeInset (before BT_SHOT, so shots include it).
- btl4main.cpp: glass preset (cockpit default, per-display panels opt-in); mode
  resolution -> gBTGaugeCockpit with work-area-clamped window sizing (-res = view
  size); WndProc WM_L/RBUTTON routing.
- L4VIDEO.cpp: BTWorldAspectOf cockpit branch (view rect on-screen aspect).
- L4VIDRND.cpp: CameraShipHUDRenderable::Render made viewport-relative.

Buttons = the L4GLASSWIN address banks x0.5 (Heat 0x2F, Mfd2 0x27, Comm 0x37, Mfd1
0x0F, Mfd3 0x07 red; radar rails 0x10-0x1B yellow; flight 0x38-0x47 blue, labeled).
Full rect is the hit target; the surface draws over it so only the lamp edge shows.
Precedence: BT_GLASS_PANELS=1 stands cockpit down > BT_COCKPIT=1 > _WINDOW/_DOCK
opt-out > cockpit default; BT_COCKPIT=0 = dock-bottom. Green tunable via
BT_COCKPIT_TINT. Renders in ALL builds; only the PadRIO click/lamp seam is
BT_GLASS-gated.

Verified (awaiting playtest): both build/ + build-glass/ compile (0 error C);
glass BT_SHOT matches the mockup; 500-click storm during a live mission survived
(Gitea #18 Receiver-gap fix holds); BT_COCKPIT=0 dock-bottom unregressed;
BT_GLASS_PANELS=1 stands cockpit down; pod build renders the panels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 20:39:34 -05:00
arcattackandClaude Fable 5 2215932016 KB: Gitea #12 incident findings -- @004bbd04 firing machine recovered, gauge-freeze root cause, dirty-bit gotcha
- decomp-reference: ProjectileWeaponSimulation @004bbd04 FULLY RECOVERED (capstone):
  fault gate reads simulationState@0x40==1 (not simulationFlags); ammo-bin gates;
  Loaded-case view/target denial = SetLevel(4);SetLevel(2) blip BEFORE FeedAmmo;
  updateModel@0x18|=1 is the replication dirty (== ForceUpdate); slot 17 @004b9c9c
  = rechargeLevel writer; FireWeapon @004bcc60 carries no gating.
- gauges-hud: CORRECTION -- launcher recharge dial is NOT authentically static
  (slot 17 @004b9c9c animates it); MP gauge-window freeze root-caused = lobby-built
  gauge tree dangling over the launch mech re-stream + permanent SEH disable.
- reconstruction-gotchas #20: updateModel@0x18 vs simulationFlags@0x28 bit 0
  (DelayWatchersFlag) mis-mapping class (Emitter::SetDirty, mislanch salvo mark).
- open-questions: #12 status ledger -- fixes pending a live-verify window; J/K/L
  preset hypothesis refuted for solo (scripted A/B); symptom-4 pip set unresolved.
- GAUGE_COMPOSITE row 13 verdict corrected (WRONG-STATIC, fix pending).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 22:46:10 -05:00
arcattackandClaude Fable 5 63b168cb92 SeekVoltageGraph: full reconstruction -- the eng-page POWER graph + top-box eraser (Gitea #11)
The #10 audit's one WRONG: the port Execute was a bring-up no-op, so the
emitter/myomer engineering pages never erased their top data box -> stale
sibling-page ghosts on the shared Eng bit-plane (SYSTEM 10 PPC showing the
Streak ammo box, etc).  Full faithful widget landed:

- btl4gau2: ctor/BecameActive/Execute/clear/ticks/cursor recovered from the
  capstone disasm of @004c6798/@004c6920/@004c6934/@004c6be4/@004c6c30/
  @004c6c6c (Ghidra dropped every x87 arg).  Plot: v=0..12000 step 1200,
  x=Round(response(v)*230), y=Round(v*(1/12000)*187) (ld80 @004c6bd0/@004c6d74
  = exactly 1/12000); change-test samples the response at 12000V vs the 9999
  activation sentinel; XOR op for tick/cursor move-by-redraw; destroyed branch
  centres edestryd.pcc and revives via own vtbl+0xC (BecameActive, slot 3 of
  PTR_0051a1fc -- vtable-dump verified).
- The vtbl+0x3C sampler identified from the binary vtables: Emitter slot 15
  @004bb42c = sqrt(P(v)/2.0e8), P @004bb3f4 = damageFraction*v^2*0.5*
  energyCoefficient; Myomers slot 15 @004b8f94 = sqrt(AvailableOutput(v)*3.6/
  350).  FUN_004dd138 == sqrt (part_015.c:4026): myomers' fabs reading
  corrected, GetSpeedReading renamed SeekVoltageResponse.  Port dispatch via
  complete-type bridges BTSeekVoltageSample/BTMyomersSeekSample +
  BTSubsystemDestroyed (databinding rule).
- Emitter's AUTHENTIC attribute table recovered (binary @0x511dd4, ids
  0x1D-0x25) and published: Laser*/Seek*/OutputVoltage@0x414 (currentLevel,
  RAW volts -- the live-cursor feed).  Field renames per the table:
  0x3F8 minSeekVoltageIndex / 0x3FC maxSeekVoltageIndex (static_assert-locked).
  The MechWeapon 0x1D OutputVoltage PORT ALIAS retired (binary table ends
  0x1C; Find walks lowest-id-first, the alias shadowed the authentic row).
  PPC::DefaultData -> Emitter::GetAttributeIndex().
- mech4: BT_PRESET_HOLD=<n> (freeze the #9 preset cycler after n pulses) for
  steady-state pixel verification; BT_SEEK_LOG diag.

Verified live (BLH, autofire): both #10 repro pairs held ghost-free for
minutes (SYS09->SYS10 PPC, SYS02->SYS04 ERMed, SYS05->SYS06 Myomers); curves
draw with live cursors; one replot per activation; quad panels/J-K-L/sec
panel un-regressed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 20:27:39 -05:00
arcattackandClaude Fable 5 f1a6ccbaaf Gitea #10: systematic gauge-data audit -- 45-row verdict table + findings
docs/GAUGE_COMPOSITE.md gains the AUDIT 2026-07-19 section: every widget on the
6 pod MFD surfaces + the secondary screen + the HUD reticle audited for
binary-correct data (static pass vs decomp + two instrumented live sessions).
Verdicts: 34 CORRECT / 1 WRONG (filed) / 6 AUTH-STATIC / 6 DEFERRED-FEED.

Findings:
A. The 'SYSTEM 10 PPC ammo readout' (entry a) root-caused: header/label/screen
   mapping are CORRECT ([vss] dump == authored sub+0x1dc); the ammo box is a
   GHOST -- stale pixels from the sibling ballistic/sensor page on the shared
   Eng plane, persisting because the port SeekVoltageGraph::Execute is a
   bring-up no-op. In the binary the graph IS the top-box eraser (view =
   (0x97,0x80)-(0x17d,0x13b) @004c6798; clear @004c6be4 on the @004c6920
   activation sentinel) -- ghost-free on the pod. Full reconstruction FILED
   (4 Seek* attrs + vtbl+0x3C sampler + x87 recovery); no erase-only stand-in.
B. HUD attr table @5110c0 conflict (entry b) RESOLVED -- see commit 904c75a.
C. Entry c: no [T3] tags remain in gauges-hud.md; the single [T4]
   (reticlePosition writer) stays flagged.
D. BT_SHOT dock-capture regression found + fixed (commit 904c75a).

Live re-verifications this audit: heat digits climb (515->680 under autofire),
reservoir flush 6->0 (~1s held) + PFX cloud, ammo counter moves (24->19 after
missile autofire), heading 089->120 + speed 0->182 under BT_GOTO, secondary
dama->crit->heat cycle (masks 0x450421/0x490421/0x510421), preset cycles visit
exactly the populated page set (MFD1:1,2,4 MFD2:1-4 MFD3:1,2), mission clock,
Comm 0/0, pips/compass/tape/caret render.

Audit performed by the agent; findings await human review, fixes await human
verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 18:33:59 -05:00
arcattackandClaude Fable 5 1d6339b226 Gitea #6: secondary MFD Damage/Critical/Heat cycling -- reconstruct the
NotifyOfDisplayModeChange override (vtbl+0x4C @4d1ae4) + wire desktop 'N'

The secondary screen's schematic selector was mislabeled: @004d1ae4 (the
bits-18..20 ModeSecondary* mask swap) was reconstructed as a non-virtual
"SetControlMode" that nothing called, so the desktop stayed pinned on the
Damage view.  The binary's L4 vtable @0051e440 pins the truth:

  +0x48 = @004d1acc  <- CycleControlModeMessageHandler (FUN_004afbe0):
          forwards to the base RET no-op @004b048c.  A BAS/MID/ADV
          control-mode change never touches the secondary view
          (empirically confirmed: BT_MODECYCLE_TEST cycles the CONTROL
          MODE lamp, mask bits 18-20 unchanged, schematic stays ARMOR).
  +0x4C = @004d1ae4  <- CycleDisplayModeMessageHandler (FUN_004afcac):
          THE Damage/Critical/Heat selector, indexed by displayMode
          (table @0051dbe4 = ModeSecondaryDamage/Critical/Heat).

Authentic pod inputs (streamed type-6 .CTL EventMappings, dumped via the
new BT_CTRLMAP_LOG EVENT records): secondary-panel button 0x15 -> msg
0x15 CycleDisplayMode (manual p13, the "'Mech status Info center" bottom
left of the secondary screen), button 0x18 -> msg 0x14 CycleControlMode
(manual p6, top right), 0x10/0x11 -> ZoomIn/Out.  The DOS keyboard
fallbacks (Keypress 0x13d/0x13e = extended F3/F4) are dead under the
WinTesla VK map, hence the desktop pin.

Port wiring (the M/ModeCycle pattern): key N / pad RightThumb -> action
DisplayCycle -> gBTDisplayCycle -> CycleDisplayModeNow() -- the same body
the pod console button message drives.  Both .MAP profiles + the
compiled-in default updated.

Verified live (docked gauges + BT_SHOT, BT_VIEWCYCLE_TEST): the sec
panel cycles ARMOR DAMAGE silhouette -> CRITICAL DAMAGE subsystem list
-> HEAT DAMAGE colored list, mask 0x450421 -> 0x490421 -> 0x510421; M
control-mode cycling un-regressed (BAS/MID/ADV lamp cycles, view pinned).

Diags: BT_MODE_LOG, BT_VIEWCYCLE_TEST=<frame>, BT_MODECYCLE_TEST=<frame>,
BT_CTRLMAP_LOG now dumps EVENT records.  KB: gauges-hud secondary-view
section rewritten, CLASSMAP +0x48/+0x4C slots, decomp-reference env
gates, GAUGE_COMPOSITE phase-4 entry resolved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 16:28:38 -05:00
arcattackandClaude Fable 5 bb795e2805 MP live-play wave: collision economy, missiles, radar transform, panel polarity, comm ticker
The interactive 2-node playtest wave -- every fix decomp-grounded and live-verified:

COLLISION ECONOMY (the ram one-shot): StaticBounce mutates worldLinearVelocity
per contact and ProcessCollisionList walks EVERY touched solid per frame; with
2007 terrain-as-solids the reflections compounded x4-x40 within one frame and a
walking bump one-shot a pristine mech for 112,375 pts (62-pt authentic economy).
Fix: frameEntryWorldVelocity restore per contact (damage always priced at the
real approach speed -- all the binary's physics ever saw); Mech::Reset zeroes
the mover motion (respawn = teleport); [collide-tx]/[mp-hdlr] telemetry.
Gotcha #16 (engine-facility drift class).

MISSILES: peer-visible salvos (the launcher record extension carries a salvo
counter + aim point; ForceUpdate actually enqueues it -- the dirty flag alone
never serialized), the authentic arc (authored MuzzleVelocity vector + the
Seeker's 200m/0.1/300 loft + gain-4 steering, decoded from @004beae4/@004bef78),
world-impact bursts (rounds detonate on cave geometry instead of phasing
through), contact-only damage (flight-cap expiry = fizzle, no more teleport
damage), live re-lead, and ballistic (unguided) shells for autocannons.
projweap's stale BTPushProjectile extern (the /FORCE signature trap, gotcha #6
corollary) crashed the Avatar's first AFC100 shot -- fixed + sweep rule recorded.

RADAR: two transcription bugs made the scope permanently empty -- FUN_0040b244
is the affine INVERSE (not a copy) and FUN_0040adec writes ONLY the 3x3 rotation
(never the translation); worldToView now Invert(view) built rotation-first.
CulturalIcons sorted out of the moving grid (the phantom red pips were map
props), visible-radius culls on all three draw passes, live pip verified at
|delta| x ppm px.  Gotcha #17 (verify the FUN_ body, not its call shape).

WEAPON PANELS (the frozen-dial hunt): the binary's *(subsystem+0x40) means
FAILED -- the recon's 'operating' name was backwards, inverting the destroyed-X
lamps, the panel look, the children enable and the ready-lamp gate (which had
NEVER executed).  Polarity chain corrected end-to-end (failedState, fed by real
damage saturation).  Root cause of the freezes: MFD page-mode gating -- the dev
composite shows ALL pages at once, so off-page dials legitimately stopped; under
BT_DEV_GAUGES the 15 page-plane bits stay active (the exclusive secondary trio
untouched).  The SEH gauge guard now names its kills; repaint-heal resets the
incremental arc after panel repaints; [panel]/[arc] probes added.

COMM/SCORE: MessageBoard LIVE (the engine already shipped the whole
Player__StatusMessage queue; wired the binary's one producer -- the kill branch,
victim's name, 6s -- plus the consumer bridge and a lazy source bind); MP DEATHS
counted via the observed-death tally (each node scores every pilot from locally
observed events, the same model as the KILLS credit) and the -2/-1 engine seed
clamped for display.

DEV UX: node-tagged window titles (-net port), gauge panel reworked (1320x480,
true 4:3 MFD cells, the portrait secondary UNROTATED upright, linear filtering,
BT_GAUGE_SCALE), fixed close spawns via BT_SPAWN_XZ, Boreas flies an Avatar
(first second-chassis live outing).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 17:24:15 -05:00
arcattackandClaude Fable 5 dd27238ceb Gauges: the CLOSEOUT wave -- radar pips, Myomers dedup, attribute parity, ledger sweep (tasks #14-#17)
TASK #17 -- AUTHENTIC RADAR SYMBOLOGY (the cross-blip stand-in retired).
The recon proved the 'missing pip raster set' never existed as BT game code:
pips are the ENGINE's L4GaugeImage vector-stroke system (T0 source in tree,
L4GAUIMA.cpp == FUN_0046f0c0 line-for-line), the 'pip table' is L4Warehouse::
gaugeImageBin keyed by Entity::resourceID, and BTL4.RES ships 110 type-0x12
shapes (every mech/vehicle/building/tree).  The six btl4rdr stubs are wired
to the real facilities: contacts draw their authentic model silhouettes with
LOD selection, the target gets the binary's 4px-inflated box highlight, and
player-name labels resolve via Mission::GetSmallNameBitmap (the prebuilt
64x16 egg rasters keyed by the player's bitmapindex).  DECODED: the invented
'VideoObject' was Entity::owningPlayer all along (+0x190; nameID =
playerBitmapIndex@0x1E0, target = BTPlayer::objectiveMech@0x284 -- new
bridge BTPlayerObjectiveMechOf); the 'LabelledEntity' class is Landmark
(cultural.h; label path dormant -- no landmark content ships, no runtime
landmarkID writer).  The L4GREND BT_DEV_GAUGES warehouse guard is removed
(its AV had a different culprit, below); resource type 18 corrected to
GaugeImageStream in decomp-reference (was mislabeled 'ModelList').

TASK #14 -- the Myomers ODR duplicate ELIMINATED: the powersub.cpp/hpp
'Myomers' (classID 0xBC3 -- actually Sensor) is retired whole; it duplicated
?DefaultData@Myomers@@ against the real class (dumpbin-verified) and /FORCE
picked the winner by link order.  The real Myomers (0xBC6) now chains
PoweredSubsystem's handler set (ids 4-8) and publishes its SEVEN binary
attributes (@00511588: SpeedEffect/Current/Recommended/Min/MaxSeekVoltage-
Index/SeekVoltage/OutputVoltage) -- the old empty unchained index starved
the Myomer engineering panel of every resolve.

TASK #16 -- attribute parity: MechWeapon publishes the FULL binary table
@0x511890 (11 entries; ids renumbered to binary truth -- the port aliases
had squatted the binary's DistanceToTarget/TargetWithinRange ids; the
streamed TriggerState 0x13 binding unchanged).  Binary names resolved two
TODO members: pipState -> estimatedReadyTime (attr 0x1A), and the EXT-model
flag is the binary's RearFiring (0x1B).  ThermalSight LightState published.
HUD (offset conflicts) + missile-side tables (id encoding suspect)
documented for a re-dump instead of publishing blind.
  THE CRASH THIS EXPOSED [T2, cdb-verified]: gotcha #11's dense-table gap
is a LATENT AV, not a guaranteed one -- the old table's 0x0D..0x12 gap
(task #5) survived on heap luck; the renumber reshuffled allocations and
Find() AV'd on a garbage entryName in WeaponCluster's PercentDone resolve.
Fixed with five named PAD entries (the mech.cpp attrPad idiom) + a
static_assert locking the pad base to PoweredSubsystem::NextAttributeID.
Gotcha #11 amended with the proof.

TASK #15 -- stale-ledger sweep: GAUGE_COMPOSITE ('composite not yet built',
Reservoir shadow, PlayerStatus/vehicleSubSystems 'remaining', valve-dormant-
until-0xBD3, sensor guard, the superseded 'Heat MFD near-static' reframe --
all banner-corrected), gauges-hud frontmatter, L4VB16 + powersub comments.

Verified live: 50/50 config attribute bindings resolve, 0 NULLs, 0 parse
skips, mech spawns and simulates 31/31 subsystems, no cross-blip fallbacks,
the pip cache fills through entity registration without the old guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 00:57:45 -05:00
arcattackandClaude Opus 4.8 5b9100da74 docs: GAUGE_COMPOSITE Phase 2 COMPLETE — SectorDisplay/PrepEngrScreen/MessageBoard registered; gauge system done
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 21:43:16 -05:00
arcattackandClaude Opus 4.8 d54e0009d7 docs: GAUGE_COMPOSITE Phase 3 — aggregate bank + condenser valve gauge DONE
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 20:30:28 -05:00
arcattackandClaude Opus 4.8 13f9c1f274 docs(gauge): DEATHS runaway + RANK=-1 fixed (solo scoreboard correct)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 19:19:22 -05:00
arcattackandClaude Opus 4.8 99e2078b83 docs(gauge): scoring feed done (KILLS/SCORE track combat)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 19:04:06 -05:00
arcattackandClaude Opus 4.8 f998f2a023 docs(gauge): GeneratorCluster done -- Phase 2 complete (4/4 widgets)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 17:57:42 -05:00
arcattackandClaude Opus 4.8 e4d939424a docs(gauge): mark P2 widgets done (3/4) + GeneratorCluster gated
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 17:44:24 -05:00
arcattackandClaude Opus 4.8 8379693a76 docs(gauge): record the gauge data-binding wave (P1 done, P2/P3/P4 plan)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:32:11 -05:00
arcattackandClaude Opus 4.8 099871ef4c docs: record PlayerStatus done (mission-review scoreboard; cameraInit)
Mark PlayerStatus reconstructed + registered in the remaining-widget scope note;
record the key finding that it lives in cameraInit (the mission-review camera
cockpit) not MechInit, so it is build/link/mech-cockpit-safe but not runtime-
verifiable via a normal mech egg.  vehicleSubSystems (from-scratch, 26 uses)
remains.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 23:14:28 -05:00
arcattackandClaude Opus 4.8 3bee47a9ea docs: record the remaining-widget scope (PlayerStatus / vehicleSubSystems)
Both need a full multi-function reconstruction (not the registration-glue the
other widgets were): PlayerStatus's ctor/Execute/helpers (CreateMutantPixelmap8,
PlayerStatusMappingGroup, App Players-node lookup) are comment-only stubs and its
existing Make/header ctor are buggy (drop the graphics_port_number param, pass
entity/renderer as x/y); vehicleSubSystems is undeclared/from-scratch.  Records
the decomp addresses + the ctor arg order for a focused follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 22:32:56 -05:00
arcattackandClaude Opus 4.8 ab039bcc1e docs: map radar contacts now render (grid population + projection fix)
Update GAUGE_COMPOSITE.md + CLAUDE.md: the radar plots the enemy contact inside
the FOV wedge.  Record the two fixes (engine RebuildEntityGrid populating the
dropped entity feed; the delta-relative blip projection replacing the wrong
camera-matrix point transform) and narrow the remaining map work to the authentic
pip/name infrastructure + SetTargetRange zoom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 22:23:39 -05:00
arcattackandClaude Opus 4.8 1746c5bb1f docs: record the map (radar) gauge as done + its follow-ups
GAUGE_COMPOSITE.md + CLAUDE.md: add the map/MapDisplay radar (increment 6, the
view wedge renders) to the done list; note the systemic pattern (a newly-consumed
reconstructed gauge is the first reader of stubbed helpers -- the map exposed 3);
narrow the remaining gauge widgets to PlayerStatus + vehicleSubSystems, and list
the map follow-ups (contact classification / stubbed pip+name infra, SetTargetRange
zoom).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 21:17:52 -05:00
arcattackandClaude Opus 4.8 eae631ba04 docs: record oneOfSeveralPixInt (increment 5) + update remaining-widget list
GAUGE_COMPOSITE.md + CLAUDE.md: add the OneOfSeveral/oneOfSeveralPixInt button-
lamp reconstruction to the done list; narrow the remaining widgets to map (radar
marquee), PlayerStatus, vehicleSubSystems, plus the follow-ups to make the
duck/searchlight buttons dynamic (extend the Mech table to 0x37 + a Searchlight
table).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 19:34:58 -05:00
arcattackandClaude Opus 4.8 c2c4ab1f67 docs: record the gauge attribute-wave (mechanism + 4 increments) + durable RE tools
Document the cockpit-gauge attribute-pointer system in GAUGE_COMPOSITE.md
(§Attribute wave) and CLAUDE.md §10: how a Subsystem/Attribute or bare-Attribute
binding resolves (ParseAttribute -> FindSubsystem/GetName -> GetAttributePointer
-> activeAttributeIndex), what a class must publish (AttributeID enum +
AttributePointers[] + GetAttributeIndex chained + DefaultData wiring), the
DENSE-TABLE HAZARD (Build leaves gap slots uninitialized, Find strcmps every
slot -> tables must be a dense prefix), and the base-primitive vs BT-specific
widget split.  Records the 4 committed increments (HeatSink table, Mech table,
vertBar, segmentArcRatio) and the follow-ups (oneOfSeveralPixInt/map/PlayerStatus/
vehicleSubSystems, the #if0'd HeatSink-bank AmbientTemperature, the Reservoir
coolantCapacity shadow).

Promote the two reverse-engineering helpers from the session scratchpad into
tools/ (durable): disas2.py (capstone + PE parse -- recovers x87 float math
Ghidra drops; FUN_004dcd94=round, FUN_004dcd00=fabs) and vtdump.py (dump a class
vtable to find an override the assert-anchored decomp never exported, e.g.
SegmentArcRatio::Execute).  Doc references updated scratchpad/ -> tools/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 18:52:53 -05:00
arcattackandClaude Opus 4.8 d006b40927 gauges: reconstruct cmCrit -- subsystem-critical tint; ColorMapper family complete [widget recon 5]
Reconstructs ColorMapperCritical -- tints a schematic colour by a subsystem's
operational state (the "critical" secondary display mode):
- CriticalConnection (@004c3598/@004c3610): src==0 -> 0; src->simulationState==1
  (DestroyedState) -> 100; else the subsystem's own damage-zone damageLevel*100.
- ColorMapperCritical::Make/ctor (@004c3ddc/@004c3e40): FindSubsystem by name +
  wire the CriticalConnection.

Resolved the subsystem damageZone@0xE0 shadow: MechSubsystem::damageZone is
declared ReconDamageZone*, but the assignment (mechsub.cpp: damageZone =
(ReconDamageZone*)new DamageZone(...)) shows the pointer IS a real engine
DamageZone -- so cast it back and read damageLevel. Added public accessors
GetSimulationState()/GetDamageZoneProxy() to MechSubsystem (those fields are
protected; mirrors how HeatConnection reads the public currentTemperature).

Completes the ColorMapper family (cmHeat, cmArmor, colorMapperMultiArmor,
cmCrit). Verified: parses, builds, all subsystems resolve (0 warnings), no
/FORCE unresolved, combat un-regressed (TARGET DESTROYED, 0 crashes), stable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 17:14:54 -05:00
arcattackandClaude Opus 4.8 fb8c1d6ace gauges: colorMapperMultiArmor (worst-of-8-zones) + fix the interpreterTable overflow [widget recon 4]
Reconstructs colorMapperMultiArmor -- tints one schematic colour (a whole torso
section) by the WORST of up to 8 damage zones:
- MultiArmorConnection (@004c346c/@004c34f4): scans 8 zones via
  entity->damageZones[idx], keeps the max damageLevel, count==0 ? 100 :
  Round(worst*100).
- ColorMapperMultiArmor::Make/ctor (@004c3c48/@004c3d60): resolves 8 named
  zones to indices via Entity::GetDamageZoneIndex (13-param methodDescription).

ALSO fixes a latent HEAP CORRUPTION that registering this widget exposed (and
that would have blocked every further widget): GaugeInterpreter's bytecode
buffer is a fixed interpreterTableSize=46864 (GAUGREND.h) tuned for RP's config,
and its Insert() bounds guard is a Verify() that compiles out at DEBUG_LEVEL 0.
As each BT gauge widget is registered, more of BT's larger L4GAUGE.CFG resolves
into bytecode (vs being parse-skipped); colorMapperMultiArmor's 13 params x
hundreds of calls pushed the total past 46864 -> silent overflow past the char[]
-> heap smash detected later in mission bitmap loading (not the gauge code).
Fix: interpreterTableSize -> 262144 (sized for BT's full config).

Verified: parses, builds, all zones resolve, no /FORCE unresolved, combat
un-regressed (TARGET DESTROYED, 0 crashes), stable. Details: docs/GAUGE_COMPOSITE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 16:56:13 -05:00
arcattackandClaude Opus 4.8 1d2ddd8399 gauges: reconstruct cmArmor -- the per-zone ARMOR DAMAGE schematic [widget recon 3]
Each armor zone on the cockpit schematic is now tinted by that zone's live
damage. Render-verified: the schematic shows an all-green Blackhawk (all zones
damageLevel=0 at spawn), replacing the static green+red it showed before cmArmor
was registered -- i.e. cmArmor now owns those zone colors and shows the real
undamaged state; damaged zones shift toward red.

Reuses the ColorMapper base (from cmHeat) + two pieces reconstructed from the
binary (Ghidra dropped the x87, recovered by disassembly):
- ArmorZoneConnection (@004c33a4/@004c3430): resolves one zone from the owner's
  inherited Entity::damageZones[zone_index]; per-frame feed = zone==NULL ? 100
  : Round(zone->damageLevel * 100) (the 0..1 damage ratio -> 0..100 percentage
  -> the colour index ColorMapper::Execute pushes into the palette slot).
- ColorMapperArmor::Make/ctor (@004c3aa4/@004c3b98): Make resolves the CFG zone
  name (dz_ltorso etc., the 6th param) to an index via Entity::GetDamageZoneIndex
  (== the binary's FUN_0042076c, scanning damageZones[] by name); the ctor wires
  the ArmorZoneConnection.

Decomp fact (corrects the cmHeat note): the ColorMapper base ctor takes NINE
args -- an owner_ID sits between renderer and graphics_port_number. Our 8-arg
ColorMapper::ColorMapper folds owner_ID=0 in (gauges have owner 0), so it is
equivalent; cmArmor's mapping matches cmHeat plus the zone name.

Verified: parses, builds, all dz_* zones resolve (0 "not found"), no /FORCE
unresolved, combat un-regressed (TARGET DESTROYED, 0 crashes). The dynamic
red-on-damage needs the player to take damage (the passive spawn dummy never
hits back). Details: docs/GAUGE_COMPOSITE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 16:30:45 -05:00
arcattackandClaude Opus 4.8 e869b00181 gauges: reconstruct headingPointer -- the rotating compass + heading readout [widget recon 2]
The visible money-shot: a green compass needle that rotates with the mech's
facing plus a numeric heading in whole degrees (render-verified: the needle
swung and the number went 247 -> 182 as the mech turned).

Full class reconstruction from the binary (Make/ctor/dtor/TestInstance/
ShowInstance/BecameActive/Execute). Ghidra dropped the x87 float math feeding
Execute's needle endpoints and the ctor's NumericDisplay centering, so it was
recovered by disassembling BTL4OPT.EXE with capstone (scratchpad/disas_hp.py):
the needle is a radial line from innerRadius(20) to outerRadius(39) at the
heading angle, endpoints rounded half-up; the readout is round(360 - deg).

Two corrections vs the map:
- The header ctor was 12 args; the binary's is 14 -- widened it. Fields @0x98/
  @0x9C are the needle's inner/outer RADIUS (Execute multiplies them by sin/cos),
  not "color/spacing" as the old field names implied; renamed accordingly.
- ENGINE-CONVENTION FIX: the binary read the heading from EulerAngles index [0],
  but the WinTesla EulerAngles(Quaternion) decomposition is ambiguous for a
  yawing mech (the quaternion double-cover flips it to a pitch=roll=pi branch as
  yaw sweeps past +/-pi -> the needle spins erratically). Switched to
  YawPitchRoll, whose yaw-first .yaw is the clean continuous heading
  (pitch=roll~0). Faithful to the binary's intent (heading), correct for this
  engine's decomposition.

renderer->GetLinkedEntity() resolves the viewpoint mech (a GetViewpointEntity
fallback is kept belt-and-braces). Deps (NumericDisplay/GraphicsViewRecord/
GraphicGauge) are real engine classes -> no /FORCE risk; verified no unresolved
HeadingPointer externals, no parse desync, combat un-regressed (TARGET
DESTROYED, 0 crashes). Details + the disasm technique: docs/GAUGE_COMPOSITE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 16:05:47 -05:00
arcattackandClaude Opus 4.8 547f25e043 gauges: reconstruct + register the first BT gauge widget (cmHeat) [widget recon 1]
Begins the BT gauge WIDGET reconstruction -- making the cockpit instruments show
live data instead of just frame art. The BT gauge method table
(BTL4MethodDescription[]) was a chain-only stub with zero registered
methodDescriptions, so every BT-specific gauge keyword was parse-skipped.

Registers the first widget, cmHeat (ColorMapperHeat -- tints the heat/critical
schematic palette by a named subsystem's live temperature):
- Added ColorMapperHeat::methodDescription (keyword "cmHeat", param list
  R,Md,C,St,St,St matching the binary .data) and registered it in
  BTL4MethodDescription[] (chain link stays last).
- Rewired ColorMapperHeat::Make to read methodDescription.parameterList[]
  (the interpreter restores each instance's parsed params there) instead of the
  earlier unrecovered DAT_* placeholders -- so it uses the real CFG rate/mode/
  colour-slot/palette/subsystem-name, and the runtime port arg as the graphics
  port.

Registering it exposed the /FORCE trap: two REAL functions in the chain were
undefined (silently stubbed -> would AV once the gauge builds). Reconstructed
both from the decomp:
- ColorMapper::BecameActive (@004c395c): invalidate the cached colour index +
  last-written RGB so the next Execute re-pushes the hardware palette.
- ColorMapperHeat::~ColorMapperHeat: empty -- the base chain (~ColorMapper ->
  ~Gauge) releases the HeatConnection + palettes.

Verified: register->parse->Make->ctor->FindSubsystem("GeneratorA") (resolves,
no "does not exist")->HeatConnection->Execute->palette-push runs end-to-end
under BT_DEV_GAUGES; no parse desync; combat un-regressed (TARGET DESTROYED,
0 crashes). Establishes the registration recipe + the /FORCE-check discipline
for every remaining widget. Details + priority order: docs/GAUGE_COMPOSITE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 15:05:47 -05:00
arcattackandClaude Opus 4.8 b526e92cad gauges: all 6 cockpit MFD surfaces in a SEPARATE dev window (Milestone C)
Generalizes the single-'sec'-inset (Milestone B) into a full 6-surface
compositor presented in its own top-level window -- the default under
BT_DEV_GAUGES (BT_DEV_GAUGES_DOCK=1 docks it into the main window instead).
A 960x384 window tiles all six pod instrument screens: Heat (COOLANT/BALANCE/
RES + condenser gauges), Comm (KILLS/DEATHS/SELECT TARGET), Mfd1/2/3 (DISPLAY/
PROGRAM/NEAREST frames), and the color radar (SCALE grid + dials + ARMOR
DAMAGE). Main 800x600 3D view is un-occluded; default DEV un-regressed
(TARGET DESTROYED after 8 hits, 0 crashes).

Design (mapped by the mfd-multisurface-map workflow):
- All 6 surfaces are bit-plane MASKS over the ONE shared SVGA16/pixelBuffer
  (sec=palette low byte; Heat=0x4000 UL, Mfd2=0x0400 UC, Comm=0x8000 UR,
  Mfd1=0x0100 LL, Mfd3=0x1000 LR), so the compositor reaches the SVGA16 once
  and extracts each by mask. No port-name reconcile needed on the dev path --
  fetch the BT names directly; the RP aux* names only matter to the pod's own
  SVGA16::Update demux (a deferred pod-only fallback).
- SVGA16::DrawDevSurface: two kernels -- palette-LUT (sec) + mono bit-plane->
  tint ((word&mask)?tint:0). BTDrawGaugeSurfaces iterates a 6-entry table.
- Separate window = one CreateAdditionalSwapChain on the existing device (no
  2nd D3D device). BTGaugeWindowRenderAndPresent (after the main EndScene):
  SetRenderTarget -> SetDepthStencilSurface(NULL) -> Clear -> BeginScene ->
  6 tiles -> EndScene -> restore -> swap->Present.
- KEY BUG fixed: the main 800x600 depth surface stays bound when rendering to
  the 960x384 gauge backbuffer; a bound depth smaller than the RT is invalid
  -> all draws silently fail (clear color, no geometry). Unbind depth (Z is
  off), restore after.

Pod SVGA16::Update/BuildWindows path byte-unchanged; all gated BT_DEV_GAUGES.
Content is still the authored cockpit frames + base-table gauges -- the live
MFD widgets need the BTL4MethodDescription reconstruction (this window is now
the live viewer for that work). Details: docs/GAUGE_COMPOSITE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 11:43:31 -05:00
arcattackandClaude Opus 4.8 a256813395 gauges: secondary/radar MFD composites live on a dev box (Milestone B)
The pod's secondary/radar cockpit surface now renders as an inset in the
800x600 dev window under BT_DEV_GAUGES: radar/tactical grid, SPEED/HEADING/
MAGNETIC/PROP dials, and the color-coded ARMOR DAMAGE mech schematic -- real
gauge content (nzSec=27247), composited from the shared CPU pixelBuffer.

Composite pass (engine): SVGA16::DrawDevInset palette-expands the secondary
plane into a MANAGED texture on the MAIN device and draws an XYZRHW inset quad;
BTDrawGaugeInset() reaches the gauge renderer's 'sec' port and is called from
DPLRenderer::ExecuteImplementation as the last draw before EndScene.

Three reconstruction bugs fixed to get real content:
1. BTL4Application::MakeGaugeRenderer signature mismatch (REAL fix): the
   reconstructed no-arg override only HID the 2007-engine's widened 3-arg
   virtual MakeGaugeRenderer(int*,int*,int*), so the engine built a base
   L4GaugeRenderer whose ctor never parsed gauge/l4gauge.cfg -> empty symbol
   table -> "undefined label 'bhk1Init'" -> no ports/gauges. Matched the 3-arg
   signature so it truly overrides (args ignored; BT is fullscreen). Same bug
   class as the BTL4GaugeRenderer(false,NULL,NULL,NULL) ctor fix.
2. Parse hung on undefined primitives (gated dev accommodation): the BT gauge
   primitive table (BTL4MethodDescription) is still a stub, so an unknown
   primitive -> ReportParsingError -> Fail() -> a MODAL dialog freezing the
   parse. Under BT_DEV_GAUGES, skip the unknown primitive's params so labels
   register and the base "configure" primitive builds the ports.
3. Gauge widgets AV on NULL data bindings (gated): NumericDisplayScalar's NULL
   value_pointer -> GaugeConnectionDirectOf ctor deref (bind NULL->static zero);
   RankAndScore::Execute derefs unreconstructed game state (Gauge::GuardedExecute
   SEH wrapper -> Disable(True) on first fault).

All guards gated on BT_DEV_GAUGES: default DEV un-regressed (TARGET DESTROYED,
0 crashes) and the pod path is byte-unchanged (strict Fail, no guards).

Remaining (docs/GAUGE_COMPOSITE.md): the real gauge WIDGET reconstruction
(BTL4MethodDescription method table + gauge->game-state data bindings), then
Step 2 (RP<->BT MFD port-name reconcile) + Step 3 (2-window layout).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 10:59:45 -05:00
arcattackandClaude Opus 4.8 c13e72d0ba platform profile + gauge dev-composite (Milestone A: gauge renderer woken)
PLATFORM PROFILE (-platform pod|dev / BT_PLATFORM / run.cmd pod; default dev):
an env-preset selector, NOT a code fork -- it picks which environment the
existing video/gauge/input code reads.  dev = single 800x600 window + keyboard
(the working build); pod = RIO input + (on real hardware) the multi-surface
gauges/MFDs, mirroring content/SETENV.BAT (the pod path -- FindBestAdapterIndices
/ SVGA16 -- is left untouched).  The multi-surface needs the pod's 2 video cards,
so -platform pod does NOT auto-enable L4GAUGE on a dev box (stays bootable,
single-window).  Also fixes a real engine bug: SVGA16::BuildWindows
PostQuitMessage'd on a CreateDevice failure but fell through to a null-device
deref (segfault) -- now breaks (inert on the pod).

GAUGE DEV-COMPOSITE, Milestone A (option B; opt-in BT_DEV_GAUGES, default OFF):
wake the (dormant) gauge renderer so its CPU-rastered pixelBuffer can later be
composited into the dev window, WITHOUT touching the pod SVGA16 output path.
SVGA16 does no per-surface D3D in this mode (a DevGaugeComposite() gate forces
the no-surface path + short-circuits Update/Refresh).  Waking the never-exercised
gauge subsystem exposed 4 latent reconstruction bugs, each guarded:
SVGA16::Update / Refresh (empty mSurfaces[]), LBE4ControlsManager::MakeLinkedLamp
(NULL lamp manager), L4GaugeRenderer::NotifyOfNewInterestingEntity (garbage
warehouse chain).  It now boots STABLY; the gauge reconstruction is incomplete
(shadowed lamp-manager + warehouse members) -- Milestone B (the composite pass)
will reveal whether the content is real.  See docs/GAUGE_COMPOSITE.md.

Verified: default DEV un-regressed (combat DESTROYED, 0 crashes); pod path
untouched; BT_DEV_GAUGES boots stable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 10:18:40 -05:00