Operator decision: the player base is not on Windows 11, so the RGB keyboard
mirror should not be shipped or promised. The feature is NOT deleted -- Cyd's
implementation stays intact behind the gate for whoever wants it -- it is
simply off by default and invisible:
* PadRIO gate flips from opt-out (BT_KEYLIGHT=0 disables) to OPT-IN
(BT_KEYLIGHT=1 enables). Default boots produce zero keylight lines, and a
stub build and a real build now behave identically for players: nothing.
* Removed from every user-facing artifact: the README paragraph, the
CONTROLS.md section, the CONTROLS.html footer line.
* environ.ini template: the entry stays (it is the discoverable home for the
opt-in) but now says OFF-unless-1, Windows 11 22H2+, and why it is
unshipped.
* mkdist's cut-time stub warning removed -- it existed to protect a README
promise that no longer exists.
* context/glass-cockpit.md records the decision, the opt-in, and the
build-time SDK stub gate in one place.
VERIFIED: default boot logs zero keylight lines; BT_KEYLIGHT=1 engages the
feature (the stub on this machine: its one honest log line + the key map).
All three console suites pass; checkctx CLEAN.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Full audit of every user-facing artifact in a freshly cut zip (33 checks, all
passing -- README substitutions/controls/migration story, CONTROLS.txt ASCII
flatten, CONTROLS.html loader + wrap, launchers byte-identical, exe carries the
migration):
* README's intro still said 'Press V any time to toggle the external camera'
-- the one key claim outside the rewritten controls block, stale for every
fresh/migrated install since the board took V. Backtick now.
* mkdist warns at cut time when the exe carries the KEYLIGHT STUB (build
machine's SDK < 10.0.22000), because the README promises the RGB feature
-- nobody should ship a stub build believing that promise ships with it.
The audit zip itself was deleted after verification: its version stamp predates
today's doc commits, and a release cut should rebuild the exe at final HEAD
first (the 554 procedure).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Operator decision: "make this version devour their custom settings and re-output
the correct binding file with the updates -- I'd like to eliminate competing
code paths." Before this, the preserved-bindings.txt convention meant upgraders
stayed frozen on whatever layout their file was written under, forever: two
player populations, two sets of true documentation, and every future default
improvement reaching only fresh installs.
THE MIGRATION (PadBindingProfile::MigrateBindingsFile, run once at load):
* A file carrying the new "# bindings-board 2" marker is left alone -- the
check is the first thing that runs, so this is a no-op forever after.
* Otherwise: every row matching the UNION OF EVERY DEFAULT SET EVER SHIPPED
(85 canonical rows, harvested from c2ee729..1cda880 and embedded as a
table; comments stripped, whitespace collapsed, uppercased) is dropped --
those rows were never a player's choice, they were just the old board.
* Rows the player actually authored are CARRIED with their original text and
comments, and WIN over the new defaults: the losing default row is emitted
commented out as "# (yours wins) ...", keyed by the control ("KEY T",
"PAD A", "PADAXIS LX") so key rows only displace key rows.
* The joystick wizard's marker-delimited section is preserved VERBATIM (the
same markers L4JOY rewrites between), so HOTAS players migrate without
re-running the wizard.
* The previous file is saved as bindings.old.txt first. Restoring it over
bindings.txt just re-migrates next boot -- deliberate: the old WORLD is not
restorable, only the player's own rows persist. That is the point.
VERIFIED end-to-end with the real exe (three planted scenarios):
A. old-world file (8 old-default rows + 2 authored rows incl. a rate tweak +
a wizard section): both authored rows carried with comments, both
conflicting new defaults stood down with "(yours wins)", wizard section
byte-preserved, backup written, log line states all counts, and the
migrated file parses with ZERO malformed lines.
B. already-migrated file: byte-identical md5 after a full boot, no migration
line, no backup touched. (First attempt at this check clobbered its own
evidence by running case C first -- re-run properly.)
C. no file: fresh defaults now carry the marker as line 1, no migration.
The operator's real bindings.txt was stashed before testing and restored
untouched; it will migrate on their own next launch, as intended.
Docs flipped to the new reality: the README's "your keys DO NOT change"
upgrade promise is replaced by the migration story (customs + stick kept, old
stock keys replaced, bindings.old.txt escape hatch); CONTROLS.md gains the same
note; context/glass-cockpit.md records the mechanism, the 85-row table's
provenance, and why restoring the backup re-migrates.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Applied on top of the glass-cockpit-refit merge, all from the pre-merge review:
1. README CONTROLS TABLE REWRITTEN for the new default board. It still taught
"W/S throttle, A/D turn" while the defaults moved flight to the numpad and
handed the letter rows to the MFD banks -- a fresh install would read
controls that do not work. The new table teaches the numpad flight block,
the panel-on-your-keys groups, F7 for control mode (was M), backtick for the
camera (was V), the coolant valves on 1-3/QWE with the detent warning kept,
and PgUp/PgDn volume. UPGRADERS ARE TOLD EXPLICITLY that their preserved
bindings.txt keeps their old keys, and that deleting it opts into the board.
2. BACKTICK VIEW-TOGGLE GUARDED. The bindings-row-wins rule (KeyHasBinding)
covered V and J/K/L but not backtick itself, which polled unconditionally --
and BACKTICK is a nameable, unbound key, so binding it to a button
double-dispatched (button + camera). Both halves of the view poll are now
guarded.
3. ENVIRON.INI REFUSES ONE-SHOTS. The loader applied ANY key=value line, and
BT_JOYCONFIG in the file would defeat the #66 one-shot in a sneaky way: the
wizard DELETES the process var after running, so in every relaunched child
there is no real env var left to win over the file -- capture wizard at
every mission start. BT_JOYCONFIG is now skipped with the reason in the
template header, which also warns that test/debug hooks (BT_MP_FORCE_DMG
and friends) do not belong in a file that silently persists forever.
4. VOLUME DOCUMENTED EVERYWHERE IT WAS MISSING. CONTROLS.md gains the
PgUp/PgDn row (the old "- / = (see below)" cell pointed at a note that never
mentioned volume) and the note that the keys moved so they no longer share a
key with anything in any layout; the bindings template header reserves
PgUp/PgDn informally.
5. L4KEYLIGHT BUILD GATE (found by building the merge on this machine). The
C++/WinRT Dynamic Lighting TU does not compile against SDK 10.0.19041 --
its bundled cppwinrt fails inside winrt/base itself (C2039 'wait_for').
CMake now detects an SDK older than 10.0.22000 and builds a dormant stub
with the same five-function scalar interface (logs once at Start, identical
behaviour otherwise) -- extending the feature's own runtime philosophy
("no Dynamic Lighting -> log once, stay dormant") to build time. Dynamic
Lighting is a Windows 11 22H2 feature, so nothing real is lost on an older
build machine, and machines with a newer SDK build the real mirror
unchanged.
VERIFIED on the merged tree (4.11.572):
* build clean -- 0 compile errors; the 40 /FORCE-tolerated unresolved
externals are byte-identical to every pre-merge build (20
CreateStreamedSubsystem + 20 DefaultData; the earlier claim that all 40
were CSS was shorthand -- corrected here for the record).
* solo boot: environ.ini template written + loaded, mode resolver announces
the surround, historical bands L276/R276/T223/B336 confirmed, keylight
stub logs its dormant line, BT_SHOT capture shows the under-glass button
treatment and the corrected hat labels, 0 faults.
* PgUp/PgDn volume PROVEN LIVE via injected keys: 5%->10%->15%->10%, saved
to volume.cfg. (Side catch: content\volume.cfg had been sitting at 0.00
from an old test -- anyone using this tree had a silent game; reset.)
* full relay cycle on the merged exe: 2 pods staged, launch pair, mission,
StopMission, round RESET -- all clean.
* all three console suites still pass; checkctx CLEAN.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
KEYBOARD LAMP MIRROR (L4KEYLIGHT). Ported from RP412, itself a port of vRIO's
KeyboardLampMirror. Keys bound to a lamp address in bindings.txt glow with the
panel palette through Windows Dynamic Lighting -- yellow for the map's side
columns (0x10-0x1F), red for the rest -- flashing in step with the on-screen
buttons (its LampLevel copy matches the FIXED BTLampBrightnessOf). Per-key
boards light each bound key; zone-lit boards mirror the strongest lamp
board-wide. All WinRT runs on a private worker thread: watcher, claim, and a
100 ms paint loop that repaints only on change.
RP412's packing hazard does NOT transfer: it forces default struct packing there
because its engine is /Zp1, which would break the WinRT ABI. BT411's BT_OPTS
carries no /Zp, so only the dialect flags are needed -- /std:c++17
/permissive- per-file, since the project otherwise builds C++14 /permissive.
The scalars-only interface is kept anyway so the isolation survives if packing
is ever added.
Wired in PadRIO: map built from bindings.keyBindings (ActionButton binds only,
first-binding-wins per key), fed from PadRIO::SetLamp, stopped in the dtor
(which hands the LEDs back to Windows). BT_KEYLIGHT=0 opts out; a machine
without Dynamic Lighting logs once and stays dormant.
Verified live: claimed this machine's 24-zone keyboard and mirrors 25 bound
keys; BT_KEYLIGHT=0 and the pod profile produce zero keylight lines and run
clean.
SHIPPED CONTROLS REFERENCE. docs/CONTROLS.md carries the keyboard table plus
the full 72-BUTTON POD MAP, grouped by the display each bank surrounds, with the
coolant-valve detent warning (1-5-50-CLOSED, one press past max shuts the loop)
and the jam/eject procedure. mkdist flattens it to ASCII as CONTROLS.txt at the
zip root, the README's own idiom. players/README.txt gained pointers to it, to
environ.ini, to -fit, and to the RGB mirror.
KB: context/glass-cockpit.md and docs/GLASS_COCKPIT.md updated for the whole
branch -- layout modes, L4RIOBANK and the under-glass rule, the letterbox fit
and its ordering trap, player-tunable displays, the measured legend grid, the
closed dead-button backlog, and this mirror.
Verified: five-mode regression (surround/exploded/dock/pod/dev) boots and
simulates with zero faults; mkdist writes a 5149-byte pure-ASCII CONTROLS.txt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The operator caught this: I had carried the old "please send the operator your
matchlog" instruction into the release notes, and the shipped README has said
it for a while. It is out of date.
Since 2026-07-22 the client auto-uploads its match forensic log to the relay
after every round: btl4main.cpp:914 calls BTRelayUploadMatchLog() once
RunMissions() returns, and the console receives it on route -9 and saves it as
matchlogs/<peer>_<name> (btconsole.py:1026-1042). content/matchlogs/ is
already full of them.
So the instruction was making every tester hunt down and attach a file the
operator already had. Corrected to state the real contract, including the
gaps the auto-upload genuinely cannot cover:
* RELAY modes only -- join.bat and join_lan.bat both set BT_RELAY, so both
upload. play_solo.bat and play_steam.bat set none, so those still need a
manual send.
* The upload fires when a round ends CLEANLY (after RunMissions returns), so
a mid-round CRASH loses it -- exactly the case we most want the file for.
* join.log is NEVER uploaded -- no mechanism exists -- so that one is still
a manual ask, and is now called out separately rather than buried.
Also added the same correction to dist/RELEASE_NOTES_4.11.550.md.
The zip cut before this (BT411_4.11.550.zip) carries the stale README and must
be re-cut.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Asked to double-check the launchers and make sure the user-facing docs in the
zip are up to date. players/README.txt is the ONLY user-facing doc mkdist
ships (content/VIDEO/RESULT2.TXT is a game asset), and it was last touched
2026-07-24 -- before all of today's work.
1. CORRUPTED PATHS (real defect, worst possible placement). The file
contained literal CONTROL BYTES where two paths should be:
offset 440 0x08 ...personal files (content<BS>indings.txt with...
offset 490 0x0b ...joystick setup, content<VT>olume.cfg with...
i.e. `content\bindings.txt` and `content\volume.cfg` had been written
through a string that interpreted \b as backspace and \v as vertical tab.
Testers read "contentindings.txt" / "contentolume.cfg" -- files that do not
exist -- in the UPGRADE paragraph, the one place that tells them which
personal files to preserve when extracting over an old install. Repaired
by byte, and verified: 0 control bytes remain in all four packaging
variants. (The other two occurrences, content\join.log and a later
content\bindings.txt, were undamaged -- \j and the second \b happened to
survive.)
2. CONTENT now current with today's 13-issue wave, tester-relevant parts only:
* AMMO BAY FIRE is now LETHAL (#46) -- flagged prominently as a gameplay
change, with the ~10 s fuse, what it costs, and the EJECT-hold escape.
Previously these fires were harmless; a tester who ignores one now dies.
* ENG-page buttons FLASH on a jam or bay fire (#47) -- the restored pod
annunciator, so testers know to look for it.
* KNOWN ISSUE + WORKAROUND for #62: the BUS MODE button's second press
detaches a weapon from its generator and the auto re-attach is dead, so a
weapon can go permanently dark. Documented with the recovery (press a
GENERATOR SELECT A/B/C/D button) to pre-empt a wave of "my weapon broke"
reports.
Verified by simulating mkdist's own marker processing across all four
steam-on/off x expire-on/off variants: new content survives every path, no
leftover {VERSION}/{STEAM}/{EXPIRE} markers reach the tester, no control bytes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The same careful tester hit the BASIC-mode trap twice because 'expert/
veteran modes' (menu experience) sound like control modes (M key).
Spelled out the distinction + the numpad NumLock dependency.
The advertised upgrade flow was delete-and-unzip, which wipes the two
per-player files (key/joystick bindings incl. joyconfig output, saved
volume) every version. The zip carries neither file, so extract-over-
top preserves them by construction -- now the documented flow, with the
copy-out fallback for clean-delete preferrers.
Field report (new tester, 510): keyboard A/D + arrows 'not working' --
can twist and drive but not turn. Root-caused live via key injection:
fresh spawns start in BASIC control mode, where steering rides the
STICK (authentic rookie mode) and the pedal-bound keys are inert by
design; one M press (MID) makes A/D steer. (A false regression-bisect
chase established the metric noise came from an attached pad's stick
drift nudging BASIC steering.) README now warns about the default
mode; kept the env-gated [padwrite] diagnostic from the hunt.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 1-5-50-0 cycle's closed detent one press past max is what cooked
both SRM6s in the 2026-07-23 field session -- players need the warning
and the shared-supply note.
Tester ask: configure non-Xbox controllers. New L4JOY layer (DI8):
enumerates every non-XInput game device (up to 4), DIJOYSTATE2 polling
with range normalization, reacquire-on-loss, 3s hot-plug re-enum.
XInput devices are excluded via the documented RawInput IG_ VID/PID
check (live-verified skipping a real XBOX360 pad -- no double-feed).
bindings.txt grows joydev/joyaxis/joybutton/joyhat rows (device slots
by name substring or ordinal; axes X..RZ/SL0/SL1 with invert/slew/
deadzone; hats as 4-direction buttons with diagonal chords). PadRIO
runs them through the existing channel/event machinery -- per-binding
edge arrays release automatically on detach (the #24 rule), and a
direct throttle axis maps full lever travel onto the 0..1 channel
while the gait detent still snaps a lever parked in the walk/run dead
band.
BT_JOYCONFIG=1 (joyconfig.bat) runs a console capture wizard: move
each control when prompted (stick/twist/throttle/fire), invert derived
from the move direction, hat look cluster auto-added, output written
between markers in bindings.txt with the rest of the file preserved.
Legacy L4DINPUT DIJoystick (L4CONTROLS=DIJOYSTICK) untouched.
Verified: grammar accept/reject per line, XInput-exclusion live,
30s in-mission no-device polling clean. Awaiting a tester with real
stick hardware for axis verification.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Disasm-faithful transcription of the export-gap handler onto
ProjectileWeapon (MESSAGE_ENTRY id 0xb, the #19 pattern). PRESS arms
the ~3s UpdateEject countdown (bin alarm -> Ejecting(3); gate 2 parks
the weapon offline mid-eject -- authentic). HOLD to completion =
DumpAmmo jettisons the bay -> NoAmmo. TAP (release early) cycles ONE
round out via FeedAmmo and returns the weapon to Loading -- clears a
jam at the cost of a round (and recovers a FailureHeat 7 while rounds
remain). Binary structure kept exactly incl. the press-no-bin
fall-through into the release block.
AmmoBin: Ejecting(3)/Ejected(4) alarm levels decoded + named
SetAmmoState accessor (databinding rule). Always-on forensics:
'[weap] EJECT tap' + '[ammo] bay DUMPED overboard (N rounds)'.
Rig-verified live (BT_EJECTTEST=1 / =hold on LRM15): taps eject one
round each and return the weapon to service (15->8 across cycles);
hold dumps all 16 -> NoAmmo. Awaiting a human press of the actual
ENG-page button on a jammed weapon.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Old-timer testimony (tap = eject one round, hold = eject the bay, eject
clears a jam) checked against the binary: raw disasm of the export-gap
handler @004bb9b8 confirms it byte-for-byte. PRESS arms the ~3s
UpdateEject countdown (hold-to-completion = DumpAmmo whole bay ->
NoAmmo); RELEASE before completion (TAP) ejects ONE round (@004bd4f4)
and, with ammo remaining, sets weaponAlarm 3 + restarts the reload --
the weapon returns to service. A tap even recovers a FailureHeat
level-7 launcher while rounds remain (empty-bin gate 2 re-pins 7 only
when the bay is dry).
The earlier 'jams are mission-permanent / no unjam exists' claim read
only the HOLD path -- corrected + swept: projweap.cpp case-5/jam-log/
CheckForJam comments, players/README.txt, decomp-reference.md
(message-tables entry), open-questions.md (state-7 recovery note).
Gitea #31 updated; wiring the handler now restores the pod's actual
jam-recovery mechanic.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live verification closed the loop on issue #30: heat-jammed SRMs light
the authentic BTEJAM lamp on the weapon panel's ENG DATA page; the
default view shows nothing (authentic). Testers get told where to look.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The game audio plays hot with no in-game control -- testers couldn't
hear voice chat without the Windows mixer (playtest night). The -/=
keys now step the OpenAL listener gain (the master scale every source
inherits) in 5% steps, clamped 0..150%, edge-detected in the per-frame
poll with a foreground guard; the value persists to content\volume.cfg
and reloads at boot (BT_AUDIO_VOLUME env still wins when set; default
stays 0.6). README updated.
Verified: volume.cfg 0.25 -> boot log "master gain=0.25"; key stepping
needs a live focused session (awaiting live verification).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The between-rounds arcade flow (first-playtest-night field request): at
mission end every pod exited, seats dropped, and every round required
everyone to re-run join.bat with no way to adjust the mission.
1. AUTO-REJOIN: StopMission (operator END / mission clock -- NOT a
window close) sets gBTMissionStoppedByConsole; after the matchlog
upload btl4main relaunches the pod into the join wait with the same
cmdline (BT_CALLSIGN/BT_MECH ride the inherited environment).
2. SEAT RECLAIM: the assigned tag is mirrored file-scope
(BTRelaySelfTag) and re-presented as the 3rd SEAT_REQUEST payload
field (BT_SEAT_CLAIM); the relay holds a dropped seat for its tag
for 90s and a returning player gets their EXACT seat back -- static
ordering across rounds, the real-pod model. Non-claim joiners skip
reclaim-held seats.
3. LIVE MISSION EDITS: the console gains "Apply mission settings"
(enabled while a session runs) writing arena/time/weather/length
into the session egg; the relay re-reads the file at round reset and
egg release (roster shape locked mid-session).
Verified 2-pod 3-round: pods self-relaunched after each clock end; the
SECOND-to-rejoin still reclaimed its original seat (ordering held); the
between-rounds edit landed ("mission length now 75s"). Known edge
noted in the KB: a stale ready flag from a not-yet-exited old conn can
satisfy the launch gate mid-rejoin -- operators launch on green dots.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 1995 front-desk conversation, over the internet. join.bat/
join_lan.bat now open a JOIN-GAME menu (the FE in BT_FE_JOIN trim:
CALLSIGN edit + the 18-mech list + JOIN); the choice relaunches into the
normal join with BT_CALLSIGN/BT_MECH env, rides the relay SEAT_REQUEST
as {callsign NUL mech NUL} (empty payload = roster defaults, wire-
compatible), and the relay validates the mech tag, HOLDS egg delivery
until every pod's console pad is connected, rewrites the egg via
eggmodel (vehicle= + rasterized callsign bitmaps, graceful no-PySide6
fallback) and streams it to all pads -- so every player's egg copy
carries every player's callsign.
The hold is gated on CONSOLE-PAD count, not game-side registration: a
pod HELLOs only after parsing the egg's roster, so a registration gate
deadlocks (hit live in the first run; pods animate in WAITING FOR
MISSION ASSIGNMENT during the hold).
Verified 2-node e2e (env-injected requests): thor/vulture requested over
roster defaults bhk1/ava1 -> relay held 1/2, released at 2/2, rewrote
both seats (relay log), the delivered egg carries vehicle=thor/vulture +
name=VIPER/MONGOOSE, both pods launched and built [cyl] tables for both
requested mechs. Join-menu layout screenshot-verified; the menu's
click-through relaunch awaits live human verification.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Distributed test zips go stale and stray. BT_EXPIRE (CMake option,
DEFAULT ON so a zip can never accidentally ship without it) makes the
exe refuse to start BT_EXPIRE_DAYS (default 14) after its build day:
boot logs the remaining window, an expired build logs + shows a
"test build expired -- ask the operator for the current build" box and
exits. btversion.h now stamps BT_BUILD_UNIX (UTC-midnight-rounded so
the only-on-change header churns once per day, not every build) +
BT_BUILD_DATE. Dev is unaffected: every build re-stamps the day.
-DBT_EXPIRE=OFF builds a non-expiring exe; mkdist reads the cache and
marks such a zip "-noexpire" + prints a warning, and the tester README
expiry note is {EXPIRE}-marker-gated like the steam lines. Second
compile-gate exception after BT_STEAM (convention updated in
glass-cockpit.md). Quality gate, not DRM -- a clock rollback defeats it.
Verified live: default build logs "test build window: 14 day(s) left";
a -DBT_EXPIRE_DAYS=0 build logs TEST BUILD EXPIRED and parks on the
explanation box (window title checked) without starting the game;
restored to 14 and rebuilt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
For the 8-player playtest. Damage authority is VICTIM-side, so no single
peer sees the whole match: every -net instance now writes a compact
machine-parseable matchlog_<date>_<time>_<pid>.txt (auto-armed by -net;
BT_MATCHLOG=1/0 overrides; solo silent), and at mission end a relay-mode
pod dials the relay game port (the seat-request throwaway-dial pattern,
envelope route -9) and streams the file back -- the relay saves every
peer's copy under matchlogs/ on the operator's machine, so testers send
nothing by hand. tools/matchcheck.py <dir> reconciles all of them:
damage claimed (FIRE/PROJ/SPLASH/RAM, shooter-side) vs applied (DMG,
victim-side authoritative), kill/death replication across observers,
PLAYER_DEAD counts, scores, version skew, replicant-application and
unattributed-damage anomalies, mid-mission disconnects.
Hooks at the authoritative sites: Mech::TakeDamageMessageHandler (DMG,
post-base, zone + post-application damageLevel), MechWeapon::
SendDamageMessage (FIRE), projectile impact/splash/collision dispatch
(PROJ/SPLASH/RAM), wreck entry (DEATH -- a ONE-SHOT latch at
MovementMode 9, NOT the transition: a replicant arrives at 9 straight
from the type-6 record header and never runs the transition branch),
BTPlayer vehicle/death/score handlers (VEHICLE/PLAYER_DEAD/SCORE), zone
crit cascade (CRIT), APP.cpp RunningMission (MISSION), L4NET host
handlers (PEER_UP/PEER_DOWN). Every line t=/w=/st=-stamped + fflushed.
Verified 2-node: mesh run pairs A's 22 FIRE 1:1 with B's 22 DMG (same
amounts, cylinder-resolved zones, ~200ms latency); force-damage kill run
logs 103 DMG + 3 DEATH inst=M + 3 PLAYER_DEAD across three respawn
cycles victim-side and the kill SCOREs shooter-side; relay-mode run
auto-uploaded BOTH peers' files at the mission-clock stop and matchcheck
produced the full report with exactly one (correct) anomaly -- the force
hook's claim-less damage, the unattributed-damage detector working.
Relay gained a route-specific 8MB cap for the upload frame (game frames
stay capped at 1600).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
play_solo hardcoded -egg ARENA1.EGG, bypassing the miniconsole front end
that already carries the full pickers: SOLO/RAW-SOLO/HOST-LAN/JOIN-LAN
modes, 8 maps, all 18 mechs, 4 experience levels, colours/badges/patches,
drop zones, advanced damage, time-of-day, weather and mission length.
The bat now launches zero-arg (BT_PLATFORM=glass -> the menu); the menu
relaunches the exe with the generated egg and the cockpit/gauge env
(BT_START_INSIDE/BT_DEV_GAUGES) inherits into the mission child. Menu-boot
verified with the bat's exact env; the solo menu->mission->menu cycle was
verified at FE landing. README + the btoperator generated-bat template
updated to match.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
The first zips (pre-mkdist) shipped d3dx9_43.dll / msvcp140.dll /
vcruntime140.dll next to the exe -- testers without the VC++ / DirectX
redistributables cannot load the exe otherwise. mkdist missed them (the
clean-extract boot test passed only because this machine has them system-
wide). Carried into redist/ (recovered from BT411_4.11.341.zip) and
packaged next to the exe.
Also: players/README.txt -- the tester instructions (updated for the
unified build: one exe, clickable cockpit surround, trigger-config regroup,
the settled keymap, play_steam.bat) -- stamped with the version and placed
at the zip root, matching the original zips' shape.
Clean-extract verified on BT411_4.11.435.zip: root has README + 4 bats,
exe dir carries all 4 DLLs, glass-profile boot simulates.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>