#154 settled: the generator-out warning was authored on the WRONG EDGE in the shipped 1995 content -- BTL4.RES keys it (trigState=4, inverse=1, Start) = fire on LEAVING GeneratorOut, so a thermal trip was SILENT and the klaxon played at recovery/respawn (live-benched both ways; decode triple-verified: T0 read order, binary @0043e7d4/@0043eb40 semantics, independent byte parse; 144-row census = the game's ONLY inverse-Start fault trigger). DELIBERATE DEVIATION (user-approved, the port's only content override): AudioStateTrigger ctor flips it to enter-4 at stream time -- warning at the TRIP, silent at recovery + the respawn 4->0 edge; BT_GEN_WARN_SHIPPED=1 restores shipped. Threshold audit closes AUTHENTIC: recovery at heatAlarm NormalHeat = T<=1000 vs trip at 2000 = exactly the manual p23 half-cooled rule, and the heat bar's red fill maps to it to the tick. Era-witness reconciliation: BT 4.10 shipped NO speech samples (full AUDIO1/2.RES census) -- the remembered voice at trip is Firestorm's. Rig: BT_GEN_HEAT injection + BT_AUDIO_EDGE receipts, gen_edge_bench.sh PASS (4 fixups, trip FIRED, recovery 0). KB: decomp-reference generator section + no-speech note; manual p23 also answered #133 (X = crit-destroyed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
e2ab17665d
commit
08fc87ec6e
@@ -475,6 +475,46 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
|
|||||||
recomputes output via `(1 − damage) × rated` and zeroes it. stateAlarm 4's
|
recomputes output via `(1 − damage) × rated` and zeroes it. stateAlarm 4's
|
||||||
producer = the THERMAL BREAKER in GeneratorSimulation itself; a 2026-08-03
|
producer = the THERMAL BREAKER in GeneratorSimulation itself; a 2026-08-03
|
||||||
destruction→state-4 bridge was removed as unfounded.
|
destruction→state-4 bridge was removed as unfounded.
|
||||||
|
- **Generator trip/recovery + the warning edge (#154 audit, 2026-08-11, all [T1] unless
|
||||||
|
noted).** Trip: heatAlarm==FailureHeat(2) → stateAlarm 4, i.e. T > FailureTemperature;
|
||||||
|
recovery gate (case-4 body @4b1f7c) = heatAlarm back to **NormalHeat(0)**, i.e.
|
||||||
|
T ≤ DegradationTemperature — then 4→0 Starting → StartTime spin-up → 0→2 Ready.
|
||||||
|
Authored generator temps (raw BTL4.RES): start=77, degradation=**1000**,
|
||||||
|
failure=**2000** on all six vehicles — recovery at exactly HALF the trip line;
|
||||||
|
manual p23 prints the half-way rule verbatim, and the heat bar's colored fill
|
||||||
|
(VertTwoPartBar @4c48fc) exists exactly while T > 1000, so "red gone" == the
|
||||||
|
recovery gate to the tick. NO hysteresis in the heatAlarm ladder (@4ad924).
|
||||||
|
**The warning edge is authored backwards in the shipped content**: BTL4.RES
|
||||||
|
streams exactly TWO AudioStateTriggers per generator on GeneratorState —
|
||||||
|
(trigState=1 Idle, inv=0, Start) and (trigState=4 GeneratorOut, **inv=1**, Start,
|
||||||
|
= fire on LEAVING 4). No enter-4 trigger exists (RES-wide census: 144/144 rows,
|
||||||
|
triple-verified decode + live [audioedge] capture). Consequences, ALL live-benched
|
||||||
|
(`scratchpad/night15/gen_edge_bench.sh`, receipts BT_AUDIO_EDGE/[audioedge] +
|
||||||
|
BT_GEN_HEAT injection): thermal trip 2→4 SILENT; natural recovery 4→0 FIRES the
|
||||||
|
warning; respawn-with-tripped-gen RTIS (4,0) FIRES it (ghost warning during
|
||||||
|
respawn); crit-death fires enter-Idle one frame late; 4→1 (cooled while switched
|
||||||
|
off) double-fires. The generators' inv+Start is the ONLY inverse-Start fault
|
||||||
|
trigger in the game (census) — a 1995 authoring slip [T4 intent]. Port machinery
|
||||||
|
is faithful end-to-end. **DELIBERATE DEVIATION shipped 2026-08-11 (user-approved):**
|
||||||
|
the AudioStateTrigger ctor (AUDWTHR.cpp) flips the GeneratorState (trigState=4,
|
||||||
|
inv=1) record to inv=0 at stream time — warning now fires at the TRIP, silent at
|
||||||
|
recovery/respawn; `BT_GEN_WARN_SHIPPED=1` restores shipped behavior. This is the
|
||||||
|
port's ONLY authored-content override (precedent: the self-hit score deviation).
|
||||||
|
**Why the era witnesses remember a VOICE at trip: BT 4.10 shipped NO speech
|
||||||
|
samples at all** [T1 — full sample census of content/AUDIO/*.wav (the cracked
|
||||||
|
AUDIO1/2.RES set): warnings are klaxon zones only; the exe has no "generator
|
||||||
|
out"/.WAV/.VOC strings]. The spoken "Warning, generator out" they recall is
|
||||||
|
FIRESTORM's speech system (Tesla II) — memory bleed from the later game. The
|
||||||
|
manual's "spoken MechTech warnings" (p14) describe the klaxon zones
|
||||||
|
aspirationally.
|
||||||
|
Voice-steal note: night-15 field logs show up to 932 channel steals/session — even
|
||||||
|
fired Starts can be eaten at combat peak (L4AUDRND weight-steal).
|
||||||
|
Diag hooks: BT_GEN_HEAT=<units/s> (+_DELAY/_SECS) injects into viewpoint
|
||||||
|
GeneratorA (thermalMass=726000 raw units — rate ~1.5e8 for ~18-25 s trips it);
|
||||||
|
BT_AUDIO_EDGE=1 prints every state-edge trigger delivery FIRED/no-fire.
|
||||||
|
Related corrections: AmmoState 3 = EJECTING (an earlier lane's "cook-off enter-3"
|
||||||
|
label was wrong — cook-off warning rides FireCountdownStarted matchers); manual
|
||||||
|
p23 also answers #133: the MFD X = generator destroyed by CRIT, not overheat.
|
||||||
- **THE CONDITION FLAGS ARE ROUTINE, NOT ALARMS — field-verified 2026-08-08 [T2]:** a
|
- **THE CONDITION FLAGS ARE ROUTINE, NOT ALARMS — field-verified 2026-08-08 [T2]:** a
|
||||||
`[techstat] <sub> condition <n> SET` is an *operating* flag ("this component is above
|
`[techstat] <sub> condition <n> SET` is an *operating* flag ("this component is above
|
||||||
nominal / browned out RIGHT NOW"), not a fault report. Census over a full Steam match
|
nominal / browned out RIGHT NOW"), not a fault report. Census over a full Steam match
|
||||||
|
|||||||
@@ -102,7 +102,10 @@ Making a base byte-exact GROWS every subclass — they must be re-based TOGETHER
|
|||||||
(OwnerBaseSpeed reads the real base instead of the 1.0 stub).
|
(OwnerBaseSpeed reads the real base instead of the 1.0 stub).
|
||||||
5. **Electrical sourcing — the dial's REAL stake is TORSO TWIST.** A gear's voltage must be
|
5. **Electrical sourcing — the dial's REAL stake is TORSO TWIST.** A gear's voltage must be
|
||||||
<= the generator's measured output (`(1 − generator damage) × rated`, thermal breaker on
|
<= the generator's measured output (`(1 − generator damage) × rated`, thermal breaker on
|
||||||
FailureHeat; there is NO load model — demand never pulls the bus down), or the myomers leave
|
FailureHeat with recovery gated on NormalHeat — trip at T>2000, re-arm at T≤1000, exactly
|
||||||
|
the manual p23 "cooled down half way" rule; full trip/recovery/warning-edge audit in
|
||||||
|
[[decomp-reference]] §Generator trip/recovery (#154); there is NO load model — demand
|
||||||
|
never pulls the bus down), or the myomers leave
|
||||||
Ready. The Torso is a PowerWatcher on the myomers: `TorsoSimulation` ZEROES the twist rate
|
Ready. The Torso is a PowerWatcher on the myomers: `TorsoSimulation` ZEROES the twist rate
|
||||||
while the watched myomers are not Ready (halves it at DegradationHeat) — so an over-high gear
|
while the watched myomers are not Ready (halves it at DegradationHeat) — so an over-high gear
|
||||||
on a damaged generator freezes the pilot's AIM until they downshift. On a healthy mech,
|
on a damaged generator freezes the pilot's AIM until they downshift. On a healthy mech,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
#include "munga.h"
|
#include "munga.h"
|
||||||
#pragma hdrstop
|
#pragma hdrstop
|
||||||
|
|
||||||
@@ -978,6 +979,31 @@ AudioStateTrigger::AudioStateTrigger(
|
|||||||
MemoryStream_Read(stream, &excludeTransition);
|
MemoryStream_Read(stream, &excludeTransition);
|
||||||
MemoryStream_Read(stream, &excludeState);
|
MemoryStream_Read(stream, &excludeState);
|
||||||
|
|
||||||
|
// #154 DELIBERATE DEVIATION from shipped 1995 content (the ONLY authored-
|
||||||
|
// content override in the port -- revert with BT_GEN_WARN_SHIPPED=1).
|
||||||
|
// BTL4.RES authors the generator-out warning as (trigState=4, inverse=1,
|
||||||
|
// Start) = fire on LEAVING GeneratorOut -- so a thermal trip was SILENT and
|
||||||
|
// the klaxon played at RECOVERY/respawn instead (live-benched,
|
||||||
|
// gen_edge_bench.sh; bytes @0x2bf13, decode triple-verified). Every other
|
||||||
|
// fault alarm in the game is authored enter-state, inverse is otherwise
|
||||||
|
// only a Stop terminator (144-row census), and the manual (p14/p23) lists
|
||||||
|
// this as a loss-time warning: a one-bit 1995 authoring slip. Flip it to
|
||||||
|
// enter-4 so the warning speaks at the TRIP, stays silent at recovery and
|
||||||
|
// at the respawn reset's 4->0 edge. (Testers' "voice at trip" memories are
|
||||||
|
// Firestorm's speech system -- BT 4.10 shipped no speech samples at all.)
|
||||||
|
{
|
||||||
|
extern char g_lastBoundAttrName[64];
|
||||||
|
if (triggerState == 4 && inverseTrigger != 0
|
||||||
|
&& strcmp(g_lastBoundAttrName, "GeneratorState") == 0
|
||||||
|
&& getenv("BT_GEN_WARN_SHIPPED") == 0)
|
||||||
|
{
|
||||||
|
inverseTrigger = 0;
|
||||||
|
if (getenv("BT_ATTRBIND_LOG"))
|
||||||
|
DEBUG_STREAM << "[statecfg] #154 fixup: GeneratorState leave-4"
|
||||||
|
" Start -> enter-4 Start\n" << std::flush;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (getenv("BT_ATTRBIND_LOG")) { static int s_stc=0; if (s_stc++<120)
|
if (getenv("BT_ATTRBIND_LOG")) { static int s_stc=0; if (s_stc++<120)
|
||||||
DEBUG_STREAM << "[statecfg] attrPtr=" << (void*)attributePointer
|
DEBUG_STREAM << "[statecfg] attrPtr=" << (void*)attributePointer
|
||||||
<< " comp=" << (void*)audioComponentSocket.GetCurrent()
|
<< " comp=" << (void*)audioComponentSocket.GetCurrent()
|
||||||
@@ -1076,10 +1102,21 @@ void
|
|||||||
//
|
//
|
||||||
// Check for the trigger state
|
// Check for the trigger state
|
||||||
//
|
//
|
||||||
if (
|
const int bt_fires =
|
||||||
(!inverseTrigger && new_state == triggerState) ||
|
((!inverseTrigger && new_state == triggerState) ||
|
||||||
(inverseTrigger && old_state == triggerState)
|
(inverseTrigger && old_state == triggerState));
|
||||||
)
|
// #154 diag (BT_AUDIO_EDGE=1): EVERY state-edge delivery to EVERY authored
|
||||||
|
// trigger, fired or not -- state edges are rare, so this is low-volume.
|
||||||
|
// Correlate with the [gen] TRIPPED/RESTARTING lines to observe which edge
|
||||||
|
// actually starts a warning sequence.
|
||||||
|
if (getenv("BT_AUDIO_EDGE"))
|
||||||
|
DEBUG_STREAM << "[audioedge] old=" << old_state << " new=" << new_state
|
||||||
|
<< " trig=" << triggerState << " inv=" << (int)inverseTrigger
|
||||||
|
<< " ctl=" << (int)controlID << "/" << controlValue
|
||||||
|
<< " comp=" << (void*)audioComponentSocket.GetCurrent()
|
||||||
|
<< (bt_fires ? " FIRED" : " no-fire")
|
||||||
|
<< "\n" << std::flush;
|
||||||
|
if (bt_fires)
|
||||||
{
|
{
|
||||||
Check(&audioComponentSocket);
|
Check(&audioComponentSocket);
|
||||||
Check(audioComponentSocket.GetCurrent());
|
Check(audioComponentSocket.GetCurrent());
|
||||||
|
|||||||
@@ -96,6 +96,19 @@ AttributeWatcher::AttributeWatcher(
|
|||||||
|
|
||||||
attributePointer = simulation->GetAttributePointer(attribute_name);
|
attributePointer = simulation->GetAttributePointer(attribute_name);
|
||||||
|
|
||||||
|
// The bound attribute's NAME, visible to derived ctors (the stream layout
|
||||||
|
// puts the trigger fields AFTER the base bind, so a derived ctor that needs
|
||||||
|
// to know WHICH attribute it just bound -- the #154 GeneratorState fixup --
|
||||||
|
// reads it from here). Valid only during construction.
|
||||||
|
{
|
||||||
|
extern char g_lastBoundAttrName[64];
|
||||||
|
const char *an = (const char *)attribute_name;
|
||||||
|
int i = 0;
|
||||||
|
for (; an != 0 && an[i] != '\0' && i < 63; ++i)
|
||||||
|
g_lastBoundAttrName[i] = an[i];
|
||||||
|
g_lastBoundAttrName[i] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
if (getenv("BT_ATTRBIND_LOG"))
|
if (getenv("BT_ATTRBIND_LOG"))
|
||||||
{
|
{
|
||||||
extern int g_curAudioWatcherClass;
|
extern int g_curAudioWatcherClass;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ ALuint *g_buffers;
|
|||||||
int g_numBuffers;
|
int g_numBuffers;
|
||||||
const char *g_bufferNames[512]; // bufferInd -> sample file (diag: the live-playing dump)
|
const char *g_bufferNames[512]; // bufferInd -> sample file (diag: the live-playing dump)
|
||||||
int g_curAudioWatcherClass = 0;
|
int g_curAudioWatcherClass = 0;
|
||||||
|
char g_lastBoundAttrName[64] = {0}; // AttributeWatcher bind name, for derived-ctor fixups (#154)
|
||||||
|
|
||||||
//
|
//
|
||||||
// Minimal canonical-PCM WAV reader. The repo's libsndfile-1.dll is a STUB
|
// Minimal canonical-PCM WAV reader. The repo's libsndfile-1.dll is a STUB
|
||||||
|
|||||||
@@ -1295,6 +1295,49 @@ void
|
|||||||
void
|
void
|
||||||
Generator::GeneratorSimulation(Scalar time_slice)
|
Generator::GeneratorSimulation(Scalar time_slice)
|
||||||
{
|
{
|
||||||
|
// DEV: BT_GEN_HEAT=<kJ/s> pumps heat into the VIEWPOINT mech's Generator A
|
||||||
|
// for a bounded window (BT_GEN_HEAT_DELAY=<s> lead-in, default 10;
|
||||||
|
// BT_GEN_HEAT_SECS=<s> injection length, default 20) -- the deterministic
|
||||||
|
// thermal-trip bench for #154: drive T past FailureTemperature, stop, let
|
||||||
|
// it cool through DegradationTemperature, and correlate the [gen]
|
||||||
|
// TRIPPED/RESTARTING receipts against the [audioedge] receipts to observe
|
||||||
|
// which stateAlarm edge actually starts the warning sequence.
|
||||||
|
{
|
||||||
|
static const char *s_ghEnv = getenv("BT_GEN_HEAT");
|
||||||
|
if (s_ghEnv != 0)
|
||||||
|
{
|
||||||
|
static int s_ghSaid = 0;
|
||||||
|
if (s_ghSaid < 8)
|
||||||
|
{
|
||||||
|
++s_ghSaid;
|
||||||
|
DEBUG_STREAM << "[genheat] gate: gen#" << generatorNumber
|
||||||
|
<< " owner=" << (void *)owner
|
||||||
|
<< " viewpoint=" << (void *)application->GetViewpointEntity()
|
||||||
|
<< " T=" << currentTemperature
|
||||||
|
<< " mass=" << thermalMass << "\n" << std::flush;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s_ghEnv != 0 && generatorNumber == 1
|
||||||
|
&& (Entity *)owner == application->GetViewpointEntity())
|
||||||
|
{
|
||||||
|
static float s_ghClock = 0.0f;
|
||||||
|
static float s_ghDelay = -1.0f, s_ghSecs = -1.0f;
|
||||||
|
if (s_ghDelay < 0.0f)
|
||||||
|
{
|
||||||
|
const char *d = getenv("BT_GEN_HEAT_DELAY");
|
||||||
|
const char *s = getenv("BT_GEN_HEAT_SECS");
|
||||||
|
s_ghDelay = (d != 0 && *d != '\0') ? (float)atof(d) : 10.0f;
|
||||||
|
s_ghSecs = (s != 0 && *s != '\0') ? (float)atof(s) : 20.0f;
|
||||||
|
}
|
||||||
|
s_ghClock += time_slice;
|
||||||
|
if (s_ghClock >= s_ghDelay && s_ghClock < s_ghDelay + s_ghSecs)
|
||||||
|
{
|
||||||
|
heatEnergy += (Scalar)atof(s_ghEnv) * time_slice;
|
||||||
|
currentTemperature = heatEnergy / thermalMass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HeatSink::HeatSinkSimulation(time_slice); // FUN_004ad924
|
HeatSink::HeatSinkSimulation(time_slice); // FUN_004ad924
|
||||||
|
|
||||||
if (simulationState == 1) // this[0x10] @0x40
|
if (simulationState == 1) // this[0x10] @0x40
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# #154: OBSERVE (not infer) which generator stateAlarm edge starts the warning
|
||||||
|
# sequence. BT_GEN_HEAT drives the viewpoint mech's Generator A over
|
||||||
|
# FailureTemperature (trip), then the injection window closes and it cools
|
||||||
|
# through DegradationTemperature (recovery). Receipts:
|
||||||
|
# [gen] GeneratorA TRIPPED/RESTARTING (BT_HEAT_LOG, powersub.cpp)
|
||||||
|
# [audioedge] old= new= trig= inv= ... FIRED/no-fire (BT_AUDIO_EDGE, AUDWTHR.cpp)
|
||||||
|
# [statecfg] authored trigger table + the #154 fixup line (BT_ATTRBIND_LOG)
|
||||||
|
# SHIPPED content: trip (2->4) silent, recovery (4->0) fires (captured 2026-08-11).
|
||||||
|
# WITH the #154 deviation (default): trip FIRES the enter-4 Start, recovery and
|
||||||
|
# the respawn 4->0 edge are silent. PASS = trip-edge fires>=1, old=4 fires==0.
|
||||||
|
# (BT_GEN_WARN_SHIPPED=1 restores shipped behavior; bench asserts the deviation.)
|
||||||
|
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 genedge.log
|
||||||
|
bt_expert_egg MP.EGG GEN.EGG
|
||||||
|
sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; s/^vehicle=.*/vehicle=madcat/" GEN.EGG
|
||||||
|
|
||||||
|
( export BT_GEN_HEAT=150000000 BT_GEN_HEAT_DELAY=15 BT_GEN_HEAT_SECS=18
|
||||||
|
export BT_HEAT_LOG=1 BT_AUDIO_EDGE=1 BT_ATTRBIND_LOG=1
|
||||||
|
bt_launch genedge.log GEN.EGG 0x03 -egg GEN.EGG )
|
||||||
|
sleep 300
|
||||||
|
bt_kill_ours; sleep 2; taskkill //F //IM btl4.exe >/dev/null 2>&1
|
||||||
|
|
||||||
|
python - <<'PY'
|
||||||
|
import io, re
|
||||||
|
txt = io.open("genedge.log", encoding="latin-1", errors="replace").read()
|
||||||
|
lines = txt.splitlines()
|
||||||
|
trip = [i for i,l in enumerate(lines) if "TRIPPED" in l and "GeneratorA" in l]
|
||||||
|
rest = [i for i,l in enumerate(lines) if "RESTARTING" in l and "GeneratorA" in l]
|
||||||
|
print("=== #154 LIVE EDGE OBSERVATION ===")
|
||||||
|
print("GeneratorA TRIPPED lines :", len(trip))
|
||||||
|
print("GeneratorA RESTARTING lines:", len(rest))
|
||||||
|
def edges_near(idx, span=30):
|
||||||
|
lo, hi = max(0, idx-span), min(len(lines), idx+span)
|
||||||
|
return [l for l in lines[lo:hi] if "[audioedge]" in l]
|
||||||
|
if trip:
|
||||||
|
print("--- [audioedge] around FIRST TRIP (line %d) ---" % trip[0])
|
||||||
|
for l in edges_near(trip[0]): print(" ", l.strip()[:120])
|
||||||
|
if rest:
|
||||||
|
print("--- [audioedge] around FIRST RESTART (line %d) ---" % rest[0])
|
||||||
|
for l in edges_near(rest[0]): print(" ", l.strip()[:120])
|
||||||
|
# the authored generator trigger table
|
||||||
|
cfg = [l for l in lines if "statecfg" in l and ("GeneratorState" in l or "Generator" in l)]
|
||||||
|
print("--- [statecfg] generator rows (%d) ---" % len(cfg))
|
||||||
|
for l in cfg[:10]: print(" ", l.strip()[:140])
|
||||||
|
# verdict material: any FIRED edge with new=4 (trip-edge start)?
|
||||||
|
fired = [l for l in lines if "[audioedge]" in l and "FIRED" in l]
|
||||||
|
trip_fired = [l for l in fired if re.search(r"old=\d+ new=4 ", l)]
|
||||||
|
rec_fired = [l for l in fired if re.search(r"old=4 new=\d+ ", l)]
|
||||||
|
print("FIRED with new=4 (trip) :", len(trip_fired))
|
||||||
|
print("FIRED with old=4 (recovery):", len(rec_fired))
|
||||||
|
fixup = sum(1 for l in lines if "#154 fixup" in l)
|
||||||
|
print("fixup receipts :", fixup)
|
||||||
|
if not trip:
|
||||||
|
print("VERDICT: INCONCLUSIVE -- no trip occurred (raise BT_GEN_HEAT)")
|
||||||
|
elif not rest:
|
||||||
|
print("VERDICT: PARTIAL -- tripped but no recovery in window (lengthen run / check cooling)")
|
||||||
|
elif fixup >= 1 and len(trip_fired) >= 1 and len(rec_fired) == 0:
|
||||||
|
print("VERDICT: PASS -- #154 deviation live: warning at TRIP, silent at recovery")
|
||||||
|
else:
|
||||||
|
print("VERDICT: FAIL -- trip-edge fires=%d (want >=1), recovery-edge fires=%d (want 0), fixups=%d"
|
||||||
|
% (len(trip_fired), len(rec_fired), fixup))
|
||||||
|
PY
|
||||||
Reference in New Issue
Block a user