c2c4ab1f6796f031b73e4fad5e80331ba6bda5ea
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |