From 4ad97b5d41a5e8c410e57d0db26c89a5fc72a0c4 Mon Sep 17 00:00:00 2001 From: Cyd Date: Mon, 10 Aug 2026 08:12:23 -0500 Subject: [PATCH] BT410 5.3.131: the instability reaches the pilot -- the sway loop closes, and every watcher in the mech turns out to be inert The gyro's sway slew (@004b275c) was already reconstructed and already read the cell the master perf writes -- swayBias -- which nothing had ever written. So the mechInstability member I added to the gyro in 5.3.127 was a duplicate of that same cell, the second such mistake this arc after realMaxSpeed/runSpeedMax. The lesson repeats: grep our own tree for an existing carve before adding a member for a binary offset. Merged, and the loop closes: the instability model now publishes into swayBias, so an unstable mech shakes its pilot harder through code that was already correct and waiting for a writer. Also corrected: the binary's impaired gate has three terms where ours had two -- a gyro destroyed outright swings to the impaired sway base, not only an unpowered or heat-failed one. Verified on the rig (BT_SWAY_LOG): bias arrives, the angle slews toward base+bias and stays inside the authored band, zero faults. AND THE LOG IMMEDIATELY EARNED ITS KEEP. It showed impaired=1 for an entire clean run. Instrumenting the three terms: state=0, heat=0 -- so the new term is inert and correct -- but watchdog=0 where Ready is 4. PowerWatcher::UpdateWatch falls back to 0 when its watched link resolves NULL, and watchedLink is default-constructed and bound NOWHERE in the tree, for PowerWatcher and HeatWatcher alike. So the whole watcher family is inert: every watcher resolves NULL, every PowerWatcher reports its target unpowered, and the gyro has been sitting on its destruction-grade sway base since the sway model landed -- invisibly, until this log existed. The fix needs the streamed watch index and a real ctor bind; guessing a binding is precisely what this project forbids, so it is recorded with its evidence for the next sitting rather than improvised now. Co-Authored-By: Claude Fable 5 --- emulator/render-bridge/pod_render_sway.conf | 87 +++++++++++++++++++++ restoration/source410/BT/GYRO.CPP | 24 +++++- restoration/source410/BT/GYRO.HPP | 26 ++++-- restoration/source410/BT/MECH.CPP | 2 +- restoration/source410/BT/MECH4.NOTES.md | 44 +++++++++++ 5 files changed, 173 insertions(+), 10 deletions(-) create mode 100644 emulator/render-bridge/pod_render_sway.conf diff --git a/emulator/render-bridge/pod_render_sway.conf b/emulator/render-bridge/pod_render_sway.conf new file mode 100644 index 00000000..5c9fa22b --- /dev/null +++ b/emulator/render-bridge/pod_render_sway.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_SWAY_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 49f97277..bff324e3 100644 --- a/restoration/source410/BT/GYRO.CPP +++ b/restoration/source410/BT/GYRO.CPP @@ -109,7 +109,6 @@ Gyroscope::Gyroscope( externalPitchPtr = &spare0; } vibrationDirection = Vector3D(0.0f, 1.0f, 0.0f); - mechInstability = 0.0f; swayAngle = 0.0f; swayVelocity = 0.0f; swayActive = 0; @@ -184,8 +183,14 @@ void // The impaired test reads the gyro's OWN mirrored alarms -- the two // UpdateWatch just drove: the voltage watchdog and the heat watch. // + // + // 5.3.131: the binary's gate (@004b275c) has THREE terms and the first + // was missing here -- a gyro DESTROYED outright swings to the impaired + // base too, not only an unpowered or heat-failed one. + // Logical impaired = + GetSimulationState() == 1 || watchdogAlarm.GetLevel() != PoweredSubsystem::Ready || heatAlarm.GetLevel() == HeatSink::FailureHeat; @@ -226,6 +231,23 @@ void swayAngle = minAnimationNoise; } + if (getenv("BT_SWAY_LOG")) + { + static Scalar s_lastSway = -1.0f; + if (swayAngle != s_lastSway) + { + s_lastSway = swayAngle; + DEBUG_STREAM << "[sway] angle=" << swayAngle + << " bias=" << swayBias + << " impaired=" << (int)impaired + << " (state=" << GetSimulationState() + << " watchdog=" << watchdogAlarm.GetLevel() + << " heat=" << heatAlarm.GetLevel() << ")" + << " band=[" << minAnimationNoise + << "," << maxAnimationNoise << "]" << endl << flush; + } + } + IntegrateEyeJoint(time_slice); IntegrateBody(time_slice); diff --git a/restoration/source410/BT/GYRO.HPP b/restoration/source410/BT/GYRO.HPP index e9d3210a..ec21dd10 100644 --- a/restoration/source410/BT/GYRO.HPP +++ b/restoration/source410/BT/GYRO.HPP @@ -184,17 +184,27 @@ // 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; + // + // The mech's instability, republished here by the master perf every + // frame (binary gyro+0x3a8) straight into the sway bias below. + // + void + SetSwayBias(Scalar instability) + { Check(this); swayBias = instability; } protected: + // + // swayAngle the live cockpit noise amplitude (binary gyro+0x3bc), + // slewed toward its target at rotationPerSecond and clamped into + // the authored [minAnimationNoise, maxAnimationNoise] band. + // swayBias THE MECH'S INSTABILITY (binary gyro+0x3a8), added to + // the sway target: the closer the mech is to going over, the + // harder the cockpit shakes. 5.3.131 -- this member always + // existed and the slew always read it, but nothing wrote it; the + // separate 'mechInstability' member added in 5.3.127 was a + // duplicate of the same cell and is retired. + // Scalar swayAngle, swayBias, swayVelocity; int swayActive; Vector3D placeRot, placePos; diff --git a/restoration/source410/BT/MECH.CPP b/restoration/source410/BT/MECH.CPP index 764144ce..c98cb293 100644 --- a/restoration/source410/BT/MECH.CPP +++ b/restoration/source410/BT/MECH.CPP @@ -1333,7 +1333,7 @@ void if (gyroSubsystem != NULL) { - ((Gyroscope *)gyroSubsystem)->mechInstability = unstablePercentage; + ((Gyroscope *)gyroSubsystem)->SetSwayBias(unstablePercentage); } } diff --git a/restoration/source410/BT/MECH4.NOTES.md b/restoration/source410/BT/MECH4.NOTES.md index 727aa701..ebe0c969 100644 --- a/restoration/source410/BT/MECH4.NOTES.md +++ b/restoration/source410/BT/MECH4.NOTES.md @@ -475,3 +475,47 @@ a bare `+ 0x354)` grep proves nothing. No Mech attribute row binds them either. Landing them would be three loops feeding dead memory, so per the project rule (record where the binary is silent, never invent a consumer) this stays decoded and unlanded until a consumer turns up. + +## 5.3.131 -- the instability loop CLOSES, and a family of watchers is found inert + +**The loop is closed.** `@004b275c` -- the gyro's sway slew -- was ALREADY +reconstructed in our tree, and it already read the cell the master perf +writes: `swayBias`, which nothing had ever written. So the 5.3.127 +`mechInstability` member I added on the gyro was a DUPLICATE of that same +cell (the second such mistake this arc, after `realMaxSpeed`/`runSpeedMax` +-- the lesson is the same: grep our own tree for an existing carve before +adding a member for a binary offset). Merged: the master perf now +publishes into `swayBias` through a setter, and the instability model +finally does something -- an unstable mech shakes its pilot harder, +through code that was already correct and waiting. + +Also landed: the binary's impaired gate has THREE terms and ours had two. +A gyro DESTROYED outright (`gyro+0x40 == 1`) swings to the impaired sway +base, not only an unpowered or heat-failed one. + +VERIFIED on the rig (BT_SWAY_LOG): bias arrives (0 -> 1.0 -> 0.90), the +angle slews toward base+bias and stays inside the authored [0, 1.2] band, +zero faults. + +### NEW DEFECT FOUND, precisely located, NOT fixed here + +The same log showed `impaired=1` for an entire clean run. Instrumenting +the three terms settled it: `state=0 heat=0` (so my new term is inert and +correct) but `watchdog=0` where Ready is 4 -- that term fires forever. + +Chasing it up: `PowerWatcher::UpdateWatch` sets the watchdog from the +WATCHED subsystem's voltage state and falls back to 0 when +`watchedLink.Resolve()` is NULL. And `watchedLink` is +DEFAULT-CONSTRUCTED (HEAT.CPP:886) and **bound nowhere in the tree** -- +not for PowerWatcher, not for HeatWatcher (HEAT.CPP:964 resolves the same +way). + +So the whole WATCHER family is inert: every watcher resolves NULL, every +PowerWatcher reports its target unpowered, and the gyro has consequently +been sitting on its `percentageOnDestruction` sway base since the sway +model landed -- invisibly, until this log existed. + +The fix needs the streamed watch index out of the watcher resource plus +the ctor bind, in the shape the crit-table binding already uses. That is +a proper brick, not a guess, and guessing a binding is exactly what this +project's rules forbid -- so it is recorded here for the next sitting.