BT410 5.3.127-128: the mech can stand on its heels, and it knows when it is about to fall over -- the super stop, the instability model and the telemetry rings

THE MODEL RESOURCE NAMED THESE BLOCKS.  The ctor loads (part_012.c:15798)
tie the master perf's anonymous offsets to field names our
Mech__ModelResource already carried, and the names confirm the arithmetic
as surely as the arithmetic confirms the names:

  rec+0x48 -> +0x5bc  superStopAcceleration
  rec+0x80 -> +0x784  maxUnstableAcceleration
  rec+0x84 -> +0x788  unstableAccelerationEffect
  rec+0x88 -> +0x78c  unstableGunTheEngineEffect
  rec+0x94 -> +0x798  unstableStopedTurnEffect

THE SUPER STOP (@0x4aa353): hauling the throttle negative while the mech
still rolls forward is a hard brake, not a reverse.  The edge swaps the
gait's acceleration rate to the authored superStopAcceleration, marks the
update, and throws ONE random-signed lurch through both gyro channels;
while it holds above a walk it repeats a pitch kick every 0.4s -- the
shudder of a hundred tons standing on its heels.  A chassis authoring
-1.1 disables the system entirely (the sentinel must survive the model
read unguarded).

THE INSTABILITY MODEL (tail @0x4aab8e): three weighted terms -- filtered
acceleration (squared), gunning the engine, and turning on the spot --
clamped to 1 into unstablePercentage, which was ALREADY a published
cockpit attribute holding a staged zero, and republished to the gyro
(whose tip/sway machinery @004b275c consumes it; that's the next gyro
brick).

THE TELEMETRY RINGS (@0x4aafba): the binary's five rings ARE the engine's
AverageOf<Scalar>, and our telemetryFilter[5] has been sized 15 and unfed
since the layout wave.  FUN_0043ae47 is CalculateOlympicAverage -- a
judge-style trimmed mean, min and max discarded.  Two oddities kept
verbatim and flagged: the vel.y ring is never reduced, and the derived
acceleration's components come from a mongrel basis (only its length is
consumed).

CORRECTION to 5.3.122: 'realMaxSpeed' was a duplicate member for
mech+0x7a0, which our tree already carried as runSpeedMax; merged.  And
the +0x7a4 'computed my own cap' flag is set by the ctor's MYOMERS sweep
-- a mech with myomers broadcasts its cap, one without adopts the
streamed value.

