master
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fe97746a51 |
Input: PadRIO -- play without the pod (L4CONTROLS=PAD), Workstream A.1
Ported from RP412: RIOBase split out of the serial RIO (L4RIO.h), rioPointer is RIOBase* (L4CTRL.h), PAD token -> new PadRIO() speaking the RIO surface from an XInput pad + keyboard (L4PADRIO/L4PADBINDINGS, vRIO bindings.txt grammar, hot-plug), KeyLight RGB mirror TU (BT412KEYLIGHT, /std:c++17 per-file). BT-side fixes PadRIO forced into the open: - Both keyboard input bridges (mech4.cpp, mechmppr.cpp BT_KEY_BRIDGE) stand down when a RIO device exists -- they overwrote the engine controls push every frame. M/X conveniences stay live. - Mapper attribute chain OFF BY ONE (latent real-pod bug): the DOS chain below MechControlsMapper carried two base attributes, WinTesla carries one, and AttributeIndexSet::Find is positional -- the .CTL stick mapping wrote throttlePosition. Pad slot + binary-locked enum; gotcha ledgered (reconstruction-gotchas #11). Verified: PAD throttle lever ramps + sticks, stick turns with the authentic speed-vs-turn clamp (61.5 -> 22.0 u/s), mech drives; keyboard fallback intact (BT_FORCE_THROTTLE harness). New diags: BT_CTRLMAP_LOG, BT_STICK_LOG. (Phase 2 of docs/BT412-ROADMAP.md) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
02cdfd6576 |
Torso: the TWIST goes LIVE -- electrical watchdog chain, centered crosshair, coherent controls (task #57/#58)
The MadCat torso twists, the view turns with it, and targeting follows. Three reconstruction fronts closed: THE ELECTRICAL WATCHDOG CHAIN (why the torso never powered up): - PowerWatcher::UpdateWatch reconstructed (@004b181c, the REAL registered Performance -- PTR @0050f5fc; Ghidra missed the fn start): the watchdog MIRRORS the watched subsystem's electrical level (+0x278), brownout downgrade when gen output <= minVoltage% x rated. @004b1804 relabeled ResetToInitialState (slot 10) -- the old "Simulation" tag was wrong. - The factory watcher-CONNECT pass reconstructed (vtable slot +0x38, @004aee2c/@004b1a40 byte-identical, recovered from raw exe bytes): watchedLink.Add(roster[watchedSubsystem]) on the master node. Was the SubProxy::Start() no-op -- every watchdog sat at 0 forever. - MinVoltageScale = 0.01 (a 10-byte x87 literal @0x4b1924; was 1.0f = permanent brownout) and PowerWatcher's Derivation chains its REAL base HeatWatcher (the HeatableSubsystem stand-in broke IsDerivedFrom for the whole Torso/Searchlight/ThermalSight family). - KB correction swept: derivation tag 0x50e604 = HEATWATCHER (not "HeatSink"); the btl4gaug heat-widget gate now tests it via the BTIsHeatWatcher bridge. THE CROSSHAIR (task #58 forensics, 6-agent workflow + live probes): - The VIEW is TORSO-MOUNTED: jointtorso -> jointeye -> siteeyepoint in every twist-capable .SKL; the camera + canopy ride the same hinge subtree through HingeRenderable's live matrix-stack compose -- ALREADY WORKING in the port. The crosshair stays screen-centered (center IS the boresight); the twist reads on the tape carets/compass/radar. - The real bug was the port's gBTAimX = tan(twist) slew (the falsified "body-mounted view" model): the camera already carried the twist, so the crosshair counter-slid to hull-forward and the fire ray with it. Deleted; the pick ray inherits the twist from the yawing eye basis. - Two instrumentation traps documented (chase-eye-as-default-camera, BT_FORCE_TORSO clobbering real joints -> the hook now only fills unresolved ones); an over-correcting explicit eye compose was added on those false readings and retired the same day. CONTROLS + REPLICATION: - Q/E spring-center on release (the axis is a twist-RATE demand; the old hold-deflection model drifted forever); X also zeroes the axis and pulses the authentic torso Recenter (@004b6918). M cycles control mode via the real CycleControlMode body. - Torso update-record DIRECTION fixed: engine truth is Write=serialize / Read=apply; @004b6a78 is the READ (was mislabeled Write) and the missing WRITE @004b6a1c recovered from raw disasm (recordLength 0x1C, twist/vel/rate at +0x10/14/18) -- kills the replicant's 0xCDCDCDCD -140-degree ghost twist. - Marching-ghost desync: 4 Standing-case guards zero stale reverse cycleSpeed (negative cadence passed the <= ZeroSpeed stop gate). - Kill credit rerouted to the OBSERVED killer (lastInflictingID -> killer's player link) -- kills count, target K/D populates. KB: subsystems.md (watcher chain), multiplayer.md (record direction), combat-damage.md + gauges-hud.md + cockpit-view.md (torso-mounted view re-correction), decomp-reference.md (new addresses + tag fix), open-questions.md (dead capability-roster loops 2-4, snapshot CD read). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
092408041c |
Cockpit: the CONFIG-MODE weapon-regrouping session + the live-play fire fixes (tasks #6+#11)
TASK #6 -- the pod's in-cockpit weapon regrouping, fully reconstructed and live-verified (hold-configure -> tap fire button -> toggle membership -> release-commit): - MechWeapon handlers id 9 ConfigureMappables @004b9550 / id 10 ChooseButton @004b95b8 (binary table @0x511860; the old "Myomers fns" mislabel swept) + the GetMessageHandlers() accessor chain through Emitter/ProjectileWeapon/ MissileLauncher/GAUSS/PPC (empty per-class sets swallowed dispatch). - Mapper vtable truth: +0x38 EnterConfiguration / +0x3C ExitConfiguration / +0x40,+0x44 AddOrErase evt/dir. No "secondary vtable @0050f498", no "CreateTemporaryEventMappings" virtual (RP-name drift; swept incl CLASSMAP). L4 Enter/Exit rebuilt complete: StartMappableButtonsConfigure (the NonMapping 0x10000 <-> Mapping 0x8000 mode flip + the gauge's active-weapon latch) + the held-button re-arm + the 4 fire-button temp maps; only the RIO mapper implements the toggle (Thrustmaster no-ops = can't regroup). - MechSubsystem +0xE8/+0xEC corrected to controlDestination/controlMessageID (ex hostEntity/subsystemId2 mislabel); MechWeapon ctor defaults the destination to &fireImpulse (@004b99a8). - ConfigMapGauge state loop reconstructed (PE-recovered DAT_00518eb4 table; buttonGroup GetMapState sampler -- the "needs ModeManager" guard rationale was wrong). Finding [T1]: the shipped binary NEVER enables this gauge (no SetColor caller) -- authentically dormant; BT_CONFIGMAP=1 is the dev enable. - Dev harness: HOLD 'G' opens the session (BT_CONFIG_SLOT picks the weapon); BT_CONFIG_TEST scripts a headless verify. @004afbc4 corrected to its real Fail-trap body (the guessed AddOrErase(NULL) body would corrupt groups). - Bonus [T1]: the binary MechWeapon ATTRIBUTE table has ELEVEN entries (PercentDone..WeaponState) -- "TriggerState is the only one" was wrong. TASK #11 -- the user-reported live-play regressions (both real bugs): - PHANTOM FIRE: the mech4 bring-up shot block painted an explosion at the victim on its OWN 0.3s cadence whenever fire was held -- desynced once the authentic recharges landed. Retired (the real impact visual flows from each discharge via the messmgr SubmitExplosion). - WEAPON BRICKING (the "cuts out" bug): the Loading->Loaded snap window (+-0.01 around seekV) assumes the pod's LOCKED 60 fps; one port dt-spike jumps it, the byte-verified >1.0 clamp (_DAT_004ba830=0) zeroes readiness, and the weapon sticks in Loading at level ~10000 forever (observed live). Fixed with pod-frame (1/60s) sub-stepping of the binary's own Loading tick -- also fixes the I^2R integral over-heating generators on big steps. NEW GOTCHA CLASS recorded: reconstruction-gotchas #12 (frame-pacing trap). - Duty-cycle measured (max-rate autofire): generators equilibrate in the degradation band (recharge stretch ~3.5-4.4x), the FailureHeat breaker never trips solo, PPCs sustain ~11.8-damage full-charge fire. The slow kill pacing and recharge gaps are the AUTHENTIC heat economy; the pilot's counters (this regrouping UI, generator reassignment, coolant valves) are the reconstruction queue. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
afefaeece5 |
Mech: THE 0xBD3 UNTANGLE -- real SubsystemMessageManager in [0x10d], the mapper's one true home is roster slot 0 (task #7)
Binary census [T1]: mech+0x434 has exactly 2 writers (ctor init + factory case 0xBD3) and ONE reader (@0x4b984b in MechWeapon::SendDamageMessage @004b9728) -- pure message-manager semantics, nothing mapper-like. The factory case now builds the real SubsystemMessageManager (ctor @0049bca4, 0x130, ConsolidateAndSendDamage Performance); the misbuilt MechControlsMapper squatter is evicted. Every mapper consumer re-pointed to MappingMapper() (roster slot 0 = the binary's **(mech+0x128); SetMappingSubsystem @0049fe40 touches ONLY slot 0 -- its [0x10d] mirror removed). Non-viewpoint mechs get a slot-0 demand LATCH (base mapper, [T3] accommodation -- the binary leaves slot 0 NULL off-viewpoint but never drives/animates those paths; ours does). Verified: solo drive+gait clean; MP replicant gait through the latch (full run lifecycle); 3x kill/respawn cycle with type-6 records on the observer. Remaining (task #7 tail): the messmgr's consolidated TakeDamageStream send + weapon-path routing (@004b9728 real body). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c9f0c2a7f1 |
MP: the Mech-level update records @0x4a0c2c/@0x4a1232 -- all 9 types byte-exact (task #1)
- Writer transcribed from the recovered disasm; reader rewritten from part_012.c with every Wword absorber promoted to named engine/port members (updateOrigin/updateVelocity/projectedOrigin/projectedVelocity/nextUpdate/ lastUpdate + bodyTargetSpeed/latches/alarms). Wire sizes verified live: 0x14/0x20/0x2c/0x78. - movementMode UNIFIED with Simulation::simulationState (binary mech+0x40 = StateIndicator@0x2c currentState) -- death/limbo/airborne now replicate in every record header. Three mislabels of the same binary fns retired: SetInstanceFlags + RequestActionFlags -> Mech::ForceUpdate (updateModel |= mask, 0xfe03 disabled filter); IsNetworkCopy -> IsDisabled. - Senders wired byte-exact: gait transitions Force(8), knockdown Force(1|0x20), death Force(1|0x40), Reset Force(0x1f) + binary zero-set, perf-loop deadbands (speed type 2, orientation type 4, heat type 7). - 2-node verified: types 2/3/4 flow while driving; kill -> type-6 record (simState=9) -> the OBSERVER's replicant runs the wreck sink loop with no double death transition; respawn 0x1f burst snaps + un-wrecks the peer; walking replicant un-regressed (run 12, cycle tracking); solo clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a110041a70 |
Fix "can't turn in MP": pilotArray[1] overrun stomped controlMode
User report from the first live 2-window session: throttle worked, A/D turn
did nothing. [mppr] showed the input REACHING the mapper (stickX=-1) but
turnDemand=0 and controlMode = pointer garbage (73583232 / 217478632 --
different per instance), from frame 1, MP only (solo mode=0).
Hunted with a cdb hardware write-breakpoint (ba w4 armed at ctor time on
&controlMode, compiled offset +0x128 from the [mode-ctor] probe). Caught the
writers red-handed: MechControlsMapper::BuildPilotArray / FillPilotArray.
ROOT CAUSE -- a shrunk-span array: the binary reserves a FIXED 10-slot pilot
block @0x15C..0x183 ((pilotArrayBuilt@0x184 - 0x15C)/4 = 10); the recon
declared `Pilot *pilotArray[1]` ("variable length"), so every write past
slot 0 stomps the members declared after it. In MP, FillPilotArray wrote the
PEER's Player pointer into slot 1 = over controlMode -> the turn shaping
dispatched on pointer garbage -> turnDemand 0. MASKED in solo: the zero-loop
overrun wrote 0 there, and 0 == BasicMode. This was ALSO the real cause of
task #48's "turnDemand zeroes out in -net" observation (worked around then
with the direct goto-turn; attribution corrected in the KB).
Fix: pilotArray sized to the binary's own 10-slot span; ctor zeroes all
slots; BuildPilotArray clamps pilotCount to capacity (local + 9 remotes; an
8-pod game fits). Diagnostics kept (BT_MODE_LOG ctor probe; [mppr] line now
prints mapper/&mode).
VERIFIED: MP session holds mode=0 throughout, turnDemand flows nonzero, the
mech turns (heading -1.33) and closes on the peer; solo kill chain intact.
KB: new "shrunk-span array" gotcha (reconstruction-gotchas 5) with the cdb
ba-w4 recipe; multiplayer.md task-#48 note corrected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
d4e139a0d9 |
Gait v5: display+travel from the LEG channel (kills the stutter class)
User-reported recurrence of "legs stutter and lose sync". Live [sync] showed
advSum/legSum drifting 5->92 during walk-up churn, then locking. Reproduced
headlessly (BT_FORCE_OSC throttle feathering + BT_FORCE_TURN + the new
per-frame BT_SYNC_LOG probe) and root-caused in two parts:
- SEED: the bring-up turn-in-place trigger arms only the LEG channel, so the
walk re-entry lands one frame apart -> a persistent clip phase offset.
- AMPLIFIER: with offset clips the two end-of-clip callbacks fire on different
frames; a demand change landing between them picks DIFFERENT next states
(one channel winds down, the other keeps walking) -> opposite-phase churn
(observed bs=6 ls=7 -- body on walk-R, leg on walk-L).
Under v4 (display+travel = BODY) the out-of-phase LEG pose showed through on
every frame the body didn't write joints (Standing/wind-down) = the visible
stutter/pop.
DISASM GROUND TRUTH (neither Advance fn has a static decomp caller; found by
byte-scanning the CODE section for e8 calls):
- master perf (0x4a9b5c gap) -> AdvanceLegAnimation @0x4aa399 (air @0x4aa388)
-> -dist/dt into localVelocity (+0x1cc): the LEG drives LOCAL travel and
(writing last) the displayed pose;
- IntegrateMotion (0x4ab1c8, body advance @0x4ab312, caller FUN_004ab430 =
the projected-origin updater) -> -dist/dt into projectedVelocity (+0x2a0):
the BODY is the dead-reckoning PROJECTOR -- locally invisible.
v4's reading of FUN_004ab430 as the travel source was wrong.
FIX: advance body FIRST (projection; its writes get overwritten), leg LAST
(displayed pose); travel = legAdv under the two-channel split. Display ==
travel through the LEG by construction -- body-channel drift can no longer
become visible, structurally (not by input-symmetry luck).
Verified: trn+feathering repro clean; straight drive locked (adv==proj);
solo goto=enemy kill chain intact; MP 2-node drive-to-range cross-pod kill
intact (241 hits, DESTROYED); no crashes. KB corrected (locomotion.md roles
+ P3_LOCOMOTION.md v5 entry). New gated instruments: BT_SYNC_LOG,
BT_FORCE_OSC.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
48b17750e5 |
HUD reticle + weapon pips LIVE: dpl2d 2D display-list port (task #35)
- dpl2d API fully recovered from the binary recorders (@487f34-488630): opcode model (points/lines/polyline/circle/color/width/matrix/push-pop), CallList = INLINE include (state persists to caller), centered coordinate frame (unit = half viewport height). game/reconstructed/dpl2d.cpp rework. - BTReticleRenderable ctor @004cc40c transcribed with the authentic calibration (originX .35, originY .25, scaleY .5, 0..1200m right range ladder, bottom heading tape, FUN_004cd938 tick ladders, lock rings, turn arrows); range caret slides from the live target range fed by the mech4 targeting step (BTSetHudTargetRange). - Weapon pips: the binary gate is IsDerivedFrom(0x511830 = MechWeapon::ClassDerivations) [T1: part_014.c:5386 hard-aborts on missing weapon attrs; part_012 counts + roster ORs capabilityFlags@+0x334] so ALL 7 BLH weapons register (3 lasers + 2 PPCs + 2 MissileLaunchers). Pip A (lit, authored PipColor) on TargetWithinRange, else dark ring B. - AddWeapon @004cdac0 store map corrected to the verified order (part_014.c:4827-4837); both state attrs are literally named "SimulationState" (strings @51d526/51d577) -> weapon simulationState. - Mech roster this[0x1ef] renamed poweredSubsystems -> weaponRoster (0x511830 is MechWeapon, not PoweredSubsystem=0x50f4bc); derivation-tag table added to context/decomp-reference.md. - Draw hook BTDrawReticle after the 3D scene, cockpit view only. Binary Execute @004cdcf0 is an un-exported gap -> Draw dynamics [T3], tracked in context/open-questions.md with the blx_cop canopy + PNAME pip meshes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0060a3e1ca |
gauge wave P2b: build the Comm KILLS/DEATHS pilot roster (PilotList)
PilotList (keyword "pilotList") was PROSE-ONLY, so the Comm surface showed only
the baked btcomm.pcx labels. Reconstructed all 7 functions from part_014.c:
3156-3434 (Make/ctor/dtor/BecameActive/TestInstance/Execute/DrawMechIcon), with
the 8x(x,y,layoutMode) layout table DAT_0051af88 PE-parsed exact from BTL4OPT.EXE.
Draws ONE roster slot/frame (round-robin) from the viewpoint mech's cockpit-mapper
pilot roster.
- Roster FEED: new BTResolveRosterPilot(slot) bridge in mechmppr.cpp (a complete-Mech
TU) resolves the viewpoint mech's ControlsMapper (subsystemArray[0]) -> GetPilot;
btl4gau3.cpp reads the returned pilot at raw BTPlayer offsets. The mapper's
FillPilotArray already fills the roster (pilotArray[0]=GetMissionPlayer,
[1..]=FindGroup("Players")).
- KILLS = killCount@0x27c (real; ScoreMessageHandler increments it in combat).
- DEATHS: the binary reads pad_0x280 which has NO writer anywhere (a shipped dead
field -> perpetual 0). FIX per "if it doesn't work, fix it": read the real
deaths counter Player::deathCount@0x200 (VehicleDeadMessageHandler increments it)
so DEATHS is meaningful in MP.
- DrawMechIcon: App+0xC8 name-bitmap cache is unwired (same deferral PlayerStatus
uses) -> LookupPlayerNameBitmap returns NULL -> the tinted name box (never an AV).
Dropped the bogus x,y from the header ctor (positions come from DAT_0051af88).
/FORCE-safe (all vtable slots real; link log clean, no unresolved PilotList/
BTResolveRosterPilot). Verified DBASE+dev gauges: the Comm surface now renders the
live local pilot row (KILLS 0 DEATHS 0 + name box; 0/0 authentic in solo -- the
combat scoring feed that moves them is Phase 3), combat un-regressed (DESTROYED),
0 crashes.
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>
|