Commit Graph
29 Commits
Author SHA1 Message Date
arcattack 26439c2e1a Boot forensics: first-breath log line + generation-safe bat logging
Field 2026-07-23 (second night): a player's join.bat failed 3x in a row
with a 0-byte join.log -- indistinguishable from 'never ran'.  Two
causes of evidence loss fixed:

1. The first flushed log line now happens the INSTANT the log opens
   (version, pid, args, first-process vs relaunched-generation).  An
   empty log after this build means the exe was killed before WinMain
   -- an external cause (antivirus/loader), not ours.
2. The menu->game relaunch chain truncated join.log each generation
   (BT_LOG_APPEND was unset in the player bats): the crashed
   generation's evidence was erased by the next attempt.  The bats now
   delete the log once per bat run and append across generations.

Also explains the confusing 'The game has exited' bat message on
SUCCESSFUL launches (the menu process exits after relaunching the game
detached) -- the log now records the whole chain.
2026-07-23 21:33:27 -05:00
arcattack 3ee980ba46 README: coolant flush/valve keys + the valve detent trap (max -> CLOSED)
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.
2026-07-23 17:32:51 -05:00
arcattackandClaude Opus 4.8 c2ee7299b2 Generic joystick / HOTAS / pedals support: DirectInput 8 layer + capture wizard
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>
2026-07-23 16:36:32 -05:00
arcattackandClaude Opus 4.8 2edd1b5363 Issue #31: wire the EJECT button (EjectAmmo msg 0xb @004bb9b8) -- the in-mission unjam
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>
2026-07-23 13:49:11 -05:00
arcattackandClaude Opus 4.8 225fe43f0c CORRECTION: EJECT tap IS the in-mission unjam (EjectAmmo @004bb9b8 decoded)
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>
2026-07-23 12:21:06 -05:00
arcattackandClaude Opus 4.8 420d739d75 README: jam behavior + where the jam lamp lives (field-confirmed)
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>
2026-07-23 10:30:17 -05:00
arcattackandClaude Opus 4.8 4e0fcbf328 Issue #26: runtime master volume (-/= keys, persisted)
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>
2026-07-23 00:53:07 -05:00
arcattackandClaude Opus 4.8 8368163b04 Lobby loop: auto-rejoin between rounds + seat reclaim + live mission edits
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>
2026-07-23 00:28:56 -05:00
arcattackandClaude Opus 4.8 fcedc046cf Walk-up callsign/mech request: join menu -> relay -> the session egg
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>
2026-07-22 13:58:51 -05:00
arcattackandClaude Opus 4.8 eb618fe9e6 BT_EXPIRE: tester builds stop working 14 days after build (compile gate)
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>
2026-07-22 13:39:19 -05:00
arcattackandClaude Opus 4.8 753540de96 MP match forensics: per-peer matchlog + relay auto-upload + matchcheck
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>
2026-07-22 13:22:10 -05:00
arcattackandClaude Opus 4.8 2717c33e4e play_steam.bat: clear BT_FE_SOLO (full menu even after play_solo in the same shell)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:56:01 -05:00
arcattackandClaude Opus 4.8 04d50c9242 FE menu: BT_FE_SOLO=1 trims to a solo-only menu (play_solo.bat)
The user hit the full session menu from play_solo -- HOST/JOIN LAN modes,
NET PORT and the Steam buttons don't belong in the solo flow (LAN goes
through the join bats + operator; Steam through play_steam.bat).

BT_FE_SOLO=1 (set by play_solo.bat + the btoperator generated bat):
- the MODE cycle keeps only the two solo entries (kModes lists them first:
  SOLO MISSION, RAW SOLO) via the GroupSize clamp
- NET PORT row and the HOST/JOIN STEAM buttons are not laid out
- a stale fe_last.ini mode of hostlan/joinlan is rejected by the existing
  LoadPersisted GroupSize clamp, so the selection can never land on a
  hidden mode
