2fb2f8a36d2adf0ca1c4413c62aac2e86d1d8ba5
21
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1271d3bc76 |
Torso elevation (pitch aim) wired: the pod stick's Y axis lives
The mechs could always tilt their aim up/down -- Torso models the full vertical axis (currentElevation, rate, VerticalLimitTop/Bottom, recenter) and EVERY 1995 control mode routes stickPosition.y into Torso::SetAnalogElevationAxis -- but the desktop bridge hard-zeroed stick Y, so the axis was dead on a keyboard rig. The one pod control the remap left unwired. - btinput: JoystickY axis -> elevTarget/elevActive/elevAbsolute - mech4 shim: sElev integrator (same walk/spring model as the twist; X recenters pitch too via gBTElevRecenter) - mechmppr bridge: feeds stickPosition.y every bridged frame (the old unconditional zero removed); both mode branches covered - CONTROLS.MAP (+ numpad profile + compiled default): R/F = aim up/down, pad LeftStickY = elevation (was unused) - torso.hpp: CurrentElevation()/ElevationVelocity() accessors (diag) - [mppr] trace gains stickY (note: the trace reads AFTER the next frame's device push re-zeroes the stick -- input flows regardless) Verified live: R held -> torso elevation climbs at the authored rate and clamps at 0.349066 rad = exactly 20.0 deg (the Blackhawk's VerticalLimitTop); release holds the aim. The eyepoint correctly stays level -- pitch aims the GUNS and reads on the HUD's vertical elevation tape, as in the pod. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0a77d8e54b |
Camera-seat ranking window LIVE: the 1995 broadcast overlay reimplemented
The stubbed dpl-instance ranking display is reborn as screen-space
quads in CameraShipHUDRenderable::Render:
- followed-player callsign banner (bottom center; the old direct
index was correct -- Execute already converts to the 0-based
texture slot)
- the RANKING WINDOW: one row per scoring player, [ordinal][callsign]
in rank order right of center; visibility = the Director's
authentic flash logic (10s on / 15s off, solid final 30s); rows
follow LIVE playerRank pointers so they re-sort as scores change
- discovery: each 128x32 ordinal bitmap packs TWO ordinals side by
side ('1st|2nd', '3rd|4th' -- why 4 bitmaps serve 8 players);
draw = texture rank/2 with a u-half selected by rank parity
- alpha-blended A4R4G4B4 white-on-transparent textures x green
diffuse = the authentic green look
BT_SHOT moved AFTER the 2D pass -- it captured the backbuffer pre-HUD,
so overlays were on screen but invisible to screenshots (cost one
false debugging round). GetOrdinalTexture accessor added.
Screenshot-verified: '1st MAVERICK' standings + MAVERICK banner over
live auto-directed coverage; 2-node mech smoke PASS (un-regressed;
the new render path only executes when a CameraDirector HUD exists).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
4459262b9c |
Camera seat LIVE + Mech::PlayerLinkMessageHandler reconstructed (@0049f624)
The spectator/broadcast seat works end-to-end: a hostType=1 +
vehicle=camera pilot page boots a CameraShip whose 62-camera arena1
network loads from the BTL4.RES type-27 resource (the engine's
CreateStreamedCameraInstances existed all along -- the file probe falls
back to it), the BTCameraDirector locks onto the first live mech, and
the ship TRACKS it (sees=1, goal advancing) and CUTS between authored
camera positions (screenshot-verified: wide arena shot -> close
tracking shot).
The missing piece was the Mech override of PlayerLinkMessageHandler
(@0049f624), which the reconstruction never filled. The engine base
resolves mech->player; BT's override adds:
1. the REVERSE link player->playerVehicle = this on EVERY node
(replicants included) -- how the camera director and scoreboard
find a REMOTE player's mech; without it a spectator parks forever
('NO goal entity')
2. clears NonScoringPlayerFlag (0x4000 = bit 14): a pilot with a
vehicle is a SCORING player -- this admits REPLICATED players to
the ranking pass, so cross-node rank/score displays work
3. master only: seeds the heat bank's ambientTemperature (bank
@0x1d4) from the mission [mission] temperature= (BTMission+0xf4)
-- correcting the heat family's 'frozen 300' deviation note (it
was never frozen; THIS is the writer)
Bridge BTSetBankAmbientTemperature lives in heatfamily_reslice.cpp
(mech.cpp cannot include subsystem headers -- local-stub collision).
BT_CAM_LOG diagnostics: camera-network count, director pick + Players
group census, ship follow state, link dispatch/receive.
Verified live: 2-seat (mech + camera) relay session -- replicated
player shows +veh, director goal locked w/ 30s timer, ship tracking a
moving mech through camera cuts; standard 2-node mech smoke PASS
(un-regressed). Lesson re-learned: a 'clean' build filtered on 'error
C' missed a linker file-lock failure -- one whole test cycle ran
against a stale exe (the /FORCE gotcha's cousin; grep -i error, not
error C).
Remaining (task open): ranking-window overlay draw (L4VIDRND stubs),
operator-app camera-seat row, shot polish.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
0af0407534 |
MP4.EGG: mixed lance (MadCat/Thor/Vulture/Avatar) -- user-verified 4-pod session
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
17176058cc |
First 4-pod session VERIFIED + the EntityID-cast diagnostic trap
4 pods, relay-assigned seats, 4-pilot MP4.EGG (eggmodel-generated incl. callsign bitmaps): 4 distinct seats, full ladder, every node replicates all 3 foreign mechs (host-qualified [repl] sets are exact complements), 4 distinct spawn positions (no dropzone stacking), UDP at 4-pod fan-out (relay tx=3x rx, zero drops), all pods alive through the session. Per-host [net-rx] census symmetric: 36 NewDynamicEntity makes (mech + subsystem roster) from every host to every node. DIAGNOSTIC TRAP that cost a false bug-hunt: EntityID::operator int() returns localID ONLY, so the [repl] log's (long)GetEntityID() collapsed different hosts' mechs onto one number and read as 'missing replicants'. The log now streams the EntityID object (host:local via its operator<<). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
980c9cd7e5 |
Input remap: CONTROLS.MAP binding engine + XInput gamepad support
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>
|
||
|
|
5e993de99d |
Operator-set callsigns: the console's name-bitmap generator recreated
The 1995 console let the operator type player callsigns; the pods render them from 1bpp BITMAPS streamed in the egg ([largebitmap]/[smallbitmap] lists + hex raster pages, bitmapindex per pilot; large 128x32 on the score/ranking display via DPLRenderer name textures, small 64x16 on the kill-feed and radar/target labels). The textual name= pilot key is dead in the shipped binary -- its pilot parse reads bitmapindex only (decomp part_014). Historical bug surfaced by this work: every egg in the repo listed only ONE name bitmap (Aeolus) while pilot 2 pointed at index 2, so player 2+'s callsign has never rendered (null-checked, silently blank). eggmodel.py: EggDoc.set_callsigns rewrites the lists + one raster page per callsign per size + per-pilot name=/bitmapindex= (duplicate names get disambiguated page tags); get_callsign_rows decodes pages back (exact inverse of the engine's nibble-stream parse, GRAPH2D.cpp:340, MSB-first bit order :558); rasterize_callsign/make_callsigns render text via Qt (auto-shrink to fit, NoAntialias, 1bpp threshold) -- the only PySide6-dependent corner of the module. validate() now catches dangling bitmapindexes and missing raster pages. btoperator.py: Callsign column in the roster (free text, PLAYERn default); every save rasterizes the column into the egg exactly like the original console did. MP_BHMC.EGG template repaired: authentic Aeolus raster preserved bit-exact, Boreas page generated and listed. Verified: eggmodel self-test 20/20 (bit-exact raster round-trip, dup disambiguation, dangling-index catch); ASCII-art decode of the app-generated VIPER/MONGOOSE rasters reads correctly; live 2-node relay session on the generated egg -- full ladder, launch, zero name-bitmap load warnings; operator GUI e2e PASS after the column shift. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
43fa53d542 |
Operator console app: PySide6 station + validated egg model (the lost 1995 console, recreated)
The operator station the archive never preserved: build missions with authoritative dropdowns, run the console/relay, watch pods arrive, launch -- LAN mesh or internet relay -- plus one-click local instances for testing. tools/eggmodel.py (headless, importable, self-tested 14/14): - Order-preserving egg parse/emit: the editor manages [mission]/[pilots]/ per-pilot pages; bitmap rasters, ordinals and role pages survive untouched. - EVERY value set read LIVE from content/BTL4.RES (the resscan parser, importable): maps = MakeMessageStream(14) INTERSECT ExistanceBoxStream(26) (the game aborts unless both exist); colors/badges/patches from the VehicleTable resource (type 25, NUL-line NotationFile text with ;-comments); vehicles = ModelList names, known-good mechs (bhk1, madcat) surfaced first. - validate(): the full required-key ruleset from MISSION.cpp/btl4mssn.cpp (missing map/time/weather/scenario, per-pilot hostType/vehicle/dropzone/ color/patch/badge/experience/role, role page model=, illegal values, duplicate addresses) -- kills the hand-edited-egg crash class, incl. the color=Red-vs-Crimson gotcha that bit the dev eggs. tools/btoperator.py (PySide6, dark Fusion theme): - Mission form + pilot roster table (all dropdowns from eggmodel), relay-tag auto-numbering, add/remove pilots, egg New/Open/Save/Validate. - Mode switch: Relay (internet; runs btconsole --relay) / Mesh (LAN legacy; dial-out console at address-port minus 1 per the +1 rule). - Session panel: QProcess drives btconsole.py; stdout parsed live into per-pilot state lights (waiting/egg/registered/LAUNCHED) + relay stats. - Local launcher: spawns btl4.exe per roster row with BT_RELAY/BT_SELF (or mesh -net port), BT_DEV_GAUGES/BT_START_INSIDE toggles. - Export player scripts: per-pilot join_as_playerN.bat with the public relay hostname (a remote player's whole setup = run one .bat). - Wire/protocol code stays in btconsole.py/eggmodel.py -- the GUI is only UI + process management; CLI + test harnesses share the same tested core. Verified: eggmodel self-test 14/14; scripted end-to-end (scratchpad/operator_e2e.py) drives the real window through its own methods -- start relay session -> launch 2 local instances -> monitor observes the mission LAUNCH (user-confirmed live on screen: both pods in-mission). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c029df6e6b |
D1 phase 2+3: client TCP relay mode (L4NET) + 2-node verification
The pod can now run entirely over the relay: one outbound TCP connection
carries both the console protocol (egg/launch) and all game traffic
(envelope-multiplexed), so internet play works behind NAT with no port
forwarding. All new code is behind 'if (relayMode)' (BT_RELAY unset => mesh
mode byte-for-byte untouched; verified un-regressed).
Env gates (parsed once in the ctor, after WSAStartup so gethostbyname works):
- BT_RELAY=<host>:<consolePort> (host may be a DNS name; game port = +1)
- BT_SELF=<exact [pilots] entry> (NIC matching can't identify us across NAT)
L4NET changes:
- CreateConsoleHost: relay branch dials OUT to the relay console port
(bounded retry; console-less continue on a re-dial) instead of listening.
- StartConnecting: relay self-match by BT_SELF string; peers become VIRTUAL
hosts (INVALID_SOCKET, NoNetworkConnectionStatus) flipped online by the
relay's PEER_UP; then ConnectRelayGame dials the game port + HELLOs. HostID
assignment / remoteHostCount / the connection gate / app ladder unchanged.
- CheckRelay (new): the receive seam -- drains {route,length} envelopes,
synthesizes HostConnected/Disconnected from PEER_UP/DOWN (same messages the
mesh accept path routes), returns game frames envelope-stripped (they
self-identify via NetworkPacketHeader.fromHost; consumers route by payload).
- Send: game-host traffic -> relay unicast envelope (console host excluded --
its legacy protocol is relay-TERMINATED, not routed).
- ExclusiveBroadcast: build ONCE, send ONCE with the broadcast route -- the
relay fans out, killing the mesh's (N-1)x upload duplication.
- RelaySendAll (new): partial-send-safe transmit (required on the multiplexed
socket -- a partial write would desync framing for all peers).
- CheckBuffers: polls CheckRelay first; skips recv on virtual game hosts.
- RelayGameDown: relay-loss synthesizes all-peers-disconnected (match
continues peer-less; pod never exits mid-match).
- Mode(): now STORES the reliable/unreliable mode (was ignored) for the UDP
phase's authentic mode+flag routing.
Verified 2-node localhost relay (MP_RELAY.EGG, tagged [pilots]):
- both pods reach 'All connections completed!' via PEER_UP, then
RunningMission; both mechs' MakeMessages cross the relay (paint x2 each) and
bidirectional 148-byte update records flow (net-tx/net-rx traces; relay
stats tcp rx==tx, registered [2,3]); cockpit/HUD render; no crashes.
- driving one node transmits pose/damage/death frames through the relay.
- mesh smoke (no BT_RELAY): 2-node session still forms + simulates unchanged.
Plan: ~/.claude/plans/partitioned-snuggling-piglet.md. Next: UDP channel.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
8616b62405 |
LAST.EGG: pilot-2 -> crimson MadCat (Red->Crimson: vehicletable has no Red)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e0474ff92a |
Per-pilot mech paint: wire the color/badge/patch substitution end-to-end
- Mech::resourceNameA/B/C -> real CString members (the binary's 16-byte CStringRepresentation; deep-copy bind from the MakeMessage = FUN_00402a98, implicit member dtors) + paint-name accessors - SetupMaterialSubstitutionList reads the real egg names ([paint] log); TearDown clears the callback first (FUN_004d11e8) - dpl_SetMaterialNameCallback is real now (L4VIDEO registry); bgfload MaterialResolver::resolve() applies it to every material name -- the port analogue of the dpl board rewriting names at load - MP_BHMC.EGG: color=Red -> Crimson (vehicletable has no Red; binary Fail()ed) Verified live 2-node MP: crimson MadCat with hip hazard stripes + yellow VGL leg emblems; white Blackhawk + emblems; replicants painted on both nodes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c581553a6c |
Audio (AUDIO_FIDELITY F14 residue): bake the authored static resonant
low-pass into the zone WAVs 232 zones author initialFilterFc(8)/initialFilterQ(9) (SBK 0..127) -- the fixed resonant low-pass the EMU8000 applied in hardware; the port played them unfiltered (flagship: the LaserLoaded charge hum, fc=57 Q=87, much brighter/harsher than the arcade). The extractor now applies an RBJ 2-pole low-pass per zone: cutoff = the AWE NRPN curve (100 + fc*7900/127 Hz), resonance = SBK Q -> 0..+12 dB peak [T3 curve, endpoints exact], designed at the zone's baked rate so note-60 playback reproduces the hardware's absolute cutoff (pitch-shifted notes carry the filter -- same limitation as the rate-baked tuning). Synthetic sweep verified: flat lows, +5.4 dB at the authored 3.6 kHz cutoff, -22 dB @10 kHz, -46 dB @14 kHz. Whole bake pipeline (filter -> attenuation) now runs in float with ONE int16 conversion + peak normalization: an int-per-stage draft hard-clipped 165 of 232 zones (resonance overshoot, worst 3% of samples); now only pre-existing source-material clipping remains (14 files). 160 WAVs re-baked; the preset table is unchanged (no engine rebuild). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
691e88569a |
Audio Phase 2 (AUDIO_FIDELITY F1/F2/F13/F14): full-zone soundbank -- key-splits,
layers, baked tuning, loop regions, release fades Extractor rewrite (tools/sf2extract.py): every sample-bearing instrument zone becomes a SAMPLEINFO slot -- 603 zones / 241 presets (68/115 + 94/126 multi- zone, matching the audit exactly). Per zone: keyRange(43), sampleModes(54), SBK samplePitch(55) + rootKey(58) + coarse/fineTune(51/52), attenuation(48, INVERTED SBK scale, 0.375 dB/step [T3], baked into the PCM), releaseVolEnv(38), pan(17), shdr loop region. F2 pitch (algebraically exact): WAV rate = round(44100 * 2^(((6000 - rootCents) + tune)/1200)) -- EMU8000 v1 base 44100. Cross-checks: FootFall 17300 Hz (the audit's +4.2 st), MissileLoaded low zone 88200 (-24 st), Warnings01 8-way klaxon split w/ 3 looped zones, Death01 162 Hz extremes. F1 zone selection: SetupPatch/PlayNote take the authored note; attach/play only zones whose [keyLo,keyHi] contains it (detach the rest so a rewound source can't replay a stale buffer). Live-verified: LaserLoaded/ MissileLoaded note 36 -> low clunk zone, note 84 -> high blip zone (pitch 4); LaserCFire plays all 3 authored layers incl the looping sustain. Stereo-pair zones pan via listener-relative AL_POSITION (distance model is AL_NONE). MAX_PRESET_SAMPLES=25 (AllExplosion); fixed PRESET_isImplemented's >=5 bound. F13 loops + releases: authored [loopStart,loopEnd] applied via AL_SOFT_loop_points at buffer load (0 rejections; kills the latent boom-loop on MechExplosion's 1.5% sustain slice + the ~2.4 Hz LaserBSustain wrap tick); StopNote now honors the authored releaseVolEnv (1.1-3.9 s on ~20 looping presets) with a dB-linear fade serviced from AudioHead::Execute; restarts reclaim fading sources. One-shots keep the instant stop (faithful). Regression (40s drive+fire): stable, loop points accepted, key-splits + layers verified in the delivery trace, chirp still dead, footfalls fire. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5b46655b82 |
Audio: ENABLE sound -- real OpenAL + in-tree WAV loader; both backend DLLs were no-op STUBS (task #50)
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> |
||
|
|
d5d512e087 |
WIP checkpoint: TCP_NODELAY + dense-send + BT_JIT/BT_ANIM probes + FOGDAY test egg (task #50 investigation)
Session-in-progress peer-motion work, checkpointed before bisecting the 07-13/07-14 gait regression. Contains: TCP_NODELAY on game sockets (L4NET), every-frame dense position send while moving + came-to-rest/REST send + per-frame BT_JIT/BT_ANIM smoothness probes (mech4). bodyTargetSpeed feed reverted to derived-velocity default. MOVER.cpp spline experiment already reverted. NOT a fix -- a checkpoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
bb795e2805 |
MP live-play wave: collision economy, missiles, radar transform, panel polarity, comm ticker
The interactive 2-node playtest wave -- every fix decomp-grounded and live-verified: COLLISION ECONOMY (the ram one-shot): StaticBounce mutates worldLinearVelocity per contact and ProcessCollisionList walks EVERY touched solid per frame; with 2007 terrain-as-solids the reflections compounded x4-x40 within one frame and a walking bump one-shot a pristine mech for 112,375 pts (62-pt authentic economy). Fix: frameEntryWorldVelocity restore per contact (damage always priced at the real approach speed -- all the binary's physics ever saw); Mech::Reset zeroes the mover motion (respawn = teleport); [collide-tx]/[mp-hdlr] telemetry. Gotcha #16 (engine-facility drift class). MISSILES: peer-visible salvos (the launcher record extension carries a salvo counter + aim point; ForceUpdate actually enqueues it -- the dirty flag alone never serialized), the authentic arc (authored MuzzleVelocity vector + the Seeker's 200m/0.1/300 loft + gain-4 steering, decoded from @004beae4/@004bef78), world-impact bursts (rounds detonate on cave geometry instead of phasing through), contact-only damage (flight-cap expiry = fizzle, no more teleport damage), live re-lead, and ballistic (unguided) shells for autocannons. projweap's stale BTPushProjectile extern (the /FORCE signature trap, gotcha #6 corollary) crashed the Avatar's first AFC100 shot -- fixed + sweep rule recorded. RADAR: two transcription bugs made the scope permanently empty -- FUN_0040b244 is the affine INVERSE (not a copy) and FUN_0040adec writes ONLY the 3x3 rotation (never the translation); worldToView now Invert(view) built rotation-first. CulturalIcons sorted out of the moving grid (the phantom red pips were map props), visible-radius culls on all three draw passes, live pip verified at |delta| x ppm px. Gotcha #17 (verify the FUN_ body, not its call shape). WEAPON PANELS (the frozen-dial hunt): the binary's *(subsystem+0x40) means FAILED -- the recon's 'operating' name was backwards, inverting the destroyed-X lamps, the panel look, the children enable and the ready-lamp gate (which had NEVER executed). Polarity chain corrected end-to-end (failedState, fed by real damage saturation). Root cause of the freezes: MFD page-mode gating -- the dev composite shows ALL pages at once, so off-page dials legitimately stopped; under BT_DEV_GAUGES the 15 page-plane bits stay active (the exclusive secondary trio untouched). The SEH gauge guard now names its kills; repaint-heal resets the incremental arc after panel repaints; [panel]/[arc] probes added. COMM/SCORE: MessageBoard LIVE (the engine already shipped the whole Player__StatusMessage queue; wired the binary's one producer -- the kill branch, victim's name, 6s -- plus the consumer bridge and a lazy source bind); MP DEATHS counted via the observed-death tally (each node scores every pilot from locally observed events, the same model as the KILLS credit) and the -2/-1 engine seed clamped for display. DEV UX: node-tagged window titles (-net port), gauge panel reworked (1320x480, true 4:3 MFD cells, the portrait secondary UNROTATED upright, linear filtering, BT_GAUGE_SCALE), fixed close spawns via BT_SPAWN_XZ, Boreas flies an Avatar (first second-chassis live outing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a9c3e96f20 |
MP combat step 1 (target any peer) DONE; step 2 (cross-pod damage) localised (task #46)
STEP 1 -- target any peer mech: DONE + verified. A live-mech registry (BTRegisterMech/BTDeregisterMech from the Mech ctor/dtor) collects every mech -- player, dummy, and peer replicants. The boresight world-pick walks BTGetTargetCandidates (all mechs != shooter, closest ray hit) and the whole fire/damage block retargets from the solo gEnemyMech to the picked hotTarget; missile/projectile validation generalised via BTIsRegisteredMech. Verified one-box: instance A enumerates B's mech as a live ReplicantInstance (20 zones, ownerID=3). Solo un-regressed (pick->damage->kill clean, 28 hits + DESTROYED). STEP 2 -- cross-pod damage: dispatch + engine reroute are CORRECT and invoked with a valid owner, but the dispatched message doesn't reach the master. Entity::Dispatch (ENTITY.cpp:244) reroutes a replicant's msg via application->SendMessage(ownerID,...); BT_MP_FORCE_DMG proves A dispatches at B's replicant with ownerID=3, yet B takes 0 STEP-6 damage. Corrects the KB's [T3] "Dispatch already reroutes": the reroute FIRES; the break is downstream in the transport/delivery of a dispatched entity-message (update records flow fine -- net-rx works). Next MP task = that wire delivery. Diagnostics BT_MP_LOG / BT_MP_FORCE_DMG retained. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
48191d6fdf |
MP-front scout (task #45): P6 survives the combat rework; LAN-fight gap map
One-box smoke test re-run on the current build: console egg -> mesh -> RunningMission both instances, 174+ x144-byte update records each side, 2 mech trees per instance, 0 crashes (~6 min). The movement-replication milestone is intact after all the targeting/weapon/death changes. Gap map to a first playable LAN fight recorded in context/multiplayer.md (wiring order): (1) generalize the world-pick from gEnemyMech to all peer mechs; (2) exercise the Dispatch reroute for cross-pod TakeDamage; (3) victim state visuals on the shooter's screen (zone replication or a death event); (4) cross-pod beam visuals via the AUTHENTIC beam-keepalive messages (FUN_0041c350, templates @0x511e6c/78 -- already stubbed in emitter.cpp); (5) 2-window driving + DEATHS scoring; respawn deferred to the P5 teardown debt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
16f5f545ce |
gauge-complete P4b: AggregateHeatSink 0xBBE -> HeatSink/AmbientTemperature bound (last config NULL cleared)
The 0xBBE heat-sink BANK was built as a plain HeatSink, so the numeric-R cockpit gauge binding HeatSink/AmbientTemperature (L4GAUGE.CFG:4552) had no publisher -> the LAST unresolved config attribute. Reconstruct AggregateHeatSink : HeatSink (ctor @4ae8d0, own GUID 0x50e590), byte-exact + static_assert-locked (heatSinkCount@0x1D0, ambientTemperature @0x1D4=300, helper@0x1D8 0xC link node, sizeof 0x1E4 == factory alloc @9993). Publish HeatSinkCount + AmbientTemperature via a dense-prefix attribute table chained to HeatSink::NextAttributeID (shared HeatSink table unchanged, so CoolantMass/CoolantCapacity keep resolving). Move CreateHeatSinkBankSubsystem into heatfamily_reslice.cpp (needs the class def) and build the real class at factory case 0xBBE; mech.cpp unchanged. DELIBERATE DEVIATION (documented in the class): keep the base HeatSinkSimulation the HeatSink ctor installs; do NOT reimplement the authentic Performance @4ae73c -- it derefs a raw self+0xE0 -> [+0x158] that does not map in our compiled layout (AV/NaN, and runs for EVERY mech), and ambientTemperature is a frozen constant so the gauge reads 300 either way. Authentic relaxation model deferred. Verified: [attr] HeatSink/AmbientTemperature OK; all 50 config attribute bindings resolve (0 NULL); no every-mech crash; combat TARGET DESTROYED, FIRED #41+, un-regressed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f52bf057e6 |
WAVE 7 Phase B: the Mad Cat's LRMs LAUNCH flying missiles that fly + damage (autocannon too)
The byte-exact WORLD-ENTITY reconstruction (Projectile @4be1bc 0x340 / Missile @4bf5b4 0x368)
is infeasible on the 2007 engine: measured sizeof(engine Entity)=0x1BC vs the 1995 binary's
0x300, so the reconstruction's raw base-offset reads (velocity@0x1dc, roster@0x124, motion@0x250)
read GARBAGE on the engine (the Mech 0x638-vs-0x854 gap, but the entity integrator DEPENDS on
those offsets). So -- like the mech drive and the beam renderer -- flying projectiles are a PORT
reconstruction (BTPushProjectile/BTUpdateProjectiles in mech4.cpp, a static array + stack
messages, ZERO heap ops): seeded from the launcher's fire with the decomp's real muzzle
(GetMuzzlePoint) / launch speed (|launchVelocity|) / per-shot damage (damageData, split across
missileCount), they fly to the target (tracer via BTPushBeam) and deliver the weapon's damage on
impact through the SAME Entity::TakeDamage path as the beam (aim Mech::FirstVitalZone()).
THREE bring-up fixes were needed to make a projectile weapon fire at all (found by tracing):
1. TRIGGER: fireImpulse was only driven for the Emitter; ProjectileWeaponSimulation now sets
fireImpulse = gBTWeaponTrigger too (else CheckFireEdge never sees an edge).
2. AMMO BIN: OwnerSubsystemCount/OwnerSubsystem were stubbed ->0, so ammoBinLink never resolved
and ConsumeRound always failed; redirected to the real roster (owner->GetSubsystemCount()/
GetSubsystem(i) -- the AmmoBin 0xBCB constructs before the weapons 0xBCD/0xBD0).
3. JAM ROLL: UniformRandom() was stubbed `return 0.0f`, so CheckForJam's `0 < jamChance` ALWAYS
jammed (a projectile weapon could NEVER fire); replaced with a real LCG [0,1) (fires
~1-jamChance of the time -- authentic occasional jams).
Also: the mech's own target slot (owner+0x388) isn't populated in bring-up (the visible fire
targets the gEnemyMech global), so BTPushProjectile falls back to gEnemyMech.
Verified: Mad Cat PUSH=62 / IMPACT=31 (LRM missiles 3.33 dmg each split across the salvo + AFC100
autocannon 25 dmg), TARGET DESTROYED, 0 crashes, construction heapcheck-clean; BLH un-regressed
(also fires its ballistic weapon now). Diagnostics BT_PROJ_LOG ([projectile] PUSH/IMPACT).
REMAINING (deferred): the byte-exact world-entity Missile (Projectile : Mover, MP-replicable via
Registry::MakeEntity) -- the port projectile is master-local only (no MP replication); the real
per-weapon fire-rate/heat wiring off the subsystem sim (mech4 beam path is still the bring-up harness).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
7b7d465e5e |
Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
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>
|