2bcca26cea83757dea40fc68e0c12fbcfef0fa3d
28
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2bcca26cea |
WAVE 7 Phase A: wire the projectile/missile weapon SUBSYSTEMS byte-exact (un-mislabeled)
An 8-agent decomp-mapping workflow mapped every weapon-family ctor + fire/spawn path
(scratchpad/wave7_maps.txt). KEY IDENTITY CORRECTION via VDATA.h enum (base 3000=0xBB8):
the factory ctor-address comments were right but the built CLASS names were stubs/base --
0xBCD == ProjectileWeapon (was building the base MechWeapon stub)
0xBCE == GaussRifle (NOT MissileLauncher -- was a MissileLauncher stub)
0xBD0 == MissileLauncher (NOT BallisticWeapon -- was a BallisticWeapon stub)
Un-mislabeled + wired via Create<Class>Subsystem bridges (mirroring CreateEmitterSubsystem),
each static_assert-locked byte-exact on the now-locked MechWeapon(0x3F0)/Emitter(0x478) bases:
* ProjectileWeapon (0xBCD, @4bc3fc : MechWeapon, sizeof 0x448)
FIX: AmmoBinConnection was an empty 1-byte struct -> retyped to the binary's 0xC
SharedData::Connection (was making the object 8 bytes short, sliding
MissileLauncher's missileCount off 0x448).
* MissileLauncher (0xBD0, @4bcff0 : ProjectileWeapon, sizeof 0x44C)
FIX: deleted muzzleVelocity (ALIAS of inherited ProjectileWeapon launchVelocity@0x410)
and salvoCount (SHADOW of inherited MechWeapon damageData.burstCount@0x3d4),
keeping the single own field missileCount@0x448.
* GaussRifle (0xBCE, @4bdcb4 : Emitter, sizeof 0x484 = Emitter + Vector3D muzzleVelocity)
DEDICATED bridge (not CreateEmitter): its FireWeapon is a no-op (mov [eax+0x414],0) --
a non-functional weapon in this 1995 build. Also fixed GAUSS.CPP's discharge write
rechargeLevel -> currentLevel@0x414 (the binary writes this+0x414).
Phase A safe-stubs FireWeapon (consume round + recoil, NO spawn): the reconstructed
Projectile (@4be1bc, 0x340) + Missile (@4bf5b4, 0x368) ENTITY classes are NOT spawn-ready
(Entity-base phantom members overflow their New() alloc -> heap corruption; New() takes
(int)this instead of the 0xD4 descriptor; Entity base size unconfirmed 2007-vs-1995, the
Mech 0x638-vs-0x854 problem). The workflow's adversarial verify flagged the live spawn as a
heap-overflow hazard and recommended exactly this phasing. Phase B = the entity byte-exactness
+ descriptor build + New(MakeMessage*) so a fired shot becomes a flying entity that damages.
Factory now 18 of 20 cases wired to real ticking classes (remaining: SubsystemMessageManager
0xBD3 [WAVE 8], Gyroscope 0xBC4 [deferred]). Verified: Mad Cat (LRM/ballistic) + BLH construct
the real weapon classes + tick authentic charge/ammo/heat/recoil, DESTROYED-in-8, 0 crashes,
heapcheck-clean through construction. Energy weapons already damage via the mech4 beam path,
so combat is unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
9d82be46a1 |
P7: subsystem-tree alarm unification -- the whole PoweredSubsystem weapon/power subtree is byte-exact
The reconstruction modeled the binary's 0x54 AlarmIndicator (FUN_0041b9ec) with undersized
stand-ins (AlarmIndicator==ReconAlarm==4B; HeatAlarm==8B) across the entire subsystem tree, so
every field above an alarm sat at the wrong compiled offset. Retype every such stand-in to
GaugeAlarm54(0x54) and de-phantom each class against its ctor, so the whole PoweredSubsystem
subtree becomes byte-exact. An 8-agent read-only decomp-mapping workflow decoded every ctor
first; then hands-on implementation. static_assert-locked chain (verified vs the raw ctors):
HeatSink 0x1D0
-> PoweredSubsystem 0x31C retype electricalStateAlarm@0x264 + modeAlarm@0x2B8 @004b0f74
-> MechWeapon 0x3F0 retype weaponAlarm@0x350; delete 5 phantom tail fields @004b99a8
-> Emitter 0x478 delete outputVoltage/beamLengthRatio/firingArmed aliases
+ beamHit*/beamColor/beamHitData/energyRampTime phantoms;
retype beamOrientation EulerAngles->Quaternion(16B) @004bb120
-> PPC 0x478 (no own fields) @004bb888
PoweredSubsystem -> Sensor 0x328 (no alarm; 3 own fields) @004b1d18
PoweredSubsystem -> Myomers 0x358 (no alarm) @004b8fec
New systemic bug-class instances fixed (added to the CLAUDE.md checklist):
* alias field - Emitter outputVoltage==inherited rechargeLevel@0x320; beamLengthRatio==
beamScale.z@0x434; firingArmed==inherited useConfiguredPip@0x3E0
* phantom field - MechWeapon segmentReference/pipSegment/hasTarget/targetPoint/muzzlePoint
(past 0x3F0); Emitter beamHitPoint/beamImpact/beamImpactScalar/beamColor/
beamHitData/energyRampTime (binary writes inherited damageData/voltageScale
or the value is a method local)
Non-layout fixes required in the same wave:
* outputVoltage->rechargeLevel also in the compiled GAUSS.CPP:74/93 (external readers of the
removed Emitter field -- must grep EVERY TU, not just the class's own .cpp)
* MechWeapon::GetMuzzlePoint reimplemented faithfully (removed muzzlePoint collided with
Emitter's own fields at 0x3F0) via a BTResolveWeaponMuzzle void* bridge in mech4.cpp,
resolving the weapon's mount segment (inherited this+0xdc) through the owner segment table
* DetachFromVoltageSource fixed to set electricalStateAlarm not modeAlarm (raw @004b0e30
writes the 0x264 alarm)
The vehicleSubSystems aux-screen gauge raw reads (btl4gau2.cpp:868/952 at subsystem+0x2b8/+0x278)
and the Sensor RadarPercent path now read the CORRECT byte offsets (garbage under the short layout).
Verified: combat DESTROYED-in-8, 28 shots, 0 crashes, heat heatEnergy=1.34e7, every static_assert
lock passes, heapcheck-clean through construction (the phase the isolated PoweredSubsystem retype
had overflowed). LESSON: a factory-bridge runtime Check(sizeof<=alloc) does NOT fail the build --
only a static_assert sizeof lock catches alloc overflow at compile time.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
1356870e56 |
P7: byte-exact re-base of the CORE heat leaf (HeatSink/Condenser/Reservoir/Generator/Myomers)
The reconstruction modeled the binary's shared alarm/connection types with undersized
stand-ins, sliding every field above them low (the 72-byte auxScreenNumber gap). Fix the
foundational heat-leaf classes byte-exact + static_assert-lock them, from the ctor decomp:
Shared types corrected:
* SubsystemConnection 4 -> 0xC (binary link node FUN_004af9cf; FUN_00417ab4 derefs +8)
* GaugeAlarm54 = 0x54 (real AlarmIndicator FUN_0041b9ec; STATUS level at +0x14,
so subsystem+0x184 == heatAlarm+0x14 == GetLevel())
WatcherGaugeAlarm now typedefs GaugeAlarm54 (Watcher branch locks stay valid).
Byte-exact + locked (ctor-verified):
* HeatSink heatEnergy@0x158 linkedSinks@0x164 heatAlarm@0x170 resource@0x1C4
pendingHeat@0x1C8, sizeof 0x1D0 (@004adda0)
* Condenser valveState@0x1D0 condenserAlarm@0x1DC (@004ae568)
* Reservoir reservoirAlarm@0x1D0 ... squirtEfficiency@0x22C, sizeof 0x230 (@4aef78)
* Generator stateAlarm@0x1FC, sizeof 0x250 (@004b225c)
* Myomers phantom moverConnection tail removed (fits 0x358)
Three systemic bug classes fixed (added to the checklist in CLAUDE.md / HARD_PROBLEMS.md):
* alias field - a subclass member re-declaring an inherited slot the ctor reuses
(Condenser refrigerationOutput==massScale@0x160; Reservoir
coolantCapacity==thermalCapacity@0x128) -> use the inherited name
* alarm-interior - a value read at alarm+0x14 modeled as a separate member
(HeatSink heatState@0x184, Reservoir injectActive@0x1e4)
-> route to alarm.GetLevel()
* phantom field - a member past the object end (Generator shortFlag@0x25C is really
*(owner+0x190)+0x25c the msg-manager, @004b0efc; Myomers
moverConnection@0x110 a write-only base slot) -> remove it
Heat conduction now reads the REAL heatEnergy=1.34e7 (not garbage); combat DESTROYED-in-8,
0 crashes, heapcheck-clean through construction.
REMAINING (measured; a distinct larger task): making PoweredSubsystem byte-exact grows it
+0x98 and cascades into MechWeapon/Emitter/PPC/Sensor/Myomers -- all model the 0x54
AlarmIndicator with 4-byte ReconAlarm / 8-byte HeatAlarm stand-ins and are short +
phantom-tailed; retyping without byte-exacting them overflows the Emitter alloc (heap
corruption). PoweredSubsystem kept on HeatAlarm(8) stand-ins (marked) pending a
subsystem-tree ALARM UNIFICATION. See docs/HARD_PROBLEMS.md P7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
dc6af8cef6 |
vehicleSubSystems: reconstruct the stubbed connection helpers from decomp
Replace the return-0 stubs with the real functions: - ResolveLink (FUN_00417ab4) = SubsystemConnection::Resolve (two-level deref of the plug's bound link); an unbound plug resolves to 0, the authentic no-source branch. - FindLinkedSubsystem (FUN_0041bf44) = GetAttributePointer(index) via the engine Simulation attribute-index system (the cooling-loop master at slot 3). - IsGeneratorDerived (FUN_0041a1a4) = the real IsDerivedFrom(PoweredSubsystem ClassDerivations 0x50f4bc) via a new powersub.cpp bridge BTIsPoweredSubsystem (shared with the aux-screen filter) -> the generator stateLamp now builds. So the cooling/power/voltage connections resolve real data when the plug is bound, instead of always reading 0. Build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4ab6748563 |
vehicleSubSystems follow-up: reconstruct BitMapInverseWipe::Execute (LeakGauge)
Replace the minimal-safe Execute stub with the authentic wipe (@004c5d08): round the level, repaint `frames` segments stacked vertically, each picking its strip frame (empty/half/full via colorA/colorB) from the level. The coolant LeakGauge in every SubsystemCluster now renders faithfully from its CoolantMassLeakRate value. Build clean, 7 panels, 0 crashes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fbf5118c9c |
vehicleSubSystems follow-up: reconstruct SegmentArc270 recharge dial
Reconstruct SegmentArc270 (@004c6244): engine SegmentArc base + a Scalar connection driving currentValue + the |n|/(|n|-1)*0.75 segment-span factor. Wire it into WeaponCluster as the recharge dial (was a deferred NULL), reading the weapon's real PercentDone attribute. Render-verified (BT_DEV_GAUGES_DOCK): the green segmented recharge circles draw in the weapon panels; 7 panels build, 0 crashes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6fceb58439 |
vehicleSubSystems Phase 2: authentic aux-screen assignment -> panels build
The engineering-screen cluster panels now build for each subsystem on its real aux-screen position. Two pieces: 1. Aux-screen bridge (powersub.cpp BTGetSubsystemAuxScreen): the assignment lives on PoweredSubsystem (auxScreenNumber/Placement/Label, resource +0x104/108/10C) which our ctor already populates -- but the reconstructed heat-leaf branch is NOT byte-exact, so the Make's raw sub[0x1dc]/[0x1e0]/[0x1e4] reads were garbage (0xCDCDCDCD). The bridge casts through the real PoweredSubsystem type + returns the NAMED fields (and is the FUN_0041a1a4/0x50f4bc type filter). Make + Subsystem Cluster now read via the bridge. 2. Reconstruct the 4 child gauges the clusters build that were declared-but-undefined (only VertTwoPartBar/OneOfSeveral/PixInt existed): HorizTwoPartBar (full, mirrors Vert), OneOfSeveralInt (ctor), OneOfSeveralStates (ctor + BecameActive + State Connection @004c3324), BitMapInverseWipe/LeakGauge (full) + SeekVoltageGraph's Execute/BecameActive/dtor -- their /FORCE-stubbed ctors were segfaulting when a cluster built them. VERIFIED (BT_DEV_GAUGES + BT_VSS_LOG): 7 authentic panels build -- Sensor(scr5, qsensors) HeatSinkCluster, Myomers(scr6,qmyomers) MyomerCluster, PPC(scr1,10) + Emitter(scr4,8,7) Energy/Ballistic clusters -- with real labels + placements, 0 crashes, combat un-regressed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a02339112a |
vehicleSubSystems: verify Phase 1 end-to-end + confirm the Phase-2 blocker
Add a BT_VSS_LOG diagnostic (Make entry + per-subsystem classID/auxScreen dump + the ConfigureForModel path). Verified under BT_DEV_GAUGES: the gauge renderer builds, ConfigureForModel(Init) runs bhk1Init, and VehicleSubSystems::Make is called with the full 33-subsystem roster -- the dispatcher iterates every subsystem and its classID switch resolves the displayable types (0xBC3 Sensor, 0xBC6 Myomers, 0xBC8/0xBD4 Emitter/PPC, 0xBCD/0xBD0 Proj/Missile). CONFIRMED (empirically) the Phase-2 blocker: subsystem[0x1dc] (the aux-screen position the Make gates on) reads 0xCDCDCDCD (uninitialized) / garbage for the displayable subsystems -> every panel hits the "Illegal aux screen position" skip, so nothing renders yet. Phase 2 = populate the aux-screen field from the subsystem resource. Combat remains un-regressed (the skip path is inert). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
674c172cf8 |
vehicleSubSystems Phase 1: reconstruct + register the cluster-panel family
Reconstruct the whole engineering-screen (MFD) subsystem cluster-panel system in btl4gau2 (from the Ghidra decomp of BTL4OPT.EXE) and register the vehicleSubSystems config primitive: - Sub-gauges: AnimatedSubsystemLamp/AnimatedSourceLamp (OneOfSeveral + cooling/ power connections), ScalarBarGauge (BarGraphBitMapScalar + generator-voltage connection), ConfigMapGauge, plus the 3 file-private GaugeConnection classes (@004c3134/31ec/3288 samplers). - SubsystemCluster base (ctor builds ~11 child gauges across the MFD + engineering ports; Execute/dtor/frame-draw/enable helpers) + the 5 subclasses HeatSink/ Myomer/Weapon/Energy/Ballistic (ctors + Executes). - VehicleSubSystems::Make dispatcher (@004cbaf0): per-subsystem factory keyed on classID (HeatSink 0xBC3 / Myomer 0xBC6 / Energy 0xBC8,0xBD4 / Ballistic 0xBCD,0xBD0), placed via the 12-row aux-screen geometry table, + registered in BTL4MethodDescription[]. SAFE + gated: gauges only build under BT_DEV_GAUGES/pod, and until Phase 2 populates the subsystem aux-screen field (subsystem[0x1dc]) the Make hits its "aux screen = zero" skip -> combat untouched. Two minor children deferred as marked NULLs (SegmentArc270 recharge dial + BitMapInverseWipeScalar eject wipe -- their ctors are btl4gaug placeholders). Raw-offset subsystem reads (0x1dc/0x1e0/ 0x1e4/0x31c/0x334/0x364/0x43c...) marked BEST-EFFORT pending Phase 2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b6918632fc |
gauges: reconstruct + register PlayerStatus (mission-review scoreboard)
PlayerStatus (btl4gau3) had only a BUGGY Make defined (it read raw DAT_ pools, passed (int)entity/(int)gauge_renderer as x/y, and DROPPED the graphics_port_number param -- the header ctor was one int short); the ctor/Execute/dtor/BecameActive and the PlayerStatusMappingGroup + CreateMutantPixelmap8 helpers were comment-only stubs. Reconstructed the whole family (mapped by the playerstatus-decomp-map workflow, 6 agents): * methodDescription (8 params: rate,modeMask,integer player#,string font,4x color) + rewired Make reading parameterList[] with the port/x/y bug fixed. * ctor (@004cb1a8): GraphicGauge base (owner folded to 0), store colours+port, SetOrigin, playerIndex=player_number-1, build a score NumericDisplay (fmt 2 = signedBlankedZeros). Fixed the header layout: nameImage is a BitMap* (not a Pixmap), and two missing members (dirty@0x2F, previousStatus@0x30). * dtor / BecameActive / TestInstance. * PlayerStatusMappingGroup (@004c9bd0): 28 ColorMapperArmor zone tints over the mech-outline schematic, one per dz_* damage zone (mech->GetDamageZoneIndex), using the EXISTING 10-arg ColorMapperArmor ctor (per the workflow's correction -- changing it would break cmArmor); added ColorMapper/Armor::SetColor (FUN_004c3c38, stores @0x6C). * Execute (@004cb358): resolve the player (WinTesla-clean via GetMissionPlayer for the local player, instead of the binary's raw App+0x24 "Players"-node dictionary walk), then draw the score + name box + alive/dead status box; null-guarded. KEY FINDING: PlayerStatus lives in the config's `cameraInit` block (the MISSION- REVIEW / spectator camera cockpit), NOT `MechInit` -- so it is NOT part of the mech cockpit and does not build during normal mech play (which is why the mech test shows it un-regressed and its Execute never runs). It is the post-mission scoreboard (all 8 players' name/score/mech-status), rendered by the BTCameraDirector game model. Registered in BTL4MethodDescription[] so `cameraInit` builds it instead of parse-skipping. STATE: complete reconstruction; compiles, links (no new /FORCE unresolved), and the mech cockpit is un-regressed (TARGET DESTROYED, 0 crashes). NOT runtime-verified -- cameraInit is only built by the mission-review camera model, which a normal `vehicle=<mech>` egg does not trigger. BRING-UP STUB (marked): CreateMutantPixelmap8 returns NULL (the mech-outline recolor needs the DynamicMemoryStream read API + Pixmap pixel-copy mapped) -> the score/name-box/status-box render but not the recoloured mech schematic. BT_PS_LOG traces the resolve. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fb3ed38fa7 |
map: fix contact-blip projection -- enemy now plots inside the radar FOV wedge
The previous commit found+drew contacts but they landed off-radar. Debugged with BT_MAP_LOG entity/blip tracing: the reads were CORRECT all along -- the enemy mech (cls 0xBB9, own=0) sits at dsq=14400 (exactly 120u) from the player (own=1, dsq=0), and the grid also carries ~311 cls-0x5E vehicle props per cycle (the map's scattered vehicles) which the 0xBB9 blip filter correctly skips. The bug was the PROJECTION: worldToView is a camera (view->world) matrix and Point3D::Multiply bakes a scale that is wrong for a point transform -- a 120u contact at ppm=0.366 projected to |167px| instead of |44px| (~3.8x), and with a spurious rotation. (The earlier "377-828m" reading was this wrong scale amplifying the varying player-drift distance, not a distance error.) Fixed by projecting delta = (entity - viewpoint) DIRECTLY: rotate by -heading (from the orientation quaternion's yaw, top-down heading-up) and scale by pixelsPerMeter. Now |screen| == |delta|*ppm exactly (blip @(0,44) for the 120u enemy), on-radar inside the view wedge. Blip is a cross + box so it reads as a distinct contact marker. Verified live (BT_DEV_GAUGES): the enemy contact renders as a box marker INSIDE the FOV wedge (render-confirmed via a 4x radar crop); combat un-regressed (TARGET DESTROYED after 8 hits), heap-clean under BT_HEAPCHECK. BT_MAP_LOG traces blip coords + scale + delta. Remaining map polish: blip-vs-wedge rotation convention (the enemy reads ahead/in-wedge, which is correct); the authentic pip symbol/name infrastructure (still stubbed); SetTargetRange (radar zoom, 1km default). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0bceb9a638 |
gauges/map: populate the radar entity grid + draw mech contact blips
Root-caused why the radar showed 0 contacts: the gauge renderer's moving/static entity grids (queried by GetMoving/StaticEntitiesWithinBounds) were NEVER populated -- the 1995 game fed them from ExecuteImplementation's InterestingEntity iterator, which the WinTesla port dropped. Fix (engine): add GaugeRenderer::RebuildEntityGrid() -- Clear + repopulate movingEntities from the world's DynamicMaster + DynamicReplicant entities (the vehicles/mechs; AllEntity would flood the grid with ~300 props/effects/terrain). Called from the map's Execute phase 0 (under the gauge's guarded Execute, so a fault disables only the map). Verified: contacts are now found, combat un- regressed (TARGET DESTROYED), heap-clean under BT_HEAPCHECK. Fix (map): DrawMoving now draws a cross blip for mech-class contacts (0xBB9) when the authentic pip raster set is absent (the BT pip table is stubbed in this engine build). KEY PROJECTION FIX: worldToView is a camera (view->world) matrix and Point3D::Multiply applies only rotation+scale (NOT translation), so projecting the absolute entity position left the viewpoint un-subtracted and the blip landed off-screen (verified: blip @(580,-1154)). Project the RELATIVE position (delta = entity - viewpoint, already computed for the range check) instead -> the blip projects viewpoint-relative + heading-rotated correctly (@(164,221)). radarRange default 500->1000 (1km zoom) so contacts within ~500m show on the radar (SetTargetRange, the real player zoom, is still stubbed). BT_MAP_LOG traces the blip coords. STATE: the grid population + viewpoint-relative blip projection are correct and combat-safe. A reliable on-screen contact demo still needs the display-scale vs spawn-distance reconciliation (the BT_SPAWN_ENEMY dummy sits at a varying, large distance -- 377-828m -- beyond the display radius across runs) + the authentic pip symbol/name/video-object infrastructure (still stubbed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
48ed3a8b73 |
gauges: register + wire the map (radar) -- the tactical display renders
MapDisplay (the radar/threat-map gauge, btl4rdr.cpp) was fully reconstructed (ctor + Execute + DrawViewWedge/DrawStatic/DrawMoving/DrawNames) but never registered and its data attributes never published. Wire it up end-to-end: DATA (the attributes the map binds): * Mech table extended to the full 0x15..0x38 (was the 0x21 prefix): add RadarRange@0x2f -> radarRange, RadarLinearPosition@0x30 -> radarLinearPosition (Point3D* -> &localOrigin.linearPosition), RadarAngularPosition@0x31 -> radarAngularPosition (Quaternion* -> &localOrigin.angularPosition), DuckState @0x37 -> duckState; the rest bind the shared attrPad (kept dense so Find can't strcmp a gap). New members set in the Mech ctor; radarRange defaults to 500 (SetTargetRange is still stubbed) so nearby contacts are on-scale. The map's position/angle attributes resolve to Point3D**/Quaternion** (AttributePointer is int Simulation::*, so the ATTRIBUTE_ENTRY reinterpret binds pointer members fine -- verified live: posPtr/anglePtr resolve). * Sensor (named "Avionics") publishes RadarPercent/SelfTest/BadVoltage: define Sensor::AttributePointers[] + build the previously-empty Sensor::AttributeIndex chained to PoweredSubsystem::GetAttributeIndex() (= HeatSink's dense index). WIDGET (the registration glue -- NOT in the assert-anchored decomp, so reconstructed from the config + ctor): * MapDisplay::methodDescription (config "map") + MapDisplay::Make. The offset_position keyword "center"/"bottom" is typed as a STRING and converted in Make, avoiding a ModeManager named-constant registration. Registered in BTL4MethodDescription[] (btl4grnd.cpp now includes btl4rdr.hpp). Three crash fixes exposed once the map ran (each a pre-existing stub the map is the first consumer of): * Sensor radarPercent went hugely negative (RadarBaseline - HeatMasterEnergy(), where the inherited heatEnergy is un-normalized in the not-byte-exact heat-leaf branch) -> the radar reported non-operational. Guard: treat an out-of-[0,1] heat term as no penalty (marked bring-up; real overheat penalty needs the heat normalization). * ResolveOperatorEntity was a stub returning NULL -> EntityPosition(NULL) crashed CalculateBounds. Fixed to the renderer linked-entity + application viewpoint fallback (same as HeadingPointer). * MapName::ExtractFromEntity didn't guard a NULL entity (the binary's Verify(False) iterator-mismatch path compiles out at DEBUG_LEVEL 0) -> EntityPosition(NULL). Guarded. Verified live (BT_DEV_GAUGES): the radar now draws the view wedge (the mech's 180-deg FOV cone) and completes the full wedge/static/moving/names cycle with operating=1, cap=1, scale=500, position/angle resolved, 0 crashes. Combat un- regressed (TARGET DESTROYED after 8 hits), heap-clean under BT_HEAPCHECK. Permanent gated diagnostic: BT_MAP_LOG traces the phase/draw pipeline. Contacts show 0 (the spatial-query entity classification is a follow-up); DuckState now resolves for the duck button too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a538d6438e |
gauges: reconstruct + register oneOfSeveralPixInt -- cockpit button-state lamps
The OneOfSeveral family was declared but undefined. Reconstruct the shared base (OneOfSeveral: ctor @004c4d88, BecameActive @004c4f14, Execute @004c4f28, dtor @004c4e7c) and the OneOfSeveralPixInt subclass (Make @004c5204, ctor @004c52d8, dtor @004c5364) + its methodDescription, and register "oneOfSeveralPixInt". OneOfSeveral is a multi-frame strip selector: the base holds a BitMap or PixMap8 strip of columns*rows frames + the per-frame size (source size / columns,rows); Execute picks frame `selected` (col=selected%columns, row=selected/columns) and blits that sub-rectangle -- DrawPixelMap8(opaque) for a pixmap strip (its own palette, no SetColor), or SetColor(bg)+DrawBitMapOpaque(fg) for a bitmap strip. Added the missing OneOfSeveral::foregroundColor field (@0x9C -- the DrawBitMap Opaque background arg). OneOfSeveralPixInt forces the PixMap path (fromImageStrip =0, bg/fg=0) and wires one GaugeConnectionDirectOf<int> to the frame index. methodDescription = (rate,modeMask,string strip,integer columns,integer rows, attribute state) matching oneOfSeveralPixInt(rate,mode,strip.pcc,cols,rows,attr). Strip resource via warehouse->pixelMap8Bin (WRHOUS.h) .Get/.Release. The config binds the duck / searchlight / display-mode / piloting-mode buttons; ControlsMapper/DisplayMode already resolves (MechControlsMapper table), and DuckState/Searchlight/LightOn degrade to frame 0 (safe) until their tables land. Verified live (BT_DEV_GAUGES): the cockpit button-lamp PixMap8 icons now render (were absent). No /FORCE unresolved externals. Combat un-regressed (TARGET DESTROYED after 8 hits), 0 crashes, no missing-image. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a2ee1181e6 |
gauges: reconstruct + register segmentArcRatio -- the cockpit SPEED arc
SegmentArcRatio (config keyword "segmentArcRatio") was declared but undefined. It is a thin subclass of the engine SegmentArc gauge: two Scalar connections (numerator=value, denominator=max) and an Execute override that computes the [0,1] fill fraction into the base's currentValue, then delegates drawing to SegmentArc::Execute (which lights numerator/denominator of the segments). Reconstruct Make (@004c62fc) / ctor (@004c6394) / Execute (@004c6488) / dtor + the methodDescription (rate,modeMask,scalar inner,scalar outer,scalar deg0, scalar deg1,integer segs,color bg,color fg,attribute numerator,attribute denominator -- matching the engine SegmentArcNormalized param types; the config gives inner/outer once and the Make duplicates them into inner0==inner1 / outer0==outer1). Execute recovered by disassembling the vtable override @004c6488 (Ghidra dropped the x87): if (denominator < 1) currentValue = 0; else currentValue = clamp(|numerator/denominator * segmentSpan|, 0, 1); SegmentArc::Execute(); where FUN_004dcd00 = fabs and the ctor precomputes segmentSpan = (Scalar)(|n|/(|n|-1)) * 0.75f // int division => 0.75 for n=36 (_DAT_004c6484 = 0.75f, read live from the binary). Fixed the header field type int segmentSpan -> Scalar (the ctor fstp's it and Execute fmul's it). The config binds numerator=LinearSpeed, denominator=MaxRunSpeed -- both now resolved by the Mech attribute table -- so the arc sweeps with speed. Verified live (BT_DEV_GAUGES + drive): the radar-surface SPEED dial now shows a green segmented arc filling with speed (was a plain ring). No /FORCE unresolved externals. Combat un-regressed (TARGET DESTROYED after 8 hits), 0 crashes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
18d58f38f6 |
gauges: reconstruct + register vertBar (VertTwoPartBar) -- cockpit coolant bars
VertTwoPartBar was declared in btl4gaug.hpp (ctor/field offsets/decomp addrs) but never defined. Reconstruct Make (@004c462c) / ctor (@004c4724) / BecameActive (@004c48e0) / Execute (@004c48fc) / dtor + the "vertBar" methodDescription, and register it in BTL4MethodDescription[]. The Execute pixel math is x87 that Ghidra dropped (FUN_004dcd94 is just (int)ROUND(ST0); the operands are on the FPU stack) -- recovered by disassembling @004c4940/@004c4960: warnPix = clamp(round(height*low/high + 0.5), 0, height) valPix = clamp(round(height*value/high + 0.5), 0, height) value is first clamped to [0,high]; on a change it tile-blits the bitmap over [0,warnPix), fills [warnPix,valPix) with fillColor, clears [valPix,height) with extraColor (reusing the existing DrawTiledBitmap helper = FUN_004c2ff8; view methods SetPositionWithinPort/SetColor/MoveToAbsolute/DrawFilledRectangleTo Absolute = vtbl +0x08/+0x18/+0x24/+0x48). methodDescription param types (rate,modeMask,vector,string,color,color,color,attribute,attribute,attribute) match the config vertBar(rate,mode,(w,h),tile,bg,fill,extra,current,warn,max). For the coolant bars the config binds current=CoolantMass, warn=max=Coolant Capacity, so the warn line sits at full and the bar is a simple level gauge that empties as CoolantMass drops -- now driven by the HeatSink attribute table committed earlier. Verified live (BT_DEV_GAUGES): the Heat surface COOLANT/RES bars now render the btwarn.pcc hatch fill (was empty), reading the real coolant attributes (full hatch = coolantLevel~=capacity). No /FORCE unresolved externals on the new symbols. Combat un-regressed (TARGET DESTROYED after 8 hits), 0 crashes, and the gauge build + mission load are heap-clean under BT_HEAPCHECK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9e31408ba2 |
gauges: publish Mech attribute table (dense prefix 0x15..0x21 -> LinearSpeed live)
mech.cpp shipped an EMPTY, unchained Mech::AttributeIndex (a default-constructed AttributeIndexSet passed to DefaultData), so Simulation::GetAttributePointer severed the whole parent chain -> EVERY bare Mech gauge binding (LinearSpeed / MaxRunSpeed / DuckState / RadarRange / ...) resolved to NullAttribute. Add the Mech AttributeID enum (full binary set 0x15..0x38, VA 0x50be84) + a Mech::AttributePointers[] table + Mech::GetAttributeIndex() chained to JointedMover::GetAttributeIndex(), and change DefaultData to pass it. DENSE-TABLE HAZARD (systemic): AttributeIndexSet::Build (SIMULATE.cpp:565) sizes the built index to max(id) and Find (SIMULATE.cpp:663) strcmps EVERY slot, and Build does NOT zero gap slots -> an unpublished id between the parent's NextAttributeID and the max published id holds a garbage entryName -> AV on the next name lookup. So the table is a DENSE PREFIX 0x15..0x21 (JointedMover's NextAttributeID through LinearSpeed): the ids the BLH cockpit doesn't bind (collision/eyepoint/reticle/footstep/anim-state) point at one shared read-only attrPad member; CurrentSpeed/MaxRunSpeed bind to the existing gait members legCycleSpeed/reverseStrideLength; LinearSpeed binds a new linearSpeed member populated each frame in PerformAndWatch (|adv|/dt = forward ground speed). Ids 0x22..0x38 stay declared in the enum for later extension (RadarRange/DuckState when the map/duck widgets land) but are NOT published yet. New members appended to Mech's own region (no locked base/gait offset shifts); inited in the ctor. Verified live (BT_DEV_GAUGES + drive): the radar-surface SPEED readout (numericSpeed(LinearSpeed), a base engine primitive already drawing NULL) now shows 225 (~63 u/s in the widget's display units) instead of 0, tracking the mech's motion. Heat surface CurrentTemperature=77 un-regressed. Combat un- regressed (TARGET DESTROYED after 8 hits), 0 attribute-not-found, and Mech construction + attribute-index build is heap-clean under BT_HEAPCHECK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9ea4bdaf50 |
gauges: publish HeatSink attribute table (CoolantMass/CoolantCapacity/CurrentTemperature)
The cockpit gauge config binds Subsystem/Attribute names (e.g. HeatSink/CurrentTemperature) resolved by ParseAttribute -> FindSubsystem -> GetAttributePointer -> the class's activeAttributeIndex. HeatSink wired GetAttributeIndex() into DefaultData but published an EMPTY table (only the inherited SimulationState id 1), so every HeatSink/* numeric read NULL -> guarded to 0. Add HeatSink::AttributePointers[] + GetAttributeIndex() publishing CoolantMass (coolantLevel @0x12C), CoolantCapacity (thermalCapacity @0x128), and CurrentTemperature (inherited currentTemperature @0x114), chained to the parent index so SimulationState is preserved and the built index stays dense (ids run contiguously from HeatableSubsystem::NextAttributeID -- a gap would make AttributeIndexSet::Find strcmp a garbage slot). Condenser and Reservoir derive from HeatSink with no override, so they inherit this table for free. Verified live (BT_DEV_GAUGES): the Heat surface heatsink-temperature readout (numeric HeatSink/CurrentTemperature, a base engine primitive that was already drawing NULL) now shows 77 (= startTemp) instead of 0. Combat un-regressed (TARGET DESTROYED after 8 hits), 0 crashes. The coolant attributes are ready for the vertBar widget (next increment). 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>
|
||
|
|
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> |
||
|
|
8b36440d05 |
subsystems: un-stub Wave 4 readouts + Wave 6 Myomers (10->15 factory cases)
WAVE 4 (standalone readouts) -- Sensor(0xBC3)/Searchlight(0xBD8)/ ThermalSight(0xBDE)/AmmoBin(0xBCB) un-stubbed via Create<Class>Subsystem bridges + Torso-style de-shim (drop cross-family shadow fields, redirect accessors to the real inherited base, static_assert layout locks). - FIX: Searchlight/ThermalSight ctors gated their Performance on the shadow segmentFlags(=0) so it NEVER installed; switched to owner->simulationFlags. - AmmoBin: retype ammoAlarm HeatAlarm->WatcherGaugeAlarm(0x54) + drop the statusState@0x40 shadow -> exact 0x22C layout. - Guard HeatWatcher::WatchSimulation against the unresolved watchedLink (null-deref exposed once these sims run; faithful fix = resolve the link). - Heat-leaf branch (Sensor) is not byte-exact -> overflow-lock only. WAVE 6 (Myomers 0xBC6, mover-coupled) -- structural un-stub, gated BT_MYOMERS (default on; =0 -> Actuator stub). Wired INERT: MyomersSimulation early-returns (advanced-damage gate stubbed) + no-op mover feed, so the live JointedMover is untouched and the gait cannot regress. De-shim drops the owner*/segmentFlags shims to fit the exact-0x358 alloc. Authentic mover/heat coupling deferred (needs messmgr 0xBD3 + reconciling the mover feed with the gait cutover). Verified: BLH tick 20->27, Mad Cat 24, combat DESTROYED un-regressed, locomotion un-regressed, 0 crashes, 0 heap detections under BT_HEAPCHECK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5aa791b245 |
launcher: bgfload.h via fwd shim, not an absolute path (relocatable)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7b7d465e5e |
Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.
Layout:
engine/ MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
models) + image codec; the minimal rp/ headers the audio HAL needs
game/ reconstructed BT logic + surviving-original BT source + fwd shims
+ WinMain launcher
content/ full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
docs/ format specs + reconstruction ledgers
reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
tools/ MP console emulator + map/resource scanners
One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|