Respawn-reset audit: valve restore + peer smoke cleanup (both binary-grounded)
Operator reports audited vs the binary (full matrix in RESPAWN_REARM_PLAN addendum): - VALVES (real gap): Condenser reset @004ae534 was missing from the decomp export -- raw disasm shows it chains HEATSINK (coolant refill runs; the old body chained HeatableSubsystem per the stale TCP shard) then, respawn- side, resets valveState to detent 1 and restores massScale from refrigerationFactor. Mech::Reset now also runs the binary's tail call (@0049f788 BTRecomputeCondenserValves) so flow fractions rebuild from the reset detents. Bench: detent 5 -> death -> "[respawn] Condenser1 valve detent 5 -> 1". - #129 SMOKE (real gap, peers-only): the replicant un-wreck edge rebuilt the model without the @004d0c14 per-entity effect cleanup, so the observer's last 10s wreck-plume window rode the teleport onto the fresh mech. BTStopEntityPfx now runs on the edge; bench shows no plume line after any un-wreck until the next death. - AUTHENTIC (no fix): weapon->generator taps persist (@004b0e6c only resolves the link) and MFD display/control modes persist (mapper vtables 0050f45c/0051e440 slots 8-11 = plain root bodies, read from the exe). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b6656e35c5
commit
26ab2fee3a
@@ -254,4 +254,20 @@ Keep all three unconditional (matchlog only arms on `-net`, `matchlog.cpp:66-76`
|
||||
7. **Do not remove the entity.** Nothing in this plan may issue `DestroyEntityMessage` on death — the wreck stays (P5 teardown crash).
|
||||
8. **Don't reconstruct dtor glue** while touching the heat/power chain terminus; the trailing base-dtor calls run the chain twice.
|
||||
9. **`AmmoBin +0x228`** is written to 0 by `@004bd26c` alongside `feedTimer`/`cookOffArmed` and is the object's last dword (`sizeof == 0x22C`); our `ammobin.hpp:270` calls it `reserved // unused`. It is a live per-feed latch — identify it from `AmmoBinSimulation @004bd394` / `FeedAmmo @004bd4f4` before shipping the refill move, or copy the write blind and flag it [T3].
|
||||
10. **Roster-order sensitivity is a non-risk.** `PoweredSubsystem::RTIS @004b0e6c` gates the electrical restore on the source Generator's `stateAlarm == 2`, but `Generator @004b215c` sets level 2 **unconditionally** (`0x4b2194`) and the death sweep already ran `DeathReset(0)` over the whole roster with no index skip — so no authored segment order can produce a partial re-arm. Do not add ordering logic to `Mech::Reset`.
|
||||
10. **Roster-order sensitivity is a non-risk.** `PoweredSubsystem::RTIS @004b0e6c` gates the electrical restore on the source Generator's `stateAlarm == 2`, but `Generator @004b215c` sets level 2 **unconditionally** (`0x4b2194`) and the death sweep already ran `DeathReset(0)` over the whole roster with no index skip — so no authored segment order can produce a partial re-arm. Do not add ordering logic to `Mech::Reset`.
|
||||
---
|
||||
|
||||
# ADDENDUM 2026-08-04 — the full-reset audit (operator request): valves, gensel, MFD modes, spawn smoke
|
||||
|
||||
Steps 0-5 above all landed via #55 and are byte-consistent (re-audited today).
|
||||
Four operator/field reports were then grounded and dispositioned:
|
||||
|
||||
| report | binary ground truth | disposition |
|
||||
|---|---|---|
|
||||
| coolant valves survive respawn | `@004ae534` (MISSING from the Ghidra export -- raw disasm): chains **HeatSink** `0x4ad760` (coolant refill RUNS), then arg-gated `valveState=1` + `massScale=refrigerationFactor`; `Mech::Reset` tail (`call 0x49f788`) re-runs RecomputeCondenserValves | **PORT GAP -> FIXED** (heat.cpp authentic body + mech4.cpp Reset tail call). Bench: detent 5 -> death -> `[respawn] Condenser1 valve detent 5 -> 1` |
|
||||
| generator switching survives | `@004b215c` sets `generatorOn=1` (landed, #55); `@004b0e6c` only RESOLVES the source link -- **the weapon->generator taps are never rewritten** | gens re-light = already correct; **taps persisting = AUTHENTIC** |
|
||||
| MFD display mode / control mode survive | mapper vtables `0050f45c` + `0051e440` slots 8-11 read from the shipped exe = plain root bodies -- **displayMode/controlMode have NO reset anywhere** | **AUTHENTIC persist** -- do not "fix" |
|
||||
| #129 fresh spawns smoke briefly | the pod's respawn ran the per-entity effect cleanup `@004d0c14` | **PORT GAP -> FIXED**: only the master's Mech::Reset ran the cleanup; the REPLICANT un-wreck edge (mechdmg.cpp MechDeathHandler::Tick) rebuilt without it, so the observer's last 10s plume window rode the teleport (peers-only symptom). BTStopEntityPfx now runs on the un-wreck edge |
|
||||
|
||||
Bench: `scratchpad/night11/respawnreset.sh` (BT_VALVE_TEST press -> BT_MP_FORCE_DMG
|
||||
kill -> read `[valve]`/`[respawn]` on A, plume/un-wreck ordering on B).
|
||||
|
||||
+28
-10
@@ -359,22 +359,40 @@ Condenser::~Condenser()
|
||||
#endif
|
||||
|
||||
//###########################################################################
|
||||
// ResetToInitialState -- Condenser (HEAT.TCP)
|
||||
// ResetToInitialState -- Condenser
|
||||
//
|
||||
// RE-TRANSCRIBED FROM THE BINARY (respawn-reset audit 2026-08-04; the function
|
||||
// is MISSING from the Ghidra export -- disassembled raw, tools/disas2.py
|
||||
// 0x4ae534 0x60). The old body chained HeatableSubsystem (temp+heatLoad only,
|
||||
// per the stale HEAT.TCP shard) -- the binary chains HEATSINK (call 0x4ad760),
|
||||
// so the coolant refill DOES run for a condenser; then, RESPAWN-side only:
|
||||
//
|
||||
// test esi, esi ; je ... -> if (powered) {
|
||||
// [this+0x1d0] = 1 -> valveState = 1 (default detent --
|
||||
// the pod RESETS every coolant valve)
|
||||
// eax = [this+0x1d8]; [0x160] = eax -> massScale = refrigerationFactor
|
||||
// (restore the refrigeration output) }
|
||||
//
|
||||
// Mech::Reset's tail then re-runs BTRecomputeCondenserValves (@0049f788), so
|
||||
// the flow fractions rebuild from the reset detents (mech4.cpp). This is the
|
||||
// operator-reported "coolant valves survive a respawn" gap [was T3, now T1].
|
||||
//
|
||||
void
|
||||
Condenser::ResetToInitialState(Logical /*powered*/)
|
||||
Condenser::ResetToInitialState(Logical powered)
|
||||
{
|
||||
HeatableSubsystem::ResetToInitialState(True);
|
||||
HeatSink::ResetToInitialState(powered); // call 0x4ad760
|
||||
if (powered)
|
||||
{
|
||||
if (getenv("BT_DEATH_LOG") && valveState != 1)
|
||||
DEBUG_STREAM << "[respawn] " << GetName() << " valve detent "
|
||||
<< valveState << " -> 1 (authentic @004ae534 restore)\n" << std::flush;
|
||||
valveState = 1; // @0x1D0 = 1
|
||||
massScale = refrigerationFactor; // @0x160 = @0x1D8
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// DeathReset (Gitea #55): the respawn sweep's entry for a Condenser -- a coolant
|
||||
// loop's valve. NOTE the body above chains HeatableSubsystem's (temperature +
|
||||
// heatLoad) and so does NOT run HeatSink's coolant refill, even though Condenser
|
||||
// derives from HeatSink. That mirrors the existing reconstruction; whether the
|
||||
// binary's Condenser reset also restores the VALVE SETTING is not established from
|
||||
// the decomp yet, so valve detents may still persist across a respawn.
|
||||
// [T3 -- do NOT claim valves are fixed; tracked on the issue.]
|
||||
// DeathReset (Gitea #55): the respawn sweep's entry for a Condenser.
|
||||
//
|
||||
void
|
||||
Condenser::DeathReset(int reset_command)
|
||||
|
||||
@@ -2142,6 +2142,17 @@ void
|
||||
((MechSubsystem *)s)->RespawnRepair(); // fresh-mech: zone + status
|
||||
}
|
||||
|
||||
// --- coolant-valve redistribution: the binary Reset tail calls @0049f788
|
||||
// after the sweep and before ForceUpdate -- the Condenser resets just
|
||||
// put every valve back at detent 1 (heat.cpp @004ae534), and this
|
||||
// rebuilds the per-loop flow fractions from those defaults. Without it
|
||||
// the fractions keep the previous life's split (the operator-reported
|
||||
// "valves survive a respawn"). [T1 @0049fb74 + raw @004ae534]
|
||||
{
|
||||
extern void BTRecomputeCondenserValves(Entity *owner);
|
||||
BTRecomputeCondenserValves((Entity *)this);
|
||||
}
|
||||
|
||||
// --- locomotion pre-run + interest gates (a reset master must tick) ---
|
||||
SetPreRunFlag();
|
||||
if (interestCount == 0) interestCount = 1;
|
||||
|
||||
@@ -1239,6 +1239,14 @@ void
|
||||
{
|
||||
extern void BTRebuildMechModel(Entity *entity);
|
||||
extern void BTStartWarpEffect(float x, float y, float z);
|
||||
// #129 (fresh spawns smoking, peers' screens only): the wreck plume
|
||||
// is re-armed LOCALLY on every node (entity-attached, 10s windows),
|
||||
// and only the master's Mech::Reset ran the @004d0c14 effect
|
||||
// cleanup -- the observer's last window rode the teleport and kept
|
||||
// puffing on the fresh mech for up to ~16s. Kill the entity's
|
||||
// effects on the un-wreck edge, same as the master path.
|
||||
extern void BTStopEntityPfx(void *entity_v);
|
||||
BTStopEntityPfx((void *)owner);
|
||||
BTRebuildMechModel((Entity *)owner);
|
||||
BTStartWarpEffect((float)owner->localOrigin.linearPosition.x,
|
||||
(float)owner->localOrigin.linearPosition.y,
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
# RESPAWN-RESET VERIFY (2026-08-04 audit): valve detents + peer smoke.
|
||||
# A (victim): BT_VALVE_TEST moves a coolant valve to detent 5 (~10s in);
|
||||
# B (killer): BT_MP_FORCE_DMG kills A; A respawns (5s). PASS:
|
||||
# A log: [valve] ... detent 5 then [respawn] ... valve detent 5 -> 1
|
||||
# B log: [respawn] replicant un-wrecked with NO later "wreck smoke plume
|
||||
# re-armed" for that entity until the next death;
|
||||
# B captures: fresh A mech smoke-free after un-wreck.
|
||||
set -x
|
||||
. /c/git/bt411/scratchpad/night6/bench_common.sh
|
||||
cd /c/git/bt411/content || exit 1
|
||||
bt_assert_player_env
|
||||
taskkill //F //IM btl4.exe > /dev/null 2>&1
|
||||
sleep 2
|
||||
rm -f rr_a.log rr_b.log rrB_*.png
|
||||
bt_expert_egg MP.EGG RR.EGG
|
||||
sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; s/^vehicle=.*/vehicle=madcat/" RR.EGG
|
||||
|
||||
# A = victim: sets a valve, waits to die, logs the reset sweep.
|
||||
BT_VALVE_TEST=1 BT_DEATH_LOG=1 BT_DMG_LOG=1 BT_MP_LOG=1 \
|
||||
bt_launch rr_a.log RR.EGG 0x0C -net 1601
|
||||
sleep 2
|
||||
# B = killer: force-damages A's replicant; captures A's fresh spawn.
|
||||
BT_MP_FORCE_DMG=1 BT_DEATH_LOG=1 BT_MP_LOG=1 \
|
||||
BT_SHOT_EVERY=150 BT_SHOT_PREFIX=rrB \
|
||||
bt_launch rr_b.log RR.EGG 0x03 -net 1501
|
||||
sleep 5
|
||||
python ../tools/btconsole.py RR.EGG 127.0.0.1:1501 127.0.0.1:1601 > rr_relay.log 2>&1 &
|
||||
RELAY=$!
|
||||
sleep 200
|
||||
kill $RELAY 2>/dev/null
|
||||
taskkill //F //IM btl4.exe > /dev/null 2>&1
|
||||
sleep 3
|
||||
echo "=== A valve + respawn lines:"
|
||||
grep -E "\[valve\]|\[respawn\]" rr_a.log | head -12
|
||||
echo "=== B un-wreck + smoke lines:"
|
||||
grep -E "un-wrecked|plume" rr_b.log | head -12
|
||||
Reference in New Issue
Block a user