#82 FINAL LAYER: the peer body-channel trn-lock -- a circling limper skated because the peer's turn-arm had no walk-demand yield and the trn speed exit read a dead replicant mapper cell

The master limped clean (218x gimp-cycle records, zero trn) -- a _ReturnAddress
trap on SetAnimationState(4) proved ALL peer trn arming came from the port's own
mech4 turn-step block, not the type-3 reader.  Two dead ends, one root:
- mech4 peer arm: gated on !wantsWalk (standSpeed < bodyTargetSpeed), exits trn
  when walking demand appears -- the leg twin's authentic precedence
  (part_012.c:12013, the Standing speed test outranks the turn test).
- mech2 body case 4: bspd reads bodyTargetSpeed on ReplicantInstance; the local
  mapper's speedDemand is a dead cell on a peer (reads 0 forever -> no exit).
Verified 2-node timeline: arena circle replicant 218x state-24 / 0x state-4
(was 116x trn churn), grass circle 235-clean, knockdowns bounded, 0 deaths.
Diagnostics kept: BT_TRNTRAP ra-trap, BT_ANIMIND_CAP, [gimpfeed], [replgimp]
extension.  KB: locomotion.md trn-lock entry + the dead-mapper-cell lesson.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRjrTQpJd6u9XyfnUbTB3v
This commit is contained in:
Joe DiPrima
2026-07-30 18:26:15 -05:00
co-authored by Claude Fable 5
parent 14ff3519ac
commit e91d447405
8 changed files with 231 additions and 15 deletions
+20
View File
@@ -247,6 +247,26 @@ figure, printed under a misleading label since renamed `ggCapL/R`); field @0x52c
has NO binary consumer. Advance caps, loader fills, crunch path and crash branch are all verified
byte-faithful — the self-damage dispatch was the only gap. Replication detail in [[multiplayer]].
**The LAST #82 layer: the peer body-channel TRN-LOCK (root-caused + fixed 2026-07-30) [T2].**
After the storm fix, a CIRCLING limper still skated on peers (user repro): post-knockdown recovery
lands the peer at body-stand; the port's peer turn-step arming block (mech4.cpp `PerformAndWatch`,
needed because the peer no longer runs the leg channel that cross-arms trn) saw
`replMppr->turnDemand` (circling → always turning) and armed body state 4 — and NOTHING could exit:
(a) body case 4's speed exit read the LOCAL mapper's `speedDemand`, a **dead cell on a replicant**
(nothing writes it → 0 forever); (b) turning never stops for a circler. Peer pinned in trn
(advance-only churn = leg-lifty skating) while `bodyTargetSpeed` held the replicated demand
(~24 u/s full). Measured via `[replgimp]` (`bts=24.6 standSpeed=6.83 bodyState=4` in 168/171
samples) and a `_ReturnAddress` trap on `SetAnimationState(4)` (`[trntrap]`, BT_TRNTRAP → linker
map: ALL 60 hits from PerformAndWatch = the port's own arm, NOT the type-3 reader — the master
broadcasts clean gimp states). Fix pair, mirroring the leg twin's authentic precedence
(part_012.c:12013 — the Standing speed test outranks the turn test): (1) mech4 peer arm gated on
`!wantsWalk` (`standSpeed < bodyTargetSpeed || bts < 0`), exit trn when wantsWalk; (2) mech2 body
case 4 `bspd` reads `bodyTargetSpeed` on ReplicantInstance (the replicated demand — the same
number the peer Standing case walks on). Verified: arena circle replicant 218×state-24/0×state-4
(was 116×trn churn), grass circle 235-clean, knockdowns bounded, 0 deaths. **Lesson: on a
replicant, every `MechControlsMapper` demand cell except those explicitly re-derived
(`turnDemand`) is DEAD — any peer-side state machine judging a local mapper read is judging 0.**
## Controls (`BT_REAL_CONTROLS`, default-on)
`MechControlsMapper` (mechmppr.cpp @004afbe0; btl4mppr.cpp mappers) interprets input → `speedDemand`
/ `turnDemand`. ⚠ **WndProc NEVER receives WM_KEYUP** (the engine's per-frame reader `GetMessage`s
+90 -6
View File
@@ -90,6 +90,7 @@
//
#include <bt.hpp>
#include <intrin.h> // _ReturnAddress -- the #82 trn-armer trap
#include <AUDCMP.hpp> // AudioComponent -- the foot-plant step-intensity broadcast
#include <AUDSRC.hpp> // AudioSource -- footstep-source identification
#include <AUDLVL.hpp> // AudioResource::GetAudioLevelOfDetail
@@ -250,11 +251,33 @@ void
// StateIndicator::SetState's Verify(state<stateCount). [T2]
if (state >= 0 && state < 0x21)
{
// DIAG (BT_TRNTRAP): who arms state 4 on a REPLICANT? Module-relative
// ra (symbolize: tools/symcrash.py) -- the #82 trn-lock armer hunt.
if (state == 4 && getenv("BT_TRNTRAP")
&& GetInstance() == ReplicantInstance)
{
static int s_tt = 0;
if (s_tt++ < 60)
{
char ttbuf[96];
sprintf(ttbuf, "[trntrap] mech=%d ra=btl4+0x%lx",
(int)GetEntityID(),
(unsigned long)_ReturnAddress()
- (unsigned long)GetModuleHandleA(0));
DEBUG_STREAM << ttbuf << std::endl << std::flush;
}
}
// #78 audio-flake diag: print the LIVE indicator address once per mech
// so a session's [attrbind] ptr can be checked against it (stale-bind
// hypothesis: watchers bound to a recreated mech's dead indicator).
if (getenv("BT_AUDIO_SPATIAL")) { static int s_ai=0;
if (s_ai++ < 6 || (state >= 22 && state <= 27 && s_ai < 200))
// BT_ANIMIND_CAP: the 200-print budget hid every post-1-minute state
// (two #82 investigations mis-read the silence as "never entered") --
// raise it for state-timeline diagnosis.
static int s_aiCap = -1;
if (s_aiCap < 0) { const char *c = getenv("BT_ANIMIND_CAP"); s_aiCap = (c && *c) ? atoi(c) : 200; }
if (s_ai++ < 6 || (state >= 22 && state <= 27 && s_ai < s_aiCap)
|| (s_aiCap > 200 && s_ai < s_aiCap))
DEBUG_STREAM << "[animind] mech=" << (int)GetEntityID()
<< " &animationState=" << (void*)&animationState
<< " inst=" << (int)(GetInstance() == ReplicantInstance)
@@ -833,8 +856,35 @@ Scalar
// alias (an AV); controlsMapper is the typed mirror of roster slot 0. A mech
// with no mapper reads demand 0 (idles) -- matching a zeroed binary roster.
MechControlsMapper *mppr = MappingMapper(); // roster slot 0 (task #7)
// REPLICANT DEMAND FEED (#82 final root, 2026-07-30): a replicant's LOCAL
// mapper cell is a dead 0 (input never drives it), so every demand
// threshold in this machine failed on peers -- from STAND the walk-begin
// could never fire and the trn state churned on the turn signal forever
// ("lifting legs in an alternating turning fashion"). It never mattered
// while a peer was mid-cycle (clip-end chains keep cycles going, which is
// why straight-line/grass limpers replicated fine) -- it bit the moment a
// KNOCKDOWN recovery dropped the replicant to STAND. The binary replicant
// reads a LIVE demand here (its mapper cell replicates with the subsystem
// records); the port's replicated equivalent is bodyTargetSpeed (@0x6b4 --
// every update record's speedDemand writes it on RX). [gimpfeed]-probe
// proof: AdvanceLegAnimationGimp never even runs on replicants, so THIS
// driver is the peer's one and only leg machine.
Scalar commandedSpeed =
(mppr != 0) ? mppr->speedDemand : 0.0f;
(GetInstance() == ReplicantInstance) ? bodyTargetSpeed
: (mppr != 0) ? mppr->speedDemand : 0.0f;
// DIAG (BT_GIMPFEED): 1 Hz -- the NORMAL driver is the only leg machine a
// replicant runs; print every threshold operand it sees.
if (getenv("BT_GIMPFEED") && GetInstance() == ReplicantInstance)
{
static Scalar s_gfAcc = 0.0f; s_gfAcc += time_slice;
if (s_gfAcc >= 1.0f) { s_gfAcc = 0.0f;
DEBUG_STREAM << "[gimpfeed] cmd=" << commandedSpeed
<< " bts=" << bodyTargetSpeed
<< " standSpeed=" << standSpeed
<< " state=" << (int)legStateAlarm.GetLevel()
<< " gl=" << ([](Mech *m){ extern int BTMechGimpLevel(void*); return BTMechGimpLevel(m); })(this)
<< std::endl << std::flush; }
}
Scalar distance = 0.0f;
// binary: legAnimationState@0x3b0 IS legStateAlarm's level (one field; the
@@ -1251,7 +1301,15 @@ Scalar
// re-enter walk on the same frame.
{
MechControlsMapper *bm = MappingMapper();
const Scalar bspd = (bm != 0) ? bm->speedDemand : 0.0f;
// REPLICANT: the local mapper's speedDemand is a dead cell (nothing
// writes it on a peer -- reads 0 forever, so a peer that entered trn
// could NEVER take the speed exit = the #82 trn-lock). The peer's
// commanded speed is the REPLICATED demand (bodyTargetSpeed@0x6b4,
// stamped by every record RX) -- same source the peer Standing case
// walks on, so entry and exit judge the same number.
const Scalar bspd = (GetInstance() == ReplicantInstance)
? bodyTargetSpeed
: (bm != 0) ? bm->speedDemand : 0.0f;
if (standSpeed < bspd || bspd < ZeroSpeed) // walk / reverse (leg-symmetric)
{
bodyStateAlarm.SetLevel(0);
@@ -1613,12 +1671,38 @@ Scalar
// straight at the speedDemand cell.
MechControlsMapper *gimpMppr = MappingMapper();
static Scalar s_nullDemand = 0.0f; // no mapper -> demand 0, writes inert
ReconMotionSource *motionSource = gimpMppr
? (ReconMotionSource *)&gimpMppr->speedDemand
: (ReconMotionSource *)&s_nullDemand;
// REPLICANT DEMAND FEED (#82 final root, 2026-07-30): a replicant's LOCAL
// mapper demand is a dead 0 (no input ever drives it), so every speed
// threshold in this machine read 0 on peers -- in particular the trn exit
// (standSpeed < commandedSpeed) could NEVER fire, and a gimped replicant
// that staggered through stand->trn was LOCKED in turn-in-place forever
// (the observed "lifting legs in an alternating turning fashion" skate;
// unblinded [animind] timeline: SIX transitions in 3 minutes, ending at
// state 4). The binary's replicant reads a LIVE demand here because the
// mapper's speedDemand cell replicates with the subsystem records; the
// port's replicated equivalent of that same value is bodyTargetSpeed
// (@0x6b4 -- every update record's speedDemand writes it on RX). Feed
// the machine from it on replicants; the gimp caps that write back
// through the source are transient there (the next record rewrites it),
// and masters keep the authentic live mapper cell.
ReconMotionSource *motionSource =
(GetInstance() == ReplicantInstance)
? (ReconMotionSource *)&bodyTargetSpeed
: gimpMppr
? (ReconMotionSource *)&gimpMppr->speedDemand
: (ReconMotionSource *)&s_nullDemand;
Scalar distance = 0.0f;
extern int BTMechGimpLevel(void *mech_v); // mechdmg.cpp (gotcha #23)
int mode = BTMechGimpLevel(this); // binary this+0x40 = gimp level
// DIAG (BT_GIMPFEED): 1 Hz -- what demand does this machine actually see?
if (getenv("BT_GIMPFEED") && GetInstance() == ReplicantInstance)
{
static Scalar s_gfAcc = 0.0f; s_gfAcc += time_slice;
if (s_gfAcc >= 1.0f) { s_gfAcc = 0.0f;
DEBUG_STREAM << "[gimpfeed] repl cmd=" << motionSource->commandedSpeed
<< " bts=" << bodyTargetSpeed << " state=" << legAnimationState
<< " standSpeed=" << standSpeed << " mode=" << mode << std::endl << std::flush; }
}
// RE-SYNC alarm -> state member (see AdvanceBodyAnimationGimp note).
legAnimationState = (int)legStateAlarm.GetLevel();
int state = legAnimationState; // this+0x3b0
+15 -3
View File
@@ -2436,10 +2436,19 @@ void
const int bs = (int)bodyStateAlarm.GetLevel();
const bool turning = (replMppr->turnDemand > 0.05f
|| replMppr->turnDemand < -0.05f);
if (turning && bs == 0) // standing + turning -> enter turn
// YIELD TO WALK DEMAND (#82 trn-lock): the master only sits in
// trn when its commanded speed is at stand (the leg Standing
// case's speed test wins first, part_012.c:12013). A circling
// LIMPER keeps full demand (bts~24) while its yaw rate reads as
// turning here -- ungated, this block pinned the peer in body
// case 4 (advance-only, no exit) forever = the skating. Mirror
// the leg twin's precedence: walking demand outranks the pivot.
const bool wantsWalk = (standSpeed < bodyTargetSpeed
|| bodyTargetSpeed < 0.0f /*reverse*/);
if (turning && bs == 0 && !wantsWalk) // standing pivot only
SetBodyAnimation(4);
else if (!turning && bs == 4) // turn stopped -> back to stand
SetBodyAnimation(0);
else if (bs == 4 && (!turning || wantsWalk))
SetBodyAnimation(0); // stand; case 0 walk-begins next tick
}
// GIMP routing (#78): a limping PEER runs the gimp machines too
// (the graphicAlarm level replicates with the damage state).
@@ -2456,6 +2465,9 @@ void
s_ga = 0.0f;
DEBUG_STREAM << "[replgimp] ent=" << (int)GetEntityID()
<< " gl=" << replGl
<< " bts=" << bodyTargetSpeed
<< " standSpeed=" << standSpeed
<< " bodyState=" << (int)bodyStateAlarm.GetLevel()
<< " sim=" << (int)GetSimulationState()
<< " hasClips=" << hasGimpClips << " zones:";
for (int zi = 0; zi < damageZoneCount; ++zi)
+1 -1
View File
@@ -51,6 +51,6 @@ PC=$!
sleep 480
kill $PC 2>/dev/null
taskkill //F //IM btl4.exe 2>/dev/null
bt_kill_ours
rm -f MP4X.EGG
echo "=== DONE ==="
+10 -4
View File
@@ -26,7 +26,13 @@ A=/c/git/bt411/scratchpad/night6/runs/$(date +%H%M%S); mkdir -p "$A"; mv matchlo
bt_novice_egg MP4L.EGG MP4LN.EGG
observer () { # $1=log $2=affinity $3=port
BT_MP_LOG=1 BT_SYNC_LOG=1 BT_REPL_LOG=1 BT_MATCHLOG=1 BT_AUDIO_SPATIAL=1 \
# STATIC + fully controllable from frame one. NO autopilot: BT_GOTO's
# handover is broken (after BT_GOTO_RELEASE the mech reads throttle 0 yet
# keeps advancing ~40u/s -- the control-mapper demand freezes instead of
# following the keyboard), so a released mech walks straight past whatever
# it was sent to watch. Map is arena1, the tightest-spawning map measured
# (pairwise 41-1055u vs grass ~1200u); the limper is the WHITE Black Hawk.
BT_MP_LOG=1 BT_SYNC_LOG=1 BT_REPL_LOG=1 BT_MATCHLOG=1 BT_AUDIO_SPATIAL=1 BT_SCORE_LOG=1 BT_DEATH_LOG=1 BT_TLOC_LOG=1 \
bt_launch "$1" MP4LN.EGG "$2" -net "$3"
}
observer mp4l_d.log 0xC0 1801
@@ -37,7 +43,7 @@ observer mp4l_b.log 0x0C 1601
sleep 1
# the LIMPER: symbolic leg zone, 2 ticks of 60 -> gimp (no kill) ~2s after
# alive; slow tight circle from t0 so it never leaves its spawn area.
BT_MP_LOG=1 BT_DMG_LOG=1 BT_MATCHLOG=1 BT_AUDIO_SPATIAL=1 \
BT_MP_LOG=1 BT_DMG_LOG=1 BT_MATCHLOG=1 BT_AUDIO_SPATIAL=1 BT_SCORE_LOG=1 BT_DEATH_LOG=1 BT_TLOC_LOG=1 \
BT_SELF_DAMAGE=60 BT_SELF_DAMAGE_ZONE=leg BT_SELF_DAMAGE_TICKS=2 BT_SELF_DAMAGE_DELAY=2 \
BT_AUTODRIVE=0.4 BT_FORCE_TURN=0.45 \
bt_launch mp4l_a.log MP4LN.EGG 0x03 -net 1501
@@ -46,8 +52,8 @@ python ../tools/btconsole.py MP4LN.EGG 127.0.0.1:1501 127.0.0.1:1601 \
127.0.0.1:1701 127.0.0.1:1801 &
PC=$!
sleep 420
sleep 660
kill $PC 2>/dev/null
taskkill //F //IM btl4.exe 2>/dev/null
bt_kill_ours
rm -f MP4LN.EGG
echo "=== DONE ==="
+1 -1
View File
@@ -43,6 +43,6 @@ PC=$!
sleep 150
kill $PC 2>/dev/null
taskkill //F //IM btl4.exe 2>/dev/null
bt_kill_ours
rm -f MP4X.EGG
echo "=== DONE ==="
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# g_circle regression only (extracted from mp_gimpAB.sh)
set -x
. /c/git/bt411/scratchpad/night6/bench_common.sh
cd /c/git/bt411/content || exit 1
taskkill //F //IM btl4.exe > /dev/null 2>&1
sleep 2
sed "s/^map=.*/map=grass/; s/^time=.*/time=day/; s/^experience=.*/experience=novice/" MP.EGG > AB.EGG
rm -f ab_g_circle_obs.log ab_g_circle_limp.log
BT_MP_LOG=1 BT_REPL_LOG=1 BT_AUDIO_SPATIAL=1 BT_SYNC_LOG=1 BT_ANIMIND_CAP=100000 \
bt_launch ab_g_circle_obs.log AB.EGG 0x0C -net 1601
sleep 2
BT_FORCE_TURN=0.45 \
BT_MP_LOG=1 BT_DMG_LOG=1 BT_AUDIO_SPATIAL=1 BT_GROUND_LOG=1 BT_ANIMIND_CAP=100000 \
BT_SELF_DAMAGE=60 BT_SELF_DAMAGE_ZONE=leg BT_SELF_DAMAGE_TICKS=2 BT_SELF_DAMAGE_DELAY=2 \
BT_AUTODRIVE=0.4 \
bt_launch ab_g_circle_limp.log AB.EGG 0x03 -net 1501
sleep 4
python ../tools/btconsole.py AB.EGG 127.0.0.1:1501 127.0.0.1:1601 &
PC=$!
sleep 180
kill $PC 2>/dev/null
bt_kill_ours
rm -f AB.EGG
echo "=== DONE ==="
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# #82 REPLICANT-LIMP A/B MATRIX: which factor breaks the peer's limp view --
# TURNING (replicant trn-arming outprioritizes the gimp cycle at gimp speeds)
# or TERRAIN (arena1 ground staggers -> type-5 knockdown broadcasts)?
#
# 4 configs x 2 nodes x ~3min, sequential:
# g_straight grass, limper walks straight (the old mp_skate baseline)
# g_circle grass, limper circles (run-6 shape)
# a_straight arena1, limper walks straight
# a_circle arena1, limper circles (run-13 shape = the user's repro)
#
# VERDICT per config, from logs alone (no eyes needed):
# observer [animind] inst=1 states 22-27 -> replicant LIMPS (good)
# observer trn(4)/knockdown(32) churn -> skating (bad)
# observer type=5 rx count + limper [knock] rows -> the stagger factor
set -x
. /c/git/bt411/scratchpad/night6/bench_common.sh
cd /c/git/bt411/content || exit 1
run_config () { # $1=tag $2=map $3=circle(0|1)
local tag=$1 map=$2 circle=$3
# headless rig only: a leftover node from the previous config holds the -net
# port and null-runs this one -- sweep before every config
taskkill //F //IM btl4.exe > /dev/null 2>&1
sleep 2
sed "s/^map=.*/map=$map/; s/^time=.*/time=day/; s/^experience=.*/experience=novice/" MP.EGG > AB.EGG
rm -f ab_${tag}_obs.log ab_${tag}_limp.log
# observer: static, replicant-diagnostics on
BT_MP_LOG=1 BT_REPL_LOG=1 BT_AUDIO_SPATIAL=1 BT_SYNC_LOG=1 BT_ANIMIND_CAP=100000 BT_GIMPFEED=1 BT_TRNTRAP=1 \
bt_launch ab_${tag}_obs.log AB.EGG 0x0C -net 1601
sleep 2
# limper: symbolic leg gimp ~2s after alive; straight or circling
if [ "$circle" = "1" ]; then
BT_FORCE_TURN=0.45 \
BT_MP_LOG=1 BT_DMG_LOG=1 BT_AUDIO_SPATIAL=1 BT_GROUND_LOG=1 BT_ANIMIND_CAP=100000 \
BT_SELF_DAMAGE=60 BT_SELF_DAMAGE_ZONE=leg BT_SELF_DAMAGE_TICKS=2 BT_SELF_DAMAGE_DELAY=2 \
BT_AUTODRIVE=0.4 \
bt_launch ab_${tag}_limp.log AB.EGG 0x03 -net 1501
else
BT_MP_LOG=1 BT_DMG_LOG=1 BT_AUDIO_SPATIAL=1 BT_GROUND_LOG=1 BT_ANIMIND_CAP=100000 \
BT_SELF_DAMAGE=60 BT_SELF_DAMAGE_ZONE=leg BT_SELF_DAMAGE_TICKS=2 BT_SELF_DAMAGE_DELAY=2 \
BT_AUTODRIVE=0.4 \
bt_launch ab_${tag}_limp.log AB.EGG 0x03 -net 1501
fi
sleep 4
python ../tools/btconsole.py AB.EGG 127.0.0.1:1501 127.0.0.1:1601 &
local PC=$!
sleep 180
kill $PC 2>/dev/null
bt_kill_ours
sleep 3
}
run_config a_circle arena1 1
rm -f AB.EGG
echo "=== A/B VERDICTS ==="
for tag in a_circle; do
obs=ab_${tag}_obs.log
gimp=$(grep -a '\[animind\]' $obs 2>/dev/null | grep 'inst=1' | grep -c 'state=2[2-7] ')
trn=$(grep -a '\[animind\]' $obs 2>/dev/null | grep 'inst=1' | grep -c 'state=4 ')
kd=$(grep -a '\[animind\]' $obs 2>/dev/null | grep 'inst=1' | grep -c 'state=32 ')
t5=$(grep -a '\[mrec-rx\]' $obs 2>/dev/null | grep -c 'type=5 ')
knocks=$(grep -ac '\[knock\]' ab_${tag}_limp.log 2>/dev/null)
gsim=$(grep -a '\[gimp-sim\]' ab_${tag}_limp.log 2>/dev/null | head -1)
echo "VERDICT $tag: replicantGimp=$gimp trn=$trn knockdown=$kd type5rx=$t5 limperKnocks=$knocks ($gsim)"
done
echo "=== DONE ==="