Merge origin/master: the D1 relay/operator line + input remap meet the glass layer
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>
This commit is contained in:
@@ -41,8 +41,11 @@ run\run.cmd [EGG] # default DEV.EGG; cd's to content\ and runs btl4.exe -eg
|
||||
unchecked → fread on NULL if cwd is wrong). Logs to `btl4.log` in `content\` (or `BT_LOG=<file>`). [T2]
|
||||
- Interactive: **WASD** drive; weapon groups (keyboard, task #43) **1/Space** = lasers, **2** =
|
||||
PPCs, **3/Ctrl** = missiles; **X** all-stop; **V** cockpit/chase view. Default egg = `DEV.EGG`
|
||||
(map=grass, time=day). Swap mech via the egg's `vehicle=` (bhk1/madcat/…). The code path is
|
||||
mech-agnostic (verified — Mad Cat booted + fought with zero code changes). [T2]
|
||||
(map=grass, time=day). Swap mech via the egg's `vehicle=` — **ALL 18 ModelList names
|
||||
CERTIFIED playable (2026-07-18 vehicle sweep)**: the canonical 8 (avatar, bhk1/blkhawk,
|
||||
loki, madcat, owens, sunder, thor, vulture) + the short variants (ava1, lok1/lok2,
|
||||
mad1/mad2, own1, snd1, thr1, vul1); each boots a solo mission, spawns, animates, no
|
||||
crash. The code path is mech-agnostic. [T2]
|
||||
|
||||
## Debug (cdb x86)
|
||||
`"C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe"`. Pattern for a faulting stack (cwd
|
||||
@@ -64,6 +67,13 @@ to a frozen abort dialog: `cdb -p <pid> -c ".lines;~*kp 30;q"`. [T2]
|
||||
- **`docs/`** — the detailed ledgers (incl. `PROGRESS_LOG.md`, the full pre-restructure CLAUDE.md).
|
||||
- **`tools/`** — btconsole.py, disas2.py, map/res scanners. **`context/`** — this knowledge base.
|
||||
|
||||
## Versioning (2026-07-18)
|
||||
`4.10` = the 1995 arcade release; `4.11` = this win32 reconstruction; dev builds =
|
||||
**`4.11.<git commit count>` + short hash**, `+` suffix = built from an uncommitted tree
|
||||
(e.g. `4.11.311 (980c9cd+)`). Stamped every build by `tools/btversion.cmake` →
|
||||
`build/btversion.h` (`BT_VERSION_*` macros); shown in the boot banner (btl4.log head) and
|
||||
the window title. Ask a tester for their title bar / btl4.log head to identify a build.
|
||||
|
||||
## Key Relationships
|
||||
- Base: [[wintesla-port]] (the engine build recipe).
|
||||
- Verify loop: [[reconstruction-method]]; env gates: [[decomp-reference]] §6.
|
||||
|
||||
@@ -36,6 +36,7 @@ NOT `SearchList(clip_id, 16)` (which treats the arg as a LIST → walks garbage
|
||||
| AnimationResourceType | **16 (0x10)** | animation clips (lowercase: `blhrrl`=904, `blhrrr`=905, `blhwwli`=910, `blhrrli`=916) |
|
||||
| damage-zone segment list | 0x14 | per-zone segment list |
|
||||
| critical-subsystem segment | 0x1e | crit subsystem segments |
|
||||
| CameraStream | **27 (0x1b)** | per-map spectator camera network, resource named after the map (all 8 maps ship; arena1 = 62 cams). Loaded by the CameraShip system when no `cameras\<map>` dev-override FILE exists (`FUN_0042be3c` → `FUN_0042c078`; records `{count; per-cam {size; type 1\|2; pos+quat…}}` @0042ad54/@0042b9f8). [[open-questions]] §camera seat. [T1] |
|
||||
|
||||
Animation clip names in `BTL4.RES` are **lowercase**. [T1]
|
||||
|
||||
@@ -258,6 +259,16 @@ default-ON (`'0'` disables).
|
||||
| `BT_MP_LOG` / `BT_MP_NET` / `BT_MP_FORCE_DMG` | MP diagnostics / forced cross-pod damage |
|
||||
| `BT_AUTODRIVE=<0..1>` | headless throttle |
|
||||
| `BT_KEY_NOFOCUS=1` | accept key input without window focus |
|
||||
| `BT_KEY_STOP=1` | enable the dev stop-mission keystroke `'&'` (Shift+7) — OFF by default since the tester crash report (APP.cpp; [[reconstruction-gotchas]]) |
|
||||
| `BT_KEY_LOG` | `[keych]` keyboard-event delivery trace (btl4mppr.cpp) |
|
||||
| `BT_RADAR_LOG` | radar ctor probe + 1 Hz `[radar-wedge]` torso-twist rot trace ([[gauges-hud]]) |
|
||||
| `BT_SPAWN_AT="x z"` | override the spawn position (mech.cpp — MP test rigs) |
|
||||
| `BT_RXJIT` | inbound update-record inter-arrival jitter histogram (mech4.cpp — the single-box affinity artifact) |
|
||||
| `BT_SLIDE` | locomotion slide diag (mech.cpp) |
|
||||
| `BT_RELAY=<host:port\|auto>` | **D1 relay mode** — dial the relay/operator console instead of the P2P mesh; `auto` = LAN UDP-broadcast discovery on udp/15999 ([[multiplayer]] D1) |
|
||||
| `BT_SELF=<[pilots] tag>` | claim a specific roster seat in relay mode; absent/`auto` = the relay assigns one (SEAT_REQUEST) |
|
||||
| `BT_RELAY_TCP_ONLY=1` | disable the relay UDP channel (all unreliable traffic rides the TCP relay conn) |
|
||||
| `BT_INPUT_LOG` | binding-engine trace: CONTROLS.MAP load, button/keypad emissions, pad connect ([[pod-hardware]] §Desktop input remap) |
|
||||
|
||||
Full render/locomotion gates (BT_RAMP, BT_MATPRI, BT_CULL, BT_SHADOW_*, BT_LODSEL, BT_ADDLOD,
|
||||
BT_PUNCH, …) are catalogued in [[rendering]]. Warp visuals: [[translocation-warp]].
|
||||
|
||||
@@ -225,6 +225,10 @@ This recovery also CONFIRMS the HUD attr-table ids 4/5/6/8/0xA/0xB/0xC/0xD name
|
||||
(hud.hpp had flagged them uncertain). Deferred: PNAME1-8.bgf 3D marker chain. (The canopy shell
|
||||
is now authentic and shows by default — see [[cockpit-view]]; `BT_HIDE_COCKPIT=1` hides it.)
|
||||
|
||||
Player CALLSIGN labels (kill/damage feed, radar/target tags, score display) are 1bpp name
|
||||
BITMAPS from the egg, not text — format, renderers, and the operator-console generator:
|
||||
[[multiplayer]] §OPERATOR-SET CALLSIGNS.
|
||||
|
||||
## Key Relationships
|
||||
- Full history: `docs/GAUGE_COMPOSITE.md`; reticle recovery: `phases/phase-02-dpl2d-reticle.md`.
|
||||
- Uses: [[attribute-pointer]] + [[reconstruction-gotchas]]; reads [[subsystems]] state.
|
||||
|
||||
@@ -261,6 +261,128 @@ Base fns in every subsystem vtable: slot 6 = 41bd34 (Read/apply), slot 7 = 41c50
|
||||
the same read; the twist clamp contains it (on the fixed-torso BLH it pins to ±0.01° —
|
||||
invisible). First real twist record overwrites it.
|
||||
|
||||
## D1 RELAY MODE — internet play without rearchitecting (2026-07-18) [T2 live]
|
||||
Opt-in relay/dispatcher topology so pods play over the internet behind NAT with no port
|
||||
forwarding, WHILE the authentic distributed-authority simulation is UNTOUCHED (each pod still
|
||||
masters its own mech; peers stay replicants). Env-gated: **`BT_RELAY=<host>:<consolePort>`**
|
||||
(host may be a DNS name; game port = +1; UDP = same number). Identity: **`BT_SELF=<exact
|
||||
[pilots] entry>`** claims a specific seat (NIC matching can't identify a pod across NAT);
|
||||
BT_SELF absent/`auto` ⇒ the RELAY assigns the seat (see RELAY-ASSIGNED SEATS below) — players
|
||||
never need to know a player number. BT_RELAY unset ⇒ every relay branch
|
||||
is dead ⇒ the classic mesh is byte-for-byte unchanged (mesh smoke-tested un-regressed each
|
||||
phase). Plan: `~/.claude/plans/partitioned-snuggling-piglet.md`.
|
||||
- **Topology**: every pod makes ONE outbound TCP connection to the relay (an extension of
|
||||
`tools/btconsole.py --relay`) carrying both the console protocol (egg/launch — direction
|
||||
flipped, the pod dials OUT; the relay TERMINATES the legacy protocol) and all reliable game
|
||||
traffic, multiplexed by an 8-byte envelope `{int32 route; uint32 length}` (route ≥2 =
|
||||
unicast hostID, −1 = broadcast-except-sender, −2 HELLO/−3 PEER_UP/−4 PEER_DOWN). Plus one UDP
|
||||
socket for the unreliable channel.
|
||||
- **The relay is DUMB**: routes envelopes by hostID (validated against the egg `[pilots]`
|
||||
roster), holds no game state. PEER_UP/DOWN control frames on the client synthesize the SAME
|
||||
`HostConnected/DisconnectedMessage` the mesh accept path routes, so the "All connections
|
||||
completed!" gate + the whole app ladder run UNMODIFIED. Peers become VIRTUAL L4Hosts
|
||||
(INVALID_SOCKET, flipped online by PEER_UP).
|
||||
- **O(N) upload fix**: `ExclusiveBroadcast` builds ONCE and sends ONCE with the broadcast route
|
||||
— the relay fans out server-side (kills the mesh's N−1× duplication at the seam where
|
||||
`EntityBroadcastToReplicants` already serialized once). Verified: relay `tcp rx==tx`, one
|
||||
copy per broadcast.
|
||||
- **UDP channel = the 1995 NETNUB reliable/unreliable split RESTORED (not invented)**:
|
||||
`Receiver::Message` defaults `messageFlags=ReliableFlag` (RECEIVER.h:195); the ~60Hz update
|
||||
path sets flags=0 (ENTITY.cpp:590); `Mode(UnreliableMode)` at LoadingMission (APP.cpp:704,
|
||||
the 2007 port's `Mode()` was a no-op — now STORES the mode). The routing gate is
|
||||
**`(mode==UnreliableMode && !(flags & ReliableFlag))`** — flag AND mode, so the map-stream
|
||||
creation messages (also flags=0 but flowing during the still-Reliable CreatingMission window)
|
||||
ride TCP for free. UDP envelope `{route, fromHost, seq}` + frame; relay learns each pod's
|
||||
endpoint per-datagram (NAT-rebind tolerant), forwards verbatim, TCP-wraps to a peer whose UDP
|
||||
endpoint is unknown (asymmetric-block fallback); per-sender seq gate drops stale/out-of-order
|
||||
(dead reckoning absorbs the gaps — that's what it's for). `BT_RELAY_TCP_ONLY=1` = escape
|
||||
hatch (UDP-blocked networks degrade to the pure-TCP checkpoint automatically).
|
||||
- **VERIFIED 2-node localhost relay**: full ladder to RunningMission via PEER_UP; bidirectional
|
||||
replication (both mechs' MakeMessages cross the relay, paint ×2 each); during a drive the
|
||||
update stream flows on UDP (relay `udp tx` climbs 2→190+, TCP frozen at 53) and the peer's
|
||||
replicant tracks the master to sub-2u; 15% forced UDP drop (`--udp-drop 15`) stays coherent
|
||||
(replicant tracked to 0.1u); TCP-only fallback confirmed (`udp-known` drops the TCP-only pod);
|
||||
no crashes; mesh mode un-regressed. Recipe: relay `python tools/btconsole.py --relay 1500
|
||||
MP_RELAY.EGG --bind 127.0.0.1`; pods `BT_RELAY=127.0.0.1:1500 BT_SELF=10.99.0.N:1502 btl4
|
||||
-egg MP_RELAY.EGG -net 1501` (egg `[pilots]` = arbitrary unique tags). Diag: relay
|
||||
`[relay-stats]` per-transport census; client `BT_NET_TRACE`.
|
||||
- **LAN AUTO-DISCOVERY (2026-07-18) [T2]**: `BT_RELAY=auto` — the pod broadcasts `BTR1DISC` on
|
||||
udp/15999 (broadcast + 127.0.0.1, 5×1s attempts); the relay answers `BTR1HERE`+<u16
|
||||
consolePort>; the replier's source IP + advertised port become the relay address. Zero-config
|
||||
for LAN players (the 90s cabinet model); broadcast never crosses routers so internet players
|
||||
keep the explicit host:port. Verified 2-node: both pods `auto`-discovered through a real
|
||||
interface and reached RunningMission. Relay side degrades gracefully if the discovery port is
|
||||
taken.
|
||||
- **RELAY-ASSIGNED SEATS (2026-07-18) [T2]**: no BT_SELF ⇒ the pod's NetworkManager ctor makes a
|
||||
short throwaway TCP dial to the relay GAME port and sends `SEAT_REQUEST` (route −6, empty);
|
||||
the relay reserves the lowest roster seat that is neither claimed (registered HELLO) nor
|
||||
reserved (60s window, so simultaneous joiners can't race onto one seat) and answers
|
||||
`SEAT_ASSIGN` (route −7, `{int32 hostID}` + NUL-terminated `[pilots]` tag) — the tag becomes
|
||||
`relaySelf` and everything downstream (egg self-match in StartConnecting, HELLO) proceeds
|
||||
exactly as if BT_SELF had been set. Roster full ⇒ `SEAT_FULL` (route −8) ⇒ clean
|
||||
`Fail()`. A real HELLO claiming the seat pops the reservation; a pod drop (PEER_DOWN) frees
|
||||
the seat for the next joiner. First-come-first-served — the arcade walk-up-to-a-pod model.
|
||||
Client: `L4NetworkManager::RelayRequestSeat` (L4NET.CPP); relay: `_handle_game_frame`
|
||||
SEAT_REQUEST branch (btconsole.py). Verified: 8/8 stub tests (distinct seats, FULL on
|
||||
exhaustion, claim clears reserve, duplicate-HELLO refused) + 2-node localhost e2e with NO
|
||||
BT_SELF on either pod → both seated, full ladder, launch, UDP flowing.
|
||||
- **OPERATOR CONSOLE (2026-07-18)**: `tools/btoperator.py` (PySide6) — the lost 1995 operator
|
||||
station recreated: mission editor with ALL values validated live from BTL4.RES
|
||||
(`tools/eggmodel.py`: maps=type14∩26, colors/badges/patches from the type-25 vehicletable,
|
||||
vehicles from ModelLists), relay/mesh mode switch, live pod-status lights parsed from the
|
||||
console/relay stdout, one-click local instance launcher, and a single universal
|
||||
`join.bat` (+ `join_lan.bat` with `BT_RELAY=auto`) export — no BT_SELF inside, every player
|
||||
gets the SAME file, the relay seats them (`players/`). Wire logic stays in
|
||||
btconsole.py/eggmodel.py (GUI = UI + QProcess only). E2E-verified: app-driven relay session →
|
||||
2 local pods → LAUNCH observed.
|
||||
- **OPERATOR-SET CALLSIGNS (2026-07-18) [T2]**: player callsigns are 1bpp BITMAPS in the egg,
|
||||
not text — `[largebitmap]`/`[smallbitmap]` list page names in `bitmapindex` order (1-based;
|
||||
`Mission::LoadNameBitmaps` MISSION.cpp:147); each `[BitMap::Large::<X>]` page = hex raster
|
||||
rows + `x/y/width`-in-words keys, large 128×32 (8 words), small 64×16 (4), MSB-first
|
||||
(0x8000 = leftmost pixel; parse GRAPH2D.cpp:340, bit order :558). Large renders on the
|
||||
score/ranking display (D3D textures, `DPLRenderer::LoadNameBitmaps` /
|
||||
`SortAndReloadNameBitmaps` L4VIDEO.cpp:9420/9512, `mNameTextures[MAX_PLAYER_NAMES=12]`);
|
||||
small = kill/damage-feed + radar/target labels (btplayer.cpp:1486, btl4rdr.cpp:1156). The
|
||||
textual `name=` pilot key is DEAD in the binary — the pilot-page parse (decomp part_014,
|
||||
BTL4MSSN) reads `bitmapindex` only [T1]. Historical bug found: every repo egg listed only ONE
|
||||
name bitmap (Aeolus) while pilot 2 pointed at index 2 → player 2+'s callsign never rendered
|
||||
(null-checked, silently blank). Now: the operator console has a Callsign roster column;
|
||||
on save `eggmodel.make_callsigns` rasterizes the text via Qt (auto-shrink to fit, NoAntialias,
|
||||
threshold to 1bpp) and `EggDoc.set_callsigns` writes the pages + lists + per-pilot
|
||||
`name=`/`bitmapindex=` (duplicate names get disambiguated page tags); `get_callsign_rows`
|
||||
decodes back (bit-exact round-trip in the self-test); `validate()` catches dangling
|
||||
bitmapindexes. MP_BHMC.EGG template fixed (authentic Aeolus raster preserved bit-exact,
|
||||
Boreas generated). Verified: eggmodel self-test 20/20; ASCII-art decode of app-generated
|
||||
VIPER/MONGOOSE rasters; live 2-node session on the generated egg.
|
||||
- **MISSION CLOCK (2026-07-18) [T2 live]**: timed missions are authentic and were HALF-implemented
|
||||
— the egg's `[mission] length=` (600 = the 10-min pod mission) drives the pod-side countdown
|
||||
(`secondsRemainingInGame`, APP.cpp:652) and the ranking-window display for the final 30s
|
||||
(DIRECTOR.cpp:113), and the full end chain exists (StopMission id 6 → EndingMission → player
|
||||
fade → Stop) — but NOTHING fired it: in 1995 the CONSOLE sent StopMission at expiry (mission
|
||||
length enforcement is console-side). Now reconstructed: the relay arms the clock at RunMission
|
||||
#2 and sends `Application::StopMissionMessage` (clientID 4, msgID 6, +int exitCode 0) at
|
||||
length+2s grace; the operator app gets an ⏹ END MISSION button (stdin `stop`, like `launch`).
|
||||
Pod behavior at stop: authentic fade → mission loop exits → process exits cleanly
|
||||
(`[boot] RunMissions returned`). Verified live: 40s test mission ended on the clock. So real
|
||||
sessions now END after the configured length with the score display in the last 30 seconds —
|
||||
the arcade experience, complete.
|
||||
- **4-POD SESSION VERIFIED (2026-07-18) [T2 live]**: first >2-player run — 4 pods, relay-assigned
|
||||
seats (no BT_SELF), 4-pilot egg (`content/MP4.EGG`, generated via eggmodel incl. 4 callsign
|
||||
bitmaps): 4 distinct seats, 4/4 registered, launch, all 4 "All connections completed", **every
|
||||
node replicates all 3 foreign mechs** (host-qualified `[repl] mech h:l` sets are exact
|
||||
complements), 4 DISTINCT spawn positions (dropzone=one spreads spawns — no stacking), UDP
|
||||
flowing at 4-pod fan-out rates (relay tx = 3×rx), zero drops, all processes alive. Per-host
|
||||
`[net-rx]` census symmetric (36 NewDynamicEntity makes = mech + subsystem roster from each
|
||||
host to each node). **DIAGNOSTIC TRAP found**: `EntityID::operator int()` returns localID
|
||||
ONLY — a `(long)GetEntityID()` log collapses different hosts' mechs onto one number and fakes
|
||||
"missing replicants" (cost a full false bug-hunt); stream the EntityID object instead
|
||||
(`operator<<` prints `host:local`). [repl] log fixed. Capacity: roster-bound (operator app);
|
||||
relay load-tested to 8 trivially; residual 8-player unknowns: ordinal bitmaps only 1-4 in the
|
||||
egg template (rank graphics 5-8), untested beyond 4 live pods.
|
||||
- **STILL UNTESTED (config, not code)**: a real two-physical-machine / internet run (only the
|
||||
relay's console+game ports need to be reachable inbound — one forwarded port total; or a
|
||||
playit.gg-style tunnel in front of the relay, host-side only).
|
||||
|
||||
## Remaining (P6 phase 4 / Phase 7)
|
||||
Pod-LAN config: **real-IP path VALIDATED (2026-07-15)** — an egg with the machine's real LAN IP in
|
||||
`[pilots]` (not `127.0.0.1`) connected + replicated + moved end-to-end (`Connected to
|
||||
|
||||
@@ -28,6 +28,36 @@ authentic path scoped.
|
||||
ERMLaser_2/3 (back gun ports) are the only rear weapons in the roster; see [[combat-damage]].
|
||||
Remaining: bind the side/down look views to keys (only 'V' look-back is bound); pod look-button
|
||||
mappings for Phase 8.
|
||||
- ~~The `cameras\` directory (per-map camera-network files)~~ **RESOLVED from our own data
|
||||
(2026-07-18, same day)**: the `cameras\<mapname>` FILE path (CAMMGR.cpp:390-415, RP-style
|
||||
loose `.CAM` notation — present for RP maps in the nick-games 410SRC archive) is only the DEV
|
||||
OVERRIDE; the shipped binary falls back to **BTL4.RES resource TYPE 27 (0x1b) named after the
|
||||
map** (`FUN_0042be3c` probe → `FUN_0042c078(res, mapname, 0x1b)`; binary records
|
||||
`{count; per-cam {size; type 1|2; pos+quat+...}}`, parsers @0042ad54/@0042b9f8) — and our
|
||||
BTL4.RES has ALL 8 maps' networks (arena1 = 62 cameras; cavern/dbase/grass ~57KB each).
|
||||
[T1] **CAMERA SEAT LIVE (2026-07-18, same day) [T2]**: the type-27 loader ALREADY existed in
|
||||
the engine (`CreateStreamedCameraInstances`, CAMMGR.cpp:530 — the file probe falls back to it;
|
||||
62 arena1 cameras load, `[cam]` diag under BT_CAM_LOG). The REAL missing piece was
|
||||
**`Mech::PlayerLinkMessageHandler` (@0049f624)** — the BT override the reconstruction never
|
||||
filled: base resolves mech→player; BT adds the REVERSE link `player->playerVehicle = this` on
|
||||
EVERY node (replicants included — how the director/scoreboard find a remote player's mech),
|
||||
clears `NonScoringPlayerFlag` (0x4000 = bit 14: a pilot with a vehicle SCORES/ranks — this
|
||||
also makes cross-node RANKINGS work), and on the master seeds the heat bank's
|
||||
`ambientTemperature` from the mission `temperature=` (mech+0x7dc bank +0x1d4 ← BTMission+0xf4
|
||||
— the heat family's "frozen 300" note stands CORRECTED). Verified live: mech+camera 2-seat
|
||||
relay session — replicated player links (`bmp=1+veh`), director locks the goal (30s timer),
|
||||
camera ship tracks the moving mech (`sees=1`) and CUTS between authored cameras
|
||||
(screenshots). Egg: pilot page `hostType=1` + `vehicle=camera`. **RANKING WINDOW LIVE (same day)**: the stubbed
|
||||
1995 overlay reimplemented as screen-space quads in `CameraShipHUDRenderable::Render` — the
|
||||
followed player's callsign banner (bottom center, the Execute index is ALREADY 0-based) + a
|
||||
rank-ordered row per scoring player ([ordinal][callsign], right of center), visibility = the
|
||||
Director's flash logic (10s on/15s off, solid final 30s), live `playerRank` pointers re-sort
|
||||
rows as scores change. **Each 128×32 ordinal bitmap packs TWO ordinals side by side**
|
||||
("1st|2nd", "3rd|4th" — why 4 bitmaps serve 8 players): texture rank/2, u-half by parity.
|
||||
BT_SHOT now captures AFTER the 2D pass (it used to dump pre-HUD — overlays were on screen
|
||||
but invisible to screenshots). Screenshot-verified: "1st MAVERICK" + banner over live
|
||||
coverage. Remaining: operator-app camera-seat row helper (hostType not in the roster UI),
|
||||
shot-composition polish. Status: broadcast display COMPLETE.
|
||||
|
||||
## Pod-hardware readiness gaps (Phase 8 assessment, 2026-07-17) [[pod-hardware]]
|
||||
Everything below EXISTS in the tree but has NEVER run against real pod hardware. Mechanism
|
||||
@@ -621,8 +651,10 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
||||
[T3]: stream the model's UpdatePositionDiffrence/TurnVelocity/TurnDegree deadband constants
|
||||
to the senders (stand-in thresholds meanwhile).
|
||||
- The WinSock2 stack (L4NET.CPP) is 2-node verified end-to-end (cross-pod kills, beams,
|
||||
replicant gait — tasks #46-#51); remaining = the real pod-LAN config (real IPs, bare-IP
|
||||
pilot entries).
|
||||
replicant gait — tasks #46-#51). Real-IP pod-LAN config VALIDATED 2026-07-15; **D1 relay mode
|
||||
+ UDP channel + LAN discovery + relay-assigned seats + operator console shipped 2026-07-18**
|
||||
(see [[multiplayer]] D1). Remaining (config, not code): an actual two-physical-machine
|
||||
internet session through the relay (ports verified open; `players/join.bat` ready).
|
||||
- **✅ Subsystem-record replication IS wired — the AC just wasn't using it (task #61,
|
||||
2026-07-13) [T2, CORRECTED].** (An earlier draft of this note WRONGLY claimed "no
|
||||
subsystem-record channel" — that was concluded from an AC-only test before the AC's
|
||||
|
||||
@@ -25,6 +25,41 @@ to keyboard/gamepad on a dev box; real wiring is a pod bring-up task (Phase 8).
|
||||
model — `LBE4ControlsManager` groups are fed by all devices (RIO on the pod, DirectInput on dev);
|
||||
the `MechControlsMapper` interprets them ([[locomotion]]). [T2]
|
||||
|
||||
### The button space + lamps [T0, L4CTRL.h enum]
|
||||
buttonGroup addresses 0x00-0x47: `AuxLowerRight/Left 1-8` (0x00-0x0F), `Secondary1-12` (0x10-0x1B),
|
||||
`AuxUpperCenter/Left/Right 1-8` (0x20-0x37 — the preset/eng "display eng data" banks; **0x30-0x37 =
|
||||
the target HOTBOX**, pilot select), icom/door 0x39-0x3C, `Panic` 0x3D (the config-mode lamp),
|
||||
`Throttle1` 0x3F (throttle-head = REVERSE THRUST), joystick cluster 0x40-0x47 (Main trigger 0x40,
|
||||
hat 0x41-0x44, Pinky/ThumbLow/ThumbHigh 0x45-0x47 — the four MAPPABLE fire buttons). "Lamp buttons"
|
||||
are literal: physical illuminated pushbuttons in panels around the screens; the RIO `LampRequest`
|
||||
protocol lights them (`MakeLinkedLamp`), reports burned-out bulbs (`RIODeadLamps` failure page,
|
||||
per-lamp names). A lit button = active. Keypads are NOT buttons: `keyboardGroup[KeyboardPilot/
|
||||
KeyboardExternal]` carry key VALUES ('0'-'9','A'-'F', L4CTRL.cpp:2526). RIO event convention:
|
||||
press = `buttonGroup[a].Update(a+1, modeMask)` (mask saved), release = `Update(-a-1, savedMask)`
|
||||
(L4CTRL.cpp:2470-2520).
|
||||
|
||||
### Desktop input remap — CONTROLS.MAP + XInput (2026-07-18) [T2 live]
|
||||
`game/reconstructed/btinput.cpp` + `content/CONTROLS.MAP` (WASD-classic default, compiled-in twin;
|
||||
`content/CONTROLS_NUMPAD.MAP` = the corrected community numpad profile). Grammar: `key <name>
|
||||
button <addr>|axis <axis> deflect|rate <n>|keypad pilot|external <0-15>|pckey <char>|action
|
||||
<name>`; `pad <button>` same targets; `padaxis <src> axis ... [invert] [deadzone d] [rate n]`.
|
||||
Axes: Throttle (lever, rate-walks + sticks), LeftPedal/RightPedal/Pedals (turn, springs),
|
||||
JoystickX (torso twist), **JoystickY (torso ELEVATION — pitch aim; every 1995 control mode
|
||||
routes it to Torso::SetAnalogElevationAxis; keys R/F + pad LeftStickY in the default map;
|
||||
verified to the authentic VerticalLimitTop clamp, 20° on the Blackhawk; aims the GUNS — reads
|
||||
on the HUD elevation tape, the eyepoint stays level)**. Emissions mirror the RIO conventions above exactly; the four fire
|
||||
buttons (0x40/45/46/47) publish as levels to the bring-up fire channels instead of buttonGroup
|
||||
(avoids double-fire once the streamed ChooseButton mappings are exercised). XInput loads
|
||||
dynamically (xinput1_4 → 9_1_0; disconnected-pad probing rate-limited to 1 Hz). **Keys claimed by
|
||||
a binding are SUPPRESSED from the legacy WM_CHAR/KEYUP feed** (`BTInputSuppressKey`, hook at
|
||||
L4CTRL.cpp keyboard read) — this ended the historic DOUBLE-DISPATCH: 'w' drove AND selected pilot
|
||||
0; F5's key-up value 0x74 aliased to the 't' hotkey; letter key-ups (uppercase) fed the developer
|
||||
fake-event dispatcher. Unbound keys keep their authentic 1995 dispatcher meaning (reachable from
|
||||
any key via `pckey`). The old dual-use 'V' (view toggle + look-behind) is split: V = ViewToggle,
|
||||
B = LookBehind. Verified live: bindings load, W/NumPad8 drive (spd rises), X all-stop, aux-button
|
||||
emission ([input] 0x2f PRESS), suppression both directions ('r' delivered / 'w' swallowed),
|
||||
full-keyspace WM_CHAR+WM_KEYUP fuzz survived. Diag: `BT_INPUT_LOG`.
|
||||
|
||||
### The pod throttle is ANALOG-CONTINUOUS, not notched (verified end-to-end) [T1]
|
||||
The authentic pod throttle path — traced 2026-07 (task #50 throttle-fidelity question):
|
||||
1. **Hardware → RIO:** serial `AnalogReply` packet → `Ranger("Throttle", 0, 800, .05)` — raw counts
|
||||
@@ -93,6 +128,53 @@ All cockpit surfaces are bit-plane MASKS over ONE shared `SVGA16` pixelBuffer: `
|
||||
byte; `Heat`=0x4000, `Mfd2`=0x0400, `Comm`=0x8000, `Mfd1`=0x0100, `Mfd3`=0x1000; `Eng1-3` =
|
||||
engineering-mode alt planes; `overlay`=0x00C0 (shares the sec surface). See [[gauges-hud]]. [T2]
|
||||
|
||||
## The 1995 player manual — alignment audit (2026-07-18) [T1, primary source]
|
||||
`reference/manual/Tesla40_BT_manual.pdf` (34pp, from Nick). CONFIRMS the reconstruction on
|
||||
every checked control behavior:
|
||||
- **Control modes are named BAS / MID / ADV** (basic/middle/advanced; our Basic/Standard/Veteran
|
||||
names came from the RP analog — mechanics identical): BAS = joystick turns the mech, pedals
|
||||
inactive, NO torso twist; MID = pedals turn + auto-slow for tightest radius, joystick = torso;
|
||||
ADV = as MID but NO auto-slow (wider radius at speed) — exactly our mapper's turn-clamp
|
||||
difference. Mode button = top right of the secondary screen.
|
||||
- **Stick right = torso right** in MID/ADV (p8) — the corrected sign. **Blackhawk and Owens are
|
||||
the fixed-torso exceptions** (named in print!); twist arc "about 120°" (per-mech: Loki/Thor
|
||||
tables say Torso Limit 110°, Torso speed 60°/s).
|
||||
- **Throttle is a continuous lever** (push=accelerate, pull=stop; inertia); **reverse = HOLD the
|
||||
red throttle button** (release = forward) — our 0x3F hold-state model.
|
||||
- **Hot Box Selector MFD** = callsign buttons + a **CLOSEST** button (lower right = our hotbox
|
||||
button 8 → ChooseNearestPilot); radar = travel-oriented, 60° vision wedge sweeps with twist,
|
||||
red hot box + callsign on contacts; **map zoom ± buttons** flank the secondary screen.
|
||||
- **Experience gates the hunting aids**: viewscreen hot box + hunting-guide arrow appear in
|
||||
STANDARD sim mode only (not veteran/expert); expert mode adds movement heat.
|
||||
- Cockpit: 5 MFDs (Coolant UL, Engineering top, Hot Box UR, Weapon ×2) + secondary screen with
|
||||
side button columns + **EJECT button** beside the joystick + per-mech COOLANT LOOP tables
|
||||
(weapons+generators per loop 1-6) and stat sheets (tonnage/armor/reservoir liters/heat
|
||||
sinks/top speeds incl. "Super Charged").
|
||||
NEW LEADS (manual describes, port lacks input/UI): **CROUCH** (button by the secondary screen;
|
||||
`Mech::duckState` attr 0x37 + SQUAT clips exist, nothing drives them), **EJECT** (sounds exist:
|
||||
EjectButton01_z*.wav), hot-box viewscreen framing (the deferred PNAME marker chain). Per-mech
|
||||
stat tables = a systematic cross-check source for our streamed subsystem resources.
|
||||
|
||||
### Coolant-loop cross-check RESULT (2026-07-18) [T1] -- STRUCTURE strongly faithful
|
||||
Dumped each mech's subsystem->loop (subsystem's linked-condenser number, `BTCoolingLoopFrame`)
|
||||
and diffed vs the manual's COOLANT LOOPS tables. The 4.0->4.10 drift shows here too, but the
|
||||
STRUCTURE is remarkably intact:
|
||||
- **Backbone identical on ALL 6 mechs:** Generator A/B/C/D on loops 1/2/3/5, **Sensors** (our
|
||||
"Avionics") on loop 2, **Myomers** on loop 5, LRMs on loops 1&3, autocannon on loop 4, the
|
||||
large energy weapon on loop 6 -- match the manual exactly.
|
||||
- **Weapon LOADOUT identical on 5 of 6** (Thor, Vulture, MadCat, Owens, Blackhawk carry the exact
|
||||
same weapon set 4.0->4.10). **Loki is the one full rework** (4.0 PPC x2 / AFC100 / SRM6 / Medium
|
||||
Laser -> 4.10 AFC50 x2 / ER Medium x2 / SRM4).
|
||||
- **The consistent 4.0->4.10 change:** the small energy weapons were REDISTRIBUTED across loops --
|
||||
the 2 ER Small Lasers moved off the sensor/heavy loops (4.0) onto the energy-weapon loops 4&6
|
||||
(4.10) on Thor/MadCat/Vulture; SRM/laser placement reshuffled on Blackhawk. Owens is a
|
||||
near-PERFECT match (loops 1&3 exact; only a 4.0 "second myomer set" on loop 2 is absent).
|
||||
- **`Loop 0` = correctly UNCOOLED infrastructure** (condensers, reservoir, gyro, torso, HUD,
|
||||
searchlight, thermal sight, ammo bins, message manager, mechtech, controls mapper) -- not a loop.
|
||||
CONCLUSION: the coolant-loop reconstruction is faithful; every difference is 4.0->4.10 tuning
|
||||
(loadout rework on Loki, small-laser redistribution elsewhere), NOT a bug. `BT_SPEC_LOG` (mech4)
|
||||
re-dumps on demand.
|
||||
|
||||
## Key Relationships
|
||||
- Renders: [[gauges-hud]] (the MFD surfaces), [[rendering]] (the main 3D view).
|
||||
- Input: [[locomotion]] (the mapper).
|
||||
|
||||
Reference in New Issue
Block a user