diff --git a/context/decomp-reference.md b/context/decomp-reference.md index 6868919..d244c80 100644 --- a/context/decomp-reference.md +++ b/context/decomp-reference.md @@ -436,18 +436,19 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a the Execute machine, but this handler overwrites it). The earlier "jams are mission-permanent / no unjam" claim was WRONG (read only the hold path) — corrected + swept 2026-07-23 (projweap.cpp case-5/jam-log comments, players/README.txt). - **LATENT ARCADE BUG found + diverged (issue #21, 2026-07-21) [T1 byte-verified + T2 repro]:** - the Emitter charge integrates toward the GENERATOR voltage (@004ba838 `fld [src+0x1dc]`), full - is detected only inside the +-1% snap window around `seekVoltage[idx]` (ComputeOutputVoltage - @004ba738: `|rl-1|<=0.01 -> 1.0`), and OVERCHARGE reads ZERO (`rl>1.01 -> 0`, - `_DAT_004ba830 = 0.0` byte-verified). Toggle the seek gear while a charge is in flight and - the level lands ABOVE the new gear's window -> rechargeLevel pinned 0, the `==1.0` Loaded test - (`_DAT_004bac04`) never fires, charging continues to the generator ceiling where EVERY gear - reads overcharged -> the weapon is PERMANENTLY bricked (arc dark, ready-dot dark, no fire; gen - re-select cannot help). The arcade shipped this (locked 60 fps + rare seek use hid it); the - port's clickable seek button triggers it in seconds. **Deliberate divergence** (emitter.cpp - Loading tick): overcharge (`currentLevel > seekVoltage[idx]`) counts as fully charged -> - Loaded ("full == the gear's seek voltage" is the arcade's own discharge algebra). Repro + + **#21 RETRACTED 2026-08-13 (era testimony -> adversarial re-audit; witnesses 6-0):** the +"LATENT ARCADE BUG / permanently bricked / the arcade shipped this" claim that stood here was +a CONSTANT-WIDTH MISREAD. `_DAT_004ba830` is a **DOUBLE 1.0** (bytes 00..00 f0 3f @0x4ba830 -- +the old "0.0 byte-verified" read the low DWORD of a QWORD: 1.0's mantissa zeros; Ghidra's own +"globals overlap smaller symbols" warning applies). ComputeOutputVoltage @004ba738 truth [T1 +raw disasm]: ratio = charge/seekV[idx]; snap |rl-1|<=0.01 -> 1.0f; else CLAMP to [0,1] -- +overcharge stores EXACTLY 0x3f800000, the `==1.0` Loaded test passes next tick, the weapon +FIRES the stored charge (damage divisor is the RECOMMENDED gear, so a wrap-fired charge deals +normal top-gear damage). ToggleSeekVoltage @004ba478 genuinely skips ResetFiringState on wrap +[T1] -- but nothing downstream ever bricks. The machine never had a dark laser; the port's +"rescue" was a patch over OUR misread (removed with the clamp fix, same date). Constants +extracted: 0x4ba818=0.0f 0x4ba81c=1e-4f 0x4ba820=1.0f 0x4ba824=0.01f 0x4ba828=dbl 0.0 +0x4ba830=dbl 1.0 0x4bac04=1.0f. Gotcha family 30/31 (width/scope misreads). Repro + verify: BT_SEEKTEST seek-abuse -- pre-fix deadlocks at pct=0/alarm=3; post-fix 38 rescues, ends Loaded/pct=1. `[seek]` log prints the per-gear table + rescue events. - **EJECT/PANIC cluster DECODED + WIRED 2026-08-02 [T1 raw disasm]:** `@0049f854` diff --git a/docs/WEAPONS_DRIFT_AUDIT.md b/docs/WEAPONS_DRIFT_AUDIT.md index 437fc9c..e589b79 100644 --- a/docs/WEAPONS_DRIFT_AUDIT.md +++ b/docs/WEAPONS_DRIFT_AUDIT.md @@ -24,14 +24,14 @@ the queued ticket comment until the group signs off. | 2 | PPC/ERPPC fire chain (0xBD4) | FAITHFUL | | 3 | PPC rider (a) victim display scramble | KNOWN-DIVERGENCE [T3] (unimplemented; visual OPEN) | | 4 | PPC rider (b) victim generator short | FAITHFUL (tag-label scope OPEN) | -| 5 | Seek-voltage gears + #21 overcharge brick | KNOWN-DIVERGENCE (deliberate rescue) | +| 5 | Seek-voltage gears (#21) | FAITHFUL (brick claim RETRACTED 2026-08-13 -- constant-width misread; binary clamps overcharge to 1.0 and fires; witnesses 6-0) | | 6 | Autocannons AFC25/50/100 (0xBCD) | FIXED-THIS-BUILD (#171; T2 field receipts pending) | | 7 | AC cosmetic tracer visual | KNOWN-DIVERGENCE [T3] (cosmetic; constants recovered — close) | | 8 | GAUSS (fielded, 0xBCD record) | FAITHFUL (correction sweep owed, HELD) | | 9 | GaussRifle class 0xBCE (+ shell 0xBD1) | FAITHFUL (dead code both sides) | | 10 | MissileLauncher core (0xBD0, all racks) | FAITHFUL | | 11 | Missile presentation (salvo visuals/gains) | KNOWN-DIVERGENCE [T3] (presentation-only) | -| 12 | Missile bystander sweep | KNOWN-DIVERGENCE [T3] (largest remaining gap) | +| 12 | Missile bystander sweep | FIXED-THIS-BUILD (capsule sweep [T3 radius]; T2 field receipts pending) | | 13 | Missile splash (@0042fad0) | FAITHFUL under stock content (gate wiring [T3] on custom eggs) | | 14 | LRM flight + loft | FAITHFUL | | 15 | Streak racks (strk model) | FAITHFUL | @@ -94,11 +94,13 @@ content. - **Verdict:** FAITHFUL (tag-label scope OPEN). NOTE: the two riders' port status differs — never lump them in vet-facing docs. -### 5. Seek-voltage gears + #21 overcharge brick (all energy weapons) +### 5. Seek-voltage gears (#21) -- brick claim RETRACTED 2026-08-13 - **Binary [T1]:** `ToggleSeekVoltage` @004ba478 (vet/expert only): wrap from gear 4→1 skips `ResetFiringState` → charge above snap window → - `ComputeOutputVoltage` overcharge reads ZERO → weapon PERMANENTLY - BRICKED. The arcade shipped this latent brick. + ~~overcharge reads ZERO → PERMANENTLY BRICKED~~ **RETRACTED: _DAT_004ba830 + is a DOUBLE 1.0 (width misread); the binary CLAMPS overcharge to 1.0 and + the weapon fires -- the machine never bricked. Era witnesses (6-0) triggered + the re-audit; full detail in context/decomp-reference.md.** - **Port [T3 deliberate, documented]:** overcharge counts as Loaded (issue #21, 38 benched rescues) — the port's ONE deliberate energy-lane divergence; lives in the deviations appendix. @@ -200,12 +202,25 @@ One class, data-differentiated. - **Binary [T1]:** FUN_0042291c swept real world geometry including movers each tick — a third mech in the flight path could be struck by someone else's missile. -- **Port [T3]:** pool rounds contact only the locked target + terrain; a - mech between shooter and target is flown through (splash alone can touch - bystanders). -- **Verdict:** KNOWN-DIVERGENCE [T3] — largest remaining missile-physics - gap. Field-guide watch item for Friday ("hit by missiles aimed at - someone else" beyond splash range). +- **Port [T3 radius / port-shape, landed 2026-08-13]:** `mech4.cpp + BTUpdateProjectiles` — per frame, damage-carrying GUIDED rounds (the one + salvo lead) run a nearest-approach test of the flight segment against a + VERTICAL CAPSULE per other registered mech (BTGetTargetCandidates walk; + shooter + locked target excluded — the target keeps its own 4.0u fuze). + Capsule derived from the victim's own collision template + (Mover::GetCollisionTemplate BoxedSolid: axis = origin up minY..maxY, + radius = larger horizontal half-extent; fallback 3.5u × 14u when the + template is unresolved [T3]). Earliest contact along the segment wins + (bystander vs target fuze compared by segment param); the struck mech + becomes the DIRECT victim through the unchanged dispatch path (cluster + roll + splash semantics identical, splash excludes the new direct + victim). Wrecks stay in the sweep — binary detonates on any solid; the + #174 victim-side guard keeps dead zones from cascading/scoring. Capped + `[projectile] BYSTANDER` receipt (24 prints) for field forensics. +- **Verdict:** FIXED-THIS-BUILD (radius is a [T3] capsule approximation of + the real solid sweep; T2 field receipts pending — Friday watch item + unchanged: "hit by missiles aimed at someone else" now expected INSIDE + splash range too). ### 13. Missile splash — Explosion::SplashDamage @0042fad0 - **Binary [T1]:** gated on the FIRING player's advancedDamage (+0x264; @@ -368,8 +383,9 @@ Several 'LRM5/10/15' bins author nrk ammo. Announced to the vets in the field guide §9. 3. PPC gauge-scramble rider: BLOCKED on era testimony (field guide §3) — build the visual only after the group answers. -4. Missile bystander sweep: design exists; field guide §6 arms the Friday - watch item. +4. Missile bystander sweep: LANDED 2026-08-13 (lane 12 entry) together + with coast gravity (dt-table row); field guide §6's Friday watch item + now doubles as the T2 verification channel. ## Era questions shipped to the vets @@ -560,7 +576,7 @@ round is a renderer cosmetic with no Mover physics. | **Drag** | `−COD·ρ·sign(v)·v²` per body axis, EVERY frame, burn + coast (@004bef78, power-2 inline; plain-Projectile dead class uses power 1.0 @004bddec); CODs 0.001 [T1 bytes]; terminal `sqrt(T/c)` precomputed @004bf5b4→+0x348 | **OMITTED** ("negligible") | **WAS DIVERGENT → FIXED-THIS-BUILD** | was: LRM 3030 u/s (5.5× cap), −13% time-to-800u, +40% impact speeds, map-crossing runaways past 2.5 s; now governed at 547.7/774.6 | | Speed clamp | none — drag IS the clamp | none — drag now is | **FAITHFUL** (post-fix) | — | | Velocity↔seeker slaving | burning: lateral local vel/accel zeroed each frame ⇒ velocity ≡ speed×nose; nose slerps ≤ MaxThrusterRotationRate·dt (@004be474); at burnout slaving STOPS (ballistic coast) | rotate-toward-aim at port gains 4/8·dt, speed-normalized; steers through coast | KNOWN-DIVERGENCE [T3] (lane 11) | turn dynamics shape; coast homing (real impacts are in-burn) | -| Gravity | none during burn; coast: `worldAccel.y −= 6.5` fresh each frame (env default) + y<−1 kill plane | absent (loft arc + ttl only) | DIVERGENT [T3 **minor**, unfixed] | post-burnout flights only (beyond ~1400u SRM / ~4800u LRM); missed rounds fly level instead of dropping | +| Gravity | none during burn; coast: `worldAccel.y −= 6.5` fresh each frame (env default) + y<−1 kill plane | absent (loft arc + ttl only) | **WAS DIVERGENT → FIXED 2026-08-13** (`vel.y −= 6.5·coast_dt` on thruster rounds after burnout, exact at the burnout boundary; speed re-synced to |vel|) | post-burnout flights only; missed rounds now drop instead of flying level | | Position step | `p += v_old·dt + ½a·dt²` | `p += v_new·dt` (semi-implicit) | FAITHFUL-approx | ≤ ~12u lead over a full SRM burn @60fps; imperceptible | | Expiry | burn+10 s, y<−1 (@004bef78 tail) | same (#168) | **FAITHFUL** | none | | Seeker lead | `aim += targetVel × range/terminal(+0x348)`; loft climb-gated (thruster+0xF0 > _DAT_004bec1c) | live-position re-lead; loft constants exact | KNOWN-DIVERGENCE [T3] (lane 11) | small lead-shape difference | diff --git a/docs/WEAPONS_FIELD_GUIDE.txt b/docs/WEAPONS_FIELD_GUIDE.txt index c2f46f7..ec7c393 100644 --- a/docs/WEAPONS_FIELD_GUIDE.txt +++ b/docs/WEAPONS_FIELD_GUIDE.txt @@ -80,31 +80,32 @@ WE NEED YOUR MEMORY > ----------------------------------------------------------------------------- -2. THE CHARGE GEAR (ALL ENERGY WEAPONS) -- AND THE BRICK THE ARCADE SHIPPED +2. THE CHARGE GEAR (ALL ENERGY WEAPONS) -- SETTLED BY YOUR MEMORY ----------------------------------------------------------------------------- THE ORIGINAL'S BEHAVIOR Veteran/expert pilots could cycle an energy weapon's charge gear - (four settings). The machine shipped with a latent flaw: cycling - PAST the top gear wrapped to the bottom without resetting the firing - state. The stored charge now sat above the new gear's release - window, the output read zero, and the weapon went permanently dark - -- charged, lit, and unable to fire until re-arm. The arcade - shipped this. + (four settings). Cycling past the top wraps to the bottom and the + weapon keeps working: a stored charge above the new gear's level + simply counts as FULL and fires (a wrap-fired top-gear charge deals + its normal top-gear damage -- no secret super-shot, no dead weapon). + + FOR THE RECORD: an earlier version of this document claimed the + machine shipped a flaw here -- that wrapping the gear left the + weapon permanently dark. Several of you said flatly that never + happened. You were right. The re-audit you triggered found our + reading of one number in the original's code was wrong by a single + byte-width, and the "brick" existed only in our port, never on the + machine. This section is your correction, adopted. OUR PORT TODAY - We deliberately rescue it: an overcharged weapon counts as loaded - and fires (#21; 38 rescues proven on the bench). This is the port's - ONE deliberate deviation in the energy lane, and it stays on the - deviations list so nobody mistakes it for the original. + Fixed to match: cycling behaves exactly as you remember. (Before + the fix our gauges could briefly read EMPTY on an over-charge where + the machine read FULL -- if you saw that, it dies in the next + build.) CHANGING IN THE NEXT BUILD - Nothing -- unless the group votes for the authentic brick. - -WE NEED YOUR MEMORY - - Did cycling a laser's gear past the top setting ever leave the - weapon DARK -- charged but refusing to fire until re-arm? If pods - genuinely ate weapons this way, some of you will remember it. - + The correction above ships. The old "deviation" entry is withdrawn + -- the energy lane now has ZERO deviations from the original. YOUR NOTES (callsign first, one entry per line): > diff --git a/game/reconstructed/emitter.cpp b/game/reconstructed/emitter.cpp index b2b1996..06c9558 100644 --- a/game/reconstructed/emitter.cpp +++ b/game/reconstructed/emitter.cpp @@ -24,7 +24,10 @@ // _DAT_004ba820 = 00 00 80 3f = 1.0f (full-charge reference / upper clamp) // _DAT_004ba824 = 0a d7 23 3c = 0.01f (snap-to-1.0 tolerance) // _DAT_004ba828 = 00 00 00 00 = 0.0f (below-range clamp result) -// _DAT_004ba830 = 00 00 00 00 = 0.0f (above-range clamp result) +// _DAT_004ba830 = 00 00 00 00 00 00 f0 3f = DOUBLE 1.0 (above-range clamp result; +// #21 CORRECTION 2026-08-13: this operand is a QWORD -- the old +// "byte-verified 0.0f" dword-read only the LOW 4 bytes (1.0's mantissa +// zeros) of the 8-byte constant. gotcha 30/31 literal-width family.) // _DAT_004ba9a4 = 00 00 00 00 = 0.0f (dischargeTimer expiry threshold) // _DAT_004bac04 = 00 00 80 3f = 1.0f (outputVoltage "fully charged" test) // _DAT_004bac08 = 00 00 00 00 = 0.0f (firing dischargeTimer expiry threshold) @@ -498,21 +501,16 @@ void // generator + calibrated voltageScale/EC; TrackSeekVoltage integrates // the charge over the authored RechargeRate seconds). // - // POD-FRAME SUB-STEPPING (task #11, THE WEAPON-BRICK FIX): the binary's - // Loading tick assumes the pod's LOCKED 60 fps -- the charge integrates - // toward the generator's 10000V and the Loaded transition only fires - // while rechargeLevel crosses the +-0.01 snap window around seekV - // (level in [7920, 8080]; ~15 pod frames wide). Under the port's - // variable dt, one spike frame (dt >= ~0.25s) jumps the whole window: - // the level overshoots, ComputeOutputVoltage's byte-verified >1.0 - // clamp (_DAT_004ba830 = 0.0) zeroes rechargeLevel, and the weapon is - // PERMANENTLY stuck Loading at level ~10000 (observed live: - // "level=9999.68 alarm=3 edge=1"). Fix: run the binary's own + // POD-FRAME SUB-STEPPING (task #11): run the binary's own Loading // tick -- TrackSeekVoltage + ComputeOutputVoltage + the Loaded test -- - // at the binary's own frame rate. This also keeps the I^2R generator - // feed integral accurate (big steps evaluate seekRate at a stale level - // and OVER-heat the generators). Sub-step count is bounded; leftover - // time under extreme throttling just resumes next frame. + // at the binary's locked 60 fps. Justification: the I^2R generator + // feed integral -- TrackSeekVoltage's seekRate^2 heat evaluated over + // big variable-dt steps uses a stale level and OVER-heats the + // generators. (The former "weapon-brick fix" rationale was FALSE -- + // see the #21 correction in ComputeOutputVoltage: the >1.0 clamp + // stores 1.0f, so an overshot charge goes Loaded next tick at any dt.) + // Sub-step count is bounded; leftover time under extreme throttling + // just resumes next frame. { static const Scalar kPodFrame = 1.0f / 60.0f; Scalar remaining = time_slice; @@ -531,32 +529,13 @@ void weaponAlarm.SetLevel(2); // -> Loaded break; } - // OVERCHARGE RESCUE (issue #21, 2026-07-21) -- a DELIBERATE - // divergence from the binary, which DEADLOCKS here: change the - // seek gear while a charge is in flight and the level can land - // ABOVE the new gear's snap window (level/seekV > 1.01) -- the - // byte-verified ComputeOutputVoltage clamp then zeroes - // rechargeLevel (_DAT_004ba830 = 0.0) and the ==1.0 Loaded test - // can never fire again while TrackSeekVoltage keeps charging - // toward the generator voltage: the weapon is PERMANENTLY - // bricked (arc dark, dot dark, never fires -- reproduced - // headless, and trivial to trigger from the clickable seek - // button; the pod's locked 60 fps + rare seek use hid it). - // The arcade's own math says "full == the gear's seek voltage" - // (discharge energy computes from seekV[rec]), so an - // overcharged weapon IS fully charged -- treat it as Loaded. - if (currentLevel > seekVoltage[seekVoltageIndex] - && seekVoltage[seekVoltageIndex] > 0.0f) - { - rechargeLevel = 1.0f; // full (the display/damage ratio) - weaponAlarm.SetLevel(2); // -> Loaded - if (getenv("BT_SEEK_LOG")) - DEBUG_STREAM << "[seek] " << GetName() - << " overcharge rescue: level=" << currentLevel - << " > seekV=" << seekVoltage[seekVoltageIndex] - << " -> Loaded" << std::endl; - break; - } + // The old OVERCHARGE RESCUE (issue #21, 2026-07-21) lived here. + // REMOVED 2026-08-13: it patched an artifact of the + // _DAT_004ba830 width misread (double 1.0 dword-read as float + // 0.0). With the faithful clamp in ComputeOutputVoltage an + // overcharged weapon snaps rechargeLevel to exactly 1.0f, so + // the ==1.0f test above takes it to Loaded naturally -- the + // binary never deadlocked here and neither do we. } } break; @@ -801,7 +780,17 @@ void } else if (rechargeLevel > 1.0f) // _DAT_004ba820 { - rechargeLevel = 0.0f; // _DAT_004ba830 (NB: also 0.0f) + // #21 CORRECTION (2026-08-13, adversarial re-audit, raw-byte disasm): + // _DAT_004ba830 is a DOUBLE 1.0 (bytes 00 00 00 00 00 00 f0 3f), NOT + // float 0.0 -- the old "byte-verified 0.0f" dword-read the LOW HALF of + // the qword (1.0's mantissa zeros; gotcha 30/31 literal-width family). + // Overcharge clamps to EXACTLY 1.0f (0x3f800000 stored), so the ==1.0f + // Loaded test (_DAT_004bac04) passes next tick and the weapon fires. + // The machine NEVER bricked on overcharge; era testimony that no weapon + // ever went permanently dark triggered the re-audit. This faithful + // clamp supersedes the old "overcharge rescue" divergence (removed + // from EmitterSimulation's Loading case). + rechargeLevel = 1.0f; // _DAT_004ba830 (double 1.0, stored as float) } } diff --git a/scratchpad/night17/seek21_bench.sh b/scratchpad/night17/seek21_bench.sh new file mode 100644 index 0000000..94488d1 --- /dev/null +++ b/scratchpad/night17/seek21_bench.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# #21 CORRECTION bench (2026-08-13): the faithful overcharge clamp. +# +# THE OLD SHAPE (21b2bfc, 2026-07-21): 2.5 min of BT_SEEKTEST abuse (dispatches +# the SAME ReceiverDataMessageOf id-0xb press the clickable seek button sends, +# every ~2s) bricked the laser pre-rescue and produced "38 overcharge rescues" +# post-rescue. +# +# THE NEW PASS (post-correction -- _DAT_004ba830 is a DOUBLE 1.0, overcharge +# clamps rechargeLevel to EXACTLY 1.0f): +# 1. gear cycling happens, including WRAPS past top (prev > next skips +# ResetFiringState -- the overcharge composition); +# 2. overcharge compositions observed ([seek] gear-switch lines where the +# in-flight level lands ABOVE the new gear's snap window, level > 1.01*seekV); +# 3. the weapon KEEPS FIRING after the last overcharge (no dark state); +# 4. gauges non-zero: [fire-probe] pct=1 (rechargeLevel, the recharge-dial +# source) seen after the last overcharge; +# 5. ZERO "rescue" prints anywhere in the log (the rescue block is gone). +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 3 +rm -f sk21.log +bt_expert_egg MP.EGG SK21.EGG +sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; s/^vehicle=.*/vehicle=vulture/" SK21.EGG + +( export BT_SEEKTEST=1 BT_AUTOFIRE=1 BT_FIRE_AT_ICON=1 + export BT_SEEK_LOG=1 BT_FIRE_LOG=1 BT_DMG_LOG=1 + bt_launch sk21.log SK21.EGG 0x03 ) +sleep 160 +bt_kill_ours; sleep 2; taskkill //F //IM btl4.exe >/dev/null 2>&1 + +python /c/git/bt411/scratchpad/night17/seek21_check.py /c/git/bt411/content/sk21.log diff --git a/scratchpad/night17/seek21_check.py b/scratchpad/night17/seek21_check.py new file mode 100644 index 0000000..94547b5 --- /dev/null +++ b/scratchpad/night17/seek21_check.py @@ -0,0 +1,68 @@ +# #21 correction bench checker -- see seek21_bench.sh for the PASS contract. +import io, re, sys + +path = sys.argv[1] if len(sys.argv) > 1 else "/c/git/bt411/content/sk21.log" +L = io.open(path, encoding="latin-1", errors="replace").read().splitlines() + +# Emitter gear-switch lines (Myomers [seek] lines lack "seekV["): +# [seek] NAME -> gear N seekV[N]=X table={...} max=M level=L genV=... +gear_re = re.compile( + r"\[seek\] (\S+) -> gear (\d+) seekV\[\d+\]=([-\d.eE+]+) .*level=([-\d.eE+]+)") +gears = [] # (line_idx, name, gear, seekV, level) +for i, l in enumerate(L): + m = gear_re.search(l) + if m: + gears.append((i, m.group(1), int(m.group(2)), + float(m.group(3)), float(m.group(4)))) + +names = sorted(set(g[1] for g in gears)) +print("=== #21 CORRECTION BENCH (faithful overcharge clamp) ===") +print("weapon(s) under seek cycling:", names or "NONE") + +ok = True +if not gears: + print("FAIL: no emitter [seek] gear lines -- rig broken (expert gate? seektest?)") + ok = False + +for name in names: + G = [g for g in gears if g[1] == name] + wraps = sum(1 for a, b in zip(G, G[1:]) if b[2] < a[2]) + over = [g for g in G if g[3] > 0.0 and g[4] > 1.01 * g[3]] + fired_all = [i for i, l in enumerate(L) if ("FIRED '%s'" % name) in l] + last_over = over[-1][0] if over else None + fired_after = ([i for i in fired_all if i > last_over] + if last_over is not None else []) + # gauge source: [fire-probe] NAME ... pct=1 after the last overcharge + pct1_after = 0 + if last_over is not None: + pr = re.compile(r"\[fire-probe\] %s .*pct=([-\d.eE+]+)" % re.escape(name)) + for l in L[last_over:]: + m = pr.search(l) + if m and float(m.group(1)) >= 0.999: + pct1_after += 1 + # tail liveness: still firing in the last 25% of the run + tail = int(len(L) * 0.75) + fired_tail = sum(1 for i in fired_all if i >= tail) + + print("-- %s: gearSwitches=%d wraps=%d overchargeEvents=%d " + "FIRED total=%d afterLastOver=%d pct=1 probes after=%d tailFIRED=%d" + % (name, len(G), wraps, len(over), len(fired_all), + len(fired_after), pct1_after, fired_tail)) + + if wraps < 1: + ok = False; print("FAIL(%s): no wrap past top gear" % name) + if not over: + ok = False; print("FAIL(%s): no overcharge composition ever formed" % name) + if last_over is not None and not fired_after: + ok = False; print("FAIL(%s): NEVER FIRED after the last overcharge -- dark state" % name) + if last_over is not None and pct1_after < 1: + ok = False; print("FAIL(%s): recharge gauge (pct) never read full after overcharge" % name) + if fired_tail < 1: + ok = False; print("FAIL(%s): not firing in the run tail -- possible late brick" % name) + +rescues = sum(1 for l in L if "rescue" in l) +print("rescue prints in log:", rescues) +if rescues != 0: + ok = False; print("FAIL: rescue print still present") + +print("RESULT:", "PASS" if ok else "FAIL")