#137 ("respawn came back with MYOMERS heat MAXED", Oracle; "overheating generator D", Sauron) sent us through a full two-sided audit of Mech::Reset and the whole RTIS chain. Both sides were correct. The answer was in the field log all along, one line after the reset: [respawn] Mech::Reset 3:30 healed+moved to (...) alive=1 [techstat] ... every live condition CLEARED [techstat] Myomers condition 3 SET <- Overheating, immediately [mppr] in thr=1 -> ... [gaitSM] cycleSpeed=14.6 state=12 <- already RUNNING [techstat] Condenser5 condition 3 SET <- "dumping into coolant loop 5" [techstat] GeneratorD condition 3 SET <- Sauron's generator D The mech respawns STILL UNDER POWER and earns the heat honestly. Two facts close it: 1. condition 3 is an OPERATING flag, not an alarm. Census over one match: LLaser_2 33 SET / 33 CLEARED, LLaser_1 31/31, SRM4 26/26, PPC_2 18/18 -- every volley trips it and clears it. EVERY subsystem is balanced (GeneratorD 5/4, Myomers 5/4, Condenser5 1/1; the extra SET is only the log ending mid-heat). cond 6 BadPower behaves the same (Myomers 8/8). Nothing latches. A post-respawn SET is not evidence of anything. 2. Mech::Reset's subsystem loop starts at index 2 and the ControlsMapper is index 0, so the throttle is never reset -- and the BINARY does the same. That is right for a pod: the throttle is a PHYSICAL lever still under the pilot's hand. Respawning under power is authentic and stays. Oracle's read that the myomer heat rate "felt right" was correct. WHAT IS a real defect (#146), desktop only: the glass bridge merely EMULATES that lever, with the static ramp accumulator sLever (mech4.cpp:3250) zeroed ONLY by the X all-stop and a direction-crossing snap. A pad/keyboard pilot is physically holding nothing and cannot see the lever, so they respawned at speed for no reason they could perceive -- and ate the heat load above. The Thrustmaster/RIO path was never affected: InterpretControls (@004d2150) rebuilds throttlePosition every frame from the databound throttleForward. Fix: queue the existing all-stop at Mech::Reset, reusing the proven path (it already clears the zero-crossing detent too). LOCAL VIEWPOINT MECH ONLY -- gBTDrive is the local bridge's state and Reset also runs for replicants, so an ungated write would all-stop the player whenever a REMOTE mech respawned. Pod-safe besides: with a RIO present the key bridge is off and gBTDrive.throttle is never read. BT_NO_RESPAWN_THROTTLE_RELEASE=1 reverts. Benched 2-node (scratchpad/night13/throttlerespawn.sh): the release fires 1:1 with local respawns on both nodes independently (A 2/2, B 1/1) and never spuriously. HONEST LIMIT: the viewpoint gate was NOT stressed -- B ran Mech::Reset 0 times for A's mech, so the remote-respawn path never fired. The gate is correct by construction (the isPlayerMech idiom), not proven. BT_AUTODRIVE cannot test the lever itself (forced mode reads forcedThrottle, never sLever), and the zeroing path is the X button, proven in the field. Also keeps BTReportHeatAtReset (heat.cpp, BT_HEAT_LOG): the [heat-t] census runs on a 5s timer, far too coarse to sample AT the reset. It is what proved every roster subsystem including all six Condensers sits at T=77 start=77, and it corrected an earlier false negative from filtering on IsDerivedFrom(HeatSink). KB: context/decomp-reference.md gains the routine/self-clearing condition semantics + this post-mortem, so it is not re-chased; cross-ref in context/gauges-hud.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018SgmXGNMXavXiafKXf9MDC
128 lines
6.2 KiB
Python
128 lines
6.2 KiB
Python
"""Night 13: close #137 (not a bug) and file the desktop throttle-latch follow-up. ASCII only."""
|
|
import sys
|
|
sys.path.insert(0, r"C:\git\bt411\scratchpad\night7")
|
|
import gitea
|
|
|
|
BODY_137 = """**NOT A BUG -- closing (2026-08-08).** The reset is correct on both sides. The
|
|
heat is real, and the mech earned it: it respawns still under power.
|
|
|
|
## What the field log actually shows
|
|
|
|
From Sauron's match log (`steam_20260806_c_michael_XIAOLONG.log`), the respawn at line 39450:
|
|
|
|
```
|
|
39450 [respawn] Mech::Reset 3:30 healed+moved to (...) alive=1 zones=21 subsys=33
|
|
[techstat] SLaser_1 / LLaser_1 / HUD / Gyroscope / Avionics / GeneratorA / HeatSink
|
|
... condition CLEARED <- every live condition clears
|
|
[techstat] Myomers condition 3 SET <- Overheating, immediately
|
|
[mppr] in thr=1 pre=1 -> ...
|
|
[gaitSM] cycleSpeed=14.6 legCycle=14.6 state=12 <- already RUNNING
|
|
[techstat] Condenser5 condition 3 SET <- "dumping into coolant loop 5"
|
|
39519 [techstat] GeneratorD condition 3 SET <- Sauron's generator D
|
|
```
|
|
|
|
`[rstat]` fires about once per second (98 frames @ 10.2ms), and the reset plus the myomers trip sit
|
|
inside one rstat block -- so myomers overheat in well under a second, generator D in one to two.
|
|
|
|
The reset itself is clean: **every** live condition CLEARs 12-16 lines after each `Mech::Reset`, at
|
|
every respawn in the log. The synthetic bench agrees -- every roster subsystem, including all six
|
|
Condensers, reads `T=77 start=77` at the reset.
|
|
|
|
## Why it looked like maxed heat
|
|
|
|
**`condition 3` is an operating flag, not an alarm.** Census over the whole match:
|
|
|
|
| subsystem | cond 3 SET | CLEARED |
|
|
|---|---|---|
|
|
| LLaser_2 | 33 | 33 |
|
|
| LLaser_1 | 31 | 31 |
|
|
| SRM4 | 26 | 26 |
|
|
| PPC_2 | 18 | 18 |
|
|
| GeneratorD | 5 | 4 |
|
|
| Myomers | 5 | 4 |
|
|
| Condenser5 | 1 | 1 |
|
|
|
|
Every weapon trips Overheating on each volley and clears it on cooldown. **Every subsystem is
|
|
balanced** -- the odd extra SET is only the log ending mid-heat. Nothing latches, nothing sticks.
|
|
`condition 6` (BadPower) behaves the same way (Myomers 8/8, PPC_1 3/3, MLaser_1 3/3) as the bus
|
|
browns out under simultaneous draw. GeneratorD's longest continuous overheat came out normally.
|
|
|
|
So the post-respawn SET is the mech *operating*, not a failed reset.
|
|
|
|
## Why the heat arrives instantly
|
|
|
|
The mech comes back **at whatever throttle the pilot left**: `thr=1`, `cycleSpeed=14.6`, gait state
|
|
12 at the instant of reset. `Mech::Reset`'s subsystem loop starts at **index 2**, and the
|
|
ControlsMapper is **index 0** -- so the throttle is never reset.
|
|
|
|
**The binary does exactly the same.** That is correct for a pod: the throttle is a physical lever
|
|
still under the pilot's hand. Respawning under power is authentic behaviour.
|
|
|
|
Oracle's read that the myomer heat rate "felt right" was correct, and matches the data.
|
|
|
|
## Ruled out along the way
|
|
|
|
`Mech::Reset` dispatch, the full subsystem RTIS chain (Generator `@004b215c` is an
|
|
instruction-for-instruction match; HeatSink `@004ad760` faithful; every RTIS class has a
|
|
DeathReset), stale coolant-loop links (`linkedSinks` is written only in the streaming ctor and
|
|
cannot drift), and the valve fractions (`BTRecomputeCondenserValves == @0049f788`).
|
|
|
|
## Follow-up
|
|
|
|
The one genuine defect found is a desktop-only input issue, filed separately -- see the throttle
|
|
latch ticket. It does not affect the pod.
|
|
|
|
Knowledge base updated so this is not re-chased: `context/decomp-reference.md` §TechStatus (the
|
|
routine/self-clearing semantics + this post-mortem) and `context/gauges-hud.md`."""
|
|
|
|
TITLE_NEW = "Desktop/Steam: virtual throttle lever survives death -- you respawn already running"
|
|
|
|
BODY_NEW = """Split out of #137, which was closed as not-a-bug. The heat model and the reset are
|
|
both correct; this is a port-layer input issue and it affects **desktop/Steam only, never the pod**.
|
|
|
|
## The defect
|
|
|
|
On the pod the throttle is a physical lever. Respawning under power is authentic -- the pilot's hand
|
|
is on it, they can see and feel where it is, and `Mech::Reset` deliberately does not touch it (the
|
|
subsystem loop starts at index 2; the ControlsMapper is index 0). The binary behaves the same way.
|
|
|
|
The desktop glass bridge emulates that lever with a **file-static ramp accumulator**:
|
|
|
|
* `sLever` -- `static float sLever = 0.0f;` (`game/reconstructed/mech4.cpp:3250`)
|
|
* published each frame as `gBTDrive.throttle = sLever` (mech4.cpp:3892/3897)
|
|
* consumed as `key_throttle` -> `throttlePosition` (mechmppr.cpp:677, :780)
|
|
|
|
The only things that zero `sLever` are the **X-button all-stop** (mech4.cpp:3754) and a
|
|
direction-crossing snap (:3842). **Nothing on death or respawn touches it.**
|
|
|
|
So a pad/keyboard pilot who dies at speed respawns at speed -- while physically holding nothing, and
|
|
with the lever position invisible to them. The pod's authenticity argument does not cover this case:
|
|
there is no lever to feel, and a gamepad stick self-centers, so the input affordance actively
|
|
contradicts the latched state.
|
|
|
|
Observed consequence in the field (Sauron's log, the #137 evidence): the mech leaves the drop zone
|
|
at `thr=1` / `cycleSpeed=14.6` the instant it spawns, and the myomers, Condenser5 and GeneratorD all
|
|
trip Overheating within one to two seconds. Reads to the player as "respawned with heat maxed".
|
|
|
|
Note this is **not** the same as the Thrustmaster/RIO path, which is already correct:
|
|
`MechThrustmasterMapper::InterpretControls` (`@004d2150`) recomputes `throttlePosition` from scratch
|
|
every frame off the live `throttleForward`/`throttleReverse` states, and `throttleForward` is
|
|
databound (written by the input layer each frame). Only the desktop `sLever` accumulator latches.
|
|
|
|
## Suggested fix
|
|
|
|
Zero `sLever` (and the detent) on respawn in the desktop bridge only -- the same treatment the
|
|
X all-stop already applies -- so a desktop pilot comes back stopped. Gate it so pod builds keep the
|
|
authentic physical-lever behaviour.
|
|
|
|
This is a gameplay-behaviour decision as much as a bug fix: it changes whether desktop players
|
|
respawn moving or stopped. Worth a call before it ships.
|
|
|
|
## Not yet done
|
|
|
|
Not fixed, not benched. Filed from a static read of the input path plus the field log."""
|
|
|
|
gitea.close(137, BODY_137)
|
|
num = gitea.create(TITLE_NEW, BODY_NEW)
|
|
print("closed 137; created:", num)
|