Oracle: "missiles are firing in the direction the mech feet are facing ...
and then coming around to track the target", peer POV only -- the shooter's
own view is correct.
REPRODUCED AND MEASURED (scratchpad/night13/missileframe.sh, 2-node: only A
sweeps its torso and only A fires, so every REPLICANT line in B's log is the
mirror of one A salvo). New [launchframe] receipt prints the yaw of the
launch forward vs the BODY forward on both nodes:
master n=165 |twistDelta| max=2.2962 mean=1.2283 >0.1rad: 100%
REPLICANT n=165 |twistDelta| max=0.0000 mean=0.0000 >0.1rad: 0%
segResolved=1 on BOTH, and segYaw == bodyYaw EXACTLY on the peer.
WHAT IT IS NOT. Both sides already pass the mount segment (mislanch.cpp:363
master, :478 replicant mirror, both `GetSegmentIndex()` from task #67), and
the peer's torso data is fine end to end: records arrive (atUpd=2.44/-2.39,
rate 0.305), the copy extrapolates correctly (cur=-2.13987 target=-2.13987
copy=1), and the copy torso demonstrably writes its joint (PushTwist COPY
twist=-1.49601). Hierarchy is identical too: same seg 18, same parentIdx 4,
non-null parent + joint subsystem on both.
ROOT CAUSE. BTPushProjectile composed the frame BY HAND --
`mw.Multiply(seg->GetSegmentToEntity(), localToWorld)`. But
EntitySegment::GetSegmentToEntity (SEGMENT.cpp:262) recomputes ONLY when
`segmentModified` is set, and the thing that sets it after a joint moves is
JointedMover::GetSegmentToWorld (JMOVER.cpp:136-146), which tests
AreJointsModified() and then marks every segment dirty. Hand-composing skips
that, so you read whatever cache is sitting there. On the MASTER that was
invisible -- the renderer/cockpit camera call GetSegmentToWorld for the local
mech every frame, AFTER the local torso pushes its joint, so the cache was
already correct. A REPLICANT gets no such refresh: its cache stayed at the
BIND POSE, and the twist never reached the launch direction.
FIX. Use the engine accessor, and set the joints-dirty flag first so it
actually refreshes (by fire time the frame's render pass has already consumed
and cleared it -- measured jointsDirty=0 on BOTH nodes).
RESULT (same bench):
REPLICANT max 0.0000 -> 2.1145 mean 0.0000 -> 0.8252 0% -> 64%
PARTIAL, and I am not claiming otherwise. 36% of peer salvos still read the
exact-zero stale signature while the master is 100%. Forcing every per-joint
`jointModified` flag as well (GetSegmentToParent's own gate, SEGMENT.cpp:196)
was tried and moved the number by NOTHING -- 64% either way -- so the residual
is a different cause, most likely frame ORDER (the salvo mirror running before
the copy torso has posed that frame). Cheap form kept.
Also fixes a SAMPLING TRAP in the torso probe: PushTwist sampled one shared
static every 30th call, and with a master torso and a copy torso ticking 1:1
every 30th call is always the SAME instance -- so the probe showed only the
local untwisted torso and hid the copy's writes entirely. Now sampled per
instance-kind, which is what made the copy's correct joint writes visible and
moved the search downstream to the segment cache.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018SgmXGNMXavXiafKXf9MDC
106 lines
5.0 KiB
Bash
106 lines
5.0 KiB
Bash
#!/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.
|
|
#
|
|
# 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_TORSO_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 170
|
|
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
|