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
+14 -6
View File
@@ -1466,8 +1466,9 @@ SubsystemCluster::SubsystemCluster(
engPort, 0x254, 0x13b, "bteloop.pcc", 7, 1, subsystem_in, "CoolingLoop");
modeLamp = new OneOfSeveralInt(ChildRate(), eng_mode, renderer_in, // @004c5148
engPort, 400, 10, "btecmode.pcc", 1, 2,
(int *)((char *)subsystem_in + 0x134), "OneOfSeveralInt"); // BEST-EFFORT raw offset
engPort, 400, 10, "btecmode.pcc", 0xff, 0, 1, 2, // colours byte-verified:
(int *)((char *)subsystem_in + 0x134), "OneOfSeveralInt"); // part_014.c:1479-1481
// (+0x134 = coolantAvailable)
if (inputVoltage == NULL)
{
@@ -1488,9 +1489,16 @@ SubsystemCluster::SubsystemCluster(
// stateLamp gate: IsDerivedFrom 0x50f4bc = POWEREDSUBSYSTEM (#47 correction --
// the old 'Generator' label was wrong; every powered subsystem passes).
if (IsGeneratorDerived(subsystem_in))
// bg=0xff fg=0 -- byte-verified from the binary's own call site
// @004c866c (`push 3 / push 1 / push 0 / push 0xff / push
// "btemode.pcc"`). These were previously dropped and hardcoded 0/0
// inside the ctor, which painted the lamp in colour 0: the connect
// mode cycled correctly but nothing was ever drawn, so Auto looked
// like it did not exist.
stateLamp = new OneOfSeveralStates(ChildRate(), eng_mode, renderer_in, // @004c5470
engPort, 0xbe, 0, "btemode.pcc", 1, 3,
(Entity *)((char *)subsystem_in + 0x2b8), "OneOfSeveralStates"); // BEST-EFFORT raw
engPort, 0xbe, 0, "btemode.pcc", 0xff, 0, 1, 3,
(Entity *)((char *)subsystem_in + 0x2b8), "OneOfSeveralStates"); // +0x2b8 = modeAlarm
// (offset static_asserted)
else
stateLamp = NULL;
}
@@ -1748,7 +1756,7 @@ MyomerCluster::MyomerCluster(
(AttributeAccessor *)&seekValue, "edestryd.pcc", False, "EngrGraph");
seekStepLamp = new OneOfSeveralInt(ChildRate(), eng_mode, renderer_in, // @004c5148
engPort, 0xf, 0, "bteseek.pcc", 1, 4,
engPort, 0xf, 0, "bteseek.pcc", 0xff, 0, 1, 4, // colours byte-verified: part_014.c:2146-2147
// gauge-complete wave: 0x800 was OOB for a 0x358 Myomers (garbage) -> the seek
// gear step is Myomers::currentSeekVoltageIndex@0x320 (the analog of the
// EnergyWeaponCluster's Emitter::seekVoltageIndex@0x3f0; same 1..4 semantics).
@@ -1934,7 +1942,7 @@ EnergyWeaponCluster::EnergyWeaponCluster(
"edestryd.pcc", True, "EngrGraph");
seekStepLamp = new OneOfSeveralInt(ChildRate(), eng_mode, renderer_in, // @004c5148
engPort, 0xf, 0, "bteseek.pcc", 1, 4,
engPort, 0xf, 0, "bteseek.pcc", 0xff, 0, 1, 4, // colours byte-verified: part_014.c:2146-2147
(int *)((char *)subsystem_in + 0x3f0), "OneOfSeveralInt"); // BEST-EFFORT raw
}