play_steam.bat keeps the full menu.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:54:09 -05:00
arcattackandClaude Opus 4.8 aaa252a4a3 play_solo.bat: open the mission menu (mech/map/options pickers)
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>
2026-07-21 10:49:01 -05:00
arcattackandClaude Opus 4.8 ec44bc5dfa play_steam.bat: cockpit view + gauges (BT_START_INSIDE + BT_DEV_GAUGES)
The steam bat missed the two client flags the join bats set -- steam-launched
missions booted 3rd-person (the same field bug fixed for join.bat earlier).
Env inherits through the menu's mission relaunch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:42:32 -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 dd74d13120 mkdist: redist runtime DLLs + versioned tester README
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>
2026-07-21 10:32:11 -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 3b17b60c19 Join bats: same glass-preference treatment as play_solo
join.bat + join_lan.bat + the btoperator.py generator template now prefer
build-glass\Release\btl4.exe when present and set BT_PLATFORM=glass with it
-- so MP sessions on a dev checkout get PadRIO (mouse-clickable cockpit
buttons: trigger config, MFD banks, radar rails) and the bindings.txt keymap,
identical to solo play.  Player zips (which ship only build\) fall back to
the pod build exactly as before.

Verified: 2-node loopback MP smoke on the GLASS build (the documented
procedure: A -egg MP.EGG -net 1501 + B -net 1601 + tools/btconsole.py) --
both nodes cross-connected (GameMachineHost 1502/1602), ~150 sim ticks
each, no errors.  btoperator.py syntax-checked; regenerated bats carry the
same guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:04:02 -05:00
arcattackandClaude Opus 4.8 4215b98655 Trigger config: root-cause the dead cockpit clicks (missing BT_PLATFORM=glass)
The user held the MFD PROGRAM button and nothing happened (weapons kept
firing).  Diagnosis, verified end-to-end:

- The cockpit mouse handler DID register the click ([cockpit] CLICK addr=0x8
  press) and 0x8 IS a streamed PROGRAM element (EVENT msg 0x9 -> weapon,
  Mfd1 quad page mask).
- But every launch ran the DEV platform profile (no BT_PLATFORM) ->
  L4CONTROLS=KEYBOARD -> no PadRIO instance -> PadRIO::SetScreenButton
  no-ops (activeInstance NULL) -> the press never entered the RIO queue ->
  ConfigureMappables (id 9) never dispatched -> the mode never flipped ->
  fire keys kept firing.  The config machinery itself was never broken.
- With BT_PLATFORM=glass (the GLASS profile: L4CONTROLS=PAD -> PadRIO), the
  full chain now proves out headlessly:
    [btntest] PRESS 0x8 -> [cfgmap] ENTER session on ERMLaser_1
      mode 0x450421 -> 0x448421 (NonMapping 0x10000 -> Mapping 0x8000)
    [btntest] RELEASE 0x8 -> [cfgmap] EXIT (mode restored)

Landed:
- mechweap.cpp: [cfgmap] BT_FIRE_LOG diagnostics in the real
  ConfigureMappables/ChooseButton handlers (they were silent -- the G-key
  harness had logs but the authentic button path had none).
- L4PADRIO.cpp: BT_BTNTEST="addr,pressPoll,releasePoll" scripted screen-
  button harness through the REAL click seam (EmitButton -> RIO queue ->
  manager drain -> buttonGroup mapping) for headless verification.
- play_solo.bat: prefer the glass build when present AND set
  BT_PLATFORM=glass so PadRIO exists and cockpit clicks work.

Side finding (the user's keymap "flip-flop"): CONTROLS.MAP (btinput, DEV/pod
profile) and bindings.txt (PadRIO, GLASS profile) are two parallel binding
engines; which one runs depends on the platform profile, so the felt keymap
changes with the boot flavor.  Unification pending (user decision).

