four ENG-page lamps were invisible, not missing: the ctors dropped their colour parameters

Playtest report: "generators don't seem to have an Auto setting" / "no auto mode
is ever displayed", plus the operator's own memory that the BUS MODE button once
stepped through three states.  The state machine was NEVER wrong: @004b0abc has
exactly two branches (<2 -> Auto(2); ==2 -> detach + Off(0)), Manual(1) is set
only by the four SelectGenerator buttons, and the auto-hunt gates on ==2 -- all
byte-verified, and the #62 re-attach fires live.  Manual is a one-way door out
of BUS MODE by design: a single cycle button cannot know WHICH generator manual
should mean.  Verified on-screen by the operator this session: A-D returns to
manual, BUS MODE toggles auto/off thereafter.

What was actually broken: the connect-mode lamp -- and three siblings -- never
drew a pixel.  OneOfSeveralStates (@004c5470) and OneOfSeveralInt (@004c5148)
forward caller-supplied background/foreground colours in the binary; the recon
dropped both parameters and hardcoded 0,0 into the base.  A BitMap-strip lamp
draws SetColor(bg) + DrawBitMapOpaque(fg,...), so 0/0 painted colour-0 on
colour-0: invisible.  Affected: btemode (connect mode, 1x3 -- THE report),
btecmode (coolant on/off, 1x2), and both bteseek gear-step lamps (1x4).  The
cluster call sites pass 0xff/0, byte-verified (@004c866c disasm;
part_014.c:1479-1481, :2146-2147).  Same dropped-element family as issue #42's
MoveToAbsolute.

THE TRAP THAT WAS NOT LANDED, recorded so it stays unlanded: with the lamp
first made visible, the frames appeared inverted against the levels (art reads
AUTO/MANUAL/OFF top-down; levels run OFF/MANUAL/AUTO), and a
row=(rows-1)-selected "fix" was proposed.  An adversarial workflow proved it
wrong: the gauge blit addresses SOURCE rows BOTTOM-UP
(Video16BitBuffered::DrawBitMapOpaque, L4VB16.cpp:3846-3850 -- sTop =
map_max_y - sTop, rows walked upward), the 1995 blit @0046bdfc performs the
identical flip, and the vertical strips are AUTHORED bottom-up to match.
Identity level->row therefore draws the pod-correct display; the inversion
would have created the very bug it claimed to cure.  Two of five investigators
(and the first human pass) assumed top-down; the engine source overruled all
three.  Convention + warning now recorded in context/gauges-hud.md.
btecmode doubles as the standing tripwire: a second vertical strip that must
show ON when coolant is available -- if it ever reads inverted, the bottom-up
verdict is falsified.

Also corrected: @004c552c is OneOfSeveralStates' EXECUTE override (clamp >= 0,
chain the base draw) -- the port had the body on BecameActive under a wrong
label; vtable-diffed against OneOfSeveral (0x518b24 vs 0x518bf0).  BecameActive
is inherited.  Behaviorally inert today (the state source never goes negative),
byte-faithful now.

README: the "KNOWN ISSUE -- automatic re-attach is not working yet" text is
replaced with how power routing actually works (A-D = manual, BUS MODE = auto
then off, two presses off / one back to auto).  That text shipped in 600, which
already contained the #62 fix -- players were being TOLD auto was broken while
it worked, which is half of how a painted-out lamp became "no auto mode".

Diagnostic kept: BT_GENSEL_TEST=<id> now drives any of the five power-routing
message ids (4..8, default 7) and pulses four times, so the whole mode cycle is
observable headlessly under BT_FIRE_LOG ([gensel] lines).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-07-28 13:22:21 -05:00
co-authored by Claude Opus 5
parent c32d02b3cc
commit 901cf1b459
6 changed files with 130 additions and 26 deletions
+16 -1
View File
@@ -5518,12 +5518,27 @@ void
// task #12 scripted verify (BT_GENSEL_TEST=1): one SelectGeneratorD
// press at frame ~2500 -- the PPC re-taps from its authored GeneratorA
// onto GeneratorD; the charging I^2R then lands on GenD ([heat-t]).
// BT_GENSEL_TEST=<id> -- the id defaults to 7 (SelectGeneratorD, the
// original task-#12 verify). Set it to 8 to drive ToggleGeneratorMode
// and watch the three-level cycle: SelectGenerator leaves the subsystem
// at Connected(1), then presses run 1 -> Auto(2) -> Manual/off(0) ->
// Auto(2) -> ... Pulsed FOUR times so the whole cycle is observable in
// one headless run ([gensel] lines under BT_FIRE_LOG).
if ((Entity *)this == application->GetViewpointEntity()
&& getenv("BT_GENSEL_TEST"))
{
static int s_gsFrame = 0;
static const int s_gsID =
(atoi(getenv("BT_GENSEL_TEST")) > 0) ? atoi(getenv("BT_GENSEL_TEST")) : 7;
++s_gsFrame;
gBTGenSelKey = (s_gsFrame >= 600 && s_gsFrame < 610) ? 7 : 0;
int pulse = 0;
for (int p = 0; p < 4; ++p)
{
const int at = 600 + p * 150;
if (s_gsFrame >= at && s_gsFrame < at + 10)
pulse = 1;
}
gBTGenSelKey = pulse ? s_gsID : 0;
}
// task #13 scripted verify (BT_VALVE_TEST=1): one MoveValve press at