BT410 5.3.56: the intermittent pod crash is the skeleton walk's -- attributed by same-binary A/B

Added BT_NO_SKL, which skips the skeleton build so ONE executable can be run
with and without it.  That is the only clean way to test an intermittent
fault, and it settles attribution:

  walk enabled   ~50% of runs die (page fault at 66D9) at DIFFERENT points
  walk disabled  0 crashes in 2 runs
  shipped exe    0 crashes in 2 runs, same rig and conf

So it is our new code, not the rig, and the varying fault point points at
memory corruption surfacing later rather than a bad instruction in the walk.

Hypotheses killed by reading the private library headers: the matrix array is
the right size (dpl_MATRIX is float32[4][4]); and neither s_dplobject nor the
common dpl_node header retains an object name, weakening the dangling-string
theory (a private name cache inside the library is still possible and is the
best surviving suspect).

Also checked something that would have been much worse than a crash:
~NotationFile REWRITES its file when dirty, and these are the game's shipped
.SKL data files.  MAD.SKL is untouched, and ReadSKLFile now carries the
engine's own Verify(!IsDirty()) guard.

Next tests are listed cheapest-first in the roadmap: keep the NotationFile
alive, copy the Object= names, count board allocations, and re-check whether
MakeEntityRenderables runs twice per mech.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-28 20:45:28 -05:00
co-authored by Claude Fable 5
parent d66c2b0812
commit f54e1a3c19
5 changed files with 136 additions and 3 deletions
@@ -0,0 +1,70 @@
[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=namedpipe pipe:vrio rxpollus:100 rxburst:16
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_NO_SKL=1
set BT_FORCE_THROTTLE=0.6
set BT_FORCE_TURN=0.25
set HEAPSIZE=15000000
set L4GAUGE=640x480x16
call setenv.bat r s n p
32rtm.exe -x
BTL4REC.EXE -egg testarn.egg > COM3
-1
View File
@@ -8,7 +8,6 @@ ColorMapperArmor warning: damage zone dz_lmissle not found
ColorMapperArmor warning: damage zone dz_rmissle not found ColorMapperArmor warning: damage zone dz_rmissle not found
ColorMapperArmor warning: damage zone dz_ftorso not found ColorMapperArmor warning: damage zone dz_ftorso not found
ColorMapperArmor warning: damage zone dz_btorso not found ColorMapperArmor warning: damage zone dz_btorso not found
L4VIDEO.cpp wrong video resource type for object mad.skl
BTL4Application::RunMissionMessageHandler BTL4Application::RunMissionMessageHandler
Turning Plasma Score Display On Turning Plasma Score Display On
BTL4Application::RunMissionMessageHandler BTL4Application::RunMissionMessageHandler
+4 -2
View File
@@ -1,7 +1,7 @@
BattleTech v4.10 BattleTech v4.10
BTL4Application::BTL4Application BTL4Application::BTL4Application
GaugeInterpreter: undefined label 'Initialization' GaugeInterpreter: undefined label 'Initialization'
MakeVehicleSystems hud=0x011503f0 MakeVehicleSystems hud=0x01154164
Warning: damage zone 'dz_missle' not found! Warning: damage zone 'dz_missle' not found!
Warning: damage zone 'dz_missle' not found! Warning: damage zone 'dz_missle' not found!
Warning: damage zone 'dz_lmissle' not found! Warning: damage zone 'dz_lmissle' not found!
@@ -9,7 +9,9 @@ Warning: damage zone 'dz_rmissle' not found!
Warning: damage zone 'dz_ftorso' not found! Warning: damage zone 'dz_ftorso' not found!
Warning: damage zone 'dz_btorso' not found! Warning: damage zone 'dz_btorso' not found!
LBE4ControlsManager::Execute, lost RIO analog request LBE4ControlsManager::Execute, lost RIO analog request
iThinkIRQIsOn =1, actual state =1, characters sent =713 iThinkIRQIsOn =1, actual state =1, characters sent =62
LBE4ControlsManager::Execute, lost RIO analog request
iThinkIRQIsOn =1, actual state =1, characters sent =687
BTL4Application::RunMissionMessageHandler BTL4Application::RunMissionMessageHandler
Turning Plasma Score Display On Turning Plasma Score Display On
BTL4Application::RunMissionMessageHandler BTL4Application::RunMissionMessageHandler
+16
View File
@@ -159,6 +159,12 @@ dpl_DCS *
<< " -> " << node_count << " nodes, " << " -> " << node_count << " nodes, "
<< object_count << " objects\n" << flush; << object_count << " objects\n" << flush;
//
// The engine guards this the same way (L4VIDEO.CPP, DPLReadEnvironment):
// ~NotationFile REWRITES the file when dirtyFlag is set, and these are
// the game's shipped .SKL data files.
//
Verify(!skeleton->IsDirty());
Unregister_Object(skeleton); Unregister_Object(skeleton);
delete skeleton; delete skeleton;
return root; return root;
@@ -388,6 +394,16 @@ void
Logical Logical
handled = False; handled = False;
//
// BT_NO_SKL skips the skeleton build so the SAME binary can be
// run with and without it -- the only way to attribute the
// intermittent pod crash without a rebuild between samples.
//
if (getenv("BT_NO_SKL") != NULL)
{
break;
}
Entity_Being_Created = entity; Entity_Being_Created = entity;
video_iterator.First(); video_iterator.First();
while ((video_wrapper = video_iterator.ReadAndNext()) != NULL) while ((video_wrapper = video_iterator.ReadAndNext()) != NULL)
@@ -776,3 +776,49 @@ That defect has therefore gone from background annoyance to the top of the
list: it is what stops a mech frame from ever being seen, because a run has list: it is what stops a mech frame from ever being seen, because a run has
to survive BOTH the skeleton build AND the launch to render anything, and at to survive BOTH the skeleton build AND the launch to render anything, and at
~50% per run that is a coin flip on a four-minute cycle. ~50% per run that is a coin flip on a four-minute cycle.
--------------------------------------------------------------------------------
THE INTERMITTENT POD CRASH IS THE SKELETON WALK'S -- attributed, not yet fixed
--------------------------------------------------------------------------------
Attributed with a same-binary A/B, which is the only clean way to test an
intermittent fault: BT_NO_SKL skips the skeleton build, so one executable can
be run both ways with nothing else differing.
walk ENABLED ~50% of runs die ("Reference to a page you don't own",
PF at 66D9), at DIFFERENT points each time -- sometimes
mid-walk, sometimes after it, sometimes before launch
walk DISABLED 0 crashes in 2 runs
SHIPPED exe 0 crashes in 2 runs on the same rig and conf
So it is our new code, it is not the rig, and the varying fault point says
memory corruption surfacing later rather than a bad instruction in the walk.
HYPOTHESES KILLED so far (all by reading the private library headers, so they
stay killed):
* matrix array too small -- dpl_MATRIX is float32[4][4] (DPL_PRIV.H:137),
exactly the 16 floats we pass.
* dangling Object= name. dpl_LoadObject is handed a pointer INTO the
NotationFile, which we then delete -- but neither s_dplobject
(DPL_PRIV.H:377) nor the common dpl_node header (DPL_PRIV.H:92) keeps a
name field, so the library does not retain it there. A private name
cache is still possible and remains the best surviving suspect.
* NotationFile rewriting the game's data. ~NotationFile DOES rewrite the
file when dirtyFlag is set, which would have corrupted the shipped .SKL
files -- checked, MAD.SKL is untouched (11847 bytes, Jan 2001), and the
engine's own Verify(!IsDirty()) guard is now copied into ReadSKLFile.
STILL TO TRY, cheapest first:
1. Keep the NotationFile alive for the mission instead of deleting it (a
~12KB leak) and see if the crash rate drops -- that settles the name
cache question outright.
2. Copy each Object= name into storage that outlives the walk, same test.
3. Count what the walk allocates on the board (26 DCS + 19 objects + 19
instances + 1 zone per mech) against whatever the VPX HLE's node tables
allow -- resource exhaustion would also present as a late, varying
fault.
4. Check whether MakeEntityRenderables is invoked more than once per mech
(inside AND outside views). The [skl] summary line printed once per run
here, so this is unlikely, but it is cheap to re-check with two mechs.
BT_NO_SKL stays in the tree: it is how any future "is it the skeleton?"
question gets answered in one build instead of two.