Awaiting live verification of the full hold-PROGRAM + tap-fire regroup flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 09:31:52 -05:00
arcattackandClaude Opus 4.8 cae616dc7c join_lan.bat: pin the operator's LAN IP (auto-discovery was unreliable)
Field report: LAN auto-discovery (BT_RELAY=auto) didn't work -- a joiner had to
hand-set BT_RELAY=10.0.0.46:1500 to reach the console every time.

- players/join_lan.bat: BT_RELAY=auto -> BT_RELAY=10.0.0.46:1500 (the operator PC's
  LAN IP), with a comment on updating it if the IP changes.
- tools/btoperator.py: the export now DETECTS the operator PC's LAN IP (UDP
  default-route probe, no packet sent) and stamps BT_RELAY=<lan-ip>:port into the
  generated join_lan.bat instead of "auto" -- so regeneration keeps it correct and
  doesn't revert to the broken auto-discovery.  Verified: detects 10.0.0.46 here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 22:27:12 -05:00
arcattackandClaude Fable 5 80c9394131 play_steam.bat: dev-tester launcher for the Steam path (EXPERIMENTAL)
Zero-mission-arg launch of the build-steam exe -> lands in the glass FE
menu, where Steam sessions actually form (host or join an ISteamMatchmaking
lobby; there is no address to dial, so this is a LAUNCHER not a joiner --
unlike join.bat/join_lan.bat).  Sets BT_PLATFORM=glass + BT_STEAM_NET=1,
logs to content\steam.log.  Guarded like the sibling bats, with the two
failure modes spelled out (zip without build-steam\; Steam client not
running = menu works, no lobbies).

Requires distributing the build-steam variant (BT_GLASS+BT_STEAM) alongside
content\ -- a different zip shape; the pod-build zips are unaffected.  For
dev testers to exercise the Steam path further (prior tests were buggy);
NOT for general players yet: the live 2-machine exit criterion
(docs/STEAM_TEST.md) is still open and the AppID is the 480 test id.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 10:04:01 -05:00
arcattackandClaude Fable 5 1cb93d7b75 play_solo.bat: open gauges too (BT_DEV_GAUGES), matching join bats
Solo practice already started in-cockpit (BT_START_INSIDE) but didn't
open the MFD gauges window -- inconsistent with the join bats.  Add
BT_DEV_GAUGES=1 so single-player practice mirrors the pod experience.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 20:15:24 -05:00
arcattackandClaude Fable 5 2fb2f8a36d join bats: default to cockpit view + gauges (BT_START_INSIDE + BT_DEV_GAUGES)
Field report: a remote player had smooth gameplay but in 3RD-PERSON with
NO gauges.  Cause: view mode + gauges are CLIENT launch flags, not
something the relay/console can enforce -- and join.bat set neither, so
remote players booted in the game's desktop default (external camera, no
MFDs).  The operator's own local launch sets both (the app's 'Start in
cockpit' + 'Dev gauges' checkboxes, on by default), which is why the
operator saw the correct view but remote players didn't.