Soaks (pod_render_superstop / pod_render_telem, BT_SUPERSTOP_SOAK cycling
+0.8 for 6s then -0.5 for 4s): the super stop engages on the correct edge
(speed 35.6, demand -28.0) and releases cleanly, repeatedly; the rings
fill and the instability sweeps its whole range to the 1.0 clamp; zero
faults.  The new gauge immediately earned its keep as a diagnostic -- it
pins high while walking because the mech demands 35.9 u/s and the
reconstructed gait delivers 7.0, which is now a measurable open question
rather than an invisible one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-10 00:46:25 -05:00
co-authored by Claude Fable 5
parent e682f64acb
commit 79aba77309
8 changed files with 694 additions and 17 deletions
@@ -0,0 +1,86 @@
# pod_render_norio.conf -- pod_render_rec with the RIO serial port OFF.
#
# The emulator log shows a steady stream of serial1 RX OVERRUN errors on
# the RIO pipe, and controls post their events at HighEventPriority
# UNCONDITIONALLY (CONTROLS.HPP:250) -- so a chattering RIO port would
# flood a priority the background pump always serves first, starving the
# priority-0 renderer events the load gate waits on. This conf tests that
# by removing the port entirely. Everything else is identical to the rec
# conf, so a launch here and a hang there isolates the RIO.
#
[sdl]
output=opengl
# higher,higher not highest: HIGH_PRIORITY_CLASS starved the host desktop;
# with the retry patches a rare dropout self-recovers (see gauge_rio.conf).
priority=higher,higher
[dosbox]
memsize=32
machine=svga_s3
[cpu]
core=dynamic
cputype=pentium
cycles=max
[sblaster]
sbtype=sb16
sbbase=220
irq=5
dma=1
hdma=5
[mixer]
# match the EMU8000s' native rate (no resample) and buffer ~60ms so brief
# emulation-thread stalls (RIO retry recovery) don't audibly chop
rate=44100
blocksize=1024
prebuffer=60
[serial]
# RIO on COM1 with the low-latency options (rxpollus/rxburst) so the board's
# few-ms ACK deadline is met; plasma display on COM2 (real pod has both).
# VWE fork namedpipe backend (com0com/realport retired -- COM1/COM2 gone):
# DOSBox = pipe client (retry), vRIO/vPLASMA apps = servers; an unconnected
# pipe behaves as an unplugged cable so the mission still runs. serialnamedpipe.h
serial1=disabled
serial2=namedpipe pipe:vplasma
# live UNBUFFERED game output: DOS char devices are not buffered, so
# redirecting stdout to COM3 lands every line immediately. A normal
# '> file' redirect stays 0 bytes until the process exits, which hides
# all progress on a run that does NOT crash.
serial3=file file:C:\VWE\TeslaRel410\emulator\render-bridge\podlog.txt
[autoexec]
mount c "C:\VWE\TeslaRel410\ALPHA_1"
c:
cd \REL410\BT
set VIDEOFORMAT=svga
rem production pod card init (PARAMETR.BAT:181-186): DIAGNOSE + AWEUTIL per
rem card -- AWEUTIL /S does the EMU8000 bring-up and DRAM detect the HMI SOS
rem driver relies on; skipping it left the cards uninitialized (silent).
rem aweutil /s SKIPPED for now: it verifies the AWE32 GM ROM, which the
rem emulated cards lack (hangs in a retry loop) -- restore once the ROM is
rem dumped from a real card. diagnose /s kept (passes, sets mixer config).
set BLASTER=A220 I5 D1 H5 P330 T6
c:\sb16\diagnose /s
set BLASTER=A240 I7 D3 H6 P300 T6
c:\sb16\diagnose /s
set BLASTER=A220 I5 D1 H5 P330 T6
set TEMP=c:\
rem arena1 city mission (TESTARN.EGG: map=arena1, time=day) with the RIO
rem attached; stdout redirected so mission-load progress survives kills.
set BT_JOINTS=1
set L4VIEWEXT=1
set BT_STACK_LOG=1
set BT_MECH_LOG=1
set BT_SUPERSTOP_SOAK=1
set BT_GAUGE_LOG=1
set BT_FORCE_TURN=0
set HEAPSIZE=15000000
set L4GAUGE=640x480x16
call setenv.bat r s n p
32rtm.exe -x
BTL4REC.EXE -egg testarn.egg > COM3
echo GAME-RC=%errorlevel% >> RC.TXT
32rtm.exe -u
echo ALPHA1-RUN-DONE
pause
@@ -0,0 +1,87 @@
# pod_render_norio.conf -- pod_render_rec with the RIO serial port OFF.
#
# The emulator log shows a steady stream of serial1 RX OVERRUN errors on
# the RIO pipe, and controls post their events at HighEventPriority
# UNCONDITIONALLY (CONTROLS.HPP:250) -- so a chattering RIO port would
# flood a priority the background pump always serves first, starving the
# priority-0 renderer events the load gate waits on. This conf tests that
# by removing the port entirely. Everything else is identical to the rec
# conf, so a launch here and a hang there isolates the RIO.
#
[sdl]
output=opengl
# higher,higher not highest: HIGH_PRIORITY_CLASS starved the host desktop;
# with the retry patches a rare dropout self-recovers (see gauge_rio.conf).
priority=higher,higher
[dosbox]
memsize=32
machine=svga_s3
[cpu]
core=dynamic
cputype=pentium
cycles=max
[sblaster]
sbtype=sb16
sbbase=220
irq=5
dma=1
hdma=5
[mixer]
# match the EMU8000s' native rate (no resample) and buffer ~60ms so brief
# emulation-thread stalls (RIO retry recovery) don't audibly chop
rate=44100
blocksize=1024
prebuffer=60
[serial]
# RIO on COM1 with the low-latency options (rxpollus/rxburst) so the board's
# few-ms ACK deadline is met; plasma display on COM2 (real pod has both).
# VWE fork namedpipe backend (com0com/realport retired -- COM1/COM2 gone):
# DOSBox = pipe client (retry), vRIO/vPLASMA apps = servers; an unconnected
# pipe behaves as an unplugged cable so the mission still runs. serialnamedpipe.h
serial1=disabled
serial2=namedpipe pipe:vplasma
# live UNBUFFERED game output: DOS char devices are not buffered, so
# redirecting stdout to COM3 lands every line immediately. A normal
# '> file' redirect stays 0 bytes until the process exits, which hides
# all progress on a run that does NOT crash.
serial3=file file:C:\VWE\TeslaRel410\emulator\render-bridge\podlog.txt
[autoexec]
mount c "C:\VWE\TeslaRel410\ALPHA_1"
c:
cd \REL410\BT
set VIDEOFORMAT=svga
rem production pod card init (PARAMETR.BAT:181-186): DIAGNOSE + AWEUTIL per
rem card -- AWEUTIL /S does the EMU8000 bring-up and DRAM detect the HMI SOS
rem driver relies on; skipping it left the cards uninitialized (silent).
rem aweutil /s SKIPPED for now: it verifies the AWE32 GM ROM, which the
rem emulated cards lack (hangs in a retry loop) -- restore once the ROM is
rem dumped from a real card. diagnose /s kept (passes, sets mixer config).
set BLASTER=A220 I5 D1 H5 P330 T6
c:\sb16\diagnose /s
set BLASTER=A240 I7 D3 H6 P300 T6
c:\sb16\diagnose /s
set BLASTER=A220 I5 D1 H5 P330 T6
set TEMP=c:\
rem arena1 city mission (TESTARN.EGG: map=arena1, time=day) with the RIO
rem attached; stdout redirected so mission-load progress survives kills.
set BT_JOINTS=1
set L4VIEWEXT=1
set BT_STACK_LOG=1
set BT_MECH_LOG=1
set BT_SUPERSTOP_SOAK=1
set BT_TELEMETRY_LOG=1
set BT_GAUGE_LOG=1
set BT_FORCE_TURN=0
set HEAPSIZE=15000000
set L4GAUGE=640x480x16
call setenv.bat r s n p
32rtm.exe -x
BTL4REC.EXE -egg testarn.egg > COM3
echo GAME-RC=%errorlevel% >> RC.TXT
32rtm.exe -u
echo ALPHA1-RUN-DONE
pause
+1
View File
@@ -109,6 +109,7 @@ Gyroscope::Gyroscope(
externalPitchPtr = &spare0;
}
vibrationDirection = Vector3D(0.0f, 1.0f, 0.0f);
mechInstability = 0.0f;
swayAngle = 0.0f;
swayVelocity = 0.0f;
swayActive = 0;
+11
View File
@@ -184,6 +184,17 @@
// powered/impaired percentages) and the placement scratch the
// damage fan-out rotates hit directions through.
//
//
// The mech's instability, republished here every master frame
// (binary gyro+0x3a8, written by the master perf tail). The gyro's
// own tip/sway machinery (@004b275c) reads it summed with +0x3b0
// against the +0x3bc threshold -- that consumer is a later brick;
// the publish lands now so the value is where the binary puts it.
//
public:
Scalar mechInstability;
protected:
Scalar swayAngle, swayBias, swayVelocity;
int swayActive;
Vector3D placeRot, placePos;
+312 -5
View File
@@ -313,9 +313,25 @@ Mech::Mech(
defaultStateRequests = 0; // the streamed mode-request counters
gimpLeftRequests = 0; // (binary ctor @004a1674 zeroes
gimpRightRequests = 0; // +0x334/+0x338/+0x33c)
realMaxSpeed = 0.0f; // the 0x15 share triplet (+0x7a0/4/8)
realMaxSpeedKnown = 0;
realMaxSpeedSent = 0;
runSpeedMaxKnown = 0; // set by the Myomers sweep (see MECH.HPP)
runSpeedMaxSent = 0;
superStopping = 0;
bodyAccelRate = 0.0f; // all three seeded from the model
forwardAccelRate = 0.0f; // resource below (rec+0x44 / rec+0x48)
superStopRate = -1.1f; // the DISABLED sentinel until read
superStopShudderTimer = 0.0f;
bodyAcceleration = Vector3D(0.0f, 0.0f, 0.0f);
bodyTurnAcceleration = 0.0f;
previousOlympicVelocityZ = 0.0f;
previousMeanVelocityX = 0.0f;
previousMeanTurnRate = 0.0f;
previousMeanVelocityZ = 0.0f;
maxUnstableAcceleration = 0.0f; // the instability constants, likewise
unstableAccelerationEffect = 0.0f;
unstableGunTheEngineEffect = 0.0f;
unstableSuperStopEffect = 0.0f;
unstableHighVelocityEffect = 0.0f;
unstableStopedTurnEffect = 0.0f;
combatIneffective = 0; // the @0049fa1c census cell (+0x414)
minimumWeaponCount = 2; // AUTHENTIC ctor constant (+0x448,
// part_012.c:15749: param_1[0x112] = 2)
@@ -637,7 +653,34 @@ Mech::Mech(
//
forwardCycleRate = model->maxAcceleration;
gimpCycleRate = model->maxAcceleration;
//
// The live accel rate and its forward figure are the
// same authored value (binary: +0x344, +0x5b0 and
// +0x5b8 ALL take rec+0x44).
//
bodyAccelRate = model->maxAcceleration;
forwardAccelRate = model->maxAcceleration;
}
//
// The super-stop rate (rec+0x48). -1.1 is the authored
// "no super stop on this chassis" sentinel and must pass
// through unguarded -- the whole system tests for it.
//
superStopRate = model->superStopAcceleration;
//
// The instability constants (rec+0x80..0x94), taken as
// authored: the model is a weighted sum, so a zero term
// simply contributes nothing.
//
maxUnstableAcceleration = model->maxUnstableAcceleration;
unstableAccelerationEffect = model->unstableAccelerationEffect;
unstableGunTheEngineEffect = model->unstableGunTheEngineEffect;
unstableSuperStopEffect = model->unstableSuperStopEffect;
unstableHighVelocityEffect = model->unstableHighVelocityEffect;
unstableStopedTurnEffect = model->unstableStopedTurnEffect;
if (model->throttleAdjustment > 0.05f && model->throttleAdjustment < 20.0f)
{
forwardThrottleScale = model->throttleAdjustment;
@@ -899,9 +942,9 @@ void
Check(this);
Check_Pointer(message);
if (realMaxSpeedKnown == 0)
if (runSpeedMaxKnown == 0)
{
realMaxSpeed = *(Scalar *)((char *)message + 0x1c);
runSpeedMax = *(Scalar *)((char *)message + 0x1c);
}
}
@@ -1083,6 +1126,255 @@ void
combatIneffective = ineffective;
}
//
//#############################################################################
// UpdateSuperStop -- binary master perf @0x4aa353-0x4aa393. Hauling the
// throttle negative while the mech still rolls FORWARD is a hard brake: the
// gait's acceleration rate swaps to the authored super-stop figure and the
// gyro throws the cockpit. Entry is an EDGE (one lurch, random sign, both
// gyro channels at 0.4); while it holds above the walk threshold the pitch
// kick repeats every 0.4s -- the shudder of a mech standing on its heels.
// The -1.1 rate sentinel disables the whole system for a chassis.
//#############################################################################
//
void
Mech::UpdateSuperStop(Scalar time_slice, Scalar speed_demand)
{
Check(this);
if (superStopRate != -1.1f)
{
if (currentBodySpeed <= 0.0f || speed_demand >= 0.0f)
{
if (superStopping != 0)
{
superStopping = 0;
ForceUpdate(0x100);
bodyAccelRate = forwardAccelRate;
}
}
else if (superStopping == 0)
{
superStopping = 1;
ForceUpdate(0x100);
bodyAccelRate = superStopRate;
if (gyroSubsystem != NULL)
{
//
// Two rolls: the magnitude, then a coin flip for which way
// the mech lurches (binary: 0.5 @0x4ab170).
//
Scalar
lurch = (Scalar)Random,
flip = (Scalar)Random;
if (flip > 0.5f)
{
lurch = -lurch;
}
((Gyroscope *)gyroSubsystem)->
ApplyDamageTorque(0.0f, 0.0f, lurch, 0.4f);
((Gyroscope *)gyroSubsystem)->
ApplyDamageImpulse(0.0f, 0.0f, lurch, 0.4f);
}
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[superstop] engaged: speed="
<< currentBodySpeed << " demand=" << speed_demand
<< " accelRate " << forwardAccelRate << " -> "
<< superStopRate << endl << flush;
}
}
}
//
// The sustained shudder: only while the brake holds AND the mech is
// still moving faster than a walk.
//
if (
superStopping != 0 &&
currentBodySpeed > standSpeed &&
gyroSubsystem != NULL
)
{
if (superStopShudderTimer > 0.0f)
{
superStopShudderTimer -= time_slice;
}
else
{
((Gyroscope *)gyroSubsystem)->
ApplyDamageImpulse(0.0f, 1.0f, 0.0f, 0.2f);
superStopShudderTimer = 0.4f;
}
}
}
//
//#############################################################################
// UpdateInstability -- binary master perf tail @0x4aab8e-0x4aaf42. Three
// weighted terms, each named by the model resource's own field:
//
// ACCELERATION |acceleration| / maxUnstableAcceleration, clamped to 1,
// times unstableAccelerationEffect, SQUARED.
// GUNNING THE ENGINE how far the throttle demand outruns the actual
// speed, as a fraction of the demand (the demand first clamped into
// [reverse cap, runSpeedMax]), clamped to 1, times
// unstableGunTheEngineEffect.
// STOPPED TURN a flat unstableStopedTurnEffect while the legs are in
// the turn-in-place gait.
//
// Sum clamped to 1 -> unstablePercentage (the published cockpit attribute)
// -> republished to the gyro. unstableSuperStopEffect and
// unstableHighVelocityEffect are streamed but NOT summed here; their
// consumers are elsewhere (OPEN).
//#############################################################################
//
void
Mech::UpdateInstability(Scalar speed_demand)
{
Check(this);
//
// Term 1: the acceleration the ring-buffer tail derives. That tail is
// not landed yet, so bodyAcceleration holds 0 and this term is inert
// ([T1] -- it lights up with the rings brick).
//
Scalar unstable = 0.0f;
if (maxUnstableAcceleration > 0.0f)
{
Scalar accel_ratio = bodyAcceleration.Length() / maxUnstableAcceleration;
if (accel_ratio > 1.0f)
{
accel_ratio = 1.0f;
}
unstable =
(accel_ratio * unstableAccelerationEffect) *
(accel_ratio * unstableAccelerationEffect);
}
//
// Term 2: gunning the engine.
//
{
Scalar demand = speed_demand;
if (demand > runSpeedMax)
{
demand = runSpeedMax;
}
if (demand < reverseSpeedMax)
{
demand = reverseSpeedMax;
}
Scalar shortfall = 0.0f;
if (demand > 0.0f)
{
shortfall = (demand - currentBodySpeed) / demand;
}
else if (demand < 0.0f)
{
//
// Reversing: the same fraction taken on magnitudes.
//
Scalar gap = demand - currentBodySpeed;
if (gap < 0.0f)
{
gap = -gap;
}
shortfall = gap / -demand;
}
if (shortfall > 1.0f)
{
shortfall = 1.0f;
}
unstable += shortfall * unstableGunTheEngineEffect;
}
//
// Term 3: turning on the spot.
//
if ((int)legStateAlarm.GetLevel() == 4)
{
unstable += unstableStopedTurnEffect;
}
if (unstable > 1.0f)
{
unstable = 1.0f;
}
unstablePercentage = unstable;
if (gyroSubsystem != NULL)
{
((Gyroscope *)gyroSubsystem)->mechInstability = unstablePercentage;
}
}
//
//#############################################################################
// UpdateTelemetryRings -- binary master perf tail @0x4aafba-0x4ab04a. Push
// this frame's velocity / turn rate / slice into the five filters, then
// derive the acceleration as the change in the FILTERED values over the
// filtered slice -- which is why a mech's instability reacts to a sustained
// shove and not to one noisy frame.
//
// Two faithful oddities, kept verbatim because the binary is explicit:
// * the Y filter is fed every frame and NEVER reduced;
// * the acceleration's three components are NOT x/y/z of one quantity --
// x comes from the mean velocity-X, y from the MEAN velocity-Z and z
// from the OLYMPIC (min/max-trimmed) velocity-Z. Only its LENGTH is
// consumed (by the instability model), so the mongrel basis is
// immaterial to behaviour; do not "fix" it into a tidy vector.
//#############################################################################
//
void
Mech::UpdateTelemetryRings(Scalar time_slice)
{
Check(this);
telemetryFilter[VelocityZFilter].Add(localVelocity.linearMotion.z);
telemetryFilter[VelocityYFilter].Add(localVelocity.linearMotion.y);
telemetryFilter[VelocityXFilter].Add(localVelocity.linearMotion.x);
//
// The binary pushes its computed turn rate (+0x1d4); ours lives in the
// body angular motion the turn dispatcher writes.
//
telemetryFilter[TurnRateFilter].Add(localVelocity.angularMotion.y);
telemetryFilter[TimeSliceFilter].Add(time_slice);
Scalar
olympic_z = telemetryFilter[VelocityZFilter].CalculateOlympicAverage(),
mean_x = telemetryFilter[VelocityXFilter].CalculateAverage(),
mean_z = telemetryFilter[VelocityZFilter].CalculateAverage(),
mean_turn = telemetryFilter[TurnRateFilter].CalculateAverage(),
mean_dt = telemetryFilter[TimeSliceFilter].CalculateAverage();
bodyAcceleration = Vector3D(0.0f, 0.0f, 0.0f);
if (mean_dt > 0.0f)
{
bodyAcceleration.x = (mean_x - previousMeanVelocityX) / mean_dt;
bodyAcceleration.y = (mean_z - previousMeanVelocityZ) / mean_dt;
bodyAcceleration.z = (olympic_z - previousOlympicVelocityZ) / mean_dt;
bodyTurnAcceleration = (mean_turn - previousMeanTurnRate) / mean_dt;
}
previousOlympicVelocityZ = olympic_z;
previousMeanVelocityZ = mean_z;
previousMeanVelocityX = mean_x;
previousMeanTurnRate = mean_turn;
if (getenv("BT_TELEMETRY_LOG"))
{
DEBUG_STREAM << "[telem] vel=(" << localVelocity.linearMotion.x
<< "," << localVelocity.linearMotion.z
<< ") meanDt=" << mean_dt
<< " accel=" << bodyAcceleration.Length()
<< " unstable=" << unstablePercentage << endl << flush;
}
}
//
//#############################################################################
// EjectPilotMessageHandler -- binary @0049f854 (authentic name from the
@@ -2006,6 +2298,12 @@ void
}
bodyTargetSpeed = speedDemand;
//
// THE SUPER STOP (master perf @0x4aa353): the brake latch, the accel
// swap and the cockpit lurch, evaluated from this frame's demand.
//
UpdateSuperStop(time_slice, speedDemand);
//
//-----------------------------------------------------------------------
// THE GAIT (mech2.cpp). The leg channel poses the skeleton from the live
@@ -2292,6 +2590,15 @@ void
// cockpit eyepoint rides. Gated off during the death clips exactly as
// the tail is.
//
//
// THE INSTABILITY ACCUMULATOR (master perf tail @0x4aab8e): the three
// weighted terms, clamped, published to the cockpit attribute and
// republished to the gyro -- computed BEFORE the gyro writes its
// joints, as the binary orders it.
//
UpdateTelemetryRings(time_slice);
UpdateInstability(speedDemand);
if (gyroSubsystem != NULL && MovementMode() < 5)
{
((Gyroscope *)gyroSubsystem)->WriteEyeJoint();
+95 -12
View File
@@ -711,6 +711,17 @@
void
UpdateCombatEffectiveness();
//
// The super stop (master perf @0x4aa353) and the instability
// accumulator (@0x4aa5f6 tail); both run inside Simulate.
//
void
UpdateSuperStop(Scalar time_slice, Scalar speed_demand);
void
UpdateInstability(Scalar speed_demand);
void
UpdateTelemetryRings(Scalar time_slice);
DamageLookupTable*
GetDamageLookupTable() const { Check(this); return damageLookupTable; }
@@ -877,7 +888,36 @@
}
int limpModeOverride; // DEV: 3/4 from BT_FORCE_LIMP; 0 = off
AverageOf<Scalar> telemetryFilter[5];
//
// THE TELEMETRY RINGS (binary mech+0x7e0/+0x7ec/+0x7f8/+0x804/
// +0x810). Pushed once per master frame from the local velocity,
// the turn rate and the frame slice; reduced into the derived
// acceleration below. Indexed by TelemetryFilter.
//
enum TelemetryFilter {
VelocityZFilter, // +0x7e0
VelocityYFilter, // +0x7ec -- FED BUT NEVER REDUCED (verbatim)
VelocityXFilter, // +0x7f8
TurnRateFilter, // +0x804
TimeSliceFilter, // +0x810
TelemetryFilterCount
};
AverageOf<Scalar> telemetryFilter[TelemetryFilterCount];
//
// The previous frame's reduced values (binary +0x81c/+0x820/
// +0x824/+0x828), the other half of the difference quotient.
//
Scalar previousOlympicVelocityZ;
Scalar previousMeanVelocityX;
Scalar previousMeanTurnRate;
Scalar previousMeanVelocityZ;
//
// The derived angular acceleration (binary +0x1ec), alongside
// bodyAcceleration (+0x1dc, copied to +0x82c for the instability).
//
Scalar bodyTurnAcceleration;
CString resourceNameA;
CString resourceNameB;
CString resourceNameC;
@@ -976,18 +1016,20 @@
int gimpRightRequests;
//
// The real-max-speed share (binary +0x7a0/+0x7a4/+0x7a8). +0x7a0
// is the base every throttle command snaps to (5 notches, master
// perf head). A mech that has COMPUTED its own value (+0x7a4 set;
// that writer is still undecoded) sends it once over the wire
// (message 0x15, payload at byte 0x1c; latch +0x7a8) and ignores
// incoming shares; anyone else adopts the streamed value (handler
// @0049f604). The send rides the EntityManager stream path
// (@0041f640) -- staged with the ForceUpdate/update-record feed.
// The real-max-speed share (binary +0x7a4/+0x7a8; the VALUE itself
// is +0x7a0 == our existing runSpeedMax -- 5.3.127 CORRECTION, the
// 5.3.122 'realMaxSpeed' member was a duplicate of that same cell).
// A mech that computed its OWN cap has runSpeedMaxKnown set -- the
// binary sets it in the ctor's Myomers sweep (part_012.c:15874:
// walk mech+0x7ac, FUN_004b8ef0 per item, then +0x7a4 = 1), i.e.
// exactly our Myomers -> RaiseRunSpeedMax path -- and it SENDS the
// value once (message 0x15, latch +0x7a8) while ignoring incoming
// shares; a mech without myomers ADOPTS the streamed value
// (handler @0049f604). The send itself rides the EntityManager
// stream path (@0041f640) and waits on the update-record feed.
//
Scalar realMaxSpeed;
int realMaxSpeedKnown;
int realMaxSpeedSent;
int runSpeedMaxKnown;
int runSpeedMaxSent;
//
// The combat-effectiveness census (binary @0049fa1c -> mech+0x414,
@@ -1003,6 +1045,47 @@
int combatIneffective;
int minimumWeaponCount;
//
// THE SUPER STOP (binary +0x3f4 / +0x344 / +0x5b8 / +0x5bc /
// +0x5c4, master perf @0x4aa353-0x4aa393). Hauling the throttle
// negative while still rolling forward is a hard brake, not a
// reverse: the mech swaps its acceleration rate from the authored
// forward figure to the authored superStopAcceleration and the
// gyro throws the cockpit about.
//
// superStopping +0x3f4, the latch (an EDGE drives the kick)
// bodyAccelRate +0x344, the live rate the gait slews by
// forwardAccelRate +0x5b8, model rec+0x44 (== maxAcceleration)
// superStopRate +0x5bc, model rec+0x48; the authored -1.1
// sentinel (@0x4ab174) means THE WHOLE SYSTEM IS DISABLED
// superStopShudderTimer +0x5c4, the 0.4s pitch-kick cadence
//
int superStopping;
Scalar bodyAccelRate;
Scalar forwardAccelRate;
Scalar superStopRate;
Scalar superStopShudderTimer;
//
// THE INSTABILITY MODEL (binary +0x784..+0x798, all streamed from
// the model resource rec+0x80..0x94 -- and the resource's own field
// names say what each term is). unstablePercentage (+0x3f0) is the
// sum, clamped to 1, republished to the gyro every frame.
//
//
// The derived acceleration vector (binary +0x82c, written by the
// master perf's ring-buffer tail). Holds zero until that brick
// lands, which leaves the acceleration instability term inert.
//
Vector3D bodyAcceleration;
Scalar maxUnstableAcceleration;
Scalar unstableAccelerationEffect;
Scalar unstableGunTheEngineEffect;
Scalar unstableSuperStopEffect;
Scalar unstableHighVelocityEffect;
Scalar unstableStopedTurnEffect;
void
DuckRequestMessageHandler(Receiver::Message *message);
void
+79
View File
@@ -337,3 +337,82 @@ Still to land from the roster:
(pod_render_eject.conf). THE AUTHENTIC MESSAGE ROSTER IS NOW 6/6.
Mode roster: 0 default, 2 burning, 3/4 gimp L/R, 5-8 falls, 9
killed, 10 ejected; writers of 5-8 still open.
## 5.3.127 -- THE SUPER STOP + THE INSTABILITY MODEL (landed)
**The model resource's own field names decoded these blocks.** The ctor
loads (part_012.c:15798-15804) tie the master perf's anonymous offsets to
the authored names our `Mech__ModelResource` already carried:
| model rec | mech | name (authentic, from the struct) |
|---|---|---|
| +0x44 | +0x344, +0x5b0, +0x5b8 | `maxAcceleration` (live rate + forward figure) |
| +0x48 | +0x5bc | **`superStopAcceleration`** |
| +0x4c | +0x5c0 | `throttleAdjustment` |
| +0x80 | +0x784 | `maxUnstableAcceleration` |
| +0x84 | +0x788 | `unstableAccelerationEffect` |
| +0x88 | +0x78c | **`unstableGunTheEngineEffect`** |
| +0x8c | +0x790 | `unstableSuperStopEffect` (streamed, consumer OPEN) |
| +0x90 | +0x794 | `unstableHighVelocityEffect` (streamed, consumer OPEN) |
| +0x94 | +0x798 | **`unstableStopedTurnEffect`** |
That naming is what made the instability formula legible: the term added
when the leg gait state is 4 is `unstableStopedTurnEffect` and gait 4 IS
turn-in-place; the speed-shortfall term is `unstableGunTheEngineEffect`.
The names confirm the arithmetic, and the arithmetic confirms the names.
**The super stop** (@0x4aa353): the throttle going negative while the
mech still rolls forward is a hard brake, not a reverse. Edge -> swap
+0x344 from the forward rate to `superStopAcceleration`, MarkUpdate(0x100),
and ONE random-signed lurch through both gyro channels at weight 0.4
(magnitude roll + a 0.5 coin flip @0x4ab170). While it holds above the
walk threshold, a (0,1,0)@0.2 pitch kick every 0.4s. Release restores the
forward rate. `superStopAcceleration == -1.1` (@0x4ab174) DISABLES the
system for that chassis -- the sentinel must survive the model read
unguarded.
**The instability accumulator** (tail @0x4aab8e): three weighted terms,
clamped to 1, into `unstablePercentage` (+0x3f0 -- ALREADY a published
cockpit attribute in our tree, so this lights a live gauge) and
republished to gyro+0x3a8. The gyro's own tip/sway machinery
(@004b275c) reads that sum against its +0x3bc threshold -- that consumer
is the next gyro brick. Term 1 rides the ring-buffer acceleration
(+0x82c) and stays inert [T1] until the rings land.
**CORRECTION to 5.3.122**: `realMaxSpeed` was a DUPLICATE member for
mech+0x7a0, which our tree already carried as `runSpeedMax` (the myomers
raise it). Merged; the 0x15 handler writes `runSpeedMax`. And the
+0x7a4 "we computed our own" flag is set by the ctor's MYOMERS sweep
(part_012.c:15874) -- i.e. a mech with myomers broadcasts its cap, a mech
without adopts the streamed one.
**Gyro primitives already existed**: FUN_004b2d8c == our
`Gyroscope::ApplyDamageImpulse` (eyeForce), FUN_004b2de4 == our
`ApplyDamageTorque` (bodyForce). OPEN, worth a look: the binary's torque
zeroes bodyForce.y then negates ONLY .x (+0x308); ours negates the whole
vector, so the .z sign may differ.
## 5.3.128 -- THE TELEMETRY RINGS (landed)
The five ring buffers ARE the engine's `AverageOf<Scalar>`, and our tree
already carried them: `telemetryFilter[5]`, sized 15 in the ctor since the
Mech-layout wave, fed by nothing until now. `FUN_0043ade4` = `Add` (with
wraparound), `FUN_0043ae0b` = `CalculateAverage`, and `FUN_0043ae47` =
**`CalculateOlympicAverage`** -- sum minus min minus max over count-2, the
authentic name for a judge-style trimmed mean.
Feeds: vel.z / vel.y / vel.x / turn rate / frame slice. The derived
acceleration is the change in FILTERED values over the FILTERED slice,
which is why instability answers a sustained shove and not one noisy frame.
TWO ODDITIES KEPT VERBATIM (the binary is explicit; do not tidy them):
the vel.y ring is fed every frame and never reduced, and the acceleration's
components are a mongrel -- x from mean vel.x, y from MEAN vel.z, z from
OLYMPIC vel.z. Only the LENGTH is consumed, so behaviour is unaffected.
LIVE RESULT + A FREE DIAGNOSTIC: `unstablePercentage` now moves over its
whole range (0 -> 1.0 clamp) on the rig. Walking, it pins near 0.84-0.99,
dominated by the gun-the-engine term -- because the mech demands 35.9 u/s
and the reconstructed gait delivers 7.0. Whether that gap is authentic
acceleration or a gait shortfall is a SEPARATE open question, and the new
gauge is now the instrument for answering it (BT_TELEMETRY_LOG).
+23
View File
@@ -211,6 +211,29 @@ void
e = getenv("BT_FORCE_LOOK");
if (e != NULL) { s_hasLook = 1; s_look = atoi(e); }
}
//
// BT_SUPERSTOP_SOAK=1: run forward 6s, then haul the throttle
// negative for 4s, forever -- the edge, the shudder cadence and the
// release, headlessly. Overrides BT_FORCE_THROTTLE while set.
//
{
static int s_soak = -1;
static Scalar s_soakClock = 0.0f;
if (s_soak < 0)
{
s_soak = (getenv("BT_SUPERSTOP_SOAK") != NULL) ? 1 : 0;
}
if (s_soak)
{
s_soakClock += time_slice;
if (s_soakClock >= 10.0f)
{
s_soakClock = 0.0f;
}
s_hasThrottle = 1;
s_throttle = (s_soakClock < 6.0f) ? 0.8f : -0.5f;
}
}
if (s_hasThrottle)
{
throttlePosition = (s_throttle >= 0.0f) ? s_throttle : -s_throttle;