diff --git a/emulator/render-bridge/pod_render_superstop.conf b/emulator/render-bridge/pod_render_superstop.conf new file mode 100644 index 00000000..fd493c29 --- /dev/null +++ b/emulator/render-bridge/pod_render_superstop.conf @@ -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 + diff --git a/emulator/render-bridge/pod_render_telem.conf b/emulator/render-bridge/pod_render_telem.conf new file mode 100644 index 00000000..49fb9f5d --- /dev/null +++ b/emulator/render-bridge/pod_render_telem.conf @@ -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 + diff --git a/restoration/source410/BT/GYRO.CPP b/restoration/source410/BT/GYRO.CPP index 909004cd..5e7af23e 100644 --- a/restoration/source410/BT/GYRO.CPP +++ b/restoration/source410/BT/GYRO.CPP @@ -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; diff --git a/restoration/source410/BT/GYRO.HPP b/restoration/source410/BT/GYRO.HPP index 73333daf..e9d3210a 100644 --- a/restoration/source410/BT/GYRO.HPP +++ b/restoration/source410/BT/GYRO.HPP @@ -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; diff --git a/restoration/source410/BT/MECH.CPP b/restoration/source410/BT/MECH.CPP index ff949da0..4f10f8e6 100644 --- a/restoration/source410/BT/MECH.CPP +++ b/restoration/source410/BT/MECH.CPP @@ -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(); diff --git a/restoration/source410/BT/MECH.HPP b/restoration/source410/BT/MECH.HPP index 3661f747..90da41a3 100644 --- a/restoration/source410/BT/MECH.HPP +++ b/restoration/source410/BT/MECH.HPP @@ -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 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 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 diff --git a/restoration/source410/BT/MECH4.NOTES.md b/restoration/source410/BT/MECH4.NOTES.md index 93520491..4932ae57 100644 --- a/restoration/source410/BT/MECH4.NOTES.md +++ b/restoration/source410/BT/MECH4.NOTES.md @@ -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`, 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). diff --git a/restoration/source410/BT/MECHMPPR.CPP b/restoration/source410/BT/MECHMPPR.CPP index fc422361..65a61b04 100644 --- a/restoration/source410/BT/MECHMPPR.CPP +++ b/restoration/source410/BT/MECHMPPR.CPP @@ -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;