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>
This commit is contained in:
co-authored by
Claude Fable 5
parent
065c114590
commit
02cdfd6576
@@ -337,16 +337,14 @@ void
|
||||
{
|
||||
HeatableSubsystem *watched = (HeatableSubsystem *)watchedLink.Resolve(); // FUN_00417ab4(this+0x114)
|
||||
|
||||
// BRING-UP GUARD (marked deviation): in the shipped game watchedLink ALWAYS
|
||||
// resolves -- the HeatWatcher resource's "WatchedSubsystem" name binds to a
|
||||
// roster segment at load -- so the binary (FUN_004aeac4) derefs `watched`
|
||||
// unconditionally. Our port does not yet resolve the watch link, so Resolve()
|
||||
// returns null; the deref AVs the moment a HeatWatcher-derived subsystem's
|
||||
// Performance actually runs (exposed by the WAVE 4 un-stub: AmmoBinSimulation
|
||||
// calls this directly; PowerWatcher::Simulation for Searchlight/ThermalSight).
|
||||
// With no resolved source, hold the heat alarm at Normal (same established
|
||||
// pattern as Emitter::TrackSeekVoltage's null-source guard). FAITHFUL
|
||||
// FOLLOW-UP = resolve watchedLink so the alarm tracks the real temperature.
|
||||
// The watch link is now BOUND by the authentic factory connect pass (task
|
||||
// #57: the mech factory's post-roster loop calls the binary's vtable slot
|
||||
// +0x38 -- FUN_004aee2c / FUN_004b1a40 -> BTWatcherBindTarget below), so
|
||||
// Resolve() succeeds on every master-node watcher whose resource named a
|
||||
// WatchedSubsystem. The binary derefs `watched` unconditionally; the null
|
||||
// guard stays for replicant nodes (the binary never binds there either --
|
||||
// the +0x38 body is master-gated -- and their watcher Performances don't
|
||||
// run) and for bring-up safety.
|
||||
if (watched == 0)
|
||||
{
|
||||
heatAlarm.SetLevel(0); // NormalHeat (no watched source yet)
|
||||
@@ -369,6 +367,51 @@ void
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// @004aee2c (HeatWatcher vtable slot 14, offset +0x38; the PowerWatcher/Torso
|
||||
// override @004b1a40 is byte-identical) -- the factory post-roster CONNECT
|
||||
// pass, disassembled from the raw binary (Ghidra missed both function starts):
|
||||
//
|
||||
// owner = this->owner (+0xD0)
|
||||
// if ((owner->simulationFlags & 0xC) == 0 && (owner->simulationFlags & 0x100))
|
||||
// watchedLink(+0x114).Add( owner->roster(+0x128)[ watchedSubsystem(+0x128) ] )
|
||||
//
|
||||
// The mech factory loop tests IsDerivedFrom(HeatWatcher @0x50e604) then calls
|
||||
// the slot. Split across the TU boundary the same way as the other
|
||||
// cross-family helpers: the OWNER-side master gate + roster lookup live in the
|
||||
// mech family (complete Mech type); these two bridges are the family-side
|
||||
// derivation test and the link Add. [T1: bytes @004aee2c/@004b1a40]
|
||||
//
|
||||
int
|
||||
BTWatcherWatchedIndex(Subsystem *sub) // -1 = not a HeatWatcher
|
||||
{
|
||||
if (sub == 0 || !sub->IsDerivedFrom(*HeatWatcher::GetClassDerivations()))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return ((HeatWatcher *)sub)->watchedSubsystem; // @0x128 (resource +0xE4, name index +2)
|
||||
}
|
||||
|
||||
void
|
||||
BTWatcherBindTarget(Subsystem *sub, Subsystem *target)
|
||||
{
|
||||
((HeatWatcher *)sub)->watchedLink.Add(target); // (**(link+4))(link, target) = FUN_00417a80
|
||||
}
|
||||
|
||||
//
|
||||
// Cross-family derivation probe: 0x50e604 is the HEATWATCHER derivation tag
|
||||
// (TestInstance @4aec38 checks its own class against it) -- NOT HeatSink, as
|
||||
// an old btl4gaug label claimed (HeatSink : HeatableSubsystem would make an
|
||||
// OR-test after HeatableSubsystem redundant; the binary's alternate branch is
|
||||
// the disjoint watcher family). Bridged so gauge TUs can test without
|
||||
// including this family's headers.
|
||||
//
|
||||
int
|
||||
BTIsHeatWatcher(Subsystem *sub)
|
||||
{
|
||||
return sub != 0 && sub->IsDerivedFrom(*HeatWatcher::GetClassDerivations());
|
||||
}
|
||||
|
||||
//
|
||||
// @4aec54 (468 bytes) -- parse the HeatWatcher resource. Stamps classID 0x0BBF
|
||||
// / size 0xF0; defaults the watched index to -1; requires DegradationTemperature,
|
||||
|
||||
Reference in New Issue
Block a user