BT410 Phase 5.3.32: the per-head A/B -- three engineering heads now exact
The gauge framebuffer is PLANE-PACKED: L4GAUGE.CFG gives each port a bit
mask, not a rectangle, so all six heads share the same x/y and are separated
only by bit -- the packing the VDB splits into the pod's physical screens.
Comparing RGB was therefore measuring nothing useful; the "magenta artifact"
just meant the wrong heads were lit at that pixel.
New instruments (emulator/render-bridge/gauge-ab/, with a README):
planes.py per-head scoreboard -- shipped vs ours, missing vs extra, per
port mask, recovering the 16-bit word from DOSBox's RGB565
BT_VIS_LOG a complete cockpit widget map from the single dispatch every
gauge widget is built through (MethodDescription::Execute),
printing name + port + authored position
ab.sh stages the fresh build over BTL4REC.EXE and kills any running
DOSBox before launching -- see below
The fix: the Comm page's pilot roster is the POD roster (the viewpoint mech's
ControlsMapper pilot array, mapper attributes 15/16), not the mission's
player list. It is zero in a solo mission, so the shipped page draws empty;
ours resolved the local player and painted its icon in colour 0xff. The gate
belongs in the row source, not PilotList::Execute -- Execute's empty branch
still has to run, because erasing is what the shipped page draws.
head shipped ours missing extra (was)
Eng1 17981 17981 0 0 (0 / 1030)
Eng2 17981 17981 0 0
Eng3 17981 17981 0 0
Comm 15656 13557 2099 0 (2099 / 1253)
Heat 21374 20913 566 105 (566 / 990)
Title-band magenta 404 -> 0. The 2099 Comm pixels we are missing were
missing before the gate, so it is a strict improvement.
Method note, which cost more than the bug: the rig's confs run BTL4REC.EXE
out of the mount while the build writes build410/btl4opt.exe, and nothing
connected the two. Three measurements ran an hour-old binary, so a change
that "did nothing" three times had never been tested -- and the correct first
hypothesis was discarded on that evidence. ab.sh now re-stages every launch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# gauge-ab -- the cockpit A/B rig
|
||||
|
||||
Runs the reconstruction and the shipped 4.10 binary against the **same mount**
|
||||
(same `GAUGE/L4GAUGE.CFG`, same egg, same mech) and compares what each one
|
||||
paints into the gauge framebuffer.
|
||||
|
||||
With `L4VIDEO=OFF` and `L4GAUGE=640x480x16`, the DOSBox window *is* the packed
|
||||
gauge framebuffer -- the exact buffer the pod's VDB splits into its physical
|
||||
heads. So a screen grab is a lossless read of the cockpit.
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
bash ab.sh rec # stage the fresh build + run the reconstruction
|
||||
bash ab.sh shp # run the shipped binary
|
||||
powershell -File grab.ps1 -Out ours.png
|
||||
python3 planes.py shipped.png ours.png
|
||||
```
|
||||
|
||||
**Always launch through `ab.sh`.** The confs run `BTL4REC.EXE` out of the
|
||||
mount, *not* `build410/btl4opt.exe`. Forgetting to re-stage silently measures
|
||||
the previous binary -- on 2026-07-27 that cost a full round of wrong
|
||||
conclusions (a fix "did nothing" three times because the tested exe never
|
||||
changed, and the correct first hypothesis was discarded on that evidence).
|
||||
`ab.sh` re-stages every time and kills any running DOSBox first (two
|
||||
instances share `OUT.TXT` and corrupt the trace).
|
||||
|
||||
## The instruments
|
||||
|
||||
### `planes.py` -- the per-head scoreboard (use this first)
|
||||
|
||||
The framebuffer is **plane-packed**: `L4GAUGE.CFG` gives each of the ten
|
||||
logical ports a *bit mask*, not a rectangle --
|
||||
|
||||
```
|
||||
configure(8,Heat, 0,0x4000,clut2,blue, NULL); # UL
|
||||
configure(9,Comm, 0,0x8000,clut2,red, NULL); # UR
|
||||
```
|
||||
|
||||
-- so all six heads occupy the same x/y and are separated only by bit. RGB
|
||||
comparison is therefore meaningless: a "magenta artifact" is really *the wrong
|
||||
heads lit at that pixel*. DOSBox presents the buffer as RGB565 with bit
|
||||
replication, so the original 16-bit word is recovered exactly by
|
||||
`R>>3, G>>2, B>>3`.
|
||||
|
||||
`planes.py` reports, per head, how many pixels the shipped binary lights, how
|
||||
many we light, how many we're **missing**, how many are **extra**, and the
|
||||
bounding box of the extras. Missing vs extra is the useful signal: extras are
|
||||
*our* bugs, missing is unbuilt work.
|
||||
|
||||
### `score.py` -- one whole-frame number
|
||||
|
||||
Coarse (the big MFD heads dominate it). Good for "did this regress", useless
|
||||
for locating anything. Prefer `planes.py`.
|
||||
|
||||
### `BT_VIS_LOG` -- the widget map
|
||||
|
||||
`set BT_VIS_LOG=1` in the conf makes the build print every gauge widget the
|
||||
interpreter constructs, with its port and authored position:
|
||||
|
||||
```
|
||||
[w] pilotList port=9 at 0,0
|
||||
[w] rankAndScore port=9 at 33,0
|
||||
[gen] port=0 x=443 y=322 lampA='sgena.pcc' ... on=1 col0=3 col1=9
|
||||
[cluster] port=4 x=0 y=240 title='qsensors.pcc' banner=1
|
||||
```
|
||||
|
||||
The `[w]` line comes from the single dispatch every widget is built through
|
||||
(`MethodDescription::Execute`, MUNGA/GAUGREND.CPP), so the map is complete.
|
||||
This is what turns "a diff at framebuffer (67,3)" into "that head has no
|
||||
drawing widget there, so look elsewhere".
|
||||
|
||||
## Method
|
||||
|
||||
Source inspection has now produced **five** wrong suspects on this one
|
||||
artifact; each was settled -- or killed -- by one instrumented run. So:
|
||||
|
||||
1. `planes.py` first: *which head*, and is it missing or extra?
|
||||
2. `BT_VIS_LOG`: which widgets exist on that head, at what position?
|
||||
3. Only then read code, and only that widget's.
|
||||
|
||||
And re-stage before believing any measurement.
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# gauge-ab: stage + run one side of the cockpit A/B rig.
|
||||
#
|
||||
# ab.sh rec -- stage the freshly built reconstruction and run it
|
||||
# ab.sh shp -- run the shipped 4.10 binary
|
||||
#
|
||||
# STAGING IS THE WHOLE POINT. The rig's confs run BTL4REC.EXE / BTL4SHP.EXE
|
||||
# out of the shared mount, NOT build410/btl4opt.exe. Forgetting to copy the
|
||||
# new build over BTL4REC.EXE silently measures the PREVIOUS binary -- which
|
||||
# on 2026-07-27 cost a full round of wrong conclusions (a change "did
|
||||
# nothing" three times because the tested exe never changed). So: always
|
||||
# launch through this script, and it always re-stages first.
|
||||
#
|
||||
set -e
|
||||
|
||||
R=/c/VWE/TeslaRel410/restoration
|
||||
IMG=$R/build410/run/image
|
||||
SIDE=${1:-rec}
|
||||
|
||||
# one instance at a time -- two DOSBoxes share OUT.TXT and corrupt the trace
|
||||
if tasklist //FI "IMAGENAME eq dosbox-x.exe" 2>/dev/null | grep -qi dosbox-x; then
|
||||
echo "killing running dosbox-x"
|
||||
taskkill //F //IM dosbox-x.exe > /dev/null 2>&1 || true
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
if [ "$SIDE" = "rec" ]; then
|
||||
SRC=$R/build410/btl4opt.exe
|
||||
DST=$IMG/BTL4REC.EXE
|
||||
[ -f "$SRC" ] || { echo "no build at $SRC -- run build410.sh link"; exit 1; }
|
||||
cp -f "$SRC" "$DST"
|
||||
echo "staged $(ls -la "$DST" | awk '{print $5, $6, $7, $8}') <- $(basename $SRC)"
|
||||
CONF=$R/build410/run/vis_rec.conf
|
||||
else
|
||||
echo "shipped side (no staging)"
|
||||
CONF=$R/build410/run/vis_shp.conf
|
||||
fi
|
||||
|
||||
rm -f "$IMG/OUT.TXT"
|
||||
cd /c/VWE/TeslaRel410/emulator/src/src
|
||||
./dosbox-x.exe -conf "$CONF" > /dev/null 2>&1 &
|
||||
echo "launched $SIDE ($(basename $CONF)) -- pid $!"
|
||||
@@ -1,54 +1,81 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$Out,
|
||||
[string]$Match = "DOSBox-X"
|
||||
)
|
||||
|
||||
Add-Type @"
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
public class W {
|
||||
[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
|
||||
[DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out RECT r);
|
||||
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
|
||||
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr h);
|
||||
[DllImport("user32.dll", CharSet=CharSet.Auto)] public static extern int GetWindowText(IntPtr h, System.Text.StringBuilder s, int n);
|
||||
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumProc cb, IntPtr p);
|
||||
public delegate bool EnumProc(IntPtr h, IntPtr p);
|
||||
[StructLayout(LayoutKind.Sequential)] public struct RECT { public int L, T, R, B; }
|
||||
}
|
||||
"@
|
||||
|
||||
$found = @()
|
||||
$cb = [W+EnumProc]{
|
||||
param($h, $p)
|
||||
if ([W]::IsWindowVisible($h)) {
|
||||
$sb = New-Object System.Text.StringBuilder 512
|
||||
[void][W]::GetWindowText($h, $sb, 512)
|
||||
$t = $sb.ToString()
|
||||
if ($t -like "*$Match*") { $script:found += ,@($h, $t) }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
[void][W]::EnumWindows($cb, [IntPtr]::Zero)
|
||||
|
||||
if ($found.Count -eq 0) { Write-Output "NO-WINDOW"; exit 1 }
|
||||
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
$i = 0
|
||||
foreach ($f in $found) {
|
||||
$h = $f[0]; $title = $f[1]
|
||||
[void][W]::SetForegroundWindow($h)
|
||||
Start-Sleep -Milliseconds 1200
|
||||
$r = New-Object W+RECT
|
||||
[void][W]::GetWindowRect($h, [ref]$r)
|
||||
$w = $r.R - $r.L; $ht = $r.B - $r.T
|
||||
if ($w -le 0 -or $ht -le 0) { continue }
|
||||
$bmp = New-Object System.Drawing.Bitmap $w, $ht
|
||||
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
||||
$g.CopyFromScreen($r.L, $r.T, 0, 0, (New-Object System.Drawing.Size $w, $ht))
|
||||
$path = if ($found.Count -eq 1) { $Out } else { $Out -replace '\.png$', "_$i.png" }
|
||||
$bmp.Save($path, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
$g.Dispose(); $bmp.Dispose()
|
||||
Write-Output "SAVED $path ${w}x${ht} [$title]"
|
||||
$i++
|
||||
}
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$Out,
|
||||
[string]$Match = "DOSBox-X"
|
||||
)
|
||||
|
||||
Add-Type @"
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
public class W {
|
||||
[DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out RECT r);
|
||||
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
|
||||
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr h);
|
||||
[DllImport("user32.dll")] public static extern bool PrintWindow(IntPtr h, IntPtr hdc, uint flags);
|
||||
[DllImport("user32.dll", CharSet=CharSet.Auto)] public static extern int GetWindowText(IntPtr h, System.Text.StringBuilder s, int n);
|
||||
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumProc cb, IntPtr p);
|
||||
public delegate bool EnumProc(IntPtr h, IntPtr p);
|
||||
[StructLayout(LayoutKind.Sequential)] public struct RECT { public int L, T, R, B; }
|
||||
}
|
||||
"@
|
||||
|
||||
$found = @()
|
||||
$cb = [W+EnumProc]{
|
||||
param($h, $p)
|
||||
if ([W]::IsWindowVisible($h)) {
|
||||
$sb = New-Object System.Text.StringBuilder 512
|
||||
[void][W]::GetWindowText($h, $sb, 512)
|
||||
$t = $sb.ToString()
|
||||
if ($t -like "*$Match*") { $script:found += ,@($h, $t) }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
[void][W]::EnumWindows($cb, [IntPtr]::Zero)
|
||||
if ($found.Count -eq 0) { Write-Output "NO-WINDOW"; exit 1 }
|
||||
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
function Test-Blank($bmp) {
|
||||
# sample a grid; blank/uniform => PrintWindow failed on this surface
|
||||
$seen = @{}
|
||||
for ($y = 10; $y -lt $bmp.Height - 10; $y += 23) {
|
||||
for ($x = 10; $x -lt $bmp.Width - 10; $x += 29) {
|
||||
$seen[$bmp.GetPixel($x, $y).ToArgb()] = $true
|
||||
if ($seen.Count -gt 6) { return $false }
|
||||
}
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
$i = 0
|
||||
foreach ($f in $found) {
|
||||
$h = $f[0]; $title = $f[1]
|
||||
[void][W]::SetForegroundWindow($h)
|
||||
Start-Sleep -Milliseconds 600
|
||||
$r = New-Object W+RECT
|
||||
[void][W]::GetWindowRect($h, [ref]$r)
|
||||
$w = $r.R - $r.L; $ht = $r.B - $r.T
|
||||
if ($w -le 0 -or $ht -le 0) { continue }
|
||||
|
||||
# PRIMARY: PrintWindow(PW_RENDERFULLCONTENT) -- immune to occlusion
|
||||
$bmp = New-Object System.Drawing.Bitmap $w, $ht
|
||||
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
||||
$hdc = $g.GetHdc()
|
||||
$ok = [W]::PrintWindow($h, $hdc, 2)
|
||||
$g.ReleaseHdc($hdc)
|
||||
$method = "PrintWindow"
|
||||
|
||||
if (-not $ok -or (Test-Blank $bmp)) {
|
||||
# FALLBACK: screen copy (only valid if nothing is in front)
|
||||
$g.Dispose(); $bmp.Dispose()
|
||||
$bmp = New-Object System.Drawing.Bitmap $w, $ht
|
||||
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
||||
$g.CopyFromScreen($r.L, $r.T, 0, 0, (New-Object System.Drawing.Size $w, $ht))
|
||||
$method = "CopyFromScreen(FALLBACK -- occlusion-sensitive)"
|
||||
}
|
||||
|
||||
$path = if ($found.Count -eq 1) { $Out } else { $Out -replace '\.png$', "_$i.png" }
|
||||
$bmp.Save($path, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
$g.Dispose(); $bmp.Dispose()
|
||||
Write-Output "SAVED $path ${w}x${ht} via $method [$title]"
|
||||
$i++
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#
|
||||
# PER-HEAD A/B scoreboard for the packed gauge framebuffer.
|
||||
#
|
||||
# L4GAUGE.CFG configures ten logical ports onto ONE 640x480x16 buffer, each
|
||||
# port owning a BIT MASK, not a rectangle -- the VDB later splits the planes
|
||||
# into the pod's separate physical heads. Comparing RGB is therefore
|
||||
# meaningless; the only useful question is "which HEAD differs, and where".
|
||||
#
|
||||
# DOSBox presents the buffer as RGB565 with bit replication, so the original
|
||||
# 16-bit word is recovered exactly by R>>3, G>>2, B>>3.
|
||||
#
|
||||
import sys
|
||||
from PIL import Image
|
||||
|
||||
BOX = (8, 52, 648, 531)
|
||||
|
||||
PORTS = [
|
||||
("sec", 0x003F), # the 3-D scene (6-bit colour)
|
||||
("overlay", 0x00C0),
|
||||
("Mfd1", 0x0100), ("Eng1", 0x0200),
|
||||
("Mfd2", 0x0400), ("Eng2", 0x0800),
|
||||
("Mfd3", 0x1000), ("Eng3", 0x2000),
|
||||
("Heat", 0x4000), ("Comm", 0x8000),
|
||||
]
|
||||
|
||||
|
||||
def words(path):
|
||||
im = Image.open(path).convert("RGB").crop(BOX)
|
||||
w, h = im.size
|
||||
px = im.load()
|
||||
out = []
|
||||
for y in range(h):
|
||||
row = []
|
||||
for x in range(w):
|
||||
r, g, b = px[x, y]
|
||||
row.append(((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3))
|
||||
out.append(row)
|
||||
return out, w, h
|
||||
|
||||
|
||||
def main(ship_path, ours_path):
|
||||
A, w, h = words(ship_path)
|
||||
B, w2, h2 = words(ours_path)
|
||||
if (w, h) != (w2, h2):
|
||||
print("SIZE MISMATCH")
|
||||
return
|
||||
|
||||
print("%-8s %8s %8s %8s %8s %s" %
|
||||
("head", "shipped", "ours", "missing", "extra", "extra-bbox"))
|
||||
for name, mask in PORTS:
|
||||
sa = so = both = 0
|
||||
xs = []
|
||||
ys = []
|
||||
for y in range(h):
|
||||
ra, rb = A[y], B[y]
|
||||
for x in range(w):
|
||||
a = (ra[x] & mask) != 0
|
||||
b = (rb[x] & mask) != 0
|
||||
if a:
|
||||
sa += 1
|
||||
if b:
|
||||
so += 1
|
||||
if a and b:
|
||||
both += 1
|
||||
if b and not a:
|
||||
xs.append(x)
|
||||
ys.append(y)
|
||||
bbox = ("(%d,%d)-(%d,%d)" % (min(xs), min(ys), max(xs), max(ys))
|
||||
if xs else "-")
|
||||
print("%-8s %8d %8d %8d %8d %s" %
|
||||
(name, sa, so, sa - both, so - both, bbox))
|
||||
|
||||
|
||||
main(sys.argv[1], sys.argv[2])
|
||||
@@ -0,0 +1,40 @@
|
||||
import sys
|
||||
from PIL import Image
|
||||
|
||||
BOX = (8, 52, 648, 531) # the DOSBox client area = the gauge framebuffer
|
||||
SHIPPED = "shipped.png"
|
||||
|
||||
|
||||
def load(p):
|
||||
return Image.open(p).convert("RGB").crop(BOX)
|
||||
|
||||
|
||||
def magenta(p):
|
||||
return p[0] > 150 and p[2] > 150 and p[1] < 140
|
||||
|
||||
|
||||
def score(path):
|
||||
a, b = load(SHIPPED), load(path)
|
||||
if a.size != b.size:
|
||||
return "%-10s SIZE MISMATCH %s vs %s" % (path, a.size, b.size)
|
||||
pa, pb = a.load(), b.load()
|
||||
w, h = a.size
|
||||
same = lit_ship = lit_both = mag = 0
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
s, o = pa[x, y], pb[x, y]
|
||||
if s == o:
|
||||
same += 1
|
||||
if sum(s) > 60:
|
||||
lit_ship += 1
|
||||
if sum(o) > 60:
|
||||
lit_both += 1
|
||||
if 0 <= x < 200 and 0 <= y < 60 and magenta(o):
|
||||
mag += 1
|
||||
n = w * h
|
||||
return "%-10s identical %5.1f%% coverage %3d%% title-band magenta %4d" % (
|
||||
path, 100.0 * same / n, round(100.0 * lit_both / max(lit_ship, 1)), mag)
|
||||
|
||||
|
||||
for p in sys.argv[1:]:
|
||||
print(score(p))
|
||||
@@ -152,6 +152,19 @@
|
||||
// Local Data -- the published control inputs (the streamed mappings write
|
||||
// these; InterpretControls, phase 5.3, reads them to drive the mech).
|
||||
//
|
||||
public:
|
||||
//
|
||||
// The POD ROSTER (binary mapper attributes PilotArrayPage/PilotArray,
|
||||
// ids 15/16): the comm scoreboard's row source. Zero = no roster
|
||||
// authored (a solo mission), which is why the shipped cockpit leaves
|
||||
// that page blank.
|
||||
//
|
||||
int
|
||||
GetPilotArray() const { Check(this); return pilotArray; }
|
||||
int
|
||||
GetPilotArrayPage() const { Check(this); return pilotArrayPage; }
|
||||
|
||||
|
||||
protected:
|
||||
ControlsJoystick stickPosition;
|
||||
Scalar throttlePosition;
|
||||
|
||||
@@ -1427,6 +1427,15 @@ GeneratorCluster::GeneratorCluster(
|
||||
generatorOn, "TwoState");
|
||||
Register_Object(lampB);
|
||||
|
||||
if (getenv("BT_VIS_LOG"))
|
||||
DEBUG_STREAM << "[gen] port=" << graphics_port_number
|
||||
<< " x=" << x << " y=" << y
|
||||
<< " lampA='" << lampA_image << "' at " << (x + 0xC) << "," << (y + 2)
|
||||
<< " lampB='" << lampB_image << "' at " << (x + 2) << "," << (y + 2)
|
||||
<< " on=" << *generatorOn
|
||||
<< " col0=" << lamp_on_color << " col1=" << lamp_off_color
|
||||
<< "\n" << flush;
|
||||
|
||||
subsystem = subsystem_in; // @0xAC
|
||||
_reserved0xB0 = 0; // @0xB0 (binary leaves uninit; zeroed)
|
||||
|
||||
@@ -1953,6 +1962,11 @@ SubsystemCluster::SubsystemCluster(
|
||||
);
|
||||
Register_Object(titleBanner);
|
||||
}
|
||||
if (getenv("BT_VIS_LOG"))
|
||||
DEBUG_STREAM << "[cluster] port=" << mfd_port
|
||||
<< " x=" << x << " y=" << y
|
||||
<< " title='" << (title ? title : "(null)")
|
||||
<< "' banner=" << (titleBanner ? 1 : 0) << "\n" << flush;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
#include <btl4.hpp>
|
||||
#pragma hdrstop
|
||||
|
||||
#if !defined(MECHMPPR_HPP)
|
||||
# include <mechmppr.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MISSION_HPP)
|
||||
# include <mission.hpp>
|
||||
#endif
|
||||
@@ -53,6 +57,10 @@
|
||||
# include <btplayer.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MECHMPPR_HPP)
|
||||
# include <mechmppr.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(NTTMGR_HPP)
|
||||
# include <nttmgr.hpp>
|
||||
#endif
|
||||
@@ -104,6 +112,56 @@ static Player *
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// COMM-PAGE roster gate -- the cockpit's pilot list is NOT the mission's
|
||||
// player list. The binary fed it from the viewpoint mech's ControlsMapper
|
||||
// pilot array (mapper attributes PilotArrayPage / PilotArray, ids 15/16),
|
||||
// which the pod network authors; a mission with no pod roster leaves it
|
||||
// zero and the shipped cockpit draws the page EMPTY.
|
||||
//
|
||||
// A/B EVIDENCE (5.3.32): with the ungated walk our slot 0 resolved the solo
|
||||
// mission player and painted its icon in colour 0xff -- 404 magenta pixels
|
||||
// in the Comm title band where the shipped binary paints the black erase
|
||||
// rect. Gate the ROW SOURCE here, not PilotList::Execute: Execute's empty
|
||||
// branch still has to run, because erasing IS what the shipped page draws.
|
||||
//
|
||||
// PlayerStatus (the observer/camera scoreboard) keeps the ungated walk -- it
|
||||
// is a mission-wide standings page, not the pod roster.
|
||||
//#############################################################################
|
||||
//
|
||||
static Player *
|
||||
ResolveCommRosterPlayer(int slot)
|
||||
{
|
||||
if (application == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Entity
|
||||
*viewpoint = application->GetViewpointEntity();
|
||||
if (viewpoint == NULL || viewpoint->GetSubsystemCount() <= 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Subsystem
|
||||
*controls = viewpoint->GetSubsystem(0);
|
||||
if (controls == NULL
|
||||
|| !controls->IsDerivedFrom(MechControlsMapper::ClassDerivations))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (((MechControlsMapper *) controls)->GetPilotArray() == 0)
|
||||
{
|
||||
return NULL; // no pod roster authored -> the page stays empty
|
||||
}
|
||||
|
||||
return ResolveRosterPlayer(slot);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// KILLS access (the gauge scoring wave): the KILLS column must read the SAME
|
||||
@@ -916,7 +974,7 @@ void
|
||||
*pilot;
|
||||
if (built == False)
|
||||
{
|
||||
pilot = ResolveRosterPlayer(currentSlot);
|
||||
pilot = ResolveCommRosterPlayer(currentSlot);
|
||||
if (pilot == NULL)
|
||||
{
|
||||
built = True; // past the first empty slot -> rest empty this cycle
|
||||
|
||||
@@ -622,3 +622,103 @@ use, switch the grab to PrintWindow(hwnd, hdc, PW_RENDERFULLCONTENT), which
|
||||
asks the window to render itself and works while occluded (verify it against
|
||||
the OpenGL output mode first -- if PrintWindow comes back blank on GL, set
|
||||
[sdl] output=surface in the vis_*.conf for capture runs).
|
||||
|
||||
================================================================================
|
||||
5.3.32 -- THE PER-HEAD A/B: the packed framebuffer, read one head at a time
|
||||
================================================================================
|
||||
|
||||
The stray magenta blocks are GONE (404 px -> 0), and the three engineering
|
||||
heads are now PIXEL-IDENTICAL to the shipped binary.
|
||||
|
||||
Per-head, shipped vs reconstruction (planes.py):
|
||||
|
||||
head shipped ours missing extra
|
||||
Eng1 17981 17981 0 0 <- exact
|
||||
Eng2 17981 17981 0 0 <- exact
|
||||
Eng3 17981 17981 0 0 <- exact
|
||||
Comm 15656 13557 2099 0 <- nothing wrong drawn
|
||||
Heat 21374 20913 566 105
|
||||
overlay 3283 3325 55 97
|
||||
sec 35423 36096 357 1030
|
||||
Mfd1 41784 37040 8807 4063 <- the remaining climb
|
||||
Mfd2 40799 39197 6882 5280
|
||||
Mfd3 32146 27870 6935 2659
|
||||
|
||||
TWO THINGS CAME OUT OF THIS PASS. The fix is the smaller one.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
1. THE INSTRUMENT: the framebuffer is PLANE-PACKED, so RGB diffing is a lie
|
||||
--------------------------------------------------------------------------------
|
||||
L4GAUGE.CFG gives every port a BIT MASK, not a rectangle:
|
||||
|
||||
configure(8,Heat, 0,0x4000,clut2,blue, NULL); # UL
|
||||
configure(9,Comm, 0,0x8000,clut2,red, NULL); # UR
|
||||
|
||||
All six heads live at the SAME x/y and are separated only by bit -- that is
|
||||
exactly the packing the VDB splits into the pod's physical screens. So the
|
||||
"magenta artifact" I had been chasing for two rounds was never a colour bug:
|
||||
magenta 0xD2BB simply means Comm+Heat+Mfd3+Eng1 lit at a pixel where the
|
||||
shipped buffer has Eng1+Eng2+Eng3. Decode the word (R>>3,G>>2,B>>3 inverts
|
||||
DOSBox's RGB565 bit replication), mask per port, and each head can be scored
|
||||
on its own. That is emulator/render-bridge/gauge-ab/planes.py, and it turned
|
||||
a vague 89.7%-identical number into "Eng1 has 1030 extra pixels at (67,3)".
|
||||
|
||||
Second instrument, same session: BT_VIS_LOG. MethodDescription::Execute
|
||||
(MUNGA/GAUGREND.CPP) is the ONE dispatch every gauge widget is constructed
|
||||
through, so a single line there prints the entire cockpit as a map --
|
||||
|
||||
[w] pilotList port=9 at 0,0
|
||||
[w] rankAndScore port=9 at 33,0
|
||||
|
||||
-- name, port, authored position. That is how "the Eng1 head has a spurious
|
||||
block at (67,3)" became "port 3 has no drawing widget at all, so stop reading
|
||||
Eng1 code". Both instruments are banked with a README in gauge-ab/.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
2. THE FIX: the Comm page's roster is the POD roster, not the mission's
|
||||
--------------------------------------------------------------------------------
|
||||
PilotList::Execute drew slot 0 from the EntityManager "Players" group, which
|
||||
in a solo mission resolves the local player and paints its icon in colour
|
||||
0xff. The shipped binary feeds that page from the viewpoint mech's
|
||||
ControlsMapper pilot array (mapper attributes PilotArrayPage / PilotArray,
|
||||
ids 15/16) -- authored by the pod network, zero in a solo mission, so the
|
||||
shipped page draws EMPTY.
|
||||
|
||||
The gate belongs in the ROW SOURCE (ResolveCommRosterPlayer), NOT in
|
||||
PilotList::Execute: Execute's empty branch still has to run, because erasing
|
||||
is what the shipped page actually draws there. Gating Execute instead
|
||||
removed the erase too and cost a point of coverage. PlayerStatus keeps the
|
||||
ungated walk -- it is the observer/camera standings page, not the pod roster.
|
||||
MECHMPPR.HPP grew GetPilotArray()/GetPilotArrayPage() for the gate.
|
||||
|
||||
Result: Comm extra 1253 -> 0, Eng1 extra 1030 -> 0, and the 2099 Comm pixels
|
||||
we are MISSING were missing before the change too, so the gate is a strict
|
||||
improvement. (Those 2099 are unbuilt Comm content -- a separate thread.)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
THE METHOD LESSON, WHICH COST MORE THAN THE BUG
|
||||
--------------------------------------------------------------------------------
|
||||
The rig's confs run BTL4REC.EXE out of the shared mount; `build410.sh link`
|
||||
writes build410/btl4opt.exe. Nothing connected the two. Three consecutive
|
||||
measurements ran a binary from 11:02 while I believed I was testing 11:05,
|
||||
11:13 and 11:32 builds -- so a change that "did nothing" three times was in
|
||||
fact never tested, and I discarded the CORRECT first hypothesis (the roster)
|
||||
on that evidence and went hunting through generator lamps, blit primitives
|
||||
and port tables instead.
|
||||
|
||||
Now enforced: emulator/render-bridge/gauge-ab/ab.sh re-stages the exe on
|
||||
every launch and kills any running DOSBox first (two instances share OUT.TXT
|
||||
and interleave the trace). Launch the rig no other way.
|
||||
|
||||
The earlier note in this file still stands and now has a fifth data point:
|
||||
for anything cockpit-visual, measure before reading code. Five suspects came
|
||||
from source inspection -- PilotList slot coords, PlayerStatus off-by-one, the
|
||||
generator TwoState lamp polarity, the shared DrawBitMap primitive, and the
|
||||
Plasma port table -- and every one of them was killed by an instrumented run
|
||||
that took four minutes. The generator lamps in particular looked airtight
|
||||
until the widget map showed they draw on port 0, nowhere near the pixels.
|
||||
|
||||
NEXT ON THIS HEAD-BY-HEAD LADDER: the three MFD quadrant heads (Mfd1/2/3),
|
||||
which now hold essentially all of the remaining difference -- ~22.6K missing
|
||||
and ~12K extra between them, against ~0 on the engineering heads. Those are
|
||||
the SubsystemCluster quadrant panels ([cluster] port=2/4/6 in the map).
|
||||
|
||||
@@ -2734,6 +2734,14 @@ void
|
||||
//-------------------------------------------------------------
|
||||
// Execute the call
|
||||
//-------------------------------------------------------------
|
||||
// COCKPIT WIDGET MAP (env BT_VIS_LOG): the single dispatch every
|
||||
// gauge widget is built through -- name, port and authored position.
|
||||
// This is the map that turns an A/B pixel difference into a widget.
|
||||
if (getenv("BT_VIS_LOG"))
|
||||
DEBUG_STREAM << "[w] " << name
|
||||
<< " port=" << display_port_index
|
||||
<< " at " << position.x << "," << position.y
|
||||
<< "\n" << flush;
|
||||
Check_Pointer(execute);
|
||||
# if DEBUG_LEVEL > 0
|
||||
Logical
|
||||
|
||||
Reference in New Issue
Block a user