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:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user