Commit Graph
202 Commits
Author SHA1 Message Date
arcattackandClaude Opus 4.8 21b2bfc1c9 Issue #21: overcharge-bricked weapons -- latent ARCADE bug, deliberate divergence
The tester's ER small laser died permanently (recharge arc + ready dot dark,
no fire) after idle seek-gear clicking.  Root cause, byte-verified + headless-
reproduced -- a latent bug in the SHIPPED ARCADE BINARY:

- The charge integrates toward the GENERATOR voltage (@004ba838), not the
  gear target; "full" is only detected inside the +-1% snap window around
  seekVoltage[idx] (@004ba738), and overcharge reads ZERO (the byte-verified
  _DAT_004ba830 = 0.0 clamp).
- Toggle seek while a charge is in flight and the level can land ABOVE the
  new gear's window: rechargeLevel pins to 0, the ==1.0 Loaded test can
  never fire, and charging continues to the generator ceiling where EVERY
  gear reads overcharged.  Permanent brick; generator reselect cannot help.
- The arcade dodged it by circumstance (locked 60 fps, rare seek use); the
  port's clickable seek button hits it in seconds of casual clicking.

Fix (marked divergence in the Loading tick): overcharge counts as fully
charged -> Loaded.  The arcade's own discharge algebra says full == the
gear's seek voltage, so an overcharged weapon IS full.

Verified: the same 2.5-min BT_SEEKTEST abuse that bricked the laser pre-fix
now fires 38 overcharge rescues and ends Loaded/pct=1.  [seek] log now also
prints the per-gear voltage table + gen voltages (diagnosis instrumentation).

Ruled out on the way (documented in #21): overheat fuse (no firing occurred;
a 4-min autofire+coolant-abuse run wouldn't fuse), generator detach (gen
cycling + healthy siblings), seek-table corruption (table {6000,7000,8000,
9900} healthy).

KB: decomp-reference.md records the arcade-latent-bug analysis.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:39:56 -05:00
arcattackandClaude Opus 4.8 e9db161404 Issue #20: wire Mech BalanceCoolant (id 0x16) -- the auto-cooling-balance button
Third instance of the silent-swallow pattern (unregistered handler id ->
Receiver NullHandler).  The Mech handler table @0x50BDF8 binds
{0x16, "BalanceCoolant" -> @0049f728}; the reconstruction never registered it.

Disasm-verified body (@0049f728): press-only guard (msg+0xc > 0; the binary
has NO novice lockout on this one), set EVERY condenser's valveState@0x1D0
to 1 (equal weights), then the shared redistribute @0049f788 -- which the
port ALREADY had faithfully as BTRecomputeCondenserValves (flow =
valve/total + condenserAlarm pulse; MoveValve calls it).  So the fix is:
- mech.hpp/mech.cpp: BalanceCoolantMessageID=0x16 + handler + MESSAGE_ENTRY
- heatfamily_reslice.cpp: BTBalanceCondenserValves bridge (Condenser is a
  complete type there; sets valves to 1 + redistributes)
- mech4.cpp: BT_BALTEST scripted harness (MoveValve press alternating with
  a Balance press)

VERIFIED headless (BT_BALTEST=1 BT_VALVE_LOG=1):
  boot     -> all valves 1, flows 0.1667 each
  MoveValve-> condenser#1 valve 5, flows 0.5 / 0.1x5 (the priority boost)
  BALANCE  -> all valves 1, flows 0.1667 each (equalized)
Awaiting the tester's live ADV-mode confirmation.

KB: decomp-reference.md Mech-table row marked wired.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 14:31:46 -05:00
arcattackandClaude Opus 4.8 4aab10ba89 Issue #19: wire ToggleSeekVoltage (Myomers id 9 + energy weapons id 0xb)
Root cause -- NOT issue #2 (the experience mis-seed pins everyone to VETERAN,
which PASSES the novice lockout; the gates were never the blocker).  Two
unwired handlers, the same silent-swallow pattern ToggleCooling had
(Receiver::Receive -> NullHandler for an unregistered id):

- Myomers (handler table @0x51158C {9,"ToggleSeekVoltage"@004b8a48}): the
  reconstruction had an EMPTY stub (the address falls in the untagged decomp
  gap 0x4b8837..0x4b8a8c) and never registered the id.  Body reconstructed
  faithfully from the raw disassembly (tools/disas2.py 0x4b8a48): novice
  lockout (@4ac9c8) -> heat-model gate (@4ad7d4) -> press-only (msg+0xc>0) ->
  currentSeekVoltageIndex@0x320 = (idx+1) % (maxSeekVoltageIndex@0x32C + 1).
  Modulo from zero, min index not consulted, no ResetFiringState.

- Emitter (energy-weapon table @0x511DB8 {0xb,"ToggleSeekVoltage"@004ba478}):
  the faithful body already existed as the orphaned "AdvanceSeekVoltage"
  (ZERO callers) -- converted to the registered message handler (same guards;
  cycle + ResetFiringState when not wrapping, disasm re-verified).  PPC and
  GaussRifle copy-inherit the set; the ammo branch keeps its own id 0xb ==
  EjectAmmo (still unwired, tracked separately).

Both registered via MESSAGE_ENTRY on top of the inherited chains.  Permanent
diagnostics: [seek] gear log (BT_SEEK_LOG) + BT_SEEKTEST scripted dispatch
harness (mech4.cpp, the cooltoggle pattern).

VERIFIED headless: [seek] Myomers -> 3,0,1,2,3... and [seek] PPC_1 ->
3,0,1,2,3... (modulo-4 wrap, matching the binary's arithmetic).  Awaiting
the tester's live ADV-mode confirmation for the real MFD button route (the
routing layer is the same streamed-EventMapping path ToggleCooling verified).

KB: decomp-reference.md message-table rows marked wired.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 12:57:18 -05:00
arcattackandClaude Opus 4.8 1841c5575e KB: fix the convention line (BT_STEAM is the one compile-gate exception)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:40:24 -05:00
arcattackandClaude Opus 4.8 d53fcf61ad BT_STEAM restored as THE compile gate (license) -- default OFF
The unification flattened BT_STEAM into the always-on exe; the user flagged
the constraint that makes it the one legitimate compile gate: the Steamworks
SDK license bars a public release with Steam inside, so a release build must
exclude it entirely.

- CMakeLists: option(BT_STEAM OFF) restored around the Steamworks block --
  an OFF configure touches no SDK headers, no steam_api.lib, no DLL copy.
  BT_GLASS stays retired (always defined); ONE build dir either way.
  Dev checkout: build/ configured once with -DBT_STEAM=ON.
- mkdist: reads BT_STEAM from build/CMakeCache.txt.  Steam-OFF zips exclude
  play_steam.bat + steam_api.dll (explicitly filtering a STALE DLL left by
  an earlier ON build), strip the README steam section ({STEAM} line
  markers), and take a -nosteam name suffix so the flavors never clobber
  (found live: both stamped 4.11.436 and the release zip overwrote the dev
  zip).

Verified both flavors:
  ON  (dev build):  steam smoke degrades to Winsock without the client;
      zip carries play_steam.bat + steam_api.dll + README steam lines.
  OFF (scratch build-relcheck, deleted after): 0 compile errors; exe dir =
      btl4.exe + OpenAL32.dll only; boots + simulates; BT_STEAM_NET=1 inert
      (transport not in the binary); end-to-end zip has zero steam bits and
      a clean README.

KB: context/glass-cockpit.md unified-build section records the license gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:40:03 -05:00
arcattackandClaude Opus 4.8 10ed66bd7c UNIFY: one exe -- retire the pod/glass/steam compile split
The three build dirs (build\ pod, build-glass\, build-steam\) were compile-
time splits of the same game; the tax was real: a stale steam exe shipped 2
days behind, the felt keymap flipped with the boot flavor, and cockpit
clicks were silently dead in the pod build (the trigger-config incident).

Now everything compiles into the single build\ exe:
- CMakeLists: BT_GLASS/BT_STEAM options removed; the glass TUs (PadRIO,
  bindings, panel, glass windows, plasma) + FE/console + Steam transport
  compile unconditionally; both macros always defined (seam markers).
- Steam: steam_api.lib linked with /DELAYLOAD:steam_api.dll + delayimp --
  the DLL maps only when SteamAPI is first called, and every call site
  gates on BT_STEAM_NET=1 (BTSteamNet_* short-circuit on steamActive), so
  machines without the DLL run everything but Steam sessions.
- CABINET GUARD (btl4main): the miniconsole menu previously claimed any
  zero-arg launch -- unified, that would hijack the pod cabinet's boot
  shape.  The menu now requires BT_PLATFORM=glass (or BT_FE_MENU=1);
  platform parse moved above the fe_menu_mode computation.  A bare boot
  behaves exactly like the old pod build (DEV profile, wait for egg).
- Bats (play_solo/join/join_lan/play_steam) + the btoperator.py template:
  single build\ path + BT_PLATFORM=glass.  mkdist: one exe (+DLL scan
  picks up OpenAL32 + steam_api).  build-glass\/build-steam\/build-glass2\
  (a stale Jul-20 cache) deleted.

Verified matrix on the unified exe:
  bare zero-arg boot == old pod exe (same DEV profile + egg-load path;
    the segfault on a missing egg is pre-existing, baselined vs the zip exe)
  glass + BT_BTNTEST scripted click -> [cfgmap] ENTER/EXIT (trigger config)
  runs with steam_api.dll ABSENT (delay-load proven)
  glass zero-arg -> miniconsole menu
  2-node loopback MP: 145 ticks each, cross-connected
  BT_STEAM_NET=1 without Steam client -> "staying on Winsock", game runs

