#!/usr/bin/env bash # ========================================================================= # #141 -- "missiles launch along the LEG/FOOT facing, then curve to the # target -- PEER POV ONLY" (Oracle, night 13: "the emitter is following the # foot facing"; shooter's own view correct). # # WHAT THE CODE SAYS SO FAR. Both sides already pass the mount segment: # MissileLauncher::FireWeapon (mislanch.cpp:363) and the REPLICANT salvo # mirror (mislanch.cpp:478) each hand BTPushProjectile # `GetSegmentIndex() /*task #67 mount frame*/`. BTPushProjectile then # rotates the authored MuzzleVelocity through # `seg->GetSegmentToEntity() * localToWorld` -- so the launch direction IS # the segment's world frame on BOTH nodes. Task #67 fixed exactly this # symptom once already, master-side ("missiles fire out of his back"). # # So if the peer report is real, the difference is NOT which frame is asked # for -- it is whether the replicant's SEGMENT actually carries the torso # twist. Torso pushes currentTwist into the skeleton on both paths # (TorsoSimulation and TorsoCopySimulation both call UpdateJoints), so this # has to be measured, not reasoned about. # # THE MEASUREMENT. New [launchframe] receipt (BT_PROJ_LOG) prints, on both # nodes, the yaw of the launch forward vs the BODY forward: # [launchframe] master seg=N segResolved=1 segYaw=.. bodyYaw=.. twistDelta=.. # [launchframe] REPLICANT seg=N segResolved=1 segYaw=.. bodyYaw=.. twistDelta=.. # twistDelta is the torso twist expressed in the launch direction. # # BUG CONFIRMED: A (master) shows |twistDelta| sweeping well away from 0 # while B (replicant mirror) stays pinned near 0 # -- or B shows segResolved=0 (fell back to the body basis). # NOT REPRODUCED: both sides show the same twistDelta spread. # # ⚠ READ THE PREFIX, NOT THE PERCENTAGE (learned the hard way, 2026-08-08). # BT_AUTOFIRE starts shooting IMMEDIATELY, while the app is still in # WaitingForLaunch/LaunchingMission -- and Entity::Execute (ENTITY.cpp:556, # real engine source) only calls PerformAndWatch when the state is # RunningMission/EndingMission or the entity IsPreRunnable(). A REPLICANT mech # is not pre-runnable (Entity::DefaultFlags has no PreRunFlag; only Player / # Director add it, and Mech::Reset sets it for a reset MASTER), so a peer mech # does not tick at all until the round actually starts. Every salvo fired # before that reads twistDelta=0 legitimately -- the peer has no twist yet. # That is a BENCH artifact (nobody can fire pre-round in a real match), not a # defect: it showed up as a clean leading run of zeros, e.g. # ZZZZ...(60)...ZZZZXXXX...(105)...XXXX # and the first X lands within a few lines of the RunningMission transition. # So: judge this bench by whether the failures are a PREFIX (fine) or # INTERLEAVED (real), never by the raw percentage. # # Only A fires and only A sweeps its torso, so every REPLICANT line in B's # log is a mirror of an A salvo and the comparison is unambiguous. # ========================================================================= 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 rm -f mf_a.log mf_b.log mf_relay.log bt_expert_egg MP.EGG MF.EGG sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; s/^vehicle=.*/vehicle=madcat/" MF.EGG # B: the OBSERVER. Drives at A so it stays in range, but does NOT fire and # does NOT sweep -- so every [launchframe] REPLICANT line in mf_b.log is a # mirror of one of A's salvos. ( export BT_GOTO=enemy BT_GOTO_STOP=150 export BT_PROJ_LOG=1 BT_MP_LOG=1 bt_launch mf_b.log MF.EGG 0x0C -net 1601 ) sleep 2 # A: the SHOOTER. Sweeps the torso hard so twistDelta is unmistakably # non-zero at fire time, and autofires missiles at the designated enemy. ( export BT_GOTO=enemy BT_GOTO_STOP=150 export BT_AUTOFIRE=1 BT_AF_MISSILE=1 BT_AF_PERIOD=7 export BT_LOCK_SWEEP=0.35 export BT_PROJ_LOG=1 BT_TORSO_LOG=1 BT_MP_LOG=1 bt_launch mf_a.log MF.EGG 0x03 -net 1501 ) sleep 5 python ../tools/btconsole.py MF.EGG 127.0.0.1:1501 127.0.0.1:1601 > mf_relay.log 2>&1 & RELAY=$! sleep 300 kill $RELAY 2>/dev/null sleep 3 bt_kill_ours; sleep 2; taskkill //F //IM btl4.exe > /dev/null 2>&1; sleep 3 echo "=================== #141 MISSILE LAUNCH FRAME ===================" echo "--- did A fire, and did B mirror? ---" echo -n " A [launchframe] master lines ....... "; grep -ac "launchframe\] master" mf_a.log echo -n " B [launchframe] REPLICANT lines .... "; grep -ac "launchframe\] REPLICANT" mf_b.log echo echo "--- did the segment RESOLVE on each side? (segResolved=0 would be the bug) ---" echo -n " A segResolved=0 ... "; grep -a "launchframe\] master" mf_a.log | grep -ac "segResolved=0" echo -n " B segResolved=0 ... "; grep -a "launchframe\] REPLICANT" mf_b.log | grep -ac "segResolved=0" echo echo "--- THE COMPARISON: twistDelta spread on each side ---" python - <<'PY' import re, io def stats(path, tag): v = [] try: for ln in io.open(path, encoding="latin-1", errors="replace"): if "[launchframe] " + tag in ln: m = re.search(r"twistDelta=([-\d.e+]+)", ln) if m: try: v.append(float(m.group(1))) except ValueError: pass except IOError: print(" %s: no log" % tag); return if not v: print(" %-9s no samples" % tag); return a = [abs(x) for x in v] big = sum(1 for x in a if x > 0.10) # ~5.7 deg -- clearly twisted print(" %-9s n=%-4d |twistDelta| max=%.4f mean=%.4f >0.10rad: %d (%.0f%%)" % (tag, len(v), max(a), sum(a)/len(a), big, 100.0*big/len(a))) stats(r"C:\git\bt411\content\mf_a.log", "master") stats(r"C:\git\bt411\content\mf_b.log", "REPLICANT") print() print(" VERDICT: master twisted + REPLICANT pinned near 0 => #141 CONFIRMED.") print(" both twisted alike => NOT reproduced.") PY echo echo "--- sample lines, both sides ---" grep -a "launchframe\] master" mf_a.log | head -4 grep -a "launchframe\] REPLICANT" mf_b.log | head -4