The pod is a first-person cockpit with MFD gauges and no '3rd person' at
all, so cockpit is the right default for players.  join.bat/join_lan.bat
now set BT_START_INSIDE=1 + BT_DEV_GAUGES=1.  (Players can still press V
to toggle the external camera.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:50:21 -05:00
arcattackandClaude Fable 5 0b83d71578 Harden player bats: location guard for 'cannot find path specified'
Field report: a remote player ran join.bat from the wrong place (inside
the zip, or the .bat separated from content\/build\) and got 'The system
cannot find the path specified' + dumps -- the relative 'cd %~dp0content'
failed cryptically.

Now each bat: cd to its own dir, verify build\Release\btl4.exe +
content\<egg> exist, else jump to :badpath and print a clear message
('this file must sit in the extracted folder next to content and build;
right-click the zip - Extract All and run it from there').  Verified: run
from a dir with no content/build -> the guidance prints, no dump.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:16:38 -05:00
arcattackandClaude Fable 5 954175e44e Diagnostics: pre-launch readiness warning + named friend logs (join.log)
Two additions after the live-session troubleshooting:

1. LAUNCH-SHORT is now VISIBLE (btconsole).  Pressing LAUNCH with an
   empty roster seat previously did NOTHING (eggs_done_at only sets when
   ALL seats ACK, so the manual-launch arming silently no-op'd -- the
   'I pressed launch and nothing happened' confusion).  Now the relay
   logs 'LAUNCH pressed but NOT all seats are filled' + a per-seat
   readiness line + a WARNING naming the empty seat(s) and the fix
   (reduce the roster).  When all seats DO fill it auto-arms (launch
   request persists).  Verified: 2/3 filled -> clear warning naming
   seat3.

2. Friend logs are now retrievable (btoperator exporter).  join.bat /
   join_lan.bat set BT_LOG=join.log and their exit message tells the
   player to send content\join.log to the operator if the game closed
   unexpectedly -- closes the friend-side-crash blind spot (their log
   was the generic btl4.log, un-named, easy to miss).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:05:34 -05:00
arcattackandClaude Fable 5 52ce7c58c8 play_solo.bat: single-player practice launcher in the player export
A zip recipient can dork around offline -- no -net argument means the
game self-launches a solo mission (all maps ship in content/).  Solo
gets its own exit message (btl4.log pointer, no server language).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 14:57:26 -05:00
arcattackandClaude Fable 5 dfd1894fb9 Unreachable-server UX: message boxes + bat guidance instead of a 'crash'
Field report (first internet-join attempt): both join bats 'hard
crash' on the remote machine.  Root cause was procedural -- the host
session was not running -- but the failure PRESENTATION was the bug:
Release Fail() is a bare abort(), so a dead relay = silent process
death + instantly-closing console window.

- L4NET: the two player-facing dead-ends (LAN discovery no-answer,
  seat request unreachable/full) now show a MessageBox saying what
  happened and what to do before exiting (verified live against a
  dead relay: 'BattleTech -- can't join the game' appears).
- join bats: echo + pause after exit so the window stays readable;
  join_lan.bat header now says it is LAN-only (the remote user ran
  both -- join_lan can never work over the internet).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 14:23:29 -05:00
arcattackandClaude Fable 5 f04e8019c2 D1: relay-assigned seats -- players never need a player number
A pod launched with no BT_SELF asks the relay for a seat before joining:
new control frames SEAT_REQUEST (-6) -> relay reserves the lowest roster
seat not claimed or reserved (60s reservation so simultaneous joiners
can't race onto one seat) -> SEAT_ASSIGN (-7, int32 hostID + NUL tag)
becomes relaySelf; everything downstream (egg self-match, HELLO) runs
exactly as if BT_SELF had been set. Roster full -> SEAT_FULL (-8) ->
clean Fail(). A real HELLO pops the reservation; a pod drop frees the
seat. Explicit BT_SELF still claims a specific seat (the operator's
local launches use it).

Client: L4NetworkManager::RelayRequestSeat (throwaway TCP dial to the
relay game port, 10s reply window). Relay: SEAT_REQUEST branch in
_handle_game_frame. Operator exporter collapsed from per-seat
join_as_playerN.bat to ONE universal join.bat (+ join_lan.bat with
BT_RELAY=auto) -- every player gets the same file, first come first
served, the arcade walk-up-to-a-pod model. players/ regenerated.

Verified: 8/8 seat stub tests (distinct seats in roster order, FULL on
exhaustion, claimed+reserved stays FULL, HELLO claim accepted, duplicate
HELLO refused); 2-node localhost e2e with NO BT_SELF on either pod ->
both seated, full ladder, RunMission pair, UDP flowing post-launch;
regression smoke: explicit-BT_SELF relay session clean with zero seat
requests, classic mesh (no BT_RELAY) un-regressed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 12:11:03 -05:00