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>
The one un-exported gap in the reticle chain, read via capstone
(tools/disas2.py; the annotated disasm preserved at
reference/decomp/reticle_execute_004cdcf0.disasm.txt). Draw() is now a
transcription of the real per-frame logic, and the HUD attr-table names
(hud.hpp ids 4/5/6/8/A/B/C/D) are CONFIRMED by their Execute usage:
- Range ladder: BAR from ladder-top to the caret + caret translate.
- The bottom 21-tick tape is the TORSO-TWIST indicator (NOT heading):
deflection = -/+(span/2) x (RotationOfTorsoHorizontal / twist limit),
attrs 4/5/6. Fixed-torso BLH reads centred -- authentic static.
- The circle-with-stem is the COMPASS (attr 0xD, rad->deg rotation) at
(botX, botY - 3*tickMajor - 0.03), with the THREAT trail (attr 0xC)
in its rotated frame: 0.05-unit attack-direction marks, fresh < 2s
red, expiring at 6s. Port feed: player TakeDamage pushes the impact
direction (dormant vs the passive test dummy).
- Pips (composed into subB6): hidden when destroyed (attr 1 == 1), LIT
when the fire cycle is LOADED (attr 0x1c == 2; port source
rechargeLevel >= 1) else the dark charging ring; filtered by the
weapon-GROUP bits (weaponMode & elementMask&0xF). Range plays NO part
(the stored TargetWithinRange slots are never read by Execute).
- Lock ring: subB9 at frame centre SPINNING 4 deg/frame while locked
([0x9d] is the spin matrix, not a heading list).
- Target HOTBOX: a rectangle hugging the projected extents (x+-4 around
the top-centre hotbox point, +1/-11.5 vertical; baked K=2.8145 -- the
port projects through the live per-axis projection), switching to the
edge arrows past +-1.6 or behind (BTProjectHotBox, L4VIDEO).
- Reticle state Off/On + PrimaryHudOn full-HUD/simple-X switch, aim
translate on slew move, 3D marker + PNAME player-name mesh identified
(chain still deferred).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mark the engineering-screen cluster-panel system complete in the spec + CLAUDE.md
§10. Render-verified (BT_DEV_GAUGES_DOCK): 7 authentic panels build + draw --
SENSOR CLUSTER, MYOMERS, ER MED LASER RANGE 500M x3, PPC RANGE 500M x2 -- with bar
gauges, recharge dials, and lamps; 0 crashes, combat un-regressed. Records that
the aux-screen blocker resolved cleanly via the PoweredSubsystem bridge (the fields
were already populated; no MechSubsystem core re-base was needed) and lists the
non-blocking follow-ups.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reverse-engineered the vehicleSubSystems config primitive end to end (Ghidra
headless, all ~28 functions). It is NOT a widget: it is the engineering-screen
(MFD) subsystem-panel system. Its Make (FUN_004cbaf0) is a per-subsystem factory
that builds a SubsystemCluster-family status panel onto one of 12 auxiliary MFD
positions, dispatching on subsystem classID (HeatSink/Myomer/Energy/Ballistic
clusters). The whole cluster family (base + 5 subclasses) + 4 btl4gau2 sub-gauges
(CoolingLoop/PowerSource/ScalarBarGauge/ConfigMapGauge) are declared in btl4gau2.hpp
but not reconstructed.
BLOCKER: the Make reads base subsystem fields subsystem[0x1dc] (aux-screen position),
[0x1e4]/[0x1e0]/[0x224] that our MechSubsystem reconstruction (ends 0x114) does not
have or populate -- so the panels render nothing until the core subsystem layout is
extended + populated from the resource parse, which touches the working combat/heat
subsystem code (regression risk). Checkpointed at full spec pending go/no-go on the
large core-touching implementation.
- docs/VEHICLE_SUBSYSTEMS.md: complete reconstruction spec (dispatch table, geometry
table, class family map, sub-gauge inventory, engine-primitive reuse, the blocker,
the Phase-1/Phase-2 plan).
- reference/ghidra_scripts/DecompVSS.java: headless address-list decompiler (reusable
for any function the assert-anchored exporter skipped).
- CLAUDE.md: record the finding in the gauge-widget notes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>