Measured A/B/C (Blackhawk, sustained autofire, 150s soaks): SRM6 on
Condenser2 plateaus ~531 and COOLS at share 0.91 (boost 2), climbs to
the jam band at balanced (0.167), and runs at the 2000 failure line
starved (0.018). MoveValve reruns the redistribute every press;
veteran passes the novice guard. The field 'boosted loop 2 still
cooked' therefore means the valve was NOT at the 50 detent during the
fight -- the 1->5->50->0 cycle turns the loop OFF one press past max.
MoveValve presses now log always-on ('[valve] <name> -> detent N' +
a CLOSED warning) so the next field session records the detents.
Also: solo mission clock verified live (60s egg self-ends,
'[mission] solo game clock expired' -> RunMissions returns).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The playtest report (sound plays, no gauge drop, no cloud) traced to the
Reservoir's InjectCoolant chain being dead in three places:
- The handler was never REGISTERED: the binary's Reservoir handler table
@0x50e680 has one entry {4, "InjectCoolant", @4aee70}; added
Reservoir::GetMessageHandlers + the press/release handler (press starts
the flush gated on coolantLevel@0x12C > 0 -- the old body misread +0x12C
as currentTemperature; release stops it; novice lockout via FUN_004ac9c8).
- Reservoir::InjectCoolant (@4aefa4, 1019 bytes) was an empty stub -- the
drain the coolant gauge reads. Reconstructed in full: work list =
condenser/weapon/heatable chains (+0x7cc/+0x7bc/+0x7ac, roster-walk
emulation with the binary's duplicate-visit weighting; HeatSink-filtered
[T2 guarded]) + the linked master sink; per sink with flowScale != 0 move
squirtMass x flowScale x dt (clamped to the tank / sink capacity) and
credit pendingHeat with the negative carried-heat delta capped at
sinkMass x reservoir startingTemperature -- the set%-biased flush of the
manual (p24), riding the existing heat model.
- Two latent ctor decode bugs surfaced and fixed: the master gate @4af408
(and @4aeb40 HeatWatcher, swept) reads the OWNER MECH's simulationFlags
(*(param_2+0x28)), not the resource's subsystemFlags (the misread left
the CoolantSimulation Performance unregistered); and the capacity scale
FILDs the bank's INTEGER HeatSinkCount ((float10)*(int*)(link+0x1d0)) --
the float reinterpret gave ~1e-44 -> a permanently empty tank.
Capacity = 0.05 x heatSinkCount x streamed CoolantCapacity (BLH: 6.0).
Visual: the binary's mode-1 coolant-effect renderable (FUN_00456a68, built
for classID 0xBC0 on "ReservoirState", part_014.c:5439; tick @part_007.c:
8780) starts psfx 19 = FLUSH.PFX ("Coolant flush", BTDPL.INI) when the
state changes to 1 -- BTSpawnFlushCloud (mech4.cpp) spawns it on the same
alarm edge as an attached emitter at torso height.
Input: new CONTROLS.MAP action "Flush" on 'H' (HELD; press+release both
dispatch, the held-button payload). Diags: BT_FLUSH_LOG, BT_FLUSH_TEST.
Verified live (FOGDAY): [flush] Reservoir level 6 -> 0.13 -> 0 over ~0.6 s
held (= the manual's 3-4 punches to empty), the coolant vertBar source
Reservoir/CoolantMass drains, and the bluish condensation cloud rises from
the mech (scratchpad/flush_cloud.png vs flush_before.png).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Brings Cyd's experience-level / simulation-mode research onto master (the
KB-decode portion of glass-cockpit 4e01e83; the bundled BT410 source
manifest is left for the full glass-cockpit merge).
- context/experience-levels.md (NEW): the egg per-pilot 'experience' field
(novice/standard/veteran/expert) is the pod's SIMULATION-FIDELITY tier,
decoded end-to-end. FUN_004c0bc8 reads btMission->experienceLevel(+0xe4)
and fans it into the +0x25c/+0x260/+0x26c/+0x270/+0x274 flag block:
+0x25c 'sim live' (novice lockout: jams/searchlight/powersub), +0x260 the
HEAT-MODEL master switch (veteran+expert; FUN_004ad7d4), +0x274 raw level
(FUN_004ac9c8 = ==0 novice predicate; the valve/advanced-cockpit lockout).
4.0->4.10 drift: the viewscreen hunting-aid gate is GONE in 4.10 (HUD reads
none of these flags), and movement heat is veteran+expert not expert-only.
- btplayer.cpp/.hpp: CORRECTED comments/labels -- the +0x25c..+0x274 block is
seeded from btMission experienceLevel/advancedDamageOn, NOT the scenario
role's returnFromDeath; the 'roleClassIndex/showKills' names are scoring-era
mislabels. Code logic UNCHANGED (comment-only): the ctor still reads
scenarioRole (STAND-IN, defaults 2 = veteran) pending the wiring task.
- Corrections swept into gauges-hud.md (ROOKIE->novice lockout),
open-questions.md (player+0x260/0x274 semantics now PINNED),
pod-hardware.md, subsystems.md, decomp-reference.md; CLAUDE.md router row.
No code-logic change -> no rebuild needed (comment + markdown only). The
runtime wiring (seed from BTMission::ExperienceLevel()) remains TODO.
Co-Authored-By: Cyd <cyd@falloutshelterarcade.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Investigation (workflow) found the searchlight-driven fog swap never worked in the
ORIGINAL 1995 binary either -- it's a latent bug, not a port regression:
SearchlightSimulation (@004b841c) reads requestedOn@0x1E0 (never written) while
ToggleLamp (@004b860c) toggles commandedOn@0x1DC; no bridge exists, so lightState
@0x1D8 is perpetually 0 (lamp never lights). The self-consistent sibling
ThermalSight reads the field it toggles (0x1DC) -- the tell. The port reproduces
the bug faithfully (searchlight.cpp:189). Additionally the port never constructs
PullFogRenderable, so even absent the bug the swap wouldn't fire.
Decision: LEAVE AS-IS + document (faithful to the buggy original); the port keeps
the static lights-ON fog= values. A working swap would DEVIATE from the shipped
binary (repair the sim to read commandedOn + construct PullFogRenderable at the
btl4vid MakeMechRenderables inside pass + a toggle input) -- fully scoped in the KB
if ever wanted. No code change this commit.
Documented in rendering.md (fog section), open-questions.md (deferred subsystems),
subsystems.md (Searchlight). Core per-map/time/weather fog (task #63) is unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
ConsolidateAndSendDamage @0049b784 fully implemented: the beam path submits
into AddDamageMessage; each messmgr tick builds ONE Entity::
TakeDamageStreamMessage (id 0x13; wire-verified 0x34+4+Nx12) with the
common impact record + appended {type, amount, subsystemID} entries and
Dispatches it at the victim -- the T0 handler (ENTITY.cpp:817) re-splits
into per-record TakeDamage; replicants reroute cross-pod. Real
ResolveExplosionID (firing weapon's +0x3E4, guarded) + SubmitExplosion via
the Explosion::Make port; TWO chain-purge bugs fixed (the "iterator dtor
clears it" assumption was wrong -- records re-applied every tick, observed
1->2->3->4 double-counting). @004b9728 identity corrected (SendDamageMessage,
not DrawWeaponPip). Verified solo: 25 clean single-record consolidations ->
kill -> death transition. Residue: weapon-side submission awaits the
damage-economy reconciliation (authored 0.25-scale vs kShotDamage=12).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Propagate the verified finding (0xBD3 = damage/explosion hub @0x434, NOT the valve/message
gate; the valve/Myomers gates read the owning BTPlayer @mech+0x190) into the topic files that
still carried the old claim: decomp-reference (ClassID row + both offset rows), gauges-hud,
subsystems, open-questions (Myomers coupling), + frontmatter. Graph validates clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>