#173 FINAL: port cadence defect FIXED -- the velocity ring now samples at the pod's 28Hz by default (BT_MYO_RING_HZ overrides). Measured [T2, six curves m173v2]: at ~59fps the |a| operand carried 64-66% gait-aliasing noise = drive heat 2x the machine's; at 28Hz + the authored loop-5 boost (detent 50 receipt-proven, flow 0.909) a vulture holds FULL supercharge 54-57 u/s indefinitely at stable Tm~1500 -- the machine's valve lever worked, the manual's Super Charged print was honest, witnesses 7-0 (Draco/Oracle/Elengil + the user's eyes beat the solver AND the first bench). Balanced seek-4 still cooks authentically (~18s); wrap trap stands; display pixel-verified in sync. BT_VALVE5 bench hook added (receipted detent presses). Post-promotion smoke: boosted vulture stable ~1500 dipping under. Gotcha 32 (sampled-history operands at render cadence; a validated model EXTRAPOLATES, only the bench INTERPOLATES); cadence census -> BTSimClock migration checklist in open-questions (gyro bounce timescale next, footstep smoothers, doppler; heatLoad was already pod-clocked #119 = the reference pattern). Guide 2B rewritten with the confession + the measured fix

This commit is contained in:
Joe DiPrima
2026-08-13 19:03:41 -05:00
parent 0f5c87dc84
commit ca9e5ff5c0
8 changed files with 415 additions and 19 deletions
+35 -1
View File
@@ -1233,7 +1233,18 @@ consistent with internals-only, see #103). Logs: `scratchpad/night16/` + Oracle
(x2 spawns, AFC50/Zanin Neko); once ABSENT for an entire fatal leak (5-6 voice repetitions,
no flash on any MFD, died ~9:38PM CT -- the match's only death, receipt findable in night16).
Distinct from the closed #135: this is the SET-edge / first-flash arming path.
- **#173 -- chicken-walker seek-4 myomer overheat** (see [[subsystems]] night-16 note).
- **#173 -- chicken-walker seek-4 myomer overheat: ⚑ SETTLED AUTHENTIC (2026-08-13, three
lanes, see [[subsystems]] #173 blocks).** Bench matrix reproduced Elengil exactly (vulture
seek-4: degT in 13.9 s, governor-pinned eq ~1801, loop-5 bar full-scale ~15-20 s, sheds
10/s only at standstill); the equilibrium is a chassis-blind governor thermostat — the
port's avatar (same 143-kph speed class) cooks identically, and the manual prints the big
walkers even FASTER (MadCat 175/220) than the port runs them. Counter-play measured: one
loop-5 valve press sustains drive 0.68 vs 0.54. TWO residuals stay open: (1) [T3] the
accel-term |a| ring is frame-cadence-sensitive (|a|≈14-20 at 59 fps on a straight flat
cruise = gait surge; pod 28 Hz would read ~2-3x smaller → pod eq likely ~1200-1500, softer
governor — if calibration is wanted, fix the |a| feed cadence, never the byte-exact
constants); (2) locomotion-lane: port per-chassis top speeds undershoot the manual prints
(thor 87 vs 143 N print) — correcting them raises heat further.
- **#165 ROOT-CAUSED + FIXED + BENCHED (2026-08-13, the 4-agent hunt):** gotcha SS30 -- the
binary's ONE destroyed cell was split into four port stores and the crit-lottery kill path
(MechWeapon::TakeDamage) wrote none the gate reads. Fixed (chain to MechSubsystem::TakeDamage,
@@ -1253,3 +1264,26 @@ consistent with internals-only, see #103). Logs: `scratchpad/night16/` + Oracle
wanted for the pair-leak confirmation.
- Nanook lost the coolant MFD glass window, recovered only by config edit -> #76 comment
(in-game layout-reset recovery path).
## Cadence census -- the shared 28Hz sim-clock migration checklist (2026-08-13) [T1/T2]
Census of sampled-history-at-render-cadence sites (gotcha 32; full table in the census
workflow journal wf_d875938f + classifier verdicts). DONE: velocity ring default-28Hz
(mech4.cpp, HIGH, the #173 fix -- myomer heat + instability/sway inherit); heatLoad already
pod-clocked (#119, the reference pattern). REMAINING for the BTSimClock migration (one
shared 28Hz tick source; tick-native sites subscribe; dt-native physics -- Mover/missile ODE
-- stays on real dt):
- MED gyro eye/body spring-damper integrators (gyro.cpp:503-575): per-tick position step, NO
dt, damping carries last-frame state -- cockpit bounce timescale runs 2.14x the machine's.
Needs an impulse-response bench (28 vs 60) before/after.
- LOW footstep AudioControlSmoother windows (AUDCMP.cpp:952-977): authored N=30/15 sized for
28Hz; halved at 60fps + noisy operand. Clock the watcher-poll intake.
- LOW doppler operand (AUDLOC.cpp:439-505 reads UNSMOOTHED worldLinearVelocity frame
derivative): per-stride pitch warble on nearby movers. A/B-able by ear.
- LOW published localVelocity 0.25s exponential smoothing (mech4.cpp:5578-5613): re-base to
the binary's own 15-sample@28Hz filter once the shared clock exists.
- DEFERRED dead-reckon divergence (@_DAT_004be050) -- only matters if the Missile entity
revives. ANNOTATE-ONLY: Simulate telemetry FilteredScalars (dormant).
- Productionize the ring's 16-slot dt-accumulator registry (mech4.cpp) to the heat.cpp map
pattern (degrades gracefully to per-frame push on overflow today; fine for one mission's
mech count, sloppy long-term).
+23
View File
@@ -1064,3 +1064,26 @@ passes every reader-side bench. **Bench rule (the #165 lesson, second applicatio
week):** prove the bench detects the bug BEFORE trusting its pass -- the negative control
here (fix stashed) scored 147 cascades where the fixed build scores 1.
Bench: BT_ZONE_HAMMER=<zone>[,<amt>] (mech4.cpp) + scratchpad/night16/cascade_bench.sh.
## 32. SAMPLED-HISTORY OPERANDS AT RENDER CADENCE: rate-scaling amounts cannot un-noise a signal (#173 flip, 2026-08-13)
The port's #137 pattern (term x dt x 28) correctly rate-normalizes per-tick heat AMOUNTS -- but
OPERANDS COMPUTED FROM SAMPLED HISTORY (rings, last-frame deltas, moving averages, per-tick
integrators carrying state) still sampled at RENDER cadence where the pod's board sampled at
28 Hz. The killer case: the AccelerationLastFrame velocity ring pushed per render frame read
|a| 17-26 of pure gait-aliasing noise on a flat straight cruise -- 64-66% of myomer drive-heat
generation was PHANTOM; at 28 Hz sampling the noise drops to 24-32% and generation HALVES at
equal speed. Consequence: every fast-heavy chassis was over-punished ~2x; the authored loop-5
valve boost genuinely sustains FULL supercharge seek-4 at 28 Hz (bench m173v2 leg G: 54-57 u/s
held, Tm stable ~1500) -- the machine's lever worked and the manual's Super Charged print was
honest. Witnesses (Draco/Oracle/Elengil + the user's own eyes) beat the validated-solver
extrapolation AND the first bench: 7-0 this week.
**Rules:** (1) any binary value derived from per-tick sampled history must be sampled on the
BINARY'S clock -- accumulate render dt, take one sample per 1/28 s measuring over the full
elapsed window (the heat.cpp #119 heatLoad clock is the reference implementation; the ring
default-28Hz in mech4.cpp is the second). (2) Rate-scaling the consuming term is NOT
sufficient -- it scales amounts, not signal quality. (3) A validated model EXTRAPOLATES; only
the bench INTERPOLATES -- never print "sustains indefinitely" from a solver row the bench
never flew (the #173 verdict flipped twice on exactly this). Census of the whole class (11
rows, 2 HIGH fixed, migration checklist for the shared 28 Hz sim-clock): open-questions.md
SSCadence-census + the wf_d875938f journal.
+42
View File
@@ -475,3 +475,45 @@ edge — this is the gap that would reconcile Oracle's "ran 182 kph sustained" p
If field calibration is ever wanted, the knob is the |a| feed's cadence (sample the ring at
28 Hz), NOT the formula constants (byte-exact, #137). Also note the CLIMB term is inert in
the port (g=0, EnvironmentZone unwired) — the pod added heat on slopes that we do not.
**#173 DECISIVE VALVE-BOOST + CADENCE EXPERIMENT (2026-08-13 night17;
`scratchpad/night17/m173v_bench.sh`/`m173v2_bench.sh`/`m173v_check.py`, logs
`content/m173_vul_[ABCEFG]*.log`, captures `content/vlv_*.png`) — the residual above is now
MEASURED and the verdict sharpens:**
- **Valve delivery PORT-FAITHFUL, receipt-PROVEN [T2 live / T1 operands]**: the `BT_VALVE5=N`
hook (mech4.cpp, env-gated) presses Condenser5's MoveValve N times; every landed detent is
receipted (`[valve] Condenser5 valve -> detent 5/50/0` + the zero-sum redistribute lines:
share 0.5 → 0.909091 → 0, others 0.1 → 0.0182 → 0.2, exactly @0049f788's flow=valve/total).
C5 responds as flow dictates (cruise Tc5: balanced 1396 → detent-50 712 → CLOSED 1902 pegged).
- **The bottleneck SHIFTS to the valve-independent stage [T1+T2]**: myomer→Condenser5
conduction measures 4.15.0e4 heat-units/K of dT in EVERY run (balanced/boosted/closed,
both cadences) — constant because @004ad9ec's exponent reads the CALLER's own
flowScale@+0x15C (the myomer's, pinned 1.0), never the condenser's valve share [T1 raw
pseudocode]. The solver's 2.7x boosted whole-chain throughput is CONFIRMED live (4.8e7/s
through the chain at 60fps detent 50) but its "sustains under degT 1000" corollary is
WRONG at any cadence: sub-1000 Tm caps stage-1 flux at ~3e7/s < supercharge generation, so
the governor always engages; the boost buys SPEED at a lower pinned Tm, never sub-1000 Tm.
- **The cadence residual is REAL and ~2x [T2 measured]** (BT_MYO_RING_HZ=28 knob landed in
mech4.cpp — cadence only, constants untouched): at ~59fps the |a| gait-surge noise carries
6466% of steady seek-4 generation (|a|≈1726 flat grass); at 28Hz it drops to 2432%
(|a|≈67) and generation at equal speed HALVES. The six curves (solo vulture seek-4, t1000 /
equilibrium / held v): 60fps bal 12.3s/1800/28 · 60fps d50 12.3s/1675/44 · 60fps CLOSED
13.9s/NO-EQ-1936-still-climbing/11.7 (drive 0.21, C5 pegged 1902 — the wrap-trap signature)
· 28Hz bal 18.3s/~1710/3941 · **28Hz d50 22.5s/~1500 STABLE/5457 = FULL supercharge speed
sustained indefinitely** (equilibrium sits on the soft governor shoulder, zero speed cost;
500 under failT). **The machine's loop-5 boost genuinely sustained unrestricted seek-4 — the
manual's Super Charged print is vindicated at pod cadence; the port at ~59fps over-punishes
by ~2x. Severity verdict flips to PORT CADENCE DEFECT (fix = 28Hz ring sampling; default
promotion = owners' decision).** Balance verdict unchanged: balanced-valve seek-4 cooks at
BOTH cadences (authentic, manual-warned).
- **Field "34s to governor" ≠ spawn preheat [T2]**: field-shaped run (autofire at scenery
from frame 0, no pre-settle) still takes 15.9s to degT from cold; GenD/loop-5 preheat is
real but slow (GenD 666 by 29s). The 34s feel is a WARM-START baseline (T≥800 → <3s) plus
the deep 59fps governor making the derate obvious.
- **Heat-MFD valve slider IN SYNC [T2 pixel-verified]**: the vertNormalSlider renders
coolantFlowScale@0x15C — the SAME cell the flow model consumes (single-cell design, no
split possible); measured bar fractions 0.20/0.51/0.90/0.04 for receipt shares
0.167/0.5/0.909/0.0 (perfectly linear; `vlv_*.png`). Detent 50 (≈90% up) and detent 0
(bottom) are clearly distinguishable; the residual legibility trap is CLOSED-vs-STARVED
(share 0 vs 0.0180.1 ≈ 12px apart) and the silent 4th-press wrap — "maxed and couldn't
shed" field reports match the run-C wrap signature, not a display desync.
+22 -14
View File
@@ -121,9 +121,9 @@ THE ORIGINAL'S BEHAVIOR (audited to the byte this week)
in this machine. The fast-heavy Clan walkers pay the most.
- Gears 1 through 3 make IDENTICAL heat: seek-3 is free speed.
Seek-4 roughly doubles drive heat AND raises your top speed --
a sprint gear. Cruising it on a heavy walker crosses myomer
degradation in about 14 seconds and the governor drags you to a
crawl. A same-weight humanoid at the same speed cooks the same:
a sprint gear. Cruising it on a heavy walker without the boost
crosses myomer degradation in under twenty seconds and the
governor bites. A same-weight humanoid at the same speed cooks the same:
it is physics, not a chicken-walker curse.
- The machine gave you the tool: BOOST COOLANT LOOP 5 (Generator D /
Myomers). One press visibly buys your speed back; a full boost
@@ -133,20 +133,28 @@ THE ORIGINAL'S BEHAVIOR (audited to the byte this week)
wrap. Two presses, never three.
- Standing still sheds heat fast (~10 degrees a second).
OUR PORT TODAY
All of the above, byte-faithful -- and our walkers actually run a
little SLOWER than the machine's printed speeds, so if anything the
pods cooked slightly faster than we do. One open sliver: our
governor may bite a touch harder than you remember (a frame-timing
nuance under study), which the question below settles.
OUR PORT TODAY -- AND A CONFESSION YOUR REPORTS FORCED
You were right that something was off. Our build measured your
mech's acceleration at modern frame rates, and at those rates the
measurement picks up walking-gait jitter as phantom acceleration --
nearly TWO-THIRDS of your drive heat on a flat cruise was noise
that the original's electronics (sampling at their own slower
clock) never saw. The port was punishing heavy walkers roughly
TWICE as hard as the machine did.
CHANGING IN THE NEXT BUILD
Nothing mechanical.
CHANGING IN THE NEXT BUILD (the fix, measured)
Heat sensing now samples on the original's clock. Result on the
bench: a Vulture at seek-4 WITH the loop-5 boost holds FULL
supercharge speed indefinitely, running hot but stable -- the
valve trick works exactly as the machine intended, and the
manual's printed Super Charged speed is real. WITHOUT the boost,
seek-4 still cooks you (that part is the machine's own balance,
and it stays). The wrap trap also stays: two presses, never three.
WE NEED YOUR MEMORY
- At seek-4 on a Vulture or MadCat -- balanced valves, flat ground
-- did your speed sag within ~15 seconds, or could you hold
supercharge speed sustained?
- Next build: boost loop 5, hold seek-4 on a big walker. Does the
sustained supercharge match how the machine felt? Your yes
closes the oldest heat complaint on the books.
- Did the loop-5 valve boost trick live in era pilot lore, or are
we rediscovering it?
+113 -4
View File
@@ -7835,6 +7835,57 @@ void
gBTValveKey = (s_vtFrame >= 600 && s_vtFrame < 610) ? 1 : 0;
}
// #173 decisive experiment (BT_VALVE5=N): press Condenser5's MoveValve
// exactly N times, spaced BT_VALVE5_SPACING frames (default 30) apart,
// starting at frame BT_VALVE5_AT (default 300) -- walks the detent
// cycle 1->5->50->0 (@4ae464) a CONTROLLED number of steps on the
// MYOMER loop. Dispatches the same ReceiverDataMessageOf<ControlsButton>
// press payload the pod's engineering-screen aux button delivers; the
// handler's unconditional [valve] receipt proves each landed detent.
// Self-contained (does NOT ride gBTValveKey, so it cannot collide with
// the BT_VALVE_TEST edge detector). Env-gated, off by default.
if ((Entity *)this == application->GetViewpointEntity()
&& getenv("BT_VALVE5"))
{
static int s_v5Frame = 0;
static int s_v5Sent = 0;
++s_v5Frame;
int want = atoi(getenv("BT_VALVE5"));
const char *atEnv = getenv("BT_VALVE5_AT");
const char *spEnv = getenv("BT_VALVE5_SPACING");
int startAt = (atEnv != 0) ? atoi(atEnv) : 300;
int spacing = (spEnv != 0) ? atoi(spEnv) : 30;
if (spacing < 2) spacing = 2; // edge needs a gap
if (s_v5Sent < want
&& s_v5Frame >= startAt + s_v5Sent * spacing)
{
Subsystem *condenser = 0;
for (int s = 1; s < GetSubsystemCount(); ++s)
{
Subsystem *sub = GetSubsystem(s);
if (sub != 0 && (int)sub->GetClassID() == 0xBBD // Condenser
&& sub->GetName() != 0
&& stricmp(sub->GetName(), "Condenser5") == 0)
{
condenser = sub;
break;
}
}
if (condenser != 0)
{
++s_v5Sent;
DEBUG_STREAM << "[valve-tx] BT_VALVE5 press " << s_v5Sent
<< "/" << want << " -> " << condenser->GetName()
<< " (frame " << s_v5Frame << ")\n" << std::flush;
ReceiverDataMessageOf<ControlsButton> msg(
4 /*Condenser::MoveValveMessageID*/,
sizeof(ReceiverDataMessageOf<ControlsButton>),
(ControlsButton)1 /*press*/);
condenser->Dispatch(&msg);
}
}
}
// Gitea #6 scripted verify (BT_VIEWCYCLE_TEST=<frame>): pulse one
// secondary-schematic cycle (Damage -> Critical -> Heat -> Damage) at
// the given frame and every 300 frames after -- with
@@ -8695,15 +8746,72 @@ void
// footstep smoother fed (fed per-stride only, it took 10-20 s to warm
// from fill=0: the late-footsteps bug).
{
// #173 RING CADENCE -- DEFAULT 28 Hz (promoted 2026-08-13, user call,
// six-curve bench m173v2): push velocity-ring samples at the pod's
// BOARD cadence (28 Hz nominal [T1]) instead of once per render
// frame. The ring / mean-derivative STRUCTURE is untouched
// (byte-faithful); the only change is the sampling clock, which on
// the pod WAS the frame clock (28 Hz) and here is ~60. MEASURED
// [T2]: at ~59fps the |a| operand carries 64-66% ALIASING NOISE
// (|a| 17-26 on flat straight grass); at 28 Hz it drops to 24-32%
// and drive-heat generation HALVES at equal speed -- the port was
// over-punishing every fast-heavy chassis ~2x. With the authored
// loop-5 boost, 28 Hz sustains FULL supercharge speed indefinitely
// at a stable Tm ~1500 (leg G: 54-57 u/s held) -- the machine's
// valve lever worked, and the manual's printed Super Charged stat
// was honest. BT_MYO_RING_HZ=<hz> overrides for bracketing
// (=0 or negative restores raw render-cadence sampling).
int ringPush = 1;
Scalar ringDt = dt;
{
static Scalar s_ringHz = -2.0f;
if (s_ringHz < -1.0f)
{
const char *rh = getenv("BT_MYO_RING_HZ");
s_ringHz = (rh != 0 && *rh != '\0') ? (Scalar)atof(rh) : 28.0f;
}
if (s_ringHz > 0.0f)
{
// per-instance dt accumulator (tiny fixed registry -- bench
// scaffolding; a solo bench has one mech, MP a handful)
static const void *s_raKey[16];
static Scalar s_raAcc[16];
int slot = -1, freeSlot = -1;
for (int k = 0; k < 16; ++k)
{
if (s_raKey[k] == (const void *)this) { slot = k; break; }
if (s_raKey[k] == 0 && freeSlot < 0) freeSlot = k;
}
if (slot < 0 && freeSlot >= 0)
{
slot = freeSlot;
s_raKey[slot] = (const void *)this;
s_raAcc[slot] = 0.0f;
}
if (slot >= 0)
{
s_raAcc[slot] += dt;
if (s_raAcc[slot] < 1.0f / s_ringHz)
ringPush = 0;
else
{
ringDt = s_raAcc[slot];
s_raAcc[slot] = 0.0f;
}
}
}
}
if (ringPush)
{
Scalar rdx = localOrigin.linearPosition.x - accelPrevPos.x;
Scalar rdy = localOrigin.linearPosition.y - accelPrevPos.y;
Scalar rdz = localOrigin.linearPosition.z - accelPrevPos.z;
accelPrevPos = localOrigin.linearPosition;
if (dt > 1.0e-4f)
if (ringDt > 1.0e-4f)
{
velRingFwd[velRingCursor] = (Scalar)sqrtf(rdx * rdx + rdz * rdz) / dt;
velRingVert[velRingCursor] = rdy / dt;
velRingDt[velRingCursor] = dt;
velRingFwd[velRingCursor] = (Scalar)sqrtf(rdx * rdx + rdz * rdz) / ringDt;
velRingVert[velRingCursor] = rdy / ringDt;
velRingDt[velRingCursor] = ringDt;
velRingCursor = (velRingCursor + 1) % 15;
}
Scalar fwdMean = 0.0f, vertMean = 0.0f, dtMean = 0.0f;
@@ -8723,6 +8831,7 @@ void
-((fwdMean - accelPrevFwdMean) / dtMean));
accelPrevFwdMean = fwdMean;
accelPrevVertMean = vertMean;
}
}
// (AUDIO_FIDELITY F7) latch the incoming-missile report accumulated by
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# =========================================================================
# #173 wave 2 -- the field-feel gap (3-4s to governor vs bench 12-13s) and
# the 28Hz cadence residual, decisive runs:
# E) FIELD-SHAPED spawn: no pre-settle, weapons charging + THROTTLED
# autofire at scenery from the first frame, sprint at seek-4 --
# time-to-degradation + Condenser5/GeneratorD preheat at motion start.
# F) 28Hz ring cadence (BT_MYO_RING_HZ=28), balanced valves -- does the
# |a| gait-surge term collapse and t1000 stretch?
# G) 28Hz ring cadence + PROVEN detent 50 on Condenser5 (BT_VALVE5=2) --
# THE FLIP TEST: does boosted seek-4 land UNDER degradation 1000?
# Same rig as m173v_bench.sh (solo expert vulture, grass/day, seek-4 forced,
# full autodrive, 0.12 turn), 165s each.
# =========================================================================
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
run_one () { # run_one <tag> <secs> [extra env pairs...]
local TAG=$1 SECS=$2; shift 2
bt_expert_egg MP.EGG M173V.EGG
sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; 0,/^vehicle=.*/s//vehicle=vulture/" M173V.EGG
local LOG=m173_${TAG}.log
rm -f "$LOG"
( export BT_MYO_LOG=1 BT_HEAT_LOG=1 BT_SPEC_LOG=1 BT_MYOMERS_LOG=1 BT_VALVE_LOG=1
export BT_FORCE_SEEK=3 BT_AUTODRIVE=1.0 BT_FORCE_TURN=0.12
for kv in "$@"; do export "$kv"; done
bt_launch "$LOG" M173V.EGG 0x03 )
sleep "$SECS"
bt_kill_ours
sleep 3
taskkill //F //IM btl4.exe > /dev/null 2>&1
sleep 2
}
run_one vul_E_field 165 BT_AUTOFIRE=1 BT_FIRE_AT_ICON=1 BT_AF_PERIOD=4 BT_FIRE_LOG=1
run_one vul_F_28bal 165 BT_MYO_RING_HZ=28
run_one vul_G_28d50 165 BT_MYO_RING_HZ=28 BT_VALVE5=2
echo "=================== m173v wave-2 RUNS DONE ==================="
for f in m173_vul_E_field.log m173_vul_F_28bal.log m173_vul_G_28d50.log; do
echo "--- $f"
grep -ac "myotemp" "$f" 2>/dev/null
grep -a "valve-tx\|valve -> detent" "$f" 2>/dev/null
done
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
# =========================================================================
# #173 DECISIVE EXPERIMENT -- does boosting coolant loop 5's valve sustain
# seek-4 on a Vulture, as the equilibrium solver claims, or does it still
# cook as the field reported ("loop 5 maxed and couldn't shed")?
#
# Rig: the m173 measurement pattern (night16 myo173.sh) -- solo expert
# vulture, grass/day, BT_FORCE_SEEK=3 (seek-4) + BT_AUTODRIVE=1.0 +
# BT_FORCE_TURN=0.12 (stays on-map near top speed), myomer/condenser temp
# receipts under the heat-log envs.
#
# Valve control: the NEW BT_VALVE5=N hook (mech4.cpp) presses Condenser5's
# MoveValve N times, spaced 30 frames, starting frame 300. Every press is
# receipted by the handler's unconditional "[valve] Condenser5 valve ->
# detent D" line, and BT_VALVE_LOG receipts the zero-sum share redistribute
# ("[valve] condenser#5 valveState=V flow=F (total=T)") -- the landed
# detent is PROVEN, not assumed.
#
# Three measurement runs, ~150s sustained seek-4 cruise each, identical
# otherwise:
# A) balanced (no presses) -- expected: degradation ~14s, eq ~1800
# B) BT_VALVE5=2 (detent 1->5->50) -- THE QUESTION: stays under 1000?
# C) BT_VALVE5=3 (wrap to detent 0) -- expected WORSE than A (share 0)
# Plus one visual run:
# D) idle mech, 3 presses spaced 600 frames, BT_SHOT_EVERY=90 -- captures
# the Heat MFD valve slider at each detent 1/5/50/0 (indicator audit).
# =========================================================================
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 vlv_*.png
run_one () { # run_one <tag> <secs> [extra env pairs...]
local TAG=$1 SECS=$2; shift 2
bt_expert_egg MP.EGG M173V.EGG
sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; 0,/^vehicle=.*/s//vehicle=vulture/" M173V.EGG
local LOG=m173_${TAG}.log
rm -f "$LOG"
( export BT_MYO_LOG=1 BT_HEAT_LOG=1 BT_SPEC_LOG=1 BT_MYOMERS_LOG=1 BT_VALVE_LOG=1
for kv in "$@"; do export "$kv"; done
bt_launch "$LOG" M173V.EGG 0x03 )
sleep "$SECS"
bt_kill_ours
sleep 3
taskkill //F //IM btl4.exe > /dev/null 2>&1
sleep 2
}
DRIVE="BT_FORCE_SEEK=3 BT_AUTODRIVE=1.0 BT_FORCE_TURN=0.12"
run_one vul_A_bal 165 $DRIVE
run_one vul_B_d50 165 $DRIVE BT_VALVE5=2
run_one vul_C_d0 165 $DRIVE BT_VALVE5=3
run_one vul_D_shot 100 BT_VALVE5=3 BT_VALVE5_AT=900 BT_VALVE5_SPACING=600 \
BT_SHOT_EVERY=90 BT_SHOT_PREFIX=vlv
echo "=================== m173v RUNS DONE ==================="
for f in m173_vul_A_bal.log m173_vul_B_d50.log m173_vul_C_d0.log m173_vul_D_shot.log; do
echo "--- $f"
grep -ac "myotemp" "$f" 2>/dev/null
grep -a "\[valve" "$f" 2>/dev/null | head -40
done
ls vlv_*.png 2>/dev/null | head
+68
View File
@@ -0,0 +1,68 @@
# #173 valve experiment digest -- per run: valve receipts (PROOF of detent),
# time-to-degradation-1000, equilibrium T, held speed, C5/bank finals.
import re, sys, statistics as st
def digest(path):
myotemp = [] # (t, T)
myoheat = [] # (v, a, dt)
myo = [] # speedEffect samples in file order
c5 = []; bank = []
presses = [] # ([valve-tx] BT_VALVE5 press lines)
detents = [] # ([valve] CondenserN valve -> detent D lines)
redist = [] # ([valve] condenser#N valveState=V flow=F (total=T))
for ln in open(path, encoding='latin-1', errors='replace'):
m = re.search(r"\[myotemp\] t=([\d.eE+-]+) T=([\d.eE+-]+)", ln)
if m: myotemp.append((float(m.group(1)), float(m.group(2)))); continue
m = re.search(r"\[myoheat\] v=([\d.eE+-]+) a=([\d.eE+-]+) m=[\d.eE+-]+ g=[\d.eE+-]+ ratio=[\d.eE+-]+ dmgGain=[\d.eE+-]+ dt=([\d.eE+-]+)", ln)
if m: myoheat.append(tuple(map(float, m.groups()))); continue
m = re.search(r"\[myo\] Myomers.* speed=([\d.eE+-]+).* gear=(\d+)", ln)
if m: myo.append((float(m.group(1)), int(m.group(2)))); continue
m = re.search(r"\[heat-t\] (\S+)( \(bank\))? T=([\d.eE+-]+)", ln)
if m:
name, isbank, T = m.group(1), m.group(2), float(m.group(3))
if name == 'Condenser5': c5.append(T)
elif isbank or name == 'HeatSink': bank.append(T)
continue
if '[valve-tx] BT_VALVE5' in ln: presses.append(ln.strip()); continue
m = re.search(r"\[valve\] (Condenser\d+) valve -> detent (\d+)", ln)
if m: detents.append((m.group(1), int(m.group(2)))); continue
m = re.search(r"\[valve\] condenser#(\d) valveState=(\d+) flow=([\d.eE+-]+) \(total=(\d+)\)", ln)
if m: redist.append(tuple(int(x) if i != 2 else float(x) for i, x in enumerate(m.groups()))); continue
name = path.split('m173_')[-1].replace('.log', '')
print("=" * 78)
print("RUN %s" % name)
if presses:
for p in presses: print(" " + p)
if detents:
print(" detent walk: " + " -> ".join("%s:%d" % d for d in detents))
# final redistribute state (last 6 lines)
if redist:
last6 = redist[-6:]
print(" final shares: " + " ".join("#%d v=%d f=%.4f" % (n, v, f) for (n, v, f, t) in last6)
+ " (total=%d)" % last6[-1][3])
if not myotemp:
print(" NO MYOTEMP DATA"); return
t_end = myotemp[-1][0]
t1000 = next((t for t, T in myotemp if T >= 1000), None)
t2000 = next((t for t, T in myotemp if T >= 2000), None)
Tpk = max(T for _, T in myotemp)
late = [T for t, T in myotemp if t > t_end - 40.0]
k = max(2, len(myotemp) // 5)
dT = (myotemp[-1][1] - myotemp[-k][1]) / max(1e-6, (myotemp[-1][0] - myotemp[-k][0]))
# pair myoheat with myotemp by index (printed in lockstep)
n = min(len(myoheat), len(myotemp))
vlate = [myoheat[i][0] for i in range(n) if myotemp[i][0] > t_end - 40.0]
vpk = max(x[0] for x in myoheat) if myoheat else 0.0
selate = [s for s, g in myo[-20:]]
gears = set(g for s, g in myo)
print(" t1000=%s t2000=%s Tpeak=%.0f eqT(last40s)=%.0f endSlope=%+.2f/s (window %.1fs)"
% ("%.1fs" % t1000 if t1000 else "NEVER", "%.1fs" % t2000 if t2000 else "never",
Tpk, st.mean(late), dT, t_end))
print(" vPeak=%.1f vHeld(last40s)=%.1f u/s speedEffect(last~20 samples)=%.2f gear(s)=%s"
% (vpk, st.mean(vlate) if vlate else 0.0, st.mean(selate) if selate else 0.0, sorted(gears)))
print(" Condenser5 end T=%.0f bank end T=%.0f" % (c5[-1] if c5 else 0, bank[-1] if bank else 0))
if __name__ == '__main__':
for f in sys.argv[1:]:
digest(f)