fd055281a8a24be6541b3d4a2486afa37042e325
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8ed6184d65 |
Combat: AUTHENTIC weapon groups -- streamed per-mech button bindings + the real fire chain (task #5)
The recovered system: fire channels = LBE4ControlsManager buttonGroups (0x40/0x45/0x46/0x47); default groups = the per-mech type-6 controls-map resource in BTL4.RES, installed by the T0 CreateStreamedMappings the port already called -- it needed only the TriggerState attribute (id 0x13 PINNED to the binary value; fireImpulse@0x31C is the binary's TriggerState) and an input feed. Keyboard/harness now push press/release edges into the button groups; the gBT*Trigger bypasses, per-type keyboard split and 1,0 pulse hack are retired -- weapons sharing a button fire TOGETHER (madcat Trigger = 4 weapons). Myomers @4b9550/@4b95b8 misattribution corrected (they are MechWeapon ConfigureMappables/ChooseButton). Verified 2-node: kill through the authentic chain (12 hits vs ~36 pre-groups). Config-mode session (regrouping UI) = the remaining stage, KB-scoped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
946323b696 |
Gyro: damage fan-out FUN_004b2980 -- the cockpit hit-BOUNCE is live (task #56)
Re-disassembled the unexported gap 0x4b2980-0x4b2d8b byte-exact (capstone over
section_dump.txt) and reconstructed Gyroscope::ApplyDamageResponse:
- zero-damage + Collision(type 0) no-op; hit direction = Damage::damageForce
or a RANDOM horizontal fallback when ~zero (per-component sign roll then
value roll -- binary-legal until senders fill damageForce)
- direction rotated by the yaw-only torso-twist frame (placeRot=(0,twist,0)
-> euler->matrix, the FUN_00408744 raw row-dot reproduced verbatim)
- per-type scaling amount / damageMultiplier[type] * damageResponse[type]
.{trans,pitchRoll,yaw,vibration}; Explosive alone multiplies burstCount;
each clamped at 1.3f
- four kicks: impulse(trans, dir), torque(pitchRoll, dir), impulse(vibration,
vibrationDirection@0x390) and verticalImpulse(yaw, dir)
Layout corrections (byte-verified): gyro+0x390 is ONE Vector3D vibration axis
init (0,1,0) -- was mis-split as scalars animationOffset/Scale/Phase; mech
+0x5c4 is a FLOAT rumble-period countdown (gyroRumbleTimer) -- was mis-typed
int clipLoadGuard.
Call sites wired (binary-gated):
- take-damage hub: mech.cpp TakeDamageMessageHandler, FIRST action (@0x4a0264
in hub FUN_004a0230) -- an invalid-zone hit still bounces
- crushable-icon crunch: mech4.cpp, torque 0.4 along the bounce delta-v + up
impulse 0.2 (@4aa81e/@4aa86c)
- firing recoil: projweap.cpp FireWeapon, damage>3 -> impulse (0,0.6,-1.5)
x damage/16 (@4bc136-4bc19c) via the new Mech::GetGyroSubsystem accessor
DEFERRED (recorded in KB with the byte recipe): the alternate-gait engage
jolt + 0.4s rumble (@4aa158-4aa365) -- its gates are [T3]-flagged and it
mutates the #49/#50-stabilized gait machine.
Verified live: [gyro-dmg] fires per hit with correct type scaling; eyePosition
shows the damped bounce (ramp to ~0.015u, oscillating Y, decay to zero); kill
chain un-regressed; no NaN.
Also recorded in KB: the OFFSET-COCKPIT verification -- Thor's authored
jointeye tranx=+1.13 matches its canopy X-center exactly AND our in-game eye
reproduces it to the hundredth (the placement model is correct per-mech); and
the playable roster is 8 models (strider/raptor/firstrtr/blkjack have assets
but no model resource -- BT_FORCE_MODEL with those names = NULL-resource AV,
cdb-verified, not a code bug).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
29035028fd |
Cross-pod beams: replicate emitter discharge via subsystem update records
User report: lasers only visible on the window firing them. The peer's replicant emitters never learned the master fired. THE AUTHENTIC PIPE (decomp-verified): - FUN_0041c350 (the "beam keepalive" ServiceDischarge/ContinueDischarge call) does TWO things: queue the LOCAL deferred beam-effect callback (@0x4bac0c) on the app+0x34 manager -- our per-weapon render walk already plays that role -- and set the subsystem DIRTY bit, which maps to the 2007 engine's updateModel / ForceUpdate(). - Replication rides SUBSYSTEM UPDATE RECORDS inside the mech's update message: the roster walk already hands the entity's stream to every subsystem's PerformAndWatch; Simulation::WriteSimulationUpdate serializes each requested updateModel bit; Entity::UpdateMessageHandler routes received records by subsystemID to the subsystem's ReadUpdateRecord. All engine machinery -- the missing pieces were the Emitter's serialize/apply pair + the triggers. CORRECTIONS to the dormant task-33-era transcriptions (never exercised -- nothing ever set updateModel -- so the latent misreads never surfaced): - The weapon-family VTABLE SLOT MAP was swapped: slot 6 = ReadUpdateRecord, 7 = WriteUpdateRecord, 9 = TakeDamage (evidence: Mech hierarchy symmetry + body semantics; @004ba568 resolves an EntityID at rec+0x30 through the entity index -- record semantics, not Damage). Renamed across MechWeapon / Emitter / ProjectileWeapon; the real Emitter::TakeDamage @004bafc8 is undecoded (inherits MechWeapon for now). - Emitter/MechWeapon Write: `*record = 0x38/0x18` is the record LENGTH, not recordID; rec+0x30 is the TARGET's EntityID (GetEntityID()), not a colour -- the old `CopyColor(targetEntity+0x184)` was also a databinding trap. - OVERRIDE-SIGNATURE TRAP: the decls used each class's own shadowing UpdateRecord typedef as the param type, silently NOT overriding the engine virtual (the base ran instead; nothing would ever have serialized). Base-typed params (Simulation__UpdateRecord*), casts inside. - Emitter::ReadUpdateRecord reconstructed (@004ba568): target EntityID resolve (drop unknown non-null targets), MechWeapon alarm apply chain, beam fields. - ServiceDischarge/ContinueDischarge: ForceUpdate() per keepalive tick + one final record at beam end (turns the peer's beam off). - Mech::DrawWeaponBeams extracted from the player-only drive block so the walk runs for REPLICANTS (+ per-mech gun-port cache -- the process-wide statics would have served the player's segment pointers as the replicant's muzzles). VERIFIED 2-node: A fires 57 volleys -> 225 emitter records -> B applies all 225 -> B draws 414 beams (PPC blue / laser red, from A's replicant's own gun ports). Solo un-regressed (150 beams, kill chain, no crash). Also preserved: the full Mech::WriteUpdateRecord @0x4a0c2c recovery (reference/decomp/mech_writeupdate_004a0c2c.disasm.txt) with all 9 record types decoded (pose/alarm/leg-state+heat with the body-channel write-through re-sync, knockdown, death, impact, movementMode) -- transcription deferred; it replicates remote knockdown/death/heat and was not needed for beams. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e30a61a62f |
World-pick targeting: 0x388 = whatever is downrange -- mech, terrain, or nothing (task #41)
User video evidence (lasers firing at nothing) + the colleague's
torso-locked report + the binary all reconcile into one model:
The boresight pick hits the WORLD, and the target slot mech+0x388 holds
whatever entity is downrange:
- enemy mech under the boresight -> aimed target (hull point -> STEP-6
zone under the boresight, hotbox + lock ring, damage);
- else the TERRAIN downrange (BTGroundRayHit to 1200) -> the beam and
missiles fire at the scenery ("firing at nothing", as in the pod
videos), range caret reads the ground distance (authentic :5639), no
damage;
- else (sky) -> no target, and the weapon's own double 0x388 gate
refuses the discharge.
Binary evidence for non-mech targets: HudSimulation :5620 explicitly
handles a target WITHOUT damage zones (target->0x120 == 0) -- dead code
if only mechs were ever targeted. The pick is automatic every frame
(0x388: 11 reads / 0 direct stores in CODE).
Implementation: gBTTerrainEntity captured in MakeEntityRenderables
(Terrain::GetClassDerivations); mech4 pick = enemy PickRayHit else
ground ray -> terrain entity + ground point; damage/hotbox/lock only
for the mech pick; the task-40 enemy auto-converge REMOVED (facing away
now fires at the scenery, not magically at the enemy -- the user's
complaint). Verified all three states headless (BT_AIM 0,0 / .6,.5 /
0,-.8): aimed zone hits / terrain beams + 0 damage / no discharge.
Fourth (and evidence-complete) targeting model iteration; KB updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
ebeadba78e |
Verified the BLH loadout (missiles ARE authentic) + split weapon controls
BT_ROSTER dump (the shipped subsystem stream is the authority): the pod Blackhawk mounts 5 Emitters (two laser types) + 2 MissileLaunchers each with its own AmmoBin -- the periodic missile volleys are the real loadout firing on its own recharge, NOT a placeholder weapon. What WASN'T authentic: one key firing everything (the bring-up collapse of the controls mapper). Interim split toward the real weapon groups: SPACE = energy weapons (lasers) CTRL = missile launchers (the projectile-weapon channel) New gBTMissileTrigger pulse; projweap's fireImpulse moves onto it; the BT_AUTOFIRE harness drives both channels. The full controls-mapper weapon grouping stays the deferred authentic reconstruction. Also: BT_ROSTER=1 one-shot loadout dump (class name + id per subsystem). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
070af409f7 |
gauge wave P1a: publish heat/power/weapon attributes + radar zoom
The gauge-databinding-map workflow found most cockpit gauges resolve NULL because the reconstructed subsystems publish only a fraction of the attributes the config binds. First publishing batch (attribute tables are read-only static data; ids kept a dense prefix from each parent's NextAttributeID): - HeatSink table dense-append: DegradationTemperature/FailureTemperature (the condenser temp-bar warn/max endpoints -- were NULL, so the two-part bars could not scale), NormalizedPressure/DegradationPressure/CoolantMassLeakRate, and the HeatSink link. Condenser/Reservoir inherit this -> all 6 condenser temp bars now resolve current/warn/max (verified: BT_GAUGE_ATTR_LOG all OK). - PoweredSubsystem::GetAttributeIndex() (new) publishes InputVoltage->voltageSource -- the cluster power-branch gate (the power-lamp/generator-voltage/state-lamp sub-branch is skipped when it resolves NULL). Flows to Sensor/Myomers/weapons. - MechWeapon::GetAttributeIndex() (new) publishes OutputVoltage/PercentDone-> rechargeLevel; Emitter/PPC/ProjectileWeapon/MissileLauncher/GaussRifle DefaultData re-pointed at it (they carried an EMPTY default-constructed index -> resolved NOTHING). Verified: the ER MED LASER / PPC / STREAK weapon clusters now render live recharge dials (were blank TEMP/STATUS). - Mech::SetTargetRange un-stubbed (radarRange = range) -> the radar map scale + overlay range readout track the mapper's zoom (was frozen at 1000). - GAUGREND ParseAttribute: env-gated per-binding resolution trace (BT_GAUGE_ATTR_LOG) -- durable diagnostic infra for the wave. Verified DBASE+dev gauges: no startup/gauge-construction crash (dense chain intact), combat un-regressed (TARGET DESTROYED), clusters build with InputVoltage resolving. Remaining config-binding NULLs: HeatSink/AmbientTemperature (aggregate bank, P3) + Searchlight/LightOn (P1b). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c850ffa26c |
WAVE 7 Phase B polish: missiles launch from the mech's missile ports + look like missiles
Two issues the user hit driving the Mad Cat: (1) projectiles appeared to launch from the mech's FEET -- MechWeapon::GetMuzzlePoint falls back to the mech origin (localOrigin) when the weapon's mount segment (this+0xdc) doesn't resolve, so the passed muzzle was at ground level; (2) they rendered as a thin laser-like line, not a missile. Fix: BTPushProjectile now takes the SHOOTER mech and resolves the real launch port by NAME (sitermissleport / sitelmissleport, alternating L/R for a salvo look; then torso ports, then gun ports; then a raised fallback) -- the same segment-name -> world-transform mechanism the visible laser beams use for the gun ports. The tracer is now a 3-part missile look (dim smoke trail behind + orange body + hot flame tip) instead of a single thin beam. FireWeapon (both ProjectileWeapon and MissileLauncher) passes owner as the shooter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
bf44a45ab7 |
WAVE 7 Phase B fix: projectile weapons no longer permanently jam in bring-up
Interactive repro: fire one missile salvo, then only lasers. Cause: ProjectileWeapon:: CheckForJam gates the heat-scaled jam roll on `heatLoad > 0` (true whenever firing), and the roll uses currentTemperature -- which the reconstructed heat model pins at a constant ~77 (thermalMass is huge, so temp doesn't rise/fall with fire yet). So the roll fired spuriously on ~every shot, and since JammedState clears only on ResetToInitialState, a weapon that jammed stayed jammed for the whole mission. (The auto-fire harness hid this -- it kills the target before jams accumulate.) Fix: gate the roll on the weapon being in a degraded HEAT STATE (heatAlarm level >= DegradationHeat) instead of raw heatLoad -- the authentic "jam risk scales with heat" rule. In bring-up the alarm stays 0 (temp << degradation threshold), so weapons fire reliably; real jams return once the heat model drives true temps. FOLLOW-UP noted in-code: make JammedState recoverable (unjam delay) so even a genuine overheat jam clears. Verified: 31 projectile impacts sustained over 18s, 0 crashes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f52bf057e6 |
WAVE 7 Phase B: the Mad Cat's LRMs LAUNCH flying missiles that fly + damage (autocannon too)
The byte-exact WORLD-ENTITY reconstruction (Projectile @4be1bc 0x340 / Missile @4bf5b4 0x368)
is infeasible on the 2007 engine: measured sizeof(engine Entity)=0x1BC vs the 1995 binary's
0x300, so the reconstruction's raw base-offset reads (velocity@0x1dc, roster@0x124, motion@0x250)
read GARBAGE on the engine (the Mech 0x638-vs-0x854 gap, but the entity integrator DEPENDS on
those offsets). So -- like the mech drive and the beam renderer -- flying projectiles are a PORT
reconstruction (BTPushProjectile/BTUpdateProjectiles in mech4.cpp, a static array + stack
messages, ZERO heap ops): seeded from the launcher's fire with the decomp's real muzzle
(GetMuzzlePoint) / launch speed (|launchVelocity|) / per-shot damage (damageData, split across
missileCount), they fly to the target (tracer via BTPushBeam) and deliver the weapon's damage on
impact through the SAME Entity::TakeDamage path as the beam (aim Mech::FirstVitalZone()).
THREE bring-up fixes were needed to make a projectile weapon fire at all (found by tracing):
1. TRIGGER: fireImpulse was only driven for the Emitter; ProjectileWeaponSimulation now sets
fireImpulse = gBTWeaponTrigger too (else CheckFireEdge never sees an edge).
2. AMMO BIN: OwnerSubsystemCount/OwnerSubsystem were stubbed ->0, so ammoBinLink never resolved
and ConsumeRound always failed; redirected to the real roster (owner->GetSubsystemCount()/
GetSubsystem(i) -- the AmmoBin 0xBCB constructs before the weapons 0xBCD/0xBD0).
3. JAM ROLL: UniformRandom() was stubbed `return 0.0f`, so CheckForJam's `0 < jamChance` ALWAYS
jammed (a projectile weapon could NEVER fire); replaced with a real LCG [0,1) (fires
~1-jamChance of the time -- authentic occasional jams).
Also: the mech's own target slot (owner+0x388) isn't populated in bring-up (the visible fire
targets the gEnemyMech global), so BTPushProjectile falls back to gEnemyMech.
Verified: Mad Cat PUSH=62 / IMPACT=31 (LRM missiles 3.33 dmg each split across the salvo + AFC100
autocannon 25 dmg), TARGET DESTROYED, 0 crashes, construction heapcheck-clean; BLH un-regressed
(also fires its ballistic weapon now). Diagnostics BT_PROJ_LOG ([projectile] PUSH/IMPACT).
REMAINING (deferred): the byte-exact world-entity Missile (Projectile : Mover, MP-replicable via
Registry::MakeEntity) -- the port projectile is master-local only (no MP replication); the real
per-weapon fire-rate/heat wiring off the subsystem sim (mech4 beam path is still the bring-up harness).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
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>
|
||
|
|
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>
|