KB: context/glass-cockpit.md compile-gates section rewritten for the
unified model.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:28:51 -05:00
arcattackandClaude Opus 4.8 0e013fd82d KB: trigger-config + keymap unification human-verified
context/glass-cockpit.md: record the BT_PLATFORM=glass/PadRIO click gating
(the dead-clicks root cause), the verified live regroup loop, the [cfgmap]/
BT_BTNTEST diagnostics, and the one-keymap settlement (babf5b1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 09:41:37 -05:00
arcattackandClaude Opus 4.8 89b4f53046 Gauges: trigger-config joystick is LIVE -- @004c6ee0 is LinkToEntity, not SetColor
The weapon panel's btjoy trigger-config display (ConfigMapGauge) never rendered
because the reconstruction labelled @004c6ee0 as "SetColor(int)" and, finding no
caller, concluded the gauge was authentically dormant (task #6), gating it
behind a BT_CONFIGMAP dev env.

The user challenged this (the PROGRAM/TRIGGER CONFIG button implies the display
visualizes your config).  Re-verification found the old analysis failed twice:
the "no caller" search looked for direct calls to a VIRTUAL, and its slot math
used the wrong vtable copy.  The real ConfigMapGauge vtable @0051a1b8, matched
against the T0 GaugeBase virtual roster (GAUGE.h), pins @004c6ee0 at slot 9
(+0x24) == GaugeBase::LinkToEntity, and @0x94 is the linkedEntity -- the
Execute gate is "not yet linked", not "disabled".  The engine broadcasts
LinkToEntity(viewpointEntity) to every gauge at viewpoint bind (APP.cpp:1277 ->
GaugeRenderer::LinkToEntity GAUGREND.cpp:3011), arming the gate -- the joystick
DOES render in the shipped game, showing per-trigger mapping state (solid =
mapped, matching the DOSBox reference).

Port fix: SetColor(int color) -> LinkToEntity(Entity*), color -> linkedEntity,
BT_CONFIGMAP dev enable deleted (the authentic path lights it).  Render-verified
with no env override: the joystick + mapped-trigger lamp draw on weapon panels.

KB swept per the correction mandate: gauges-hud.md, decomp-reference.md,
open-questions.md, GAUGE_COMPOSITE.md, VEHICLE_SUBSYSTEMS.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 08:49:58 -05:00
arcattackandClaude Opus 4.8 77cc62886f KB: correct the HorizTwoPartBar colour map (green tile, black remainder)
context/gauges-hud.md: record [this+0xA0]=fillColor(green)/[this+0xA4]=
backgroundColor(black), the zone colours, and the swapped-colour bug that
rendered the remainder green. Commit 4fbc911.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 08:19:29 -05:00
arcattackandClaude Opus 4.8 2e9c1c7c0d KB: document the HorizTwoPartBar tiled-pattern fix
context/gauges-hud.md: new section on the TEMP/STATUS bar's three-zone tiled
render (@004c4340) and the port bug where Execute solid-filled from warnPix and
ignored the tile image. Commit 4d4436e.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 08:04:07 -05:00
arcattackandClaude Opus 4.8 cfc7b27ba3 KB: document the ammo-count-stencils-out-of-dot fix
context/gauges-hud.md: new section on BallisticWeaponCluster::DrawWarningLamp
(@004c9b50) swapping the ammo NumericDisplayInteger colours by the fire-ready
dot state (green-on-black when absent, black-cut-out when present), and the
port bug where only the non-virtual base existed. Commit 66dcdf2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 00:45:06 -05:00
arcattackandClaude Opus 4.8 f2eaf3d0ac Gauges: convert generator-voltage bar to named-member bridge
The eng-page generator-voltage bar (evolt.pcc, ScalarBarGauge @004c721c) and
the MyomerCluster seek-voltage graph were fed by GeneratorVoltageConnection,
which walked the same dead path as the generator-letter lamp:
ResolveLink(AttributePointerOf(subsystem,"InputVoltage")) -> read resolved
+0x1DC == Generator::outputVoltage. The BT_DEV_GAUGES audio attribute rows
shifted the chained attribute ids so AttributePointerOf no longer lands on
voltageSource@0x1D0 -> the bar always read 0.

New BTGeneratorVoltage(subsystem) bridge (powersub.cpp) reads the NAMED member
via PoweredSubsystem::ResolveVoltageSource()->Generator::MeasuredVoltage()
(outputVoltage@0x1DC), bypassing the attribute table -- same pattern as
BTPowerSourceFrame/BTCoolingLoopFrame. Both GeneratorVoltageConnection callers
(the ScalarBarGauge param + the MyomerCluster seekValue) now pass subsystem_in.

[voltfeed]-verified: Myomers -> 10000V from GeneratorD (was 0 on the dead
path). Both pod + glass builds clean. Eng-page render awaiting live playtest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 00:21:41 -05:00
arcattackandClaude Opus 4.8 8c7ff639de KB: mark per-weapon loop/generator lamp fix human-verified
User confirmed live (pod build, solo ARENA1 cockpit) that the loop-number
and generator-letter boxes render correctly on the weapon MFD panels.
Fix commit e634709.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 00:14:38 -05:00
arcattackandClaude Opus 4.8 0477802971 KB: document the 3 per-weapon loop/generator lamp databinding bugs
context/gauges-hud.md: new section recording the color-drop + shadow-field +
attribute-table-shift fixes for the btploop/btpbus lamps (commit e634709),
with the lesson that gauge value feeds must read named members through a
complete-type bridge, not AttributePointerOf+ResolveLink. Notes the eng-page
GeneratorVoltageConnection as the remaining sibling on the old path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 00:09:48 -05:00
arcattackandClaude Opus 4.8 c79b9953f9 KB: ToggleCooling routing confirmed wired (the #2 audit)
The #2 audit (BT_CTRLMAP_LOG) proves the Eng-page button->msg-3 route was always
authored (121-mapping L4 ControlMappingsList, ~a dozen EVENT msg 0x3 entries;
weapons via Eng-page aux elem 0x21 mask-gated per ModeMFD page).  It was "silently
dead" ONLY for lack of the id-3 handler, which the prior commit wired -- so coolant
priority via the MFD "Display -> Coolant" button now works end-to-end.  (The
mask-0xffffffff msg-3 entries at 0x12/0x14 go to ThermalSight/Searchlight on the
PowerWatcher branch -- separate, untouched, no collision.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 23:07:30 -05:00
arcattackandClaude Opus 4.8 cbc5ff9532 Wire ToggleCooling (msg 3): restore the coolant on/off button + handler chain
Coolant priority (Lynx: weapon MFD -> Display -> Coolant) was unreachable: the
ToggleCooling handler (@004ad6f8, HeatableSubsystem table @0x50E41C id 3) was
undefined AND the handler chain skipped it -- PoweredSubsystem::GetMessageHandlers
chained straight to the Receiver root, bypassing HeatSink/HeatableSubsystem, so a
weapon's dispatch never saw id 3.

Reconstructed ToggleCooling from the disassembly (tools/disas2.py 0x4ad6f8): a
per-subsystem coolant on/off TOGGLE -- novice-locked (owner->BTPlayer->
roleClassIndex+0x274==0, the same guard as MoveValve), press-only, then flip
coolantAvailable(+0x134) 0<->1 and coolantFlowScale(+0x15C) 0.0f<->1.0f.  Cutting a
system's cooling frees the shared loop for the rest -- the emergent "coolant
priority" (the mechanism is a toggle, not a multi-level cycle).

- heat.hpp / heatfamily_reslice.cpp: HeatSink::ToggleCoolingMessageHandler (id 3)
  + HeatSink::GetMessageHandlers.  Registered at HeatSink (the abstract
  HeatableSubsystem never instantiates; every concrete heatable subsystem is a
  HeatSink, where the coolant fields live) -- identical coverage to the binary's
  base-table registration, no downcast.
- powersub.cpp + Condenser/Reservoir: chain their handler sets through
  HeatSink::GetMessageHandlers so id 3 reaches every heatable subsystem.
- mech4.cpp: BT_COOLTOGGLE_TEST scripted inject (dispatch id 3 to the first weapon)
  for verification.

Verified (BT_COOLTOGGLE_TEST + BT_COOL_LOG, expert egg): "[cool] PPC_1 ToggleCooling
reached" then coolant OFF(flow 0.0) <-> ON(flow 1.0) each press -- chain routes,
handler toggles, novice guard honored.  Both build/ + build-glass/ compile clean;
existing ids 4-8 unaffected (found before id 3).  NEXT: the #2 MFD button routing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 23:04:04 -05:00
arcattackandClaude Opus 4.8 89ddd49283 Add [ammo] fire trace (BT_AMMO_LOG); reproduces the missile FailureHeat->NoAmmo brick
Playtest: "lost missiles mid-fight, no pips, sad noise -- out of ammo?"  The default
build didn't trace ammo, so it couldn't be answered from the log.  Added a [ammo]
trace (env BT_AMMO_LOG) at the four ProjectileWeaponSimulation decision points --
FIRED+rounds-left, gate1/gate2 dry-transition edge, dry-fire blip, and denial -- plus
a public AmmoBin::GetAmmoCount() const getter.

Reproduced the actual cause: NOT ammo depletion.  The SRM6 trips gate 1 FailureHeat
(heatLvl=2) after ~5 volleys and latches into the permanent NoAmmo roach-motel with
19 rounds still in the bin:
  [ammo] SRM6_2 FIRED, rounds left=19
  [ammo] SRM6_2 -> NoAmmo (gate1): destroyed=0 failHeat=1 heatLvl=2 mechDisabled=0
Logged as the concrete instance of the heat-economy open question (open-questions.md).
Trace-only; no behavior change (all output gated on BT_AMMO_LOG).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 21:59:46 -05:00
arcattackandClaude Opus 4.8 129b27499d Cockpit surround: composite the gauges around the 3D view (default desktop layout)
Under BT_DEV_GAUGES the DEFAULT is now the pod-faithful cockpit surround: the 3D
world CENTERED with the six gauge surfaces composited AROUND it in the single main
window at 1/2 native scale, plus clickable RIO button lamps. Iterated as a visual
reference with the user, then landed in the engine.

- L4VB16.cpp/.h: BTCockpitLayout + BTCockpitComputeLayout (layout single source of
  truth, computed from the backbuffer canvas); BTDrawCockpitPanels (band fill ->
  button lamp quads -> 9-entry surface loop, phosphor-green mono / amber palette
  sec, Eng-sibling slot map -> GDI-atlas flight labels; D3DSBT_ALL state-block
  isolation = the floating-rocks trap); BTCockpitMouseDown/Up (client->bb hit-test
  + glass press/release/right-latch contract); BTApplyWorldViewport + DevGaugeDocked
  cockpit branches; shared BTLampBrightnessOf inline. Hooked at the top of
  BTDrawGaugeInset (before BT_SHOT, so shots include it).
- btl4main.cpp: glass preset (cockpit default, per-display panels opt-in); mode
  resolution -> gBTGaugeCockpit with work-area-clamped window sizing (-res = view
  size); WndProc WM_L/RBUTTON routing.
- L4VIDEO.cpp: BTWorldAspectOf cockpit branch (view rect on-screen aspect).
- L4VIDRND.cpp: CameraShipHUDRenderable::Render made viewport-relative.

Buttons = the L4GLASSWIN address banks x0.5 (Heat 0x2F, Mfd2 0x27, Comm 0x37, Mfd1
0x0F, Mfd3 0x07 red; radar rails 0x10-0x1B yellow; flight 0x38-0x47 blue, labeled).
Full rect is the hit target; the surface draws over it so only the lamp edge shows.
Precedence: BT_GLASS_PANELS=1 stands cockpit down > BT_COCKPIT=1 > _WINDOW/_DOCK
opt-out > cockpit default; BT_COCKPIT=0 = dock-bottom. Green tunable via
BT_COCKPIT_TINT. Renders in ALL builds; only the PadRIO click/lamp seam is
BT_GLASS-gated.

Verified (awaiting playtest): both build/ + build-glass/ compile (0 error C);
glass BT_SHOT matches the mockup; 500-click storm during a live mission survived
(Gitea #18 Receiver-gap fix holds); BT_COCKPIT=0 dock-bottom unregressed;
BT_GLASS_PANELS=1 stands cockpit down; pod build renders the panels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 20:39:34 -05:00
arcattackandClaude Fable 5 5ba5697a08 Fix glass panel dead-button crash: null-init MessageHandlerSet gap slots (Gitea #18)
Clicking a per-display glass Engineering panel button (0x21 on an Eng page)
hard-crashed via a wild call through an uninitialised pointer (eip=cdcdcdcd
debug / 0x01048748 release), zero btl4 frames above Receiver::Receive.

Root cause is the dense message-handler-table gap hazard (reconstruction-
gotchas #11), the message-side twin of the attribute-table one -- NOT a /FORCE
unresolved external (the glass link log carried only the known-benign mech3.obj
CreateStreamedSubsystem set). Receiver::MessageHandlerSet::Find(id) returns
messageHandlers[id-1].entryHandler for any id <= entryCount with no populated-
check, and Receive() calls it when != NullHandler. The streamed Eng-page .CTL
dispatches subsystem msg id 3/0xb to whatever subsystem is shown; a weapon
(Emitter) registers only PoweredSubsystem 4-8 + MechWeapon 9-10, so id 3 is a
gap below entryCount 10. Build did new HandlerEntry[entryCount] and left gaps
uninitialised -> (this->*garbage)(msg). The 1995 binary has the identical non-
zeroing new[] (part_002.c Build) and survived only on fresh-heap-zero luck: a
weapon receiving id 3 was always meant to IGNORE it (id 3 = a Condenser/
Reservoir action in a different subsystem branch; the uniform Eng-page button
template makes buttons for unimplemented actions authentically inert). The new
per-display windows just made the id reachable live.

Fix (engine, class-wide, faithful): Build now null-inits every slot
(entryID=0/entryName=""/entryHandler=NullHandler) before copying inherited /
placing supplied, so a gap is deterministically NullHandler -> Receive drops it
(the authentic "Receiver ignores unhandled messages"), with an empty never-NULL
name so the name-based Find() strcmp can't deref a gap. Correct dispatch
contract, not a glass-path guard -> protects the whole dead-button class (#14).

Also lands the [glasswin] CLICK crash-forensics log (names the button before
dispatch).

Verified under cdb: click-soaked every MFD bank (Engineering/L+R Weapons/Heat/
Comm, ~130 clicks through Quad<->Eng page cycles) -> zero AVs, reconstructed
mapper preset selects still fire ([mode] preset (1,1)/(2,1)...); pod build +
solo mission un-regressed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 15:59:37 -05:00
arcattack bc5ac3a0db Merge glass-per-display-windows: per-display cockpit windows (Cyd, BT_GLASS_PANELS)
One window per pod display (Heat/Engineering/Comm/Weapons x2/radar +
Flight Controls), each carrying its surface with its RIO button bank
arranged pod-faithfully; CPU-expanded surfaces, buttons under the
imagery.  Runtime gate BT_GLASS_PANELS (=0 = the legacy single panel).
Merged on top of today's landed fix pile (audio-crash, experience,
parallax); the L4PADRIO overlap with the gait-detent commit resolved
preserving BOTH (his window refactor + our at-rest band snap).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

# Conflicts:
#	context/glass-cockpit.md
2026-07-20 14:52:38 -05:00
arcattackandClaude Fable 5 70eea6c1a4 Boresight parallax FIXED + the #4/#5 verdict instrumentation (Gitea #16, #4, #5)
PARALLAX (#16): the pick/fire ray was anchored at mech.y+5.0 (a port
improvisation) while the sight line ran from the eyepoint (y=6.23) --
two parallel rays whose constant offset grew into the reported low-miss
as range closed (measured ry +0.072 @50u -> +1.54 @2.7u).  The decomp's
sight and pick share the eye origin (HudSimulation @4b7830 chain).  Fix:
the viewpoint mech's cockpit eye owns the aim-camera publish in BOTH
views, origin = its own eye translation; leveling + deliberate elevation
untouched; chase view now converges to cockpit ballistics (V cannot
change where shots land).  After: pick pinned to the crosshair (ry <=
5e-6) from 50u to point-blank; 26 laser + 8 missile center-mass hits at
3/4-screen.  Awaiting the reporters' approach-test.

VERDICT INSTRUMENTATION (#4 closed authentic, #5 verdict posted):
BT_RANGE_LOG per-frame pick tracing + BTGroundRayHitExact analytic
cross-check (0/8000 arena fall-throughs; cavern 6/8400 single-frame
grazes -- the 'crazy sliding' is the authentic world-pick + 500 m/s
slide over depth discontinuities); BT_AUD_TAIL StopNote/fade timing +
BT_FIRE_PULSE single-shot driver (the energy 'buzz' is the AUTHORED
charging loop: crescendo through recharge, 1.309s authored release,
measured within one frame).  CAVERN.EGG: solo cavern test egg.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 14:51:54 -05:00
arcattackandClaude Fable 5 d7158f1f82 Experience levels WIRED: the egg field drives the simulation tier (Gitea #2)
BTPlayer's master ctor now seeds the flag block from
btMission->ExperienceLevel() + AdvancedDamageOn() (accessors from the
SURVIVED 1995 BTMSSN.HPP [T0]) instead of the NULL-stubbed scenario
role -- the egg experience knob works for the first time.  Both switch
slot errors fixed vs the binary @4c0bc8; phantom btMission duplicate
member removed (+0x1f8 IS Player::playerMission).

SEVEN gate stubs unified onto the real flags (two found empirically):
HeatModelActive, OwnerAdvancedDamage, LiveFireEnabled,
ControlsAllowLights, powersub @4b0efc, emitter's file-local FUN_004ad7d4
+ FUN_004ac9c8 stubs.  FUN_004ac9c8 family swept (incl. a live raw-offset
databinding trap in MechSubsystem::IsDamaged).  Flag block renamed to its
true semantics (simLive/heatModelOn/...), scoring-era names kept as
comments.  Includes the #5 [aud-tail] diag in emitter.cpp.

ALL 19 shipped eggs say experience=expert -> default behavior UNCHANGED
(per-tier verified: novice/standard = no heat/jams, authentic; veteran/
expert = today's exact behavior).  Real delta: egg advancedDamage=1 now
reaches player+0x264/0x268 (was hardwired 0).  Follow-up flagged: the
glass FE defaults empty experience to veteran (one tier below shipped).

Awaiting human verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 14:51:35 -05:00
arcattackandClaude Fable 5 a0cec48e3f Death-crash FIXED: 25-voice explosion preset overflowed the 5-slot audio SourceSet (Gitea #12)
The weekend's crash family root-caused under cdb: SourceSet.sources[5]
receives the AllExplosion death preset's 25 streamed voices --
RequestAudioChannels wrote 20 OpenAL source ids past the array, smashing
the neighbouring heap object (Release: a vtable overwritten with a source
id -> delayed silent AV at AudioControlEvent::Send; Debug: CRT heap-
corruption abort in the death Explosion's audio teardown).  Explains all
three #12 crash flavors (solo enemy kill -- stack-confirmed; MP self-
death; MP peer PEER_DOWN cascade -- same generic teardown).

Fix: sources[] sized to AUDIO_SOURCESET_CAPACITY=25, static_assert
lockstep with MAX_PRESET_SAMPLES, + defence-in-depth clamps at the ctor
and acquisition sites.  Soak: 26+ deaths across glass AND pod builds
under cdb, zero faults, full wreck lifecycle every time.  Gotchas S21:
the fixed-array-vs-streamed-count overflow class + sweep note.

Awaiting human verification: the MP death-and-survive session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 14:51:35 -05:00
CydandClaude Opus 4.8 f338595685 Glass cockpit: per-display windows (BT_GLASS_PANELS)
Break the desktop glass cockpit's secondary displays out of the single
combined pad panel + D3D gauge strip into ONE window per pod display, each
carrying that display's surface with its RIO button bank, arranged
pod-faithfully around the main view.  New TU engine/MUNGA_L4/L4GLASSWIN.*
(BT_GLASS-only), selected by the -platform glass preset via the new runtime
gate BT_GLASS_PANELS (=0 falls back to the legacy single panel + dock).

- Surfaces are CPU-expanded from the shared gauge buffer
  (SVGA16::ExpandPlaneToBGRA, no D3D) and StretchDIBits'd in -- the D3D
  dev-composite path (dock / window / overlay) stands down while active.
- Buttons sit UNDER the imagery: big hidden click targets that reach into
  the display, with only a small protruding edge showing as a lamp light.
  Red MFD banks tile the top/bottom; radar rails run the sides + a bottom
  row; a Flight Controls window hosts the no-display banks.
- Windows: Heat / Engineering / Comm / Left Weapons / Right Weapons / radar
  + Flight Controls; edge-to-edge (no in-client margin/title; OS caption
  labels each).
- Fix blank surfaces: application/ghWnd are duplicate-defined and bind
  non-deterministically under /FORCE, so the fresh L4GLASSWIN TU read NULL.
  Reach the renderer/window via BTResolveGaugeRenderer/BTResolveMainWindow
  defined in btl4main.cpp off the real btl4App/hWnd pointers.  New gotcha:
  reconstruction-gotchas.md S6 duplicate-GLOBAL corollary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 14:43:40 -05:00
arcattackandClaude Fable 5 6ad1e074cc Glass lever: port the pod-build GAIT DETENT to the PadRIO throttle slew
Human re-test on the fresh build: zero-stop OK, but the lever could PARK inside
the walk/run hysteresis band (walkStrideLength@0x534 .. reverseSpeedMax@0x538)
where the gait SM has no stable state -- walk<->run hunt, EngineShiftFwd/Rev
retriggering ("mixed walk/run mode + repeated acceleration sample").  The
pod-build keyboard lever already snaps out of the band to the NEARER edge at
key-rest (mech4.cpp GAIT DETENT); the PadRIO slew channel lacked it.

Faithful port, not an invention: mech4.cpp publishes the band in lever units
every driven frame (gBTGaitDetentLo/Hi seam, same guards + the same +0.002
engage margin); L4PADRIO Poll() applies the identical nearer-edge snap to the
Throttle channel when the slew rests (no slew key held, no pad slew axis
deflected).  Sweeping through the band while held stays continuous (the
authentic moving lever, one authentic shift).

Verified (DEV.EGG glass, graduated parks each ~0.10 of the sweep,
BT_GAIT_TRACE+BT_MPPR_TRACE): band [0.358026,0.50388) = demand 22.02-30.87;
in-band parks snap both directions (0.4137->lo, 0.465126->hi); no at-rest
sample in the band; exactly ONE walk->run clip (state 10) up and ONE run->walk
(state 14) down across the whole sweep; zero-stop unaffected; pod build
rebuilt + BT_AUTODRIVE smoke clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 10:57:34 -05:00
arcattackandClaude Fable 5 1535e3e48d Glass 'keymap regression' triage: stale build-glass exe, not code — rebuild + full re-verify
The live-reported glass regressions (arrows dead / lever+detent missing /
V+J/K/L+N inert / weapons into the ground) were build-glass/Release/btl4.exe
built at 07:47 running the pre-5dd3536 image: no suppression tables, no V/J/K/L
keys, unpinned RIO ids.  content\bindings.txt already carried the merged map
(arrows = W/S lever + A/D pedals) and parsed clean.  Clean rebuild closed every
symptom; SendInput re-verification: lever holds on release + stops at zero,
LEFT/RIGHT turn in MID (wire-sign correct), V toggles with BT_SHOT pair, level
boresight at spawn, R/F elevation drive; pod build rebuilt + smoked un-regressed.

Code delta: BT_MPPR_TRACE [mppr-c] line enriched with mode/pedals/stick/turn/elev
so one env-gated line proves the whole control surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 10:36:16 -05:00
Cyd 4a38fba2c9 Merge remote-tracking branch 'origin/master' into glass-cockpit 2026-07-20 08:59:35 -05:00
arcattackandClaude Fable 5 5dd35365c7 Glass input audit: RIO mapper id fix (panel banks live), keyboard reconciliation
Root cause of the dead on-screen panel: MechRIOMapper's message-id enum
chained off L4MechControlsMapper::NextMessageID (=0x19), registering all 18
RIO override handlers at 0x19-0x2a while the streamed .CTL EventMapping
records carry the binary's ids -- the binary RIO table @0051dd30 re-registers
the BASE aux/zoom ids 3..0x13 (Hotbox 0x1a @0051de98).  Every MFD-bank/zoom
press hit the base ConfigureMappableMessageHandler FAIL trap (26/72 buttons
dead; an abort() on a trap-armed pod).  Ids now pinned to the binary
numbering + static_assert-locked (btl4mppr.hpp) -- panel goes 26 -> 52
working buttons (8 await handler reconstruction, filed on Gitea; 12 have no
streamed mapping authored = authentically inert).

Keyboard reconciliation (glass-only): the merged map -- keyboard hosts the
~20 core gameplay actions on the CONTROLS.MAP keys, the panel covers every
pod address by click (right-click = hold latch):
- W/S throttle lever, A/D pedals, Q/E twist, R/F elevation, X all-stop,
  arrows drive; numpad flight cluster kept (Cyd)
- 1/2/3/4/Space/LCTRL/RCTRL fire, LALT reverse, B look-behind (0x41)
- M=0x18 mode cycle, N=0x15 display cycle, H=0x2C coolant flush (hold),
  C=0x2F Condenser1 valve, G=0x0E weapon-1 configure (Mfd1-Quad-gated)
- F5-F9 = Mfd2 bank 0x27-0x24 + 0x22 (page-gated gen A-D / gen mode)
- hardcoded: ` or V = view toggle, J/K/L = Mfd1/2/3 preset-page cycle
  (PadRIO edges -> gBTPresetCycle; no pod "cycle" button exists)
- PadRIO::SuppressKey: bound keys are swallowed from the typed 1995 channel
  (the btinput suppression pattern, glass side) -- ends the glass double
  dispatch ('w'=pilot select 0, 'a'..'g'=MFD2 presets, NUMPAD/F-key key-up
  VK aliases like VK_F5=0x74='t').  Unbound keys keep their authentic
  meanings (5/z presets, t-o pilot select, +/- zoom, F1/F2 align).

DISPLACED from Cyd's default bindings (each reachable on the panel or by a
bindings.txt edit -- flagging for Cyd's veto): number row -> secondary panel
(1-4 now fire), QWERTY row -> pilot keypad (dropped), arrows -> hat looks
(now drive), V/C/B -> fire 0x47/0x46/0x45 (now view/valve/look-behind),
LCTRL -> throttle-down (now fire).  XInput pad section untouched.

Audit by-products (KB updated): the always-active msg-4 records identified
(0x2C = Reservoir flush, 0x29-0x2F = condenser valves, 0x1A-0x1D =
GeneratorA-D ToggleGeneratorOnOff @004b1ed0 unreconstructed); 0x13 = Mech
DuckRequest (crouch), 0x28 = BalanceCoolant; Searchlight/ThermalSight
ToggleLamp handler-sets are default-constructed EMPTY; weapon Eng-page msgs
0x3/0xb = ToggleCooling / ToggleSeekVoltage / EjectAmmo, all unwired;
unhandled messages are SILENT (no [FAIL] -- census = BT_CTRLMAP_LOG dump x
handler tables).

Verified live (build-glass2, ARENA1): panel presets/zoom/display/flush;
W drive (speedDemand 61.5, gait advances), Q turn (BAS), M -> MID, A turn
(MID), Space fires, N display, J/K/L presets, H flush drain, C valve 1->5;
suppression ('w' swallowed, 't' flows, F5-keyup swallowed); BT_SHOT frame.
Un-regression: pod build (gates OFF) compiles 0 errors, forced-walk drives,
streamed ids unchanged; CONTROLS.MAP/btinput untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 08:35:55 -05:00
arcattackandClaude Fable 5 0d032029a0 Fix 2 (Gitea #12): gauge tree survives the in-session mech re-stream
The pre-launch host drop/rejoin re-stream re-creates every mech at mission
launch; the LOBBY-built gauge tree kept attribute pointers into the freed
first-stream mechs, every widget Execute AV'd, and the BT_DEV_GAUGES SEH
guard (Gauge::GuardedExecute, GAUGE.cpp:618) disabled each one PERMANENTLY
(rate=0, no rebind) -- the frozen dev-gauges window of incident #12.  The
authentic engine flow only tears the tree down at mission transitions
(Application::Shutdown -> gaugeRenderer->Shutdown() -> ShutdownImplementation
-> Remove(0), APP.cpp:787 / GAUGREND.cpp:3264); the re-stream bypassed it.

Fix: BTL4GaugeRenderer::TearDownForViewpointRestream() performs the
ENTITY-BOUND half of that same ShutdownImplementation sequence, in order
(gaugeAlarmManager->RemoveAllAlarms() -> Remove(0) -> moving/static entity
grid Clear), keeping the mission-scoped state (warehouse, graphics ports,
interpreter, controls-owned L4Lamps -- RemoveAllLamps would delete them
behind the buttonGroup's &lamp->automaticValue registrations).  Called from
BTL4Application::MakeViewpointEntity before ConfigureForModel("Init") on any
viewpoint RE-make -- that handler runs once per stream (the incident log's
[ctrlmap] installing x2) -- so the tree rebuilds bound to the NEW mech via
the renderer's existing lazy build.  Sentinel: "[gauge] viewpoint re-stream:
tearing the gauge tree down for rebuild".

Verified with the 2-instance relay harness (port 15600, MP_RELAY.EGG,
the incident sequence): join A (dev gauges) + B, kill B by exact PID at
WAITING FOR OPERATOR LAUNCH, rejoin B, operator launch.  Pod A re-streamed
(2x ctrlmap install, 4x zonebuild), logged the sentinel, and post-launch the
docked composite is fully live (mission clock/heading/radar advancing between
BT_SHOT frames) with ZERO [gauge-fault] DISABLED lines (the incident had 11).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 23:30:43 -05:00
arcattackandClaude Fable 5 2edde29671 Fix 1 (Gitea #12): disasm-exact ProjectileWeaponSimulation @004bbd04 + the dirty-bit sweep
Replace the RivetGun-modeled ProjectileWeaponSimulation body with the fully
recovered binary machine (capstone disasm, every branch address-cited):

- gate 1 @4bbd36: simulationState@0x40==1 (destroyed) || heatAlarm==FailureHeat
  || owner disabled -> recoil=rechargeRate + alarm 7 (was: simulationFlags==1)
- gate 2 @4bbd71: bin alarm 2/3 or bin destroyed -> alarm 7 re-pinned per frame
- Loaded @4bbec2: DENIED shots (viewFireEnable off / no owner target) blip
  SetLevel(4);SetLevel(2) and STAY LOADED, no ammo pull (the old FireWeapon
  early-returns under an unconditionally-cycling caller faked a full firing
  pip cycle -- the "missiles cycle but never launch" incident mechanic);
  the ammo pull is bin->FeedAmmo @4bbee6 in the CALLER; both updateModel|=1
  marks (@4bbf05/@4bbf4c = ForceUpdate) bracket FireWeapon; recoil set @4bbf51
- Loading @4bbdd2: recoil bleeds ONLY here at electrical Ready and CLAMPS at 0
  (it ran to -114 before); bin Loaded -> weapon Loaded
- 7 @4bbe4d: binary-faithful roach motel (re-asserted unconditionally;
  recovery = ResetToInitialState only)
- slot 17 @004b9c9c reconstructed as MechWeapon::ComputeOutputVoltage
  (rechargeLevel=(rechargeRate-recoil)/rechargeRate, Emitter overrides with
  @4ba738) and called from Loading/7 -- the launcher recharge dial ANIMATES
  (the old "authentically static" claim was wrong)

FireWeapon bodies stripped to heat+spawn only per @004bcc60 (no view gate, no
ConsumeRound, no recoil); ConsumeRound retired (not a binary method).

Gotcha #20 sweep (all sites disasm-verified): simulationFlags|=0x1 == engine
DelayWatchersFlag (audio watchers muted forever) removed everywhere --
projweap/mislanch fire marks, ProjectileWeapon::ResetToInitialState (@4bbb47
= updateModel|=1); Emitter::SetDirty retired and split per binary site into
ForceUpdate() (@4bafaa/@4ba55d) vs ExecuteOnUpdate() (@4ba99a/@4ba943);
Emitter fault gate GetFlags()==1 -> simulationState==1 (@4baab9).

AmmoBin::GetAmmoState() accessor added (named-member read of ammoAlarm@0x1A8).
BT_LOOK_TEST=<frame> scripted verify added (mech4.cpp): holds the rear-view
button 300f on/300f off to drive viewFireEnable headlessly.

Verified solo ARENA1 (bhk1, autofire): 54-60 [projectile] PUSH + impact smoke;
look-back denial window = trigger pulses, state stays 2, zero launches,
resumes on release; enemy (disabled) mech launchers pinned 7 + full recoil;
STREAK-6 recharge dial sweeps across BT_SHOT frames; lasers fire throughout;
zero gauge faults.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 23:26:11 -05:00
arcattackandClaude Fable 5 2215932016 KB: Gitea #12 incident findings -- @004bbd04 firing machine recovered, gauge-freeze root cause, dirty-bit gotcha
- decomp-reference: ProjectileWeaponSimulation @004bbd04 FULLY RECOVERED (capstone):
  fault gate reads simulationState@0x40==1 (not simulationFlags); ammo-bin gates;
  Loaded-case view/target denial = SetLevel(4);SetLevel(2) blip BEFORE FeedAmmo;
  updateModel@0x18|=1 is the replication dirty (== ForceUpdate); slot 17 @004b9c9c
  = rechargeLevel writer; FireWeapon @004bcc60 carries no gating.
- gauges-hud: CORRECTION -- launcher recharge dial is NOT authentically static
  (slot 17 @004b9c9c animates it); MP gauge-window freeze root-caused = lobby-built
  gauge tree dangling over the launch mech re-stream + permanent SEH disable.
- reconstruction-gotchas #20: updateModel@0x18 vs simulationFlags@0x28 bit 0
  (DelayWatchersFlag) mis-mapping class (Emitter::SetDirty, mislanch salvo mark).
- open-questions: #12 status ledger -- fixes pending a live-verify window; J/K/L
  preset hypothesis refuted for solo (scripted A/B); symptom-4 pip set unresolved.
- GAUGE_COMPOSITE row 13 verdict corrected (WRONG-STATIC, fix pending).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 22:46:10 -05:00
Cyd 2a8f655a13 Merge remote-tracking branch 'origin/master' into glass-cockpit
# Conflicts:
#	context/gauges-hud.md
2026-07-19 20:54:32 -05:00
arcattackandClaude Fable 5 63b168cb92 SeekVoltageGraph: full reconstruction -- the eng-page POWER graph + top-box eraser (Gitea #11)
The #10 audit's one WRONG: the port Execute was a bring-up no-op, so the
emitter/myomer engineering pages never erased their top data box -> stale
sibling-page ghosts on the shared Eng bit-plane (SYSTEM 10 PPC showing the
Streak ammo box, etc).  Full faithful widget landed:

- btl4gau2: ctor/BecameActive/Execute/clear/ticks/cursor recovered from the
  capstone disasm of @004c6798/@004c6920/@004c6934/@004c6be4/@004c6c30/
  @004c6c6c (Ghidra dropped every x87 arg).  Plot: v=0..12000 step 1200,
  x=Round(response(v)*230), y=Round(v*(1/12000)*187) (ld80 @004c6bd0/@004c6d74
  = exactly 1/12000); change-test samples the response at 12000V vs the 9999
  activation sentinel; XOR op for tick/cursor move-by-redraw; destroyed branch
  centres edestryd.pcc and revives via own vtbl+0xC (BecameActive, slot 3 of
  PTR_0051a1fc -- vtable-dump verified).
- The vtbl+0x3C sampler identified from the binary vtables: Emitter slot 15
  @004bb42c = sqrt(P(v)/2.0e8), P @004bb3f4 = damageFraction*v^2*0.5*
  energyCoefficient; Myomers slot 15 @004b8f94 = sqrt(AvailableOutput(v)*3.6/
  350).  FUN_004dd138 == sqrt (part_015.c:4026): myomers' fabs reading
  corrected, GetSpeedReading renamed SeekVoltageResponse.  Port dispatch via
  complete-type bridges BTSeekVoltageSample/BTMyomersSeekSample +
  BTSubsystemDestroyed (databinding rule).
- Emitter's AUTHENTIC attribute table recovered (binary @0x511dd4, ids
  0x1D-0x25) and published: Laser*/Seek*/OutputVoltage@0x414 (currentLevel,
  RAW volts -- the live-cursor feed).  Field renames per the table:
  0x3F8 minSeekVoltageIndex / 0x3FC maxSeekVoltageIndex (static_assert-locked).
  The MechWeapon 0x1D OutputVoltage PORT ALIAS retired (binary table ends
  0x1C; Find walks lowest-id-first, the alias shadowed the authentic row).
  PPC::DefaultData -> Emitter::GetAttributeIndex().
- mech4: BT_PRESET_HOLD=<n> (freeze the #9 preset cycler after n pulses) for
  steady-state pixel verification; BT_SEEK_LOG diag.

Verified live (BLH, autofire): both #10 repro pairs held ghost-free for
minutes (SYS09->SYS10 PPC, SYS02->SYS04 ERMed, SYS05->SYS06 Myomers); curves
draw with live cursors; one replot per activation; quad panels/J-K-L/sec
panel un-regressed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 20:27:39 -05:00
arcattackandClaude Fable 5 f1a6ccbaaf Gitea #10: systematic gauge-data audit -- 45-row verdict table + findings
docs/GAUGE_COMPOSITE.md gains the AUDIT 2026-07-19 section: every widget on the
6 pod MFD surfaces + the secondary screen + the HUD reticle audited for
binary-correct data (static pass vs decomp + two instrumented live sessions).
Verdicts: 34 CORRECT / 1 WRONG (filed) / 6 AUTH-STATIC / 6 DEFERRED-FEED.

Findings:
A. The 'SYSTEM 10 PPC ammo readout' (entry a) root-caused: header/label/screen
   mapping are CORRECT ([vss] dump == authored sub+0x1dc); the ammo box is a
   GHOST -- stale pixels from the sibling ballistic/sensor page on the shared
   Eng plane, persisting because the port SeekVoltageGraph::Execute is a
   bring-up no-op. In the binary the graph IS the top-box eraser (view =
   (0x97,0x80)-(0x17d,0x13b) @004c6798; clear @004c6be4 on the @004c6920
   activation sentinel) -- ghost-free on the pod. Full reconstruction FILED
   (4 Seek* attrs + vtbl+0x3C sampler + x87 recovery); no erase-only stand-in.
B. HUD attr table @5110c0 conflict (entry b) RESOLVED -- see commit 904c75a.
C. Entry c: no [T3] tags remain in gauges-hud.md; the single [T4]
   (reticlePosition writer) stays flagged.
D. BT_SHOT dock-capture regression found + fixed (commit 904c75a).

Live re-verifications this audit: heat digits climb (515->680 under autofire),
reservoir flush 6->0 (~1s held) + PFX cloud, ammo counter moves (24->19 after
missile autofire), heading 089->120 + speed 0->182 under BT_GOTO, secondary
dama->crit->heat cycle (masks 0x450421/0x490421/0x510421), preset cycles visit
exactly the populated page set (MFD1:1,2,4 MFD2:1-4 MFD3:1,2), mission clock,
Comm 0/0, pips/compass/tape/caret render.

Audit performed by the agent; findings await human review, fixes await human
verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 18:33:59 -05:00
arcattackandClaude Fable 5 6783619069 Gitea #9: the upper-MFD PRESET pages (3 MFDs x 5) live -- SetPresetMode
table re-decoded to the ModeMFD bits + desktop J/K/L page cycle

The preset system was unwired by ONE defect in the message layer: the
SetPresetMode @004d1b24 table @0051dbf0 had been transcribed from the
section dump as BIG-endian dwords ({0x1e,0x01000000} instead of
{0x1e,0x01}), so a preset press set a garbage high bit -- and for group 1
items 3-4 / group 2 items 0-2 stomped the LIVE NonMapping / Intercom /
ModeSecondary* bits -- while the real page bits never moved.  Ground
truth (section_dump.txt:72901-72908, little-endian + BTL4MODE.HPP [T0]):
set = ModeMFD{1,2,3}{Quad,Eng1-4} = 1<<(group*5+item), bits 0-14, fully
disjoint from the #6 secondary trio (bits 18-20); group 2 is MFD3, NOT a
duplicate of the secondary views.

What the 15 presets show (l4gauge.cfg): group = the MFD (Mfd1 lower left
/ Mfd2 upper center / Mfd3 lower right), item 0 = the btquad.pcx Quad
overview (up to 4 vehicleSubSystems cluster panels), items 1-4 = the
full-screen engineering-detail pages (bteng.pcx + prepEngr screens
group*4+1..4 + the cluster eng children: GENERATOR SELECT A-D, POWER
graph, COOLING loop, DAMAGE, ammo).  Empty screens are authored per mech
(Blackhawk: 3/11/12 empty).

Authentic dispatch (streamed "L4" .CTL EventMappings, BT_CTRLMAP_LOG
dump): each MFD owns the 8-button RIO bank around it, MODE-MASK-gated --
Mfd1 = 0x08-0x0F, Mfd2 = 0x20-0x27, Mfd3 = 0x00-0x07.  Quad page ->
direct-select buttons for the POPULATED eng pages (mapper msgs
Aux1Eng1-4 0x4-0x7 / Aux2* 0x9-0xC / Aux3* 0xE-0x11); eng page -> one
back-to-Quad button (0x3/0x8/0xD) + per-subsystem controls.  These
records already install and fire on desktop (btinput passes the live
manager mask), so the NUMPAD profile's 0x20-0x27 keys page MFD2
authentically.

Port wiring (the #6 pattern): keys J/K/L -> actions Mfd1/2/3Cycle ->
gBTPresetCycle -> L4MechControlsMapper::CyclePresetModeNow(group) -- a
documented desktop shim (24 mode-dependent pod buttons don't fit a
keyboard) that cycles Quad -> populated Eng pages -> Quad, visiting
exactly the pod-reachable set; the body is the authentic SetPresetMode.

Dev-composite: BTDrawGaugeSurfaces now draws the Eng1-3 planes at their
sibling cells and skips any mono plane whose channel is BlankColor,
honoring the mode-driven reconfigure (RemapGraphicsPort) -- each dev
cell shows the ACTIVE page like the pod monitor.  This supersedes and
removes the 2026-07-12 GAUGREND "frozen-dial" scaffold (forced all 15
page bits active under BT_DEV_GAUGES; it pinned the shared Eng plane on
the highest screen and ate the page flips).

Pixel-verified (BT_PRESET_TEST + BT_DEV_GAUGES_DOCK + BT_SHOT,
Blackhawk): all three MFDs page Quad -> SYSTEM NN eng details -> back to
Quad in lockstep with the [mode] preset mask log; group 0 skips the
authored-empty screen 3, group 2 skips 11/12.  Un-regressed: N display
cycle (0x450421->0x490421->0x510421, page bits intact), M control mode,
CONTROLS.MAP 52 bindings parse clean.

Diags: BT_MODE_LOG ([mode] preset), BT_PRESET_TEST=<frame>.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 17:30:35 -05:00
arcattackandClaude Fable 5 1d6339b226 Gitea #6: secondary MFD Damage/Critical/Heat cycling -- reconstruct the
NotifyOfDisplayModeChange override (vtbl+0x4C @4d1ae4) + wire desktop 'N'

The secondary screen's schematic selector was mislabeled: @004d1ae4 (the
bits-18..20 ModeSecondary* mask swap) was reconstructed as a non-virtual
"SetControlMode" that nothing called, so the desktop stayed pinned on the
Damage view.  The binary's L4 vtable @0051e440 pins the truth:

  +0x48 = @004d1acc  <- CycleControlModeMessageHandler (FUN_004afbe0):
          forwards to the base RET no-op @004b048c.  A BAS/MID/ADV
          control-mode change never touches the secondary view
          (empirically confirmed: BT_MODECYCLE_TEST cycles the CONTROL
          MODE lamp, mask bits 18-20 unchanged, schematic stays ARMOR).
  +0x4C = @004d1ae4  <- CycleDisplayModeMessageHandler (FUN_004afcac):
          THE Damage/Critical/Heat selector, indexed by displayMode
          (table @0051dbe4 = ModeSecondaryDamage/Critical/Heat).

Authentic pod inputs (streamed type-6 .CTL EventMappings, dumped via the
new BT_CTRLMAP_LOG EVENT records): secondary-panel button 0x15 -> msg
0x15 CycleDisplayMode (manual p13, the "'Mech status Info center" bottom
left of the secondary screen), button 0x18 -> msg 0x14 CycleControlMode
(manual p6, top right), 0x10/0x11 -> ZoomIn/Out.  The DOS keyboard
fallbacks (Keypress 0x13d/0x13e = extended F3/F4) are dead under the
WinTesla VK map, hence the desktop pin.

Port wiring (the M/ModeCycle pattern): key N / pad RightThumb -> action
DisplayCycle -> gBTDisplayCycle -> CycleDisplayModeNow() -- the same body
the pod console button message drives.  Both .MAP profiles + the
compiled-in default updated.

Verified live (docked gauges + BT_SHOT, BT_VIEWCYCLE_TEST): the sec
panel cycles ARMOR DAMAGE silhouette -> CRITICAL DAMAGE subsystem list
-> HEAT DAMAGE colored list, mask 0x450421 -> 0x490421 -> 0x510421; M
control-mode cycling un-regressed (BAS/MID/ADV lamp cycles, view pinned).

Diags: BT_MODE_LOG, BT_VIEWCYCLE_TEST=<frame>, BT_MODECYCLE_TEST=<frame>,
BT_CTRLMAP_LOG now dumps EVENT records.  KB: gauges-hud secondary-view
section rewritten, CLASSMAP +0x48/+0x4C slots, decomp-reference env
gates, GAUGE_COMPOSITE phase-4 entry resolved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 16:28:38 -05:00
arcattackandClaude Fable 5 2ae9bd43ae Coolant Flush end-to-end (Gitea #7): InjectCoolant id-4 handler + drain + FLUSH.PFX cloud + 'H' key
The playtest report (sound plays, no gauge drop, no cloud) traced to the
Reservoir's InjectCoolant chain being dead in three places:

- The handler was never REGISTERED: the binary's Reservoir handler table
  @0x50e680 has one entry {4, "InjectCoolant", @4aee70}; added
  Reservoir::GetMessageHandlers + the press/release handler (press starts
  the flush gated on coolantLevel@0x12C > 0 -- the old body misread +0x12C
  as currentTemperature; release stops it; novice lockout via FUN_004ac9c8).

- Reservoir::InjectCoolant (@4aefa4, 1019 bytes) was an empty stub -- the
  drain the coolant gauge reads.  Reconstructed in full: work list =
  condenser/weapon/heatable chains (+0x7cc/+0x7bc/+0x7ac, roster-walk
  emulation with the binary's duplicate-visit weighting; HeatSink-filtered
  [T2 guarded]) + the linked master sink; per sink with flowScale != 0 move
  squirtMass x flowScale x dt (clamped to the tank / sink capacity) and
  credit pendingHeat with the negative carried-heat delta capped at
  sinkMass x reservoir startingTemperature -- the set%-biased flush of the
  manual (p24), riding the existing heat model.

- Two latent ctor decode bugs surfaced and fixed: the master gate @4af408
  (and @4aeb40 HeatWatcher, swept) reads the OWNER MECH's simulationFlags
  (*(param_2+0x28)), not the resource's subsystemFlags (the misread left
  the CoolantSimulation Performance unregistered); and the capacity scale
  FILDs the bank's INTEGER HeatSinkCount ((float10)*(int*)(link+0x1d0)) --
  the float reinterpret gave ~1e-44 -> a permanently empty tank.
  Capacity = 0.05 x heatSinkCount x streamed CoolantCapacity (BLH: 6.0).

Visual: the binary's mode-1 coolant-effect renderable (FUN_00456a68, built
for classID 0xBC0 on "ReservoirState", part_014.c:5439; tick @part_007.c:
8780) starts psfx 19 = FLUSH.PFX ("Coolant flush", BTDPL.INI) when the
state changes to 1 -- BTSpawnFlushCloud (mech4.cpp) spawns it on the same
alarm edge as an attached emitter at torso height.

Input: new CONTROLS.MAP action "Flush" on 'H' (HELD; press+release both
dispatch, the held-button payload).  Diags: BT_FLUSH_LOG, BT_FLUSH_TEST.

Verified live (FOGDAY): [flush] Reservoir level 6 -> 0.13 -> 0 over ~0.6 s
held (= the manual's 3-4 punches to empty), the coolant vertBar source
Reservoir/CoolantMass drains, and the bluish condensation cloud rises from
the mech (scratchpad/flush_cloud.png vs flush_before.png).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:15:44 -05:00
arcattackandClaude Fable 5 b85afed925 Torso-elevation aim LIVE: R/F now pitches the view + aim ray (pitch was inert)
User + tester reports: pitch does not work.  Root cause: the Torso sim
integrated the R/F / stick-Y axis into currentElevation (authored limits
and rates all correct) but NOTHING consumed it -- eyepointRotation's only
writer was the look-state switch, so neither the cockpit view nor the aim
ray (camera basis) ever pitched.  Shots always flew level; low targets
(trucks) were unhittable.

Fix (faithful: the pod's stick-Y pitches the EYE; the torso geometry does
not tilt): per-frame compose in the HUD tick --
eyepointRotation = EulerAngles(lookPitch + torsoElevation, lookYaw, 0);
BTCommitLookState now stores its look component in gBTLookPitch/Yaw; new
complete-type bridge BTGetTorsoElevation (torso.cpp, mirrors
BTGetTorsoTwist).  DPLEyeRenderable consumes it every frame, so the view
and the boresight aim pitch together (crosshair stays screen-centred).

Sign PIXEL-CALIBRATED via new diag BT_FORCE_ELEV=<-1..1> (pins the axis
headless): screenshots confirm axis +1 (key R) = aim UP, -1 (F) = down.
X still recenters.  Awaiting human verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 12:54:39 -05:00
arcattackandClaude Fable 5 6f6a39b8c9 Issue #3 (b)+(c): weapon damage reaches cultural icons -> full trkdead explosion + authored burn fire
(b) The 'tiny explosion' on the ram kill is AUTHORED: crunch res 31 = 'stephit'
(one video object, effect 1008 = ddam5 damage smoke) -- a step/ram squash is
small by design.  The FULL explosion res 32 = 'trkdead' = psfx 15 dtrkboom
(fiery omni burst) + psfx 16 dtrkburn (the burning-wreck fire), reached by
WEAPON kills -- which the 1995 binary dispatches [T1]:
  - MechWeapon::SendDamageMessage @004b9728 (part_013.c:6765) gates only
    "target NOT derived from Mech@0x50bdb4 OR aimed zone set": a non-Mech
    boresight target takes the zone=-1 damage UNCONDITIONALLY;
  - Missile contact @004be078 dispatches at the struck solid's OWNER entity
    with no class test at all.
Port wiring: Mech::WorldStructurePick returns the struck solid's owning entity
(BoxedSolid::GetOwningSimulation; TERRAIN.cpp:107/246 build every static solid
with its Terrain/CulturalIcon/Door entity as owner); the mech4 pick block
designates IT instead of the gBTTerrainEntity sentinel (sentinel = fallback);
the projectile contact path grew the non-mech damage-zoned else-branch
(@004be078 mirror, direct Dispatch).  Plain terrain ignores the damage
(ENTITY.cpp:885 zone==-1 guard); an icon's handler maps -1 -> 0 and dies.
Truck armor is WeaponDamagePoints=1 (TRK.DMG): one laser = one dead truck.

(c) The burning fire is dtrkburn.pfx, authored INTO the death package (every
icon family carries 1016: trkdead/bigdead/meddead/msldead/twrdead).  There is
NO looping BurningState fire in the binary: the damage-zone effect watcher
ctor @0042a984 has exactly one call site (the Mech ctor, part_012.c:10405), so
icon ExplosionTables are inert, and CulturalIcon has no Performance.
Documented as authentic -- nothing invented.

Verified live (BT_SHOT pixel capture): 26 laser kills, [cult] TakeDamage
type=3 -> DYING res=32 -> DPLIndependantEffect 1015+1016 at the icon origin;
frames show the orange dtrkboom fireball, dtrkburn flames on the fresh wreck,
and the rubble aftermath; ram harness re-run still fires crunch res 31.
Diags: BT_FIRE_AT_ICON (designate nearest ahead icon; live-icon census in
CULTURAL.cpp), BT_FX_TEST="1015,1016".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 12:02:15 -05:00
arcattackandClaude Fable 5 9fff079df4 Issue #3: wreck scorch quad ramp + RGBA4444 alpha cutout (the 'dark square')
MECHMD's scorch base (basev:bvx9_mtl, bexp9_tex = BEXP.BSL RGBA4444
slice 8, ramp cdusty) drew as a hard-edged dark square for two stacked
reasons:

1. The blanket 'truecolor BSL slice (channel >= 6) never ramps' gate
   blocked its cdusty ramp.  Corpus scan: only 4 shipped textures use
   truecolor slices; bexp9/bdet9 are grayscale in RGB (100% / 98.8%
   r==g==b) and their materials author ramps -- only bdam8 (damage
   sheet) is truly coloured.  bgfload rampableSlice() now probes the
   decoded slice: a truecolor slice ramps iff effectively gray (>=95%),
   keeping bdam8's colour protected.

2. The RGBA4444 authored alpha channel -- a binary 0/240 cutout mask
   (the splat silhouette, 78.5% transparent) -- was never alpha-tested,
   so the quad's transparent surround rendered as an opaque rectangle.
   New BgfDrawBatch.texAlpha (channel >= 8) routes these batches
   through the PUNCH alpha-test draw states, WITHOUT the black-texel
   keying (which would hole the near-black charred centre).  Side
   benefit: tree9 (tree/leaf cards) + bdet9 (trans-rail lattice) get
   their authored cutouts too.

Pixel-verified (ram-kill run, BT_SHOT): irregular char splat, dark
brown (71,44,34) lifting to near-terrain tan (115,100,91 vs terrain
131,119,108), no rectangle; pre-death frames unregressed (vehicles
stay lit/diffuse -- hasNormals gate untouched).  Awaiting human
verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 10:39:43 -05:00
arcattackandClaude Fable 5 83385b0661 KB: issue-3 pickup trail -- bvx9_mtl scorch quad + crunch-vs-full explosion
Human-verified: wreck swap works live; small ram explosion visible.
Remaining detail recorded for the next session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 10:06:05 -05:00
arcattackandClaude Fable 5 df2a97fe20 KB: rubble placement fix status (pixel-verified; scorch/explosion/fire still open)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 09:56:58 -05:00
arcattackandClaude Fable 5 28b01e3529 KB: truck destruction NOT fixed -- playtest falsified the harness claim
Human playtest (2026-07-19): ramming a truck makes it disappear entirely --
no rubble, no smoke/fire, no visible explosion.  The [cultvis] log evidence
verified the STATE MACHINE (pointer swaps), not pixels: the rubble child
component (DCSInstanceRenderable/DPLStaticChildRenderable) is itself
2007-stubbed and never draws, and the routed psfx 1008 produces nothing
visible.  rendering.md section corrected from 'verified live' to PARTIAL
with the three remaining gaps (rubble draw, visible explosion, burning
fire).  Gitea #3 reinstated as open/wip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 09:32:54 -05:00
arcattackandClaude Fable 5 53ac927d4b Destructible props LIVE: revive StateInstanceSwitchRenderable (Gitea issue #3)
Playtest report: trucks play a collision sound on impact but never change
state -- no explosion swap, no destroyed model, collision volume persists.

Investigation (BT_CULT_LOG census + BT_GOTO ram harness): the SIM was never
broken.  ARENA1 map-streams 124 CulturalIcon entities, each with a damage
zone, explosion resource and removeOnDeath; the mech crunch dispatch
(ProcessCollision -> BTDispatchCollisionDamage) reaches them; ~19 walking
bumps burn the zone -> CulturalIcon::TakeDamageMessageHandler spawns the
Explosion (psfx effect 1008 observed), posts the delayed BurningState and
deletes the collision boxes.  All of that ran correctly and invisibly.

The ACTUAL bug: the 2007 WinTesla port fully stubbed
StateInstanceSwitchRenderable ('STUBBED: DPL RB 1/14/07') -- ctor never
registered on the state dial, Execute never toggled anything -- so the
intact->rubble visual swap on BurningState NEVER fired, for every cultural
icon in the game.  The destroyed truck kept its intact model: exactly the
reported 'no state change'.

Revival (D3D9-native, 1995 semantics verbatim):
- StateInstanceSwitchRenderable now controls the draw COMPONENT via the
  SetDrawObj in-place drawable swap (the mech RemakeEntity mechanism):
  visible = captured d3d_OBJECT, hidden = NULL.  Initial state in the ctor,
  AddVideoWatcher on the SimulationState dial (SetState fires video
  watchers), toggle only on a real change.
- L4VIDEO cultural case passes the draw component + object (the dead
  dpl_INSTANCE param is gone); [video] Object hides at BurningState,
  Rubble shows.
- REQUIRED: cultural objects stay OUT of static-mesh consolidation
  (RecurseStaticObject, same exclusion class as banded LODs/shadows) --
  a merged static draws forever regardless of DrawObj.
- Permanent env diag BT_CULT_LOG: creation census (zones/explosion/flags/
  pos), TakeDamage trace, death transition, [cultvis] switch actions.

Verified live (BT_GOTO=-620,-328 ram, ARENA1): 19 crunches -> DYING ->
explosion 1008 at the icon -> [cultvis] HIDE x2 (intact) + SHOW (rubble)
-> contacts cease, mech walks into the former footprint.  Boot + render
un-regressed (statics consolidate as before minus the 124 icons).

KB: context/rendering.md new section (the full chain + the consolidation
gotcha).  MP replicant path shares the same SetState watcher (cross-pod
verification pending -- noted in the issue).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 09:23:02 -05:00
CydandClaude Fable 5 ec43e365fa 4.10 literal-source campaign: manifest DP upgrade, Route A closed, toolchain facts
- manifest410.py: monotone-DP TU labeling (data-cluster affinity + CLASSMAP
  seeds + recon @addr soft votes) replaces naive fill; BT region = 917 fn /
  ~230 KB, extents match every independently-known TU boundary.
- source-completeness: ROUTE A CLOSED (Nick holds no further assets);
  toolchain = BC++ 4.52 PROVEN by CW32.LIB byte-match, archived at
  TeslaRel410/BORLAND; OPT.MAK = the shipped binary's exact recipe; extender
  corrected to Borland PowerPack DPMI32 (was "Phar Lap TNT"); "all BT headers
  survive" corrected (17/36 bt-side; mech.hpp etc. reconstruction-only);
  engine gaps enumerated (vdata.hpp first, back-dated from BT412).
- phases/phase-03: rounds 1-3 of the source410 campaign - 6/10 surviving
  originals compile clean under BC4.52; console wire IDs recovered from the
  binary ctors (Killed=9 Damaged=10 ScoreUpdate=13 DWH=15 [T1], TeamScore
  guessed 12 [T4] - answers the TeslaSuite console-port spec's open item);
  round-3 finding: MECH.HPP is the capstone grown with the mech TU
  reconstructions, BTREG.CPP green = the header-family milestone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 07:33:59 -05:00
0bcba26213 KB: land the EXPERIENCE-LEVELS decode from glass-cockpit (Cyd)
Brings Cyd's experience-level / simulation-mode research onto master (the
KB-decode portion of glass-cockpit 4e01e83; the bundled BT410 source
manifest is left for the full glass-cockpit merge).

- context/experience-levels.md (NEW): the egg per-pilot 'experience' field
  (novice/standard/veteran/expert) is the pod's SIMULATION-FIDELITY tier,
  decoded end-to-end. FUN_004c0bc8 reads btMission->experienceLevel(+0xe4)
  and fans it into the +0x25c/+0x260/+0x26c/+0x270/+0x274 flag block:
  +0x25c 'sim live' (novice lockout: jams/searchlight/powersub), +0x260 the
  HEAT-MODEL master switch (veteran+expert; FUN_004ad7d4), +0x274 raw level
  (FUN_004ac9c8 = ==0 novice predicate; the valve/advanced-cockpit lockout).
  4.0->4.10 drift: the viewscreen hunting-aid gate is GONE in 4.10 (HUD reads
  none of these flags), and movement heat is veteran+expert not expert-only.
- btplayer.cpp/.hpp: CORRECTED comments/labels -- the +0x25c..+0x274 block is
  seeded from btMission experienceLevel/advancedDamageOn, NOT the scenario
  role's returnFromDeath; the 'roleClassIndex/showKills' names are scoring-era
  mislabels. Code logic UNCHANGED (comment-only): the ctor still reads
  scenarioRole (STAND-IN, defaults 2 = veteran) pending the wiring task.
- Corrections swept into gauges-hud.md (ROOKIE->novice lockout),
  open-questions.md (player+0x260/0x274 semantics now PINNED),
  pod-hardware.md, subsystems.md, decomp-reference.md; CLAUDE.md router row.

No code-logic change -> no rebuild needed (comment + markdown only). The
runtime wiring (seed from BTMission::ExperienceLevel()) remains TODO.

Co-Authored-By: Cyd <cyd@falloutshelterarcade.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 23:20:51 -05:00
CydandClaude Fable 5 4e01e83563 KB: EXPERIENCE LEVELS decoded -- the 0x25c block is the simulation-mode flags
The egg's per-pilot 'experience' (novice/standard/veteran/expert) is the
pod's simulation-fidelity tier -- stored at BTMission+0xe4 and seeded into
the BTPlayer ctor's flag block @4c0bc8 [T1].  The old 'per-role display
toggles / returnFromDeath' reading was WRONG: the source is
mission->experienceLevel, and the neighboring pair copies
mission->advancedDamageOn (the egg's technician splash/collision switch),
not role floats.  Known consumers: 0x260 = the HEAT-MODEL master switch
(FUN_004ad7d4), 0x25c = the novice sim-lockout (jams/searchlight/powersub),
0x274 = the raw level (FUN_004ac9c8 ==0 novice predicate).

NEW established topic context/experience-levels.md (data path, binary flag
rows per level, manual cross-refs, corrections log) + router row; sweeps in
decomp-reference/gauges-hud/open-questions/pod-hardware/subsystems.
btplayer.cpp/.hpp re-annotated with the corrected semantics; the guarded
role-based seeding stays as a marked [T3] stand-in (slot drift vs the
binary switch documented inline) until the wiring task points it at
BTMission::ExperienceLevel().

(Research by the parallel context session; committed from the glass line.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 22:45:37 -05:00
arcattackandClaude Opus 4.8 171c993147 HUD: missile/AC weapon pips blink on fire again -- read authentic WeaponState (attr 0x1C)
Reported: the reticle's missile pips no longer momentarily disappear when
firing.  Traced it: the pip's 'loaded' flag has read MechWeapon::rechargeLevel
(>= 0.999) since the reticle Execute was recovered (task #37).  That's a port
APPROXIMATION of the binary's authentic attr 0x1C (WeaponState @0x350).  It
works for emitters -- rechargeLevel is charge-driven and tops off at 1.0 when
Loaded -- but projectile weapons (MissileLauncher/autocannon) never write
rechargeLevel (it's authentically static at 1.0; the recharge DIAL draws full
and never moves).  So a missile pip's 'loaded' was permanently true and the pip
never blinked.  NOT an audio regression -- it never worked in the port; the
audio wave (ea85554) is actually what made the projectile fire-cycle state
(weaponAlarm Loaded/Loading/Firing) correct, i.e. the very signal the pip
should have been reading.

Fix (faithful to the binary): the pip now reads attr 0x1C = the weaponAlarm
StateIndicator level (new MechWeapon::WeaponStatePtr -> GaugeAlarm54::LevelPtr)
and compares it '== stateConst2' -- the loaded constant (2) the binary itself
already stores in AddWeapon (param_10).  The state cycles
Loaded(2)<->Firing(0)/Loading(3)/Jammed(5) for BOTH weapon families, so every
pip momentarily drops on fire.  The authentically-static rechargeLevel dial is
untouched.

Verified live (Blackhawk solo, BT_AUTOFIRE=1 BT_AF_MISSILE=1): both SRM6
missile pips toggle loaded 2<->0/5 on each salvo (emitter pips unchanged);
game boots + runs clean.  KB: context/gauges-hud.md pip entry corrected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 22:39:38 -05:00
Cyd 918abacfb8 Merge remote-tracking branch 'origin/master' into glass-cockpit 2026-07-18 18:58:56 -05:00
arcattackandClaude Fable 5 d26d0375ae Docs sweep: README + KB catch up with the full session's work
Coverage audit of this session's ~30 commits against the docs found
gaps; fixed:
- README §Multiplayer was stale (only the old mesh flow, 'combat in
  progress'): now leads with the relay + operator-console path
  (internet play, join.bat/play_solo.bat, patient wait) and lists what
  the mesh path has verified (4-pod, camera seat, callsigns, clock);
  README controls line updated (turn/twist/pitch, pad, CONTROLS.MAP)
- decomp-reference env table: added BT_CAM_LOG, BT_SPEC_LOG (were in
  code, not the hub); BT_INPUT_LOG note gains the twist/elev axis
- pod-hardware §input remap: the AXIS SIGN CONVENTION finding
  (positive sim = CCW/left; bridge negates once per channel;
  screenshot-forensics lesson) was only in commit cb82d8c -- now durable
- multiplayer.md: PATIENT WALK-UP + MISSION CLOCK sections (features
  shipped in f9f230c/f57d25f, only incidentally referenced before)

checkctx CLEAN (20 topics, 68 glossary keys).  All session features
now have topic-file coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 17:51:01 -05:00