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>
33 master commits in (relay TCP/UDP + PySide6 operator console, CONTROLS.MAP
+XInput binding engine, camera seats, torso pitch aim, sign fixes, the 1995
manual, version stamping, 18-mech certification). Conflicts: .gitignore +
CLAUDE.md router rows (combined).
SEMANTIC RECONCILIATION (the one real overlap): masters btinput binding
engine (ungated, CONTROLS.MAP) and the glass PadRIO (gated, bindings.txt)
would both read the keyboard/pad in a glass+PAD session. btinput now joins
the stand-down convention: BTInputPoll yields (and BTInputSuppressKey claims
NOTHING, so authentic hotkeys flow) when an operational cockpit device owns
the input path -- BTRIODevicePresent, BT_KEY_BRIDGE force-override honored,
forced harness exempt. One input system per mode: btinput on pod/dev
desktops, PadRIO on glass. The mechmppr/mech4 bridge merges composed clean
(masters negate-once sign fix inside our device-gated bridge). The D1 relay
keeps its own raw sockets by design (an alternative LAN wire; Steam and
relay are separate modes).
Verified post-merge: all 3 configs build; glass boots with [input] binding
engine standing down + PadRIO owning input (30 ticks); pod forced-walk
speedDemand=61.501 with btinput ACTIVE; 2-node loopback MP full 31/31
mission, 76/76 ticks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4.10 = the 1995 arcade release; 4.11 = the win32 reconstruction; the
build number is the git commit count -- monotonic, zero-maintenance,
and every exe pins to exact source via the short hash ('+' = built
from an uncommitted tree). tools/btversion.cmake regenerates
build/btversion.h on every build (write-if-changed, no rebuild churn);
the stamp shows in the boot banner (btl4.log head, replacing the stale
'v4.10' line) and the window title (incl. the MP node tag).
Verified: build stamps 4.11.311 (980c9cd+), banner + title correct on
a live boot.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pod's controls reach the game through the RIO serial board; on a
desktop that hardware is a keyboard shim of hardcoded GetAsyncKeyState
reads. New binding engine (game/reconstructed/btinput.cpp) maps PC keys
and an XInput pad onto the authentic channels through a user-editable
file (content/CONTROLS.MAP, community-suggested grammar, corrected):
key/pad -> button <addr> real buttonGroup emissions, mirroring the
RIO convention exactly (press a+1 w/ mode
mask saved, release -a-1 w/ saved mask;
L4CTRL.cpp:2470-2520) -- aux/preset banks,
hotbox, panic, reverse thrust all reachable
key/pad -> axis Throttle rate (lever), pedals/JoystickX
deflect (turn/twist) into the existing
virtual-controls integrators (feel, gait
detent, spring-centering all unchanged)
keypad pilot|external <n> keyboardGroup key values ('0'-'F')
pckey <char> any authentic 1995 typed hotkey
action <name> port dev controls (view, all-stop, ...)
Keys claimed by a binding are SUPPRESSED from the legacy WM_CHAR/KEYUP
feed -- ends the historic double-dispatch ('w' drove AND selected pilot
0; F5's key-up value 0x74 aliased to the 't' hotkey; letter key-ups fed
the developer fake-event dispatcher). Unbound keys keep their authentic
meaning. The dual-use 'V' is split: V = view toggle, B = look behind.
Default profile = WASD classic (compiled-in twin; delete the file to
restore). CONTROLS_NUMPAD.MAP ships the corrected community layout
(keypads are NOT buttons 0x50-0x6F -- that space doesn't exist; no
clickable cockpit exists so everything needs a binding; keyboard fire
buttons added; 0x36/0x37 are hotbox not 'config'; missiles moved off
Ctrl). XInput loads dynamically (1_4 -> 9_1_0), disconnected-pad
probing rate-limited; pad sticks write the axes as absolute positions
(the spring is physical), triggers/buttons per the file.
Verified live: bindings load (43), W and NumPad8 drive (speedDemand 0 ->
61.5), X all-stop (spd -> 0), aux-bank emission (D1 -> [input] 0x2f
PRESS/release under the numpad profile), suppression both directions
(posted 'r' reaches [keych], posted 'w' swallowed), full-keyspace
WM_CHAR+WM_KEYUP fuzz x3 survived with sim advancing, XInput graceful
with no pad, 2-node relay session un-regressed (full ladder + UDP).
Pad-in-hand testing still needs a physical controller.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
NEW gated (BT_STEAM) game/glass/btl4lobby: an ISteamMatchmaking room replacing
the arcade Site-Management screen -- host creates (lobby data btl4=1), members
join by filter, pilots publish name/mech/color, the host ENTER mints the
roster and signals GO.
THE FAKEIP LESSON (live-verified, prior-art assumption DISPROVEN): a fresh
process gets a DIFFERENT FakeIP, so menu-time fake addresses go stale by
mission time -- the first cycle timed out connecting to its own stale address.
Redesign: roster addresses are opaque ipv4-shaped TOKENS (169.254.77.N with
ports 1501/1502) minted by the host at GO, mapped to Steam IDENTITIES;
connections ride ConnectP2P(identity, channel) with console=0/game=1 virtual
ports; the map reaches mission processes via env (BT_FE_MYFAKE +
BT_FE_STEAMMAP); the GetMyAddress seam feeds the pod its own token for roster
self-match; CheckSocket reports peers by token. L4STEAMNET reworked (no
FakeIP allocation at all); the marshal gained the Steam-wire branch.
Verified single-machine (ON/ON, live Steam): menu -> HOST STEAM LOBBY ->
room -> GO -> token map minted -> egg roster carries the token -> mission
process up with 1 roster token incl. self -> pod self-matches -> stock
console ladder -> mission RUNS (46 ticks). Remaining: the live 2-account
cross-machine session (docs/STEAM_TEST.md).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
NEW gated (BT_STEAM) TU L4STEAMNET: ISteamNetworkingSockets with FakeIP --
pseudo-SOCKET handles (0x5EA0xxxx) behind the L4NET BTNet* seam; the engine
TCP byte stream rides reliable-NoNagle Steam messages re-assembled into
per-connection rings (nonblocking recv semantics preserved: empty ->
WSAEWOULDBLOCK, peer-closed -> 0); two FakeIP ports mirror the arcade
console/game channel convention; listen sockets + accept queues via the
global status-changed callback; graceful degrade to Winsock at every step.
Seam completions in L4NET.CPP: CheckSocket reports a pseudo-socket peer as
its FAKE ipv4:port (the lobby egg roster matches unchanged); OpenConnection
TCP_OPEN delegates FakeIP targets to BTSteamNet_Connect. WinMain installs
on BT_STEAM_NET=1 (lobby launch path / by hand). CMake: gated SDK include/
lib/dll-copy. Both pumps (SteamAPI_RunCallbacks + sockets RunCallbacks)
drive it -- the FakeIP result rides the GENERAL dispatch (first attempt with
only the sockets pump timed out).
Verified live (ON/ON build, Steam client running): SteamAPI_Init OK,
steam_appid.txt(480) auto-written, FakeIP allocated
(169.254.36.58, console 54464 / game 54465); without BT_STEAM_NET or
without Steam the game stays pure Winsock.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
NEW gated dir game/glass/: btl4fe (green-on-black GDI catalog menu -- map/
time/weather/length/mech/color/pilot/mode/port/peers; egg writer emitting the
FULL console egg shape, golden-tested vs MP.EGG: identical section sequence
at identical line offsets, the 4 static ordinal pages verbatim, GDI-rendered
128x32/64x16 pilot-name plasma bitmaps in MP.EGG framing) and btl4console
(the marshal: a worker-thread console CLIENT speaking the btconsole.py wire
protocol verbatim -- 1040-byte chunked egg, 20s settle, RunMission x2,
STAY CONNECTED, own the clock, StopMission at expiry; logs marshal.log).
The engine sees a real connected console and runs the 100% stock ladder --
NO engine hooks (less invasive than planned: the L4APP.H setters proved
unnecessary; launches go through real argv on relaunch).
WinMain (one gated block): zero-mission-arg glass launch -> menu -> relaunch
self with the mission argv (per-mission process relaunch; BT_FE_* env arms
the marshal); post-RunMissions BT_FE_LOOP tail returns to the menu. Menu
relaunches CLEAR the BT_FE_* handoff (found live: the inherited env re-armed
a marshal instead of showing the menu).
Verified end-to-end (synthetic menu drive): menu -> frontend.egg -> relaunch
-> marshal feeds self over loopback -> stock console ladder -> mission RUNS
(58+ ticks) -> 60s clock -> StopMission -> clean menu return, no env leak.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
NEW gated TUs: L4PADPANEL (GDI top-level window, all-W APIs: the pod button
banks -- upper/lower aux, 12 secondary, specials/hat/handle, 63 buttons --
clickable via PadRIO::SetScreenButton with lamp-lit faces from GetLampState,
10 Hz repaint; created by the PadRIO ctor on BT_PAD_PANEL=1) and L4PLASMAWIN
(PlasmaWindow : Video8BitBuffered -- the gauge renderer draws the 128x32
plasma into its pixelBuffer through the SAME code path as the serial device;
Update() blits orange-on-black at L4PLASMASCALE, default x4). Gated seams:
L4GREND.cpp L4PLASMA=SCREEN branch; btl4main.cpp -platform glass preset
(PAD,KEYBOARD + BT_DEV_GAUGES + SCREEN plasma + panel; env always overrides;
non-glass builds log+fall back to DEV); run.cmd glass token. MFD surfaces
need NO new code: the existing dock-bottom / BT_DEV_GAUGES_WINDOW=1 /
BT_DEV_GAUGES_DOCK=1 modes are the display story.
Verified live: -platform glass boots GLASS profile, panel + plasma windows
up, pad detected, dev gauges awake, mission loop clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
L4RIO.h splits the abstract cockpit-control surface (RIOBase: enums, the five
analog Scalars, GetNextEvent/SetLamp, no-op serial ops, NEW IsOperational) out
of the serial RIO (RIO : PCSerialPacket, RIOBase -- byte-for-byte behavior kept,
ctor assigns as before); LBE4ControlsManager holds a RIOBase* and gains the
gated L4CONTROLS=PAD factory arm (BT_GLASS; OFF build logs+ignores the token).
NEW gated TUs: L4PADRIO (XInput+keyboard synthesize the surface; 3s hot-plug
re-probe; focus-guarded keys; per-poll AnalogEvent heartbeat; lampState[] +
static SetScreenButton/GetLampState for the panel) and L4PADBINDINGS
(content\bindings.txt profile, self-documenting default written on first run;
deflect/slew/set axis model; addresses validated against ButtonCount).
Verified live (glass build, L4CONTROLS=PAD): bindings written+parsed 44/10/5,
XInput pad detected, 121 streamed mappings install via stock PrimaryRIO path,
2157 frames clean. Pod build (gates OFF) compiles the split with zero
behavioral delta.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New L4AUDEFX bridge (OpenAL Soft ALC_EXT_EFX): one EAXReverb aux slot at the
authentic AUDIO.INI global_reverb_scale (0.3) + a scratch AL_FILTER_LOWPASS
(params copied at attach).
F9 filters (was: computed then thrown away -- everything full-bright at all
distances): Dynamic3D ExecuteModel drives GAINHF from highFreqCutoffScale x
brightnessScale x maxMIDIFilterCutoff, UNGATED (decomp part_008.c:7496,
7589-7604 -- every moving 3D sound dulls with distance per the AUDIO.INI
knee-60/exp-2.0 model); Static3D from brightness x max (:7831-7884); Direct
inside its existing gated NRPN-rate block. AWE 100-8000 Hz curve -> EFX
5 kHz-reference gainhf via a 2-pole approximation [T3 curve, endpoints exact].
F11 reverb (was: bone-dry everywhere): 3D patch sources attach an aux send at
Start, exactly where the original sent CC91 = global_reverb_scale
(part_008.c:7278-7394); Direct cockpit sources keep CC91=0 -- dry. The
wet-exterior vs dry-cockpit contrast is back.
F12 placement (was: every cockpit sound dead-center): DirectPatchSource
Start places sources by the authored 6-value position enum (front/rear card
+ pan CC10, decomp @00463848/@004638a8) as listener-relative directions,
composed with the zone L/C/R pan. New PatchResource GetBankID/GetPatchID
pass-throughs (the LOD accessor is protected).
Regression (35s drive+fire): EFX READY, stable, deliveries unregressed, no AL
errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause of "no sound, ever": the two audio backend DLLs shipped in the repo
are fakes. libsndfile-1.dll exports 15 funcs BY ORDINAL ONLY (no names) and
sf_open() always returns NULL; OpenAL32.dll (72KB) imports only KERNEL32 -- no
dsound/wasapi/winmm -- so it is a pure no-op that returns fake handles
(ctx=0x00000001, alGenSources->0) and never touches the hardware. The whole
render->device->buffer->source->play chain ran clean and silent.
Fixes:
- OpenAL32.dll: replace the stub with the real OpenAL Soft 1.25.2 Win32 build
(imports AVRT/ole32/WINMM, real WASAPI backend). The exe imports the 25 AL
funcs by NAME so it is a drop-in; alGenSources now yields a live source and
alSourcePlay reaches AL_PLAYING.
- libsndfile: DROPPED entirely. It is replaced by LoadWavPCM() in L4AUDRES --
a tiny RIFF/WAVE fmt+data reader that loads our soundbank WAVs (16-bit PCM)
straight into the AL buffer. Removed the .lib/.dll from the link + copy and
git-rm'd the stub. (This also kills the "ordinal 50 could not be located in
libsndfile-1.dll" load-failure popup: adding an sf_strerror import bound to
an ordinal the 2..16-only stub could not satisfy.)
- Soundbank: 241 samples cracked from AUDIO1/2.RES (SF2 v1.0) by
tools/sf2extract.py into content/AUDIO/*.wav + the allPresets[2][128] table
(audiopresets.cpp), replacing the zero-init btstubs stub. All 241 now load
(alErr=0). BT_AUDIO_TEST plays buffer0 as proof-of-life; BT_AUDIO_LOG traces
the chain.
Remaining: in-game triggering (AudioEntities on fire/step/engine/explosion)
so PlayNote fires during play -- next audio wave.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
records; also the 2007 call-counter clock stub + two crash fixes
User-reported: peer mechs turn/move choppy ("missing frames"). Measured per-frame
(BT_REPL_HDG): 13% of frames the replicant's heading STALLED, 11% it JUMPED 2-6x.
THREE layered defects found:
1) THE CLOCK STUB (engine substrate): TIMESTUB.cpp's GetRTC/GetHiRes were 2007
`return time++` call-counters -- the "clock" advanced per CALL, not per ms --
and TIMESTUB won the /FORCE duplicate-symbol race over the REAL QPC clock in
L4TIME.cpp (LNK4006). Every Now()-domain consumer (dead-reckon above all) ran
on call-count pseudo-time. Removed TIMESTUB from the build; L4TIME covers every
symbol. [The real clock alone did NOT cure the chop -- but it was objectively
broken and un-gated the two latent bugs below.]
2) TWO CRASH FIXES the real clock exposed:
- legAnimationState@0x3b0 never ctor-initialized (the task-#56 0xCDCDCDCD
family): the type-3 writer re-dispatches SetBodyAnimation(legAnimationState)
on the WRITER; a record emitted before the leg SM's first tick passed raw
0xCDCDCDCD as a clip index -> AV (cdb-pinned, mech2.cpp:233). Init 0.
- Replicants were SERIALIZING update records: the tail WriteSimulationUpdate ran
for every instance, and the port's replicant leg-SM accommodation (task #50)
calls ForceUpdate -> replicants emitted derived/uninitialized state into the
stream. Master-gated; replicant marks discarded (master-authoritative).
3) THE CHOP ITSELF: the master's resync send-gate compares localOrigin vs
projectedOrigin -- the PEER-ESTIMATE mirror -- but the port master never
maintained projectedOrigin (the bring-up drive replaced Mover::Perform; the
engine only updates the projection inside replicant-only DeadReckon). Stale
mirror -> |local-projected| > deadband EVERY frame -> a type-4 re-base record
EVERY frame -> the replicant hard-copied the master's deadband-quantized
heading each frame (the engine lerp never engaged; nextUpdate always behind
till) -> stall/snap beat = the chop. FIX: advance the mirror each frame by the
last-SENT angular velocity (what the peer is extrapolating) and re-base it in
the type-4 writer. Records now flow only on TRUE drift (~1 per 5 frames in a
steady spin), the replicant extrapolates smoothly between them, and the lerp
horizon finally engages. Measured: STALLS 13%->1%, JUMPS 11%->1% (residue: tiny
sub-degree backward corrections on record arrival -- inherent dead-reckon
overshoot the lerp absorbs).
Diag probes: BT_REPL_HDG (per-frame replicant heading + dead-reckon internals),
BT_REPL_TRN (replicant turn/leg state). scratchpad/clockcrash_bp.txt (cdb).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.
Layout:
engine/ MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
models) + image codec; the minimal rp/ headers the audio HAL needs
game/ reconstructed BT logic + surviving-original BT source + fwd shims
+ WinMain launcher
content/ full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
docs/ format specs + reconstruction ledgers
reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
tools/ MP console emulator + map/resource scanners
One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>