#141 peer missiles: the launch frame read a STALE segment cache on replicants
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
This commit is contained in:
co-authored by
Claude Opus 5
parent
1ae57398f1
commit
05d7b5890a
@@ -1493,8 +1493,57 @@ void
|
||||
EntitySegment *seg = sm->GetSegment(muzzle_seg);
|
||||
if (seg != 0)
|
||||
{
|
||||
AffineMatrix mw;
|
||||
mw.Multiply(seg->GetSegmentToEntity(), sm->localToWorld);
|
||||
// #141 -- USE THE ENGINE ACCESSOR, not a hand-rolled
|
||||
// GetSegmentToEntity() x localToWorld.
|
||||
//
|
||||
// EntitySegment::GetSegmentToEntity (SEGMENT.cpp:262)
|
||||
// recomputes ONLY when `segmentModified` is set; otherwise it
|
||||
// returns the CACHED matrix. The thing that sets that flag
|
||||
// after a joint moves is JointedMover::GetSegmentToWorld
|
||||
// (JMOVER.cpp:136-146): it tests AreJointsModified() and, when
|
||||
// set, marks EVERY segment dirty and clears the flag. Compose
|
||||
// the matrix by hand and you skip that entirely -- you read
|
||||
// whatever cache happens to be sitting there.
|
||||
//
|
||||
// On the MASTER that was invisible: the renderer / cockpit
|
||||
// camera call GetSegmentToWorld for the LOCAL mech every frame,
|
||||
// so the cache was already fresh when we fired. A REPLICANT
|
||||
// gets no such refresh, so its cache stayed at the BIND POSE
|
||||
// and the torso twist never reached the launch frame.
|
||||
//
|
||||
// Measured (scratchpad/night13/missileframe.sh, 165 salvos
|
||||
// mirrored 1:1): master |twistDelta| max 2.2962 / mean 1.2283,
|
||||
// 100% > 0.1 rad -- REPLICANT max 0.0000, mean 0.0000, 0%,
|
||||
// with segResolved=1 and segYaw == bodyYaw EXACTLY, while that
|
||||
// same peer's copy torso was demonstrably writing its joint
|
||||
// (`PushTwist COPY ... twist=-1.49601`) off correctly
|
||||
// replicated records (`cur=-2.13987 target=-2.13987 copy=1`).
|
||||
// Twist arrived, joint moved, segment cache never refreshed.
|
||||
// That is #141: "missiles launch along the LEG/FOOT facing,
|
||||
// then curve to the target -- peer POV only".
|
||||
// FORCE the recompute. GetSegmentToWorld only refreshes when
|
||||
// AreJointsModified() is set, and by fire time the frame's
|
||||
// renderer/camera pass has already consumed and cleared that
|
||||
// flag on BOTH nodes (measured: jointsDirty=0 master AND peer).
|
||||
// On the master the cache it left behind is correct, because
|
||||
// that pass ran AFTER the local torso pushed its joint. On a
|
||||
// replicant the cache is stale, so seg 18 returned its
|
||||
// bind-pose matrix (segYaw == bodyYaw EXACTLY) even though the
|
||||
// hierarchy is identical -- same parentIdx 4, same non-null
|
||||
// parent + joint subsystem. Setting the flag makes
|
||||
// GetSegmentToWorld mark every segment dirty so the whole
|
||||
// chain re-derives from the CURRENT joint angles. Costs one
|
||||
// segment-table walk per salvo.
|
||||
// MEASURED: this alone takes the replicant from 0% to 64% of
|
||||
// salvos carrying the twist. Also forcing every per-joint
|
||||
// `jointModified` flag (GetSegmentToParent's own gate,
|
||||
// SEGMENT.cpp:196) was tried and moved the number by NOTHING
|
||||
// -- 64% either way -- so the residual 36% is a different
|
||||
// cause, not per-joint cache staleness. Kept the cheap form.
|
||||
if (JointSubsystem *jsf = sm->GetJointSubsystem())
|
||||
jsf->ModifyJoints(True);
|
||||
LinearMatrix mw;
|
||||
sm->GetSegmentToWorld(*seg, &mw);
|
||||
mw.GetFromAxis(X_Axis, &ax);
|
||||
mw.GetFromAxis(Y_Axis, &ay);
|
||||
mw.GetFromAxis(Z_Axis, &az);
|
||||
@@ -1507,6 +1556,45 @@ void
|
||||
sm->localToWorld.GetFromAxis(Y_Axis, &ay);
|
||||
sm->localToWorld.GetFromAxis(Z_Axis, &az);
|
||||
}
|
||||
// #141 DIAGNOSTIC (BT_PROJ_LOG). The peer-POV report is that the
|
||||
// round leaves along the LEG facing, ignoring torso twist, while the
|
||||
// shooter's own view is correct. Both nodes pass GetSegmentIndex()
|
||||
// as the mount frame, so if this is real the difference is whether
|
||||
// the SEGMENT actually carries the twist on a replicant. Print the
|
||||
// frame we launched through on BOTH sides: twistDelta is the yaw of
|
||||
// the launch forward vs the BODY forward, so it should equal the
|
||||
// torso twist on the master and MUST match on the replicant. A
|
||||
// replicant reading ~0 while the master reads non-zero IS the bug.
|
||||
if (getenv("BT_PROJ_LOG"))
|
||||
{
|
||||
UnitVector bz;
|
||||
sm->localToWorld.GetFromAxis(Z_Axis, &bz);
|
||||
const float kPi = 3.14159265f;
|
||||
float segYaw = atan2f(-(float)az.x, -(float)az.z);
|
||||
float bodyYaw = atan2f(-(float)bz.x, -(float)bz.z);
|
||||
float dYaw = segYaw - bodyYaw;
|
||||
while (dYaw > kPi) dYaw -= 2.0f * kPi;
|
||||
while (dYaw < -kPi) dYaw += 2.0f * kPi;
|
||||
// #141 probe 2: GetSegmentToEntity only RECOMPUTES when
|
||||
// (segmentModified && parentSegment). A null parent means it can
|
||||
// never recompute -- it returns the bind-pose baseOffset forever,
|
||||
// which would read as segYaw == bodyYaw exactly. Print the
|
||||
// hierarchy + joint-dirty state so master and peer can be diffed.
|
||||
EntitySegment *pseg = (muzzle_seg >= 0) ? sm->GetSegment(muzzle_seg) : 0;
|
||||
const void *parent = (pseg != 0) ? (const void *)pseg->GetParent() : 0;
|
||||
int parentIdx = (pseg != 0) ? pseg->GetParentIndex() : -99;
|
||||
JointSubsystem *js = sm->GetJointSubsystem();
|
||||
DEBUG_STREAM << "[launchframe] "
|
||||
<< (sm->GetInstance() == Entity::ReplicantInstance
|
||||
? "REPLICANT" : "master ")
|
||||
<< " seg=" << muzzle_seg << " segResolved=" << haveFrame
|
||||
<< " segYaw=" << segYaw << " bodyYaw=" << bodyYaw
|
||||
<< " twistDelta=" << dYaw
|
||||
<< " parent=" << parent << " parentIdx=" << parentIdx
|
||||
<< " joints=" << (void *)js
|
||||
<< " jointsDirty=" << (js != 0 ? (int)js->AreJointsModified() : -1)
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
p.vel.x = ax.x*launch_velocity->x + ay.x*launch_velocity->y - az.x*launch_velocity->z;
|
||||
p.vel.y = ax.y*launch_velocity->x + ay.y*launch_velocity->y - az.y*launch_velocity->z;
|
||||
p.vel.z = ax.z*launch_velocity->x + ay.z*launch_velocity->y - az.z*launch_velocity->z;
|
||||
|
||||
@@ -897,14 +897,21 @@ void
|
||||
|
||||
// bring-up verification (env BT_TORSO_LOG; default OFF): show the first few
|
||||
// joint writes so the per-frame path can be confirmed in a headless run.
|
||||
static const int s_log = getenv("BT_TORSO_LOG") ? 1 : 0;
|
||||
static int s_count = 0;
|
||||
if (s_log && (s_count % 30) == 0 && s_count < 1800) // sample periodically to show the sweep
|
||||
// ⚠ SAMPLING TRAP (fixed 2026-08-08, #141): this used to sample ONE shared
|
||||
// static every 30th call. With a master torso and a replicant COPY torso
|
||||
// both ticking, the calls alternate 1:1 -- so every 30th call is always the
|
||||
// SAME instance, and the probe reported only the local (untwisted) torso
|
||||
// while the copy's writes were invisible. Sample per instance-kind instead.
|
||||
static const int s_log = getenv("BT_TORSO_LOG") ? 1 : 0;
|
||||
static int s_count[2] = { 0, 0 };
|
||||
const int kind = isDamagedCopy ? 1 : 0;
|
||||
if (s_log && (s_count[kind] % 30) == 0 && s_count[kind] < 1800)
|
||||
{
|
||||
DEBUG_STREAM << "[torso] PushTwist node=" << (void*)node << " type=" << (int)jt
|
||||
DEBUG_STREAM << "[torso] PushTwist " << (kind ? "COPY " : "master")
|
||||
<< " node=" << (void*)node << " type=" << (int)jt
|
||||
<< " twist=" << (float)twist << "\n" << std::flush;
|
||||
}
|
||||
++s_count;
|
||||
++s_count[kind];
|
||||
|
||||
switch (jt) // node+0x10
|
||||
{
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/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_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
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user