#52 the peer STANDING-LOCK: case 0's fallthrough was intercepted

A replicant could not start walking between gait-change records.  The port's
body case 4 (the task-#64 lockstep twin) is an INSERTION sitting between case 0
and the advance group; in the binary case 4 is a MEMBER of that group
(FUN_004a5678 @004a5678: case 2,3,4,5,8,... -- no turn block, no speed exit),
so case 0's fallthrough is meant to land on Advance().  The insertion caught it.

On a replicant that is not a race but an identity: case 0 arms walk iff
standSpeed < bodyTargetSpeed, and the inserted block resets iff standSpeed <
bspd -- where bspd IS bodyTargetSpeed for a replicant.  Same expression, so arm
and reset fire on the same frame, forever, and a peer parked at Standing with a
live replicated demand never cycles.  bodyCycleSpeed stays 0 while position
advances from dead reckoning: the skate.

This is the sequel to e91d447 (#82).  Before it the replicant branch read the
dead local mapper cell (0 forever), the exit never fired, and the fallthrough
worked BY ACCIDENT.  Fixing the dead cell closed the escape hatch.

Fix: case 0 -> goto advance_body_normally, the leg twin's own idiom, restoring
the binary's structure without touching the #64/#82 turn logic.
BT_NO_BODY_FALLTHRU=1 reverts.

Measured (2-node, scratchpad/night13/skatelock.sh):
  legacy  336 consecutive locked seconds, bspd=39.2324 bts=39.2324 every line
  fixed   0 locks, every pass
  master body-Standing samples 52 -> 21 (it locked too, invisibly at mj=0)
  turn-in-place intact: pivoter body state 4 x9 / leg state 4 x8, in lockstep

Diagnostics (both keepers): [skate] now carries bstate= -- the field lines
proved "both channels idle" but never named the state, which was the whole
answer; [bodySM]/[peergait] under BT_BODY_SM_LOG instrument the arm->reset pair
and a moving replicant's body channel.

NOT claimed: that this accounts for the night-13 field episodes.  That link is
inference -- locked + translating IS the skate signature by construction, but no
bench caught the two together.  bstate= settles it next playtest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NCJQkvq6G2JNrpVbA75tVZ
This commit is contained in:
Joe DiPrima
2026-08-07 00:50:54 -05:00
co-authored by Claude Opus 5
parent 39144813a4
commit 6a96fb6420
11 changed files with 635 additions and 2 deletions
+37
View File
@@ -273,6 +273,43 @@ number the peer Standing case walks on). Verified: arena circle replicant 218×s
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.**
**The #52 SEQUEL: the peer body-channel STANDING-LOCK (root-caused + fixed 2026-08-07) [T2].**
Fixing (2) above closed an ACCIDENTAL escape hatch and the skate came back in a new shape. The
port's body case 4 is an INSERTION (the task-#64 lockstep twin) sitting between case 0 and the
advance group — but in the binary `case 4` is a **member of that advance group**
(`FUN_004a5678` @004a5678: `case 2,3,`**`4`**`,5,8,…`, no turn block, no speed exit) [T1], so
case 0's fallthrough is supposed to land on `Advance()`. The insertion intercepted it. On a
REPLICANT that is fatal and not a race: case 0 arms walk iff `standSpeed < bodyTargetSpeed`, and
the inserted block's exit tests `standSpeed < bspd` where `bspd` **IS** `bodyTargetSpeed` on a
replicant — the *same expression*. Arm and reset therefore fire on the same frame, every frame,
and a peer parked at Standing with a live replicated demand can never start cycling (reverse
likewise: both sides test `< ZeroSpeed`). Before e91d447 the replicant branch read the dead mapper
cell (0 forever) so the exit never fired and the fallthrough worked by accident. **Fix:** case 0
`goto advance_body_normally` — the leg twin's own idiom (`goto advance_normally`, mech2.cpp) —
restoring the binary's structure without touching the #64/#82 turn logic. `BT_NO_BODY_FALLTHRU=1`
reverts. Measured: legacy 336 consecutive locked seconds with `bspd=39.2324 bts=39.2324` identical
on every line; fixed 0 locks across every pass; the MASTER's body-Standing samples also fell 52→21
(it was locking too, invisibly — mj=0 writes no joints, and its two tests read *different* cells so
it only stalls in the window where they disagree). Turn-in-place re-verified under the fix (pivoter
reached body state 4 ×9 / leg state 4 ×8 — armed in lockstep).
**Why a peer must be able to self-arm walking at all** (the load-bearing bit behind
`mech4.cpp` "stand; case 0 walk-begins next tick"): the peer's body state is set directly from
`record->legState` only on **type-3 edges** (`ReadUpdateRecord`), and entering Standing emits one
while *leaving* it does not. So between gait-change records a replicant is REQUIRED to derive
walking itself from the replicated `bodyTargetSpeed`. That is why the lock needs a mech holding a
*steady* demand — a mech whose gait keeps changing keeps getting rescued by records, which is why
free-walking and wall-jammed benches each reproduce only half the symptom. [T2]
**The field symptom link is [T3], not T2.** The Standing-lock is proven and proven removed; that
it accounts for the night-13 episodes is inference (a locked peer has `bodyCycleSpeed==0` and never
advances its clip, so locked + translating *is* the `[skate]` signature by construction) — but no
bench caught the two together. The `[skate]` line now carries `bstate=`, so the next playtest
settles it: episodes gone → confirmed; any survivor names its own state. **NB the night-12
`skatebench` "reproductions" were a DETECTOR ARTIFACT** — the first detector build tested only
`legCycleSpeed==0`, which is normal on a peer (the body channel poses it), so it fired on every
healthy movement phase. Old-format lines (`legCycleSpeed=`, no `bodyCyc=`) are not evidence.
## 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