CROUCH complete: full cycle + MP replication verified

The WIP's 'pose does not hold' was a chain of bench-instrument errors, not a
code bug: every capture ran in COCKPIT view (the pilot cannot see their own
legs; the eye-height residual masked as reversion).  Joint probes prove the
park holds indefinitely (knee 1.138, root -2.219 steady); the 2-node bench
shows the observer's replicant fully crouched and held (duckmpA_031 -- the
type-3 state record carries it with zero new replication code); the second
scripted press (new BT_BTNTEST2 env) verifies RISE -> standing zeros.
MP button delivery confirmed mode-mask-clean (the one miss was round-start
jitter).  Diags added, all BT_DUCK_LOG-gated: SetLegAnimation re-arm tracer,
1 Hz joint probe, RIO press mode-mask, BT_TREE_LOG topology dump, and the
squat-park log.  RESIDUAL filed: pilot's own cockpit eye does not ride the
root drop (DPLEyeRenderable chain composition; cosmetic).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-06 00:50:12 -05:00
co-authored by Claude Fable 5
parent dd70061e0e
commit 591d205b19
8 changed files with 136 additions and 0 deletions
+17
View File
@@ -311,6 +311,23 @@ resolved with gait v5, no shadow code changed — though (a) may have contribute
T4]; (c) genuine slope burial when the quad is flat/mis-tilted (the depth-bias margin is finite).
Check `[shadowobj]` tag lines and `[sync]`/`BT_SYNC_LOG` before touching bias/tilt.
## CROUCH -- COMPLETE 2026-08-06 [T1 decode / T2 full-cycle + MP benched]
The manual's CROUCH button (streamed 0x13; glass key F4), end to end: DuckRequest (mech id 0x1a
@0049fa00) latches duckState@0x398 -> the master posture block (mech4, from the dark master-perf
@0x4a9cf0-0x4aa0af): posture selector @0x3f8 (mode 9/10/2/3/4 block, NOVICE simLive lockout,
myomer-alive gate) -> standing: SetLegAnimation(2) ('sqd', squat down; LegClipFinished case-2
PARKS the pose + leg alarm 1) / ducked: SetLegAnimation(3) ('squ', rise; case-3 alarm 0);
stability alarm flips; ForceUpdate(8)+(1) ships the type-3 state record -- peers pose the squat
for FREE (screenshot receipt: the crouched crimson madcat from the observer's cockpit,
duckmpA_031). Clip data verified from BTL4.RES (root joint drops -2.22, knees +-1.14; squ/sqd
are exact mirrors; interior 'squi/sqdi' variants ship for all 8 chassis). Full solo cycle
benched (BT_BTNTEST + BT_BTNTEST2): squat -> hold (joints steady for minutes) -> rise ->
standing zeros. Myomer speed-cripple feeder (@004b8be3) still deferred [T3] -- see mech4 banner.
RESIDUAL (open-questions): the pilot's own cockpit EYE height does not ride the root drop
(the interior clip authors joint-0 translation for exactly that; the DPLEyeRenderable chain
composition needs a look). Bench traps for reruns: MP button delivery can miss if pressed
during round-start jitter (press >= poll 900 after GO); diags all BT_DUCK_LOG-gated.
## Key Relationships
- Detail: `docs/P3_LOCOMOTION.md`. Uses: [[asset-formats]] (SKL/ANI), [[decomp-reference]] (offsets).
- Feeds: [[combat-damage]] (collision→damage), [[rendering]] (shadow/visual-conform).
+7
View File
@@ -1142,3 +1142,10 @@ Gitea #116 and `experience-levels.md`.
- **des_night-family pages have subtle fog deltas** (both sets near-black); arena/polar night
pages carry the dramatic 5↔40 near-plane swing. If testers report "searchlight does nothing"
on some maps, it's page authoring, not the mechanism.
## Crouch residual (2026-08-06 — the feature itself is LIVE, [[locomotion]] §CROUCH)
- **Pilot eye height does not ride the root drop.** The interior squat clip animates joint 0
(ball+translation, 2.22) precisely so the cockpit eye lowers; the joint holds (probe-verified)
and the eye's segment chain passes through jointlocal, yet the cockpit view returns to standing
height after the squat parks. Suspect: DPLEyeRenderable's parent-chain composition not
including the BallTranslateJointRenderable's live matrix. Cosmetic (body + replication correct).
+7
View File
@@ -2624,6 +2624,13 @@ void
Verify(rio_event.Data.Unit < ButtonCount);
temp = rio_event.Data.Unit + 1;
// DIAG (BT_DUCK_LOG): the mode mask a press dispatches under -- the
// crouch-button (0x13) MP-delivery hunt (mode-gated streamed rows).
if (getenv("BT_DUCK_LOG"))
DEBUG_STREAM << "[duck] RIO press unit=0x" << std::hex
<< (int)rio_event.Data.Unit << " modeMask=0x"
<< (unsigned)mode_mask << std::dec << "\n" << std::flush;
Check(&buttonGroup[rio_event.Data.Unit]);
buttonGroup[rio_event.Data.Unit].Update(&temp, mode_mask);
//----------------------------------------
+27
View File
@@ -542,6 +542,33 @@ void
DEBUG_STREAM << "[btntest] RELEASE 0x" << std::hex << s_btnAddr
<< std::dec << " at poll " << s_poll << "\n" << std::flush;
}
// BT_BTNTEST2="addr,pressPoll,releasePoll": an optional SECOND
// scripted cycle (e.g. crouch then rise) on the same poll clock.
static int s_bt2Addr = -2, s_bt2On = 0, s_bt2Off = 0, s_bt2State = 0;
if (s_bt2Addr == -2)
{
s_bt2Addr = -1;
const char *e2 = getenv("BT_BTNTEST2");
if (e2 != NULL)
sscanf(e2, "%i,%i,%i", &s_bt2Addr, &s_bt2On, &s_bt2Off);
}
if (s_bt2Addr >= 0)
{
if (s_bt2State == 0 && s_poll >= s_bt2On)
{
s_bt2State = 1;
EmitButton(s_bt2Addr, 1);
DEBUG_STREAM << "[btntest] PRESS2 0x" << std::hex << s_bt2Addr
<< std::dec << " at poll " << s_poll << "\n" << std::flush;
}
else if (s_bt2State == 1 && s_poll >= s_bt2Off)
{
s_bt2State = 2;
EmitButton(s_bt2Addr, 0);
DEBUG_STREAM << "[btntest] RELEASE2 0x" << std::hex << s_bt2Addr
<< std::dec << " at poll " << s_poll << "\n" << std::flush;
}
}
}
}
+13
View File
@@ -558,6 +558,11 @@ HierarchicalDrawComponent*
//
if (segment->IsSiteSegment() /* [0x10] */ != 0)
{
if (getenv("BT_TREE_LOG"))
DEBUG_STREAM << "[tree] SITE seg=" << segment->GetIndex()
<< " name=" << (segment->GetName() ? (const char *)segment->GetName() : "?")
<< " parent=" << (segment->GetParent() ? segment->GetParentIndex() : -1)
<< "\n" << std::flush;
// The authentic COCKPIT EYEPOINT. Built for the true inside view
// AND for the player's chase build (buildDebugChaseCamera), so the
// V-key toggle can switch to it -- the pod's only view was this
@@ -678,6 +683,14 @@ HierarchicalDrawComponent*
// the JointSubsystem's joint table.
//
int segment_slot = segment->GetIndex() /* [0xcc] */;
// DIAG (BT_TREE_LOG): the built joint topology -- segment slot, name,
// joint index/type, parent -- the crouch root-drop chain hunt.
if (getenv("BT_TREE_LOG"))
DEBUG_STREAM << "[tree] seg=" << segment_slot
<< " name=" << (segment->GetName() ? (const char *)segment->GetName() : "?")
<< " joint=" << segment->GetJointIndex()
<< " parent=" << (segment->GetParent() ? segment->GetParentIndex() : -1)
<< "\n" << std::flush;
Joint *this_joint = NULL;
Joint::JointType joint_type;
if (segment->GetJointIndex() /* [0xc0] */ == -1)
+2
View File
@@ -530,6 +530,8 @@ Scalar
return 0.0f;
// slot10 state 2 (0x4a6b37): SetLevel(1).
case 2:
if (getenv("BT_DUCK_LOG"))
DEBUG_STREAM << "[duck] squat clip parked (case 2 -> leg alarm 1)\n" << std::flush;
m->legStateAlarm.SetLevel(1); return 0.0f;
// slot2 (0x4a6b21) + slot1 state 32 (0x4a6b4d) + slot9 state 4 (0x4a6d6e): SetLevel(0).
case 3: case 4: case 8: case 9: case 20: case 21:
+21
View File
@@ -4435,6 +4435,27 @@ void
<< " squat=" << squatCapable << ")\n" << std::flush;
duckState = 0; // consumed (@0x4aa0a9)
}
// DIAG (BT_DUCK_LOG, 1 Hz): live joint sample -- does the parked
// crouch HOLD at the JOINT level? Discriminates render-side loss
// from a hidden joint writer (the crouch-hold hunt).
if (getenv("BT_DUCK_LOG"))
{
static Scalar s_duckProbe = 0.0f;
s_duckProbe += dt;
if (s_duckProbe >= 1.0f)
{
s_duckProbe = 0.0f;
JointSubsystem *duckJs = GetJointSubsystem();
Joint *knee = (duckJs != 0) ? duckJs->GetJoint(11) : 0;
Joint *root = (duckJs != 0) ? duckJs->GetJoint(0) : 0;
DEBUG_STREAM << "[duck] probe legLvl=" << (int)legStateAlarm.GetLevel()
<< " j11=" << (knee ? (float)knee->GetHinge().rotationAmount : -99.0f)
<< " j0ty=" << ((root && root->GetJointType() == Joint::BallTranslationJointType)
? (float)root->GetTranslation().y : -99.0f)
<< "\n" << std::flush;
}
}
}
{
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
# SEARCHLIGHT bench 2 (external spot cone + replication): night 2-node.
# A walks to 40u of B and stands (facing B). B presses the searchlight button
# (BT_BTNTEST 0x14) at poll 300. B's lightState replicates (subsystem record
# 0x14) -> A's replicant Searchlight LightOn flips -> A's TickSearchlight shows
# the spot.bgf cone on B's mech.
# PASS: A log "[spot] searchlight pass: ... (external: spot cone)" for B's mech
# + "[spot] cone SHOWN (seg N)" after B's press; A BT_SHOT frames brighten near
# B post-toggle. B log: "[light] ... requested ON" + lightState 0 -> 1.
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 duckmp_a.log duckmp_b.log duckmp_relay.log duckmpA_*.png
bt_expert_egg MP.EGG SG4.EGG
sed -i "s/^map=.*/map=grass/; s/^time=.*/time=night/; s/^vehicle=.*/vehicle=madcat/" SG4.EGG
(
export BT_BTNTEST=0x13,900,960 BT_DUCK_LOG=1
export BT_FIRE_LOG=1 BT_FOG_LOG=1 BT_DEATH_LOG=1 BT_MP_LOG=1
bt_launch duckmp_b.log SG4.EGG 0x0C -net 1601
)
sleep 2
(
export BT_GOTO=enemy BT_GOTO_STOP=40 BT_KEY_NOFOCUS=1 BT_DUCK_LOG=1
export BT_SHOT_EVERY=60 BT_SHOT_PREFIX=duckmpA
export BT_FIRE_LOG=1 BT_FOG_LOG=1 BT_MP_LOG=1
bt_launch duckmp_a.log SG4.EGG 0x03 -net 1501
)
sleep 5
python ../tools/btconsole.py SG4.EGG 127.0.0.1:1501 127.0.0.1:1601 > duckmp_relay.log 2>&1 &
RELAY=$!
sleep 90
kill $RELAY 2>/dev/null
taskkill //F //IM btl4.exe > /dev/null 2>&1
sleep 3
echo "=== B (lamp owner):"
grep -aE "\[light\]|btntest|\[spot\]" duckmp_b.log | head -8
echo "=== A (observer -- cone events):"
grep -aE "\[spot\]" duckmp_a.log | head -8