Commit Graph
72 Commits
Author SHA1 Message Date
Cyd 4a38fba2c9 Merge remote-tracking branch 'origin/master' into glass-cockpit 2026-07-20 08:59:35 -05:00
arcattackandClaude Fable 5 5dd35365c7 Glass input audit: RIO mapper id fix (panel banks live), keyboard reconciliation
Root cause of the dead on-screen panel: MechRIOMapper's message-id enum
chained off L4MechControlsMapper::NextMessageID (=0x19), registering all 18
RIO override handlers at 0x19-0x2a while the streamed .CTL EventMapping
records carry the binary's ids -- the binary RIO table @0051dd30 re-registers
the BASE aux/zoom ids 3..0x13 (Hotbox 0x1a @0051de98).  Every MFD-bank/zoom
press hit the base ConfigureMappableMessageHandler FAIL trap (26/72 buttons
dead; an abort() on a trap-armed pod).  Ids now pinned to the binary
numbering + static_assert-locked (btl4mppr.hpp) -- panel goes 26 -> 52
working buttons (8 await handler reconstruction, filed on Gitea; 12 have no
streamed mapping authored = authentically inert).

Keyboard reconciliation (glass-only): the merged map -- keyboard hosts the
~20 core gameplay actions on the CONTROLS.MAP keys, the panel covers every
pod address by click (right-click = hold latch):
- W/S throttle lever, A/D pedals, Q/E twist, R/F elevation, X all-stop,
  arrows drive; numpad flight cluster kept (Cyd)
- 1/2/3/4/Space/LCTRL/RCTRL fire, LALT reverse, B look-behind (0x41)
- M=0x18 mode cycle, N=0x15 display cycle, H=0x2C coolant flush (hold),
  C=0x2F Condenser1 valve, G=0x0E weapon-1 configure (Mfd1-Quad-gated)
- F5-F9 = Mfd2 bank 0x27-0x24 + 0x22 (page-gated gen A-D / gen mode)
- hardcoded: ` or V = view toggle, J/K/L = Mfd1/2/3 preset-page cycle
  (PadRIO edges -> gBTPresetCycle; no pod "cycle" button exists)
- PadRIO::SuppressKey: bound keys are swallowed from the typed 1995 channel
  (the btinput suppression pattern, glass side) -- ends the glass double
  dispatch ('w'=pilot select 0, 'a'..'g'=MFD2 presets, NUMPAD/F-key key-up
  VK aliases like VK_F5=0x74='t').  Unbound keys keep their authentic
  meanings (5/z presets, t-o pilot select, +/- zoom, F1/F2 align).

DISPLACED from Cyd's default bindings (each reachable on the panel or by a
bindings.txt edit -- flagging for Cyd's veto): number row -> secondary panel
(1-4 now fire), QWERTY row -> pilot keypad (dropped), arrows -> hat looks
(now drive), V/C/B -> fire 0x47/0x46/0x45 (now view/valve/look-behind),
LCTRL -> throttle-down (now fire).  XInput pad section untouched.

Audit by-products (KB updated): the always-active msg-4 records identified
(0x2C = Reservoir flush, 0x29-0x2F = condenser valves, 0x1A-0x1D =
GeneratorA-D ToggleGeneratorOnOff @004b1ed0 unreconstructed); 0x13 = Mech
DuckRequest (crouch), 0x28 = BalanceCoolant; Searchlight/ThermalSight
ToggleLamp handler-sets are default-constructed EMPTY; weapon Eng-page msgs
0x3/0xb = ToggleCooling / ToggleSeekVoltage / EjectAmmo, all unwired;
unhandled messages are SILENT (no [FAIL] -- census = BT_CTRLMAP_LOG dump x
handler tables).

Verified live (build-glass2, ARENA1): panel presets/zoom/display/flush;
W drive (speedDemand 61.5, gait advances), Q turn (BAS), M -> MID, A turn
(MID), Space fires, N display, J/K/L presets, H flush drain, C valve 1->5;
suppression ('w' swallowed, 't' flows, F5-keyup swallowed); BT_SHOT frame.
Un-regression: pod build (gates OFF) compiles 0 errors, forced-walk drives,
streamed ids unchanged; CONTROLS.MAP/btinput untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 08:35:55 -05:00
arcattackandClaude Fable 5 0d032029a0 Fix 2 (Gitea #12): gauge tree survives the in-session mech re-stream
The pre-launch host drop/rejoin re-stream re-creates every mech at mission
launch; the LOBBY-built gauge tree kept attribute pointers into the freed
first-stream mechs, every widget Execute AV'd, and the BT_DEV_GAUGES SEH
guard (Gauge::GuardedExecute, GAUGE.cpp:618) disabled each one PERMANENTLY
(rate=0, no rebind) -- the frozen dev-gauges window of incident #12.  The
authentic engine flow only tears the tree down at mission transitions
(Application::Shutdown -> gaugeRenderer->Shutdown() -> ShutdownImplementation
-> Remove(0), APP.cpp:787 / GAUGREND.cpp:3264); the re-stream bypassed it.

Fix: BTL4GaugeRenderer::TearDownForViewpointRestream() performs the
ENTITY-BOUND half of that same ShutdownImplementation sequence, in order
(gaugeAlarmManager->RemoveAllAlarms() -> Remove(0) -> moving/static entity
grid Clear), keeping the mission-scoped state (warehouse, graphics ports,
interpreter, controls-owned L4Lamps -- RemoveAllLamps would delete them
behind the buttonGroup's &lamp->automaticValue registrations).  Called from
BTL4Application::MakeViewpointEntity before ConfigureForModel("Init") on any
viewpoint RE-make -- that handler runs once per stream (the incident log's
[ctrlmap] installing x2) -- so the tree rebuilds bound to the NEW mech via
the renderer's existing lazy build.  Sentinel: "[gauge] viewpoint re-stream:
tearing the gauge tree down for rebuild".

Verified with the 2-instance relay harness (port 15600, MP_RELAY.EGG,
the incident sequence): join A (dev gauges) + B, kill B by exact PID at
WAITING FOR OPERATOR LAUNCH, rejoin B, operator launch.  Pod A re-streamed
(2x ctrlmap install, 4x zonebuild), logged the sentinel, and post-launch the
docked composite is fully live (mission clock/heading/radar advancing between
BT_SHOT frames) with ZERO [gauge-fault] DISABLED lines (the incident had 11).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 23:30:43 -05:00
arcattackandClaude Fable 5 2215932016 KB: Gitea #12 incident findings -- @004bbd04 firing machine recovered, gauge-freeze root cause, dirty-bit gotcha
- decomp-reference: ProjectileWeaponSimulation @004bbd04 FULLY RECOVERED (capstone):
  fault gate reads simulationState@0x40==1 (not simulationFlags); ammo-bin gates;
  Loaded-case view/target denial = SetLevel(4);SetLevel(2) blip BEFORE FeedAmmo;
  updateModel@0x18|=1 is the replication dirty (== ForceUpdate); slot 17 @004b9c9c
  = rechargeLevel writer; FireWeapon @004bcc60 carries no gating.
- gauges-hud: CORRECTION -- launcher recharge dial is NOT authentically static
  (slot 17 @004b9c9c animates it); MP gauge-window freeze root-caused = lobby-built
  gauge tree dangling over the launch mech re-stream + permanent SEH disable.
- reconstruction-gotchas #20: updateModel@0x18 vs simulationFlags@0x28 bit 0
  (DelayWatchersFlag) mis-mapping class (Emitter::SetDirty, mislanch salvo mark).
- open-questions: #12 status ledger -- fixes pending a live-verify window; J/K/L
  preset hypothesis refuted for solo (scripted A/B); symptom-4 pip set unresolved.
- GAUGE_COMPOSITE row 13 verdict corrected (WRONG-STATIC, fix pending).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 22:46:10 -05:00
CydandClaude Fable 5 ec43e365fa 4.10 literal-source campaign: manifest DP upgrade, Route A closed, toolchain facts
- manifest410.py: monotone-DP TU labeling (data-cluster affinity + CLASSMAP
  seeds + recon @addr soft votes) replaces naive fill; BT region = 917 fn /
  ~230 KB, extents match every independently-known TU boundary.
- source-completeness: ROUTE A CLOSED (Nick holds no further assets);
  toolchain = BC++ 4.52 PROVEN by CW32.LIB byte-match, archived at
  TeslaRel410/BORLAND; OPT.MAK = the shipped binary's exact recipe; extender
  corrected to Borland PowerPack DPMI32 (was "Phar Lap TNT"); "all BT headers
  survive" corrected (17/36 bt-side; mech.hpp etc. reconstruction-only);
  engine gaps enumerated (vdata.hpp first, back-dated from BT412).
- phases/phase-03: rounds 1-3 of the source410 campaign - 6/10 surviving
  originals compile clean under BC4.52; console wire IDs recovered from the
  binary ctors (Killed=9 Damaged=10 ScoreUpdate=13 DWH=15 [T1], TeamScore
  guessed 12 [T4] - answers the TeslaSuite console-port spec's open item);
  round-3 finding: MECH.HPP is the capstone grown with the mech TU
  reconstructions, BTREG.CPP green = the header-family milestone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 07:33:59 -05:00
0bcba26213 KB: land the EXPERIENCE-LEVELS decode from glass-cockpit (Cyd)
Brings Cyd's experience-level / simulation-mode research onto master (the
KB-decode portion of glass-cockpit 4e01e83; the bundled BT410 source
manifest is left for the full glass-cockpit merge).

- context/experience-levels.md (NEW): the egg per-pilot 'experience' field
  (novice/standard/veteran/expert) is the pod's SIMULATION-FIDELITY tier,
  decoded end-to-end. FUN_004c0bc8 reads btMission->experienceLevel(+0xe4)
  and fans it into the +0x25c/+0x260/+0x26c/+0x270/+0x274 flag block:
  +0x25c 'sim live' (novice lockout: jams/searchlight/powersub), +0x260 the
  HEAT-MODEL master switch (veteran+expert; FUN_004ad7d4), +0x274 raw level
  (FUN_004ac9c8 = ==0 novice predicate; the valve/advanced-cockpit lockout).
  4.0->4.10 drift: the viewscreen hunting-aid gate is GONE in 4.10 (HUD reads
  none of these flags), and movement heat is veteran+expert not expert-only.
- btplayer.cpp/.hpp: CORRECTED comments/labels -- the +0x25c..+0x274 block is
  seeded from btMission experienceLevel/advancedDamageOn, NOT the scenario
  role's returnFromDeath; the 'roleClassIndex/showKills' names are scoring-era
  mislabels. Code logic UNCHANGED (comment-only): the ctor still reads
  scenarioRole (STAND-IN, defaults 2 = veteran) pending the wiring task.
- Corrections swept into gauges-hud.md (ROOKIE->novice lockout),
  open-questions.md (player+0x260/0x274 semantics now PINNED),
  pod-hardware.md, subsystems.md, decomp-reference.md; CLAUDE.md router row.

No code-logic change -> no rebuild needed (comment + markdown only). The
runtime wiring (seed from BTMission::ExperienceLevel()) remains TODO.

Co-Authored-By: Cyd <cyd@falloutshelterarcade.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 23:20:51 -05:00
CydandClaude Fable 5 4e01e83563 KB: EXPERIENCE LEVELS decoded -- the 0x25c block is the simulation-mode flags
The egg's per-pilot 'experience' (novice/standard/veteran/expert) is the
pod's simulation-fidelity tier -- stored at BTMission+0xe4 and seeded into
the BTPlayer ctor's flag block @4c0bc8 [T1].  The old 'per-role display
toggles / returnFromDeath' reading was WRONG: the source is
mission->experienceLevel, and the neighboring pair copies
mission->advancedDamageOn (the egg's technician splash/collision switch),
not role floats.  Known consumers: 0x260 = the HEAT-MODEL master switch
(FUN_004ad7d4), 0x25c = the novice sim-lockout (jams/searchlight/powersub),
0x274 = the raw level (FUN_004ac9c8 ==0 novice predicate).

NEW established topic context/experience-levels.md (data path, binary flag
rows per level, manual cross-refs, corrections log) + router row; sweeps in
decomp-reference/gauges-hud/open-questions/pod-hardware/subsystems.
btplayer.cpp/.hpp re-annotated with the corrected semantics; the guarded
role-based seeding stays as a marked [T3] stand-in (slot drift vs the
binary switch documented inline) until the wiring task points it at
BTMission::ExperienceLevel().

(Research by the parallel context session; committed from the glass line.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 22:45:37 -05:00
arcattackandClaude Fable 5 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>
2026-07-18 16:32:23 -05:00
arcattackandClaude Fable 5 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>
2026-07-18 16:10:29 -05:00
arcattackandClaude Fable 5 74d9d3b6ff Camera networks FOUND: BTL4.RES type 27, all 8 maps -- KB corrected
The 'cameras\ directory missing, ask Nick' entry (a03b16f, same day)
was WRONG: the file path is only the DEV OVERRIDE (the RP-style loose
.CAM files -- present for RP in the nick-games 410SRC archive, which
prompted the re-check).  The shipped binary falls back to a BTL4.RES
resource TYPE 27 (0x1b) named after the map (FUN_0042be3c probe ->
FUN_0042c078 res loader; per-cam binary records, parsers @0042ad54/
@0042b9f8) -- and our res has ALL 8 maps' networks (arena1 = 62
cameras; cavern/dbase/grass ~57KB each).  First arena1 record parses
clean (pos+quat, type 2).

The spectator/broadcast seat needs no external data: remaining work is
the type-27 loader in the WinTesla CAMMGR (file flavor only today),
the stubbed ranking-window draw, and a camera-vehicle boot test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:29:48 -05:00
arcattackandClaude Fable 5 a03b16fe6c KB: camera-seat/broadcast system investigated -- cameras\ data missing
The CameraShip spectator system is engine-complete (CameraDirector
auto-follows the score leader, CAMSHIP cuts between fixed map camera
positions like TV coverage, ranking window flashes 10s/15s + final
30s), but the per-map cameras\<mapname> notation files are absent from
the archive -- added to the get-from-Nick list with the reconstruction
alternatives (author our own / chase-cam fallback) and the two code
gaps (stubbed ranking-window draw, camera vehicle never boot-tested).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:24:51 -05:00
arcattackandClaude Fable 5 0fcd0c3106 KB sweep: env-gate hub table catches up (BT_RELAY/BT_SELF/BT_KEY_STOP/...); stale MP remaining-work line updated; gauges-hud callsign cross-ref
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 12:36:53 -05:00
arcattackandClaude Fable 5 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>
2026-07-17 09:50:23 -05:00
arcattackandClaude Fable 5 dda517b65c KB: mech paint audit -- per-pilot color/badge/patch substitution authored but unwired (the 'missing stripes/coloration' report)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 09:32:53 -05:00
arcattackandClaude Opus 4.8 46446f3870 KB: pod-hardware readiness gaps -> open-questions (Phase 8 assessment)
What's carried + plausibly functional vs what will need real work on modern
hardware: RIO serial chain / RGB-splitter channel packing / multi-output
selection / plasma driver all EXIST but are pod-untested; expected work =
multi-head D3D9 fullscreen modernization, the folded-away rear sound card
(4-speaker split), RIO protocol timing, and the undocumented pod bring-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 07:32:20 -05:00
arcattackandClaude Opus 4.8 224092aaff KB: reconcile the context system with the day's work
- decomp-reference.md: the binary ATTRIBUTE-TABLE section -- 16-byte
  {id,name,off+1,0} row format (alignment warning), the walker technique,
  and the three recovered tables (Mech ids 21-56 incl the corrected
  EyepointRotation@0x360 / RearFiring@0x410 / DistanceToMissile@0x400
  labels; HeatSink 3-12; Torso 3-15; the weapon RearFiring 'b' marker).
- combat-damage.md: the REAR-FIRE + look-view system (roster survey: the
  Blackhawk's ERMLaser_2/3 are the game's only rear weapons; every missile
  rack forward), the missile mount-frame launch truth, and the BANKED
  full Missile flight-model decode (three performances, proximity fuse).
- wintesla-port.md: post-Phase-4 closures (instability model live, F14
  static filter baked, the footstep warm-up bug + its trace-cap lesson);
  deferred list trimmed to F21/HRTF.
- AUDIO_FIDELITY.md: status block -- F14 FIXED, warm-up bug noted.
- open-questions.md: "which mechs used rear fire" answered from data;
  remaining look-view key bindings noted.
checkctx: CLEAN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 18:07:15 -05:00
arcattackandClaude Opus 4.8 aa004eb7b8 Gait: keyboard detent snaps the lever out of the walk/run DEAD BAND + KB
Byte-decoded finding [T1]: the gait SM has no stable state for a demand in
(walkStrideLength@0x534, reverseSpeedMax@0x538).  Cap semantics settled from
LoadLocomotionClips @0x4a80d4: 0x538 is the walk->run TRANSITION CLIP's exit
speed (the run ENGAGE threshold -- not a reverse max) and 0x34c (run-cycle avg
root speed) is the mapper's continuous demand multiplier (FUN_004afd10).  The
finished-callbacks up-shift when tgt > 0x534 but enter/sustain run only when
tgt >= 0x538, so a demand parked between them (Blackhawk: 22.02-30.87 =
throttle 36-50%) hunts walk -> shift-up -> shift-down forever, firing the
authored EngineShiftFwd/Rev sounds each swing -- the user's repro, faithfully
reproduced by all-authentic logic + data.  Likely masked on the pod by
MECHANICAL throttle-quadrant detents ("5 speeds" lore; the software path is
notch-free) [T4] -- added to get-from-Nick.

Accommodation (keyboard = our stand-in lever): at key REST, snap the lever to
the nearer dead-band edge (walk cap, or run engage + margin since the cont
check is >=).  Sweeping THROUGH the band while held stays continuous -- an
authentic moving lever; the single shift it fires is the authentic shift.
BT_GAIT_TRACE logs [gaitdetent] snaps.

KB: locomotion.md gait-dead-band section, pod-hardware.md mechanical-notch
hypothesis, open-questions.md Nick item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 11:34:18 -05:00
arcattackandClaude Opus 4.8 3224009dc1 MP: peer accel/decel "snap" MEASURED ~0.64u (not ~2.9u) -- effectively resolved (task #50)
Went after the flagged ~2.9u peer-motion snap. Added a BT_SNAPLOG diagnostic
(mech4.cpp: logs the re-anchor drift eMag = |authority - dead-reckon| absorbed per
frame) and ran a controlled accel/decel soak: node B BT_AUTODRIVE + BT_DRIVE_SWEEP0
sweeping the throttle through STOP, node A observing, affinity-pinned.

Result: peer drift maxes at ~0.64u (median 0.54u, 18 events >0.5u over 48s), gently
absorbed at k~0.24/frame -- SUB-UNIT, at the noise floor. The ~2.9u figure was stale:
it predated the peer body-channel swap (96a896a, which put the peer on the same
channel the master's mirror predicts) AND was measured without the CPU-affinity fix
(packet-jitter-sparse records inflate the drift). The old "run the master mirror on a
leg-channel prediction" plan is moot now the peer is on the body channel.

No motion-code change -- just the measurement + the BT_SNAPLOG diagnostic (retained).
KB (multiplayer.md, open-questions.md) corrected to the measured value. checkctx CLEAN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 16:15:56 -05:00
arcattackandClaude Opus 4.8 bc4d6e5597 MP: console-death freeze NOT REPRODUCIBLE + fix the latent game-listener-close bug (task #50)
Investigated the "console death froze peer replication" open question (2026-07-14)
with a direct 2-node repro (BT_REPL_LOG on a circling peer, affinity-pinned):

- Clean relay kill -> replicant kept circling smoothly through the kill, NO
  disconnect logged, node survived. Replication did NOT freeze.
- Deliberately STUCK console (scratchpad/btconsole_stuck.py: connect, start
  mission, then stop recv() while holding the socket OPEN = the exact
  "receive pad full -> close never seen" mode hypothesized) -> replicant kept
  moving the whole run. Replication did NOT freeze.

Conclusion: peer replication is INDEPENDENT of the console (pods replicate
peer-to-peer over the GAME socket; the console is a separate egg/mission/status
channel). The original freeze was a transient -- plausibly the same single-box
packet-jitter/CPU-contention artifact root-caused in 49d73dc -- or was fixed by
later MP work.

FIX (the one real bug found): L4NetworkManager::HostDisconnectedMessageHandler's
ConsoleHostType branch closed gameListenerSocket (the GAME listener) on a CONSOLE
disconnect -- a naming bug (the comment + commented-out OpenConnection intended a
CONSOLE re-listen, which CreateConsoleHost already does). Removed. Harmless to
established peer sockets (why a live match survives console loss) but it would
have blocked a NEW peer from joining after a console cycle. Verified: 2-node
still connects (All connections completed!) + the peer circles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 15:52:35 -05:00
arcattackandClaude Opus 4.8 82e6bb306a KB: sweep STALE "unfinished" claims -> match landed commits (context-system audit)
4-agent audit cross-checked every open/pending/stub/deferred claim in the context
system against git + code. Fixed ~13 stale spots (currency only, no code change),
each contradicted by its own file body + a landed commit:

- 0xBD3 SubsystemMessageManager (task #7, afefaee): subsystems.md frontmatter+body
  line 57; decomp-reference.md 0xBD3 row + +0x434 row -- all still said
  "MISLABELS controlsMapper / WAVE 8 untangle" (done; mech.hpp names it messageManager).
- Cylinder hit-location STEP 6 (d07ac7d): combat-damage.md frontmatter;
  decomp-reference.md "not reconstructed" line.
- Per-mech turn-rate wiring (task #64b, a83995a): multiplayer.md + locomotion.md
  frontmatter (still "bring-up constant / not yet wired").
- Mech-level update records (task #1, c9f0c2a): open-questions.md "dead Wword
  branches mech.cpp:1511/1613" (those branches are gone; named fields now).
- Collision damage + DeathShutdown (2026-07-08/12): combat-damage.md frontmatter.
- Myomers un-stub (8b36440): decomp-reference.md "INERT un-stub" row.
- Day/night BT_MATPRI (shipped, default-on): rendering.md frontmatter (LOD half kept).
- Fire-trigger path (task #5, 8ed6184): open-questions.md "still gBTWeaponTrigger
  bring-up" (globals retired/never-read).
- HEAT entry FACTUAL fix: "heatLoad -> effectiveRange 0 = overheat cutout" was false
  per aab7a8a -- effectiveRange reads host-zone damage; the real cutout is the
  FailureHeat charge-hold.

Frontmatter open_questions re-pointed at genuinely-open items. VERIFIED NOT stale
(left as-is): multiplayer body-channel-projection notes (a real open [T3] residual
per the coupled-motion section's ~2.9u leg-vs-body snap). Genuinely-open items
(factory roster loops 2-4, subsystem-panel gate, missile cluster, console-death
freeze, gyro tails, etc.) untouched. checkctx CLEAN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 14:35:41 -05:00
arcattackandClaude Opus 4.8 ac599c0d2a KB: reconcile STALE peer coupled-motion "COMPLETION PENDING" entry -> DONE (task #50)
The open-questions.md block was the old completion PLAN (ea39af1) and was never
updated after the work actually landed + the residual was root-caused. It
contradicted multiplayer.md (which correctly documents both "Authentic coupled
peer motion -- DONE" and the packet-jitter resolution). Reconciled to DONE:
coupled single-source gait pipeline default-on (c52a1ad/b013742/96a896a/f094d78/
23f1532); the "needs master half" conclusion superseded by 49d73dc (residual
shakiness = single-box packet jitter, a test-rig artifact, fixed with CPU
affinity, user-confirmed -- NOT a game bug). Minor follow-ups (2.9u channel-snap,
decay constant) tracked in multiplayer.md. No code change -- KB currency only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 14:19:43 -05:00
arcattackandClaude Opus 4.8 de8f6d02c1 MP: world structures (garages/walls) TARGETABLE -- boresight ray-tests the static collision tree (task #50)
The boresight's non-mech pick only sampled the VISUAL heightfield (BTGroundRayHit),
which on arena1 is a single flat 'sky'-named ground mesh (and class-42 BuildTables
likewise holds only 'sky') -- so shots passed THROUGH the garages/walls and only mechs
moved the HUD range axis (user-reported regression: "buildings used to move the range
axis; now only mechs do, and you can only fire at a peer").

Real fix: the boresight now ALSO ray-tests the ZONE'S STATIC COLLISION SOLID TREE --
the same geometry that already blocks the mech's walk. Authentic engine mechanism:
Mover::FindBoxedSolidHitBy already tests the static world via
zone->GetCollisionRoot()->FindBoundingBoxHitBy(line). Factored its "static world" tail
into Mover::FindStaticSolidHitBy(Line*) (static solids only, no movers), wrapped by
Mech::WorldStructurePick(start,dir,range,&hit) (builds the world-space Line, reads the
entry point back via line->FindEnd since HitByBounded clips line->length=enter).

Boresight pick order is now: closest MECH (PickRayHit, damage zones + lock) -> closest
STRUCTURE (WorldStructurePick; occludes a mech BEHIND it; designates the gBTTerrainEntity
sentinel + entry point, so the range caret reads the structure distance and NO lock ring
draws, mech4.cpp:4529) -> flat ground (BTGroundRayHit) -> sky (fire-at-nothing). Also
un-skips arena1's misnamed-'sky' flat ground so the ground tier works (btvisgnd
geometry-aware skip + [mapent]/[rendent] census).

Verified headless: a BT_WSWEEP horizontal ray-fan on arena1 tracks position (3/24 hits
near the boundary -> 17-21/24 inside the interior garage cluster -> 3/24 past it =
DISCRETE interior solids, not an enclosing box), no crash/assert/AV. Interactive aim
(BTGetAimRay) can't run headless (no window -> noRay), so the sweep is the headless proof;
interactive aim is user-verified.

Note: FindBoundingBoxUnder (the ground/containedByNode BoundingBoxTree) is DOWNWARD-only
(gravity/ground-snap: *height = FindDistanceBelowBounded), useless for a horizontal
boresight; the static SOLID tree's FindBoundingBoxHitBy is the only ray-vs-world query.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 10:08:45 -05:00
arcattackandClaude Opus 4.8 ea39af1ff6 KB: peer coupled-motion architecture -- completion plan + session landings (task #50)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 20:00:25 -05:00
arcattackandClaude Fable 5 e6ad29d648 KB: log the console-death replication-freeze open question (task pending recurrence)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 09:31:21 -05:00
arcattackandClaude Fable 5 da80727467 KB: autocannon jam/coolant -- SOLVED from decomp, QA behavior CONFIRMED faithful
Reproduced the QA report end-to-end from the decomp + raw-binary-verified constants.
The coolant-priority effect is real but fire-rate-dependent: at the ~11s combat
cadence, not-boosted jams (T=1058>1000) while boosting the AFC's condenser keeps it
below the line (T=953, no jam) -- exactly as the QA described. Max-spam (8s) overheats
regardless (why the earlier 'weak boost' read was wrong); 16s never jams. All AFC100
constants verified authentic vs BTL4.RES raw bytes (afc_dump.py). No bug, no stand-in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 07:27:01 -05:00
arcattackandClaude Fable 5 21abe2212e KB: autocannon jam/coolant-priority -- mechanism is FAITHFUL + COMPLETE (decomp hunt)
Two-agent decomp hunt on the QA report ('AFCs jam on 3rd shot if coolant priority
not boosted'). Conclusion: the whole coolant->jam chain is reconstructed end-to-end
and FAITHFUL -- the weapon's constant coolantFlowScale=1.0 is authentic (only
condensers' 0x15C is written from the valve, via RecomputeCondenserValves over the
condenser chain mech+0x7cc). Authentic mechanism is second-order-in-equation but
first-order-in-outcome: weapon -> its condenser -> bank; boosting a condenser's valve
share (~5x conductance swing) keeps its weapon below degradationTemp -> no jam.
Distinguished the sticky probabilistic jam (degrad 1000, JammedState 5) from the
self-clearing overheat lockout (failure 2000, weaponAlarm 7). Empirically the port is
directionally correct (~4th-shot probabilistic) but not the QA's deterministic 3rd
shot -- a calibration/scenario question pending QA clarification, NOT a stubbed path.
Do not tune the heat economy to force it without ground-truth.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 06:56:34 -05:00
arcattackandClaude Fable 5 844edbd2aa KB: FX per-zone chain LIVENESS-VERIFIED (BT_CRIT_PROBE solo)
Closed the loop: BT_CRIT_PROBE=8 on solo DEV.EGG drove the full per-zone FX chain
end-to-end -- [zonefx] entity 1:321 seg 12 psfx 12/13 -- proving TakeDamage ->
mechdmg effect loop -> BTStartZoneEffect -> StartEntityEffectImplementation (segment
world-pos resolve) -> BTStartPfxAttached fires live. The 2-node fight harness didn't
engage (mechPicks=0, an MP-harness spawn/connect issue, not FX); BT_CRIT_PROBE is the
reliable solo liveness path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 06:12:12 -05:00
arcattackandClaude Fable 5 a198e5fb4a KB: fix my botched FX entry -- the impact/destruction FX chain IS reconstructed
Reverting a wrong 'correction' I just made: I claimed the six L4VIDRND explosion
renderables were STUBBED and 'never draw'. WRONG -- ScalingExplosionRenderable has a
real ctor/Execute body, and the impact/fire/detonation FX were reconstructed in the
Fire VISUALS (48c9c84) + Impact-FX FORENSICS (065c114) waves. The '//STUBBED: DPL RB'
markers I grepped are benign 2007 empty ctor/dtor notes on unrelated renderables
(InnerProjectile/DPLObjectWrapper/ChildLight), not the explosion FX. Only genuine
open item: the chain is not yet liveness-verified headlessly (no solo enemy).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 06:06:45 -05:00
arcattackandClaude Fable 5 31d338b6ff KB: correct stale StartEntityEffectImplementation entry (dispatcher DONE 2026-07-13)
The per-zone effect dispatcher was reconstructed + wired the same day the audit
flagged it as 'RECOMMENDED NEXT TARGET' -- the entry was never updated. Correct it:
dispatcher done (btl4vid.cpp:889, wired at mechdmg.cpp:1120); the real remaining
FX gap is the six still-STUBBED L4VIDRND scaling-explosion renderable bodies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 06:01:53 -05:00
arcattackandClaude Fable 5 8f7b304069 KB: mark authentic per-mech turn-rate DONE (task #64b)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 05:56:48 -05:00
arcattackandClaude Opus 4.8 c0a0ec5e69 KB: document the searchlight fog-swap latent bug (task #63, left as-is by decision)
Investigation (workflow) found the searchlight-driven fog swap never worked in the
ORIGINAL 1995 binary either -- it's a latent bug, not a port regression:
SearchlightSimulation (@004b841c) reads requestedOn@0x1E0 (never written) while
ToggleLamp (@004b860c) toggles commandedOn@0x1DC; no bridge exists, so lightState
@0x1D8 is perpetually 0 (lamp never lights). The self-consistent sibling
ThermalSight reads the field it toggles (0x1DC) -- the tell. The port reproduces
the bug faithfully (searchlight.cpp:189). Additionally the port never constructs
PullFogRenderable, so even absent the bug the swap wouldn't fire.

Decision: LEAVE AS-IS + document (faithful to the buggy original); the port keeps
the static lights-ON fog= values. A working swap would DEVIATE from the shipped
binary (repair the sim to read commandedOn + construct PullFogRenderable at the
btl4vid MakeMechRenderables inside pass + a toggle input) -- fully scoped in the KB
if ever wanted. No code change this commit.

Documented in rendering.md (fog section), open-questions.md (deferred subsystems),
subsystems.md (Searchlight). Core per-map/time/weather fog (task #63) is unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 22:04:29 -05:00
arcattackandClaude Opus 4.8 12fbc023a8 Combat: MISSILE SPLASH DAMAGE -- Explosion::SplashDamage reconstructed (task #62)
Area-of-effect blast on missile detonation, from the T0 source EXPLODE.cpp:50-254
(@0042fad0) + the arcade decomp. Fires on ANY missile impact (world or mech) --
the +0x360 gate sits in the Missile::Perform collision branch (part_013.c:10097).
Only missiles splash; the AC's tracer is not a Missile.

Radius source CORRECTED: it is the ROUND's own GameModel (type-0xf) +0x50, seeded
from the launcher's linked AmmoBin ammoModelFile @0x1e8 (part_013.c:8778) -- NOT
the launcher's ExplosionModelFile (which resolved to 0). Live-resolved radius = 30
for both MP missile launchers; 0 for AC/Emitters.

Burst falloff = baseBurst / dist^exp floored at 1 (arcade 1.25 = decomp 0x3ff40000;
WinTesla EXPLODE.cpp:209 drifted to 1.2f). damageType/amount pass through unchanged;
only burstCount, radial damageForce, and impactPoint are set. Excludes shooter +
direct victim; dist>radius gated; delivered via msgmgr (cross-pod) or Dispatch.

New: BTResolveSplashRadius / BTApplySplashDamage (mech4.cpp), hooked at both the
world-impact and contact detonation paths; BTAmmoRoundModelResource bridge
(ammobin). Env: BT_SPLASH_LOG, BT_SPLASH_TEST (synthetic near-miss), BT_AF_MISSILE
(missile autofire). Verified: near-miss dist=15 -> 1 burst to a bystander; live
missiles detonate + exclude the direct victim; AC does not splash; no crash.

Deferred (T3): per-player enable sub-gate missile+0x360 = BTPlayer+0x264 (writers
read as a per-frame toggle, not a config flag) -- port treats SplashRadius>0 as the
enable. KB: combat-damage.md + open-questions.md updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 19:06:24 -05:00
arcattackandClaude Fable 5 4c54f7ef0c Ballistic FX: DAFC muzzle flash + autocannon fire replication (task #61)
The autocannon gains its authentic muzzle effect AND becomes visible when an
enemy fires it -- the ballistic-FX gap behind "I only see the AC from my own
view."

MUZZLE FLASH (the genuine shipped effect, not the cut card):
- MUZFLASH.BGF is an orphaned/cut asset (nothing references it) -- so it is NOT
  rendered.  The shipped projectile-gun muzzle effect is DAFC.PFX, which
  BTDPL.INI documents as "the effect used on all projectile guns" (psfx 6
  external / 14 internal): an orange fire-smoke blast (btfx:firesmoke1),
  maxIssue 25 over ~0.2s so the emitter auto-expires = one burst per shot.
- BTFlashMuzzle (mech4.cpp) spawns it on the gun-port SEGMENT via the existing
  BTStartPfxAttached path; the segment frame sprays -Z out the barrel.  Hooked
  at ProjectileWeapon::FireWeapon (the fire edge).  Default ON (BT_MUZZLE=0
  disables).  AC only -- lasers show their beam, missiles their launch.

ENEMY AC FIRE NOW REPLICATES (the real find):
- ROOT CAUSE: the subsystem-record replication channel EXISTS and works (mech
  ticks subsystem->PerformAndWatch(update_stream); Entity::UpdateMessageHandler
  routes incoming records to GetSimulation(subsystemID-1)->ReadUpdateRecord).
  The emitter (beam) and MissileLauncher (salvo mirror) both call ForceUpdate()
  so their fire serializes -> enemy lasers + missiles ARE visible on the peer.
  The AUTOCANNON set only `simulationFlags |= 0x1` (the +0x28 instance flag,
  NOT the updateModel bit WriteSimulationUpdate walks) and had NO fire record,
  so its shot never crossed the wire -- the enemy's cannon was invisible.
- FIX (the AC twin of the missile salvo mirror): ProjectileWeapon::
  WriteUpdateRecord/ReadUpdateRecord (fire counter + aim) + ForceUpdate() in
  FireWeapon.  The replicant edge-detects the counter and mirrors ONE visual
  round + DAFC muzzle flash from its own resolved muzzle; a null/untargeted aim
  streaks straight out the barrel (launchVelocity) instead of toward origin.
- Verified live 2-node: the watching node logs REPLICANT AC shots + DAFC
  flashes at the enemy's gun-port (seg 7), aimed shots fly to the real target;
  no crashes.

TRACER: all ACs author TracerInterval=1 (every round is a tracer); the existing
amber streak is acceptable-authentic, so no tracer change was needed.

KB: open-questions.md -- CORRECTED the (wrong) earlier note that claimed no
subsystem-record channel exists; it does, the AC just wasn't using it.  Logged
the methodology lesson: the coverage audit finds UNWRITTEN functions, not
"reconstructed but inert" ones (a function present but never called -- the AC
record + the missile mirror both looked done); a LIVENESS audit would catch
that class.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 17:23:59 -05:00
arcattackandClaude Fable 5 267059ab88 Damage economy RECONCILED: 3 stand-ins fixed, 3 paths confirmed authentic (task #60)
Full 5-path audit of the damage economy vs the decomp (5-finder +
adversarial-verify workflow), resolving the KB self-contradiction the
binary-coverage audit flagged.

CONFIRMED AUTHENTIC as-is (no change needed):
- Energy beam (emitter.cpp): damagePortion = authored DamageAmount x
  (charge/seekV)^2; the ctor x1e7 and fire x1e-7 cancel (_DAT_004bafbc
  dumped from the exe = x87 80-bit 1e-7 exactly).
- Autocannon (projweap.cpp): full authored DamageAmount from resource
  +0x19C delivered unmodified; the 0.0625 at :667 is the shooter's own
  gyro recoil, not the round.
- Zone-armor BASE model: damageLevel += amount x damageScale[type]
  (engine DAMAGE.cpp:379, called mechdmg.cpp:427), legs x0.5, 1.0=dead.

3 STAND-INS FIXED (all byte-verified against the decomp):
- A. mechdmg.cpp:451 -- read the phantom `stance` member (no binary
  offset, zero writers -> perma-0), so the leg-shot-out -> fall/death
  branch was DEAD. Now MovementMode() (mech+0x40, @part_012.c:6910).
- B. mechdmg.cpp:458 -- guarded the leg partial-failure graphic on the
  always-0 IsAirborne() stub where the binary calls IsDisabled()
  (@0049fb54 = movementMode 2||9). On a wreck the binary SUPPRESSES the
  write; the stub let it corrupt graphicAlarm 9->4/3 -- the task-#52
  wreck-graphic bug, now fixed AT SOURCE (was only masked by the
  IsMechDestroyed latch).
- C. mech4.cpp:1551 -- flat kShotDamage=12 fed as the kill-score
  damageAmount (the KB self-contradiction: task #8 claimed it retired,
  but it was live). The score handler @0x4c02e4 derives the whole kill
  award from it, so every kill scored identically regardless of weapon.
  Now lastInflictingDamage -- the real killing-blow magnitude, latched
  in TakeDamageMessageHandler (mech.cpp:624), mirroring the per-hit path
  (mech4.cpp:1207). The phantom `int stance` slot is reused for the new
  Scalar member (size-neutral, no layout shift); init 0 in the ctor.

DEFERRED (task #60-D, documented): the missile CLUSTER model -- the port
fires N flying rounds (net armor total authentic) vs the binary's ONE
missile with a random burstCount cluster roll (loses cluster variance +
single-zone concentration). Blocked on an OPEN decomp semantic (does
burstCount multiply armor or only the gyro kick? settle at FUN_004bef78
-> FUN_004be078 -> EXPLODE.cpp:209-210).

VERIFIED live: clean build; 2-node fight -> clean center-mass kill (no
crash, kills 0->1); [zone-armor] dump confirms per-zone armor 50-140 +
legs x0.5. NB the displayed POINTS score still reads 0 -- a SEPARATE
open gap (scoreAward + role/team/tonnage multipliers unwired); fix C
corrected the damage INPUT to that formula.

KB swept: open-questions.md (self-contradiction resolved + task #60
summary + deferred missile item), combat-damage.md (damageScale is
type-indexed not even/odd; task-#52 source fix; kill-score section),
RECONCILE.md (missile = ONE spawn not N), stale comments in mechweap.cpp
(SendDamageMessage is LIVE), mislanch.hpp, mechdmg.cpp (FUN_0049fb54 =
IsDisabled).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 15:38:04 -05:00
arcattackandClaude Fable 5 2e9c78d604 Gait: the AUTHENTIC walk lean -- interior/exterior clip sets, level cockpit + leaning mechs (task #59)
The MadCat (and every leg-mech) leans -8deg into a walk / -11deg into a run --
an authored jointhip (hingex) pose in the EXTERIOR gait clips that the walk/run
CYCLE clips never rebind, so it HOLDS.  The 1995 game keeps a separate INTERIOR
('i'-suffix) clip set for the mech you PILOT: those shake jointshakey (cockpit
rattle) and OMIT jointhip, so your own view stays level while everyone else sees
you lean.  The port had this entirely dead -- the local cockpit mech pitched
-8deg into the ground (user-reported "staring at the ground").

ROOT CAUSE (two stacked bugs):
- The authentic ctor clip-set gate (@part_012.c:10308-10320) was reconstructed
  as no-op stubs: LoadLowDetailBody/LoadHighDetailBody MISLABELED the
  FUN_004a80d4/86c8 GAIT-CLIP loader addresses as a "body LOD" pair, so the gate
  did nothing and a separate unconditional LoadLocomotionClips always loaded
  EXTERIOR clips.
- LoadLocomotionClipsExt (the interior loader) was a stub aliased to the
  exterior loader.

FIX:
- Reconstructed the real 4-char INTERIOR loader (byte-exact vs @004a86c8; the
  'i'-suffix table dumped from the exe @0x10d74d: swri/wwri/wwli/...; all 18
  base clips confirmed present in BTL4.RES for mad/blh/ava).
- Fixed the ctor gate: getenv("L4VIEWEXT") || (instanceFlags&0xC)==4 -> exterior
  (replicant / forced external view); else -> interior (local cockpit master).
  Removed the mislabeling no-op stubs.
- PORT ADAPTATION (MaintainViewClipSet, per-frame in PerformAndWatch): the port
  never sets the replicant COPY bit (all MP mechs build as local masters --
  heat-sim/scoring/torso-watcher-connect all run on both, by design), so the
  ctor gate lands everyone on interior.  Pick the set by VIEWPOINT instead: the
  mech you pilot keeps interior (level cockpit), every other mech flips once to
  exterior (the lean).  Reloads only on a viewpoint-status change; the sets
  share stride data so the swap is seamless.  Model pointers stashed at ctor.

VERIFIED live (BT_HIP_LOG probe): your walking mech writes jointhip=0 (level);
the peer's replica in the other pod's view leans at exactly -8.0/-11.1deg --
the authored clip values.  A/B control: L4VIEWEXT=1 forces the walking master
back to 74 sustained -8/-11 writes.

KB: locomotion.md ("NO walk lean" audit conclusion CORRECTED -- the lean is
authored + sustained + clip-set-specific), asset-formats.md (.ANI parses
pitch/yaw/roll for all joint types; KeyJointPos translation; cycle clips carry
no pitch), open-questions.md (item closed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 14:46:22 -05:00
arcattackandClaude Fable 5 02cdfd6576 Torso: the TWIST goes LIVE -- electrical watchdog chain, centered crosshair, coherent controls (task #57/#58)
The MadCat torso twists, the view turns with it, and targeting follows.
Three reconstruction fronts closed:

THE ELECTRICAL WATCHDOG CHAIN (why the torso never powered up):
- PowerWatcher::UpdateWatch reconstructed (@004b181c, the REAL registered
  Performance -- PTR @0050f5fc; Ghidra missed the fn start): the watchdog
  MIRRORS the watched subsystem's electrical level (+0x278), brownout
  downgrade when gen output <= minVoltage% x rated.  @004b1804 relabeled
  ResetToInitialState (slot 10) -- the old "Simulation" tag was wrong.
- The factory watcher-CONNECT pass reconstructed (vtable slot +0x38,
  @004aee2c/@004b1a40 byte-identical, recovered from raw exe bytes):
  watchedLink.Add(roster[watchedSubsystem]) on the master node.  Was the
  SubProxy::Start() no-op -- every watchdog sat at 0 forever.
- MinVoltageScale = 0.01 (a 10-byte x87 literal @0x4b1924; was 1.0f =
  permanent brownout) and PowerWatcher's Derivation chains its REAL base
  HeatWatcher (the HeatableSubsystem stand-in broke IsDerivedFrom for the
  whole Torso/Searchlight/ThermalSight family).
- KB correction swept: derivation tag 0x50e604 = HEATWATCHER (not
  "HeatSink"); the btl4gaug heat-widget gate now tests it via the
  BTIsHeatWatcher bridge.

THE CROSSHAIR (task #58 forensics, 6-agent workflow + live probes):
- The VIEW is TORSO-MOUNTED: jointtorso -> jointeye -> siteeyepoint in
  every twist-capable .SKL; the camera + canopy ride the same hinge
  subtree through HingeRenderable's live matrix-stack compose -- ALREADY
  WORKING in the port.  The crosshair stays screen-centered (center IS
  the boresight); the twist reads on the tape carets/compass/radar.
- The real bug was the port's gBTAimX = tan(twist) slew (the falsified
  "body-mounted view" model): the camera already carried the twist, so
  the crosshair counter-slid to hull-forward and the fire ray with it.
  Deleted; the pick ray inherits the twist from the yawing eye basis.
- Two instrumentation traps documented (chase-eye-as-default-camera,
  BT_FORCE_TORSO clobbering real joints -> the hook now only fills
  unresolved ones); an over-correcting explicit eye compose was added on
  those false readings and retired the same day.

CONTROLS + REPLICATION:
- Q/E spring-center on release (the axis is a twist-RATE demand; the old
  hold-deflection model drifted forever); X also zeroes the axis and
  pulses the authentic torso Recenter (@004b6918).  M cycles control
  mode via the real CycleControlMode body.
- Torso update-record DIRECTION fixed: engine truth is Write=serialize /
  Read=apply; @004b6a78 is the READ (was mislabeled Write) and the
  missing WRITE @004b6a1c recovered from raw disasm (recordLength 0x1C,
  twist/vel/rate at +0x10/14/18) -- kills the replicant's 0xCDCDCDCD
  -140-degree ghost twist.
- Marching-ghost desync: 4 Standing-case guards zero stale reverse
  cycleSpeed (negative cadence passed the <= ZeroSpeed stop gate).
- Kill credit rerouted to the OBSERVED killer (lastInflictingID ->
  killer's player link) -- kills count, target K/D populates.

KB: subsystems.md (watcher chain), multiplayer.md (record direction),
combat-damage.md + gauges-hud.md + cockpit-view.md (torso-mounted view
re-correction), decomp-reference.md (new addresses + tag fix),
open-questions.md (dead capability-roster loops 2-4, snapshot CD read).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 13:27:49 -05:00
arcattackandClaude Fable 5 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>
2026-07-12 17:24:15 -05:00
arcattackandClaude Fable 5 d7b900d108 Cockpit: GENERATOR POWER ROUTING -- assign weapons to generators (task #12)
The pilot's second heat-management tool: the PoweredSubsystem message table
@0x50F4EC (ids 4-8) registered and implemented -- SelectGeneratorA-D
(@004b099c..@004b0a74: FindGeneratorByNumber roster walk on generatorNumber
@0x1E0 -> AttachToVoltageSource re-tap with tap accounting -> modeAlarm
Connected) and ToggleGeneratorMode (@004b0abc: Manual -> Auto -> detach+Manual
cycle).  Weapons inherit via the MechWeapon handler chain.  Desktop: F5-F8
assign the selected weapon (BT_CONFIG_SLOT) to Generator A-D, F9 toggles
reconnect mode; BT_GENSEL_TEST scripts a headless verify.

Verified live end-to-end: dispatch -> handler -> re-tap ("PPC_1 ->
GeneratorD (tapped)") -> the charging I^2R physically moved (GenD cold ~90K
baseline -> ~1570K carrying the PPC; GenA relieved), stable over a sustained
autofire soak with thermal-breaker trips.

FOUR defects found and fixed on the way [all T1/T2]:
- THE e17 HEAT EXPLOSION: Generator::SourceLevel misread *(this[0x38]+0x158)
  as linkedSinks->heatEnergy; it is the engine-base DamageZone @0xE0 ->
  damageLevel [0..1] (the same named-member pattern as the bank radiator's
  zone read).  A breaker-restarting generator emitted (1 - 4e8) x 10000
  volts; squared through the customers' I^2R feed, one restart blew the
  whole thermal network to e17.  Authentic: a damaged generator yields
  proportionally less voltage.
- FUN_004ac9c8 is NOT "IsDamaged": raw body = owner -> mech+0x190 player ->
  roleClassIndex(+0x274) == 0 -- the ROOKIE-role lockout for advanced
  cockpit systems.  New bridge BTPlayerRoleLocksAdvanced (NULL player =
  unlocked [T3]; bring-up role 2 = unlocked).  The old stand-in gated the
  handlers off permanently (healthy subsystems have simulationState==1).
- MESSAGE_ENTRY tables must be FUNCTION-LOCAL statics inside the accessor:
  as namespace-scope arrays they are read by other TUs' static-init chains
  before their own initializers run -- Build copies zeros and every id in
  the table is silently dropped (ids 9/10 only worked by TU-order luck;
  both tables relocated; gotcha recorded, reconstruction-gotchas #9).
- The AutoConnect hunt scanned GetSegment() -- the raw @004b0bd0 walks the
  subsystem ROSTER (+0x124/+0x128); EntitySegments were being cast to
  Subsystems (the classic +0x128 gotcha).

Also: the dense handler table's GAP slots (skipped ids) are uninitialized
heap -- the name-based Find strcmp-walks them and AVs; diagnostic probes use
the id-based Find only (the 1995 binary's own tables carry the same holes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 23:16:48 -05:00
arcattackandClaude Fable 5 092408041c Cockpit: the CONFIG-MODE weapon-regrouping session + the live-play fire fixes (tasks #6+#11)
TASK #6 -- the pod's in-cockpit weapon regrouping, fully reconstructed and
live-verified (hold-configure -> tap fire button -> toggle membership ->
release-commit):
- MechWeapon handlers id 9 ConfigureMappables @004b9550 / id 10 ChooseButton
  @004b95b8 (binary table @0x511860; the old "Myomers fns" mislabel swept)
  + the GetMessageHandlers() accessor chain through Emitter/ProjectileWeapon/
  MissileLauncher/GAUSS/PPC (empty per-class sets swallowed dispatch).
- Mapper vtable truth: +0x38 EnterConfiguration / +0x3C ExitConfiguration /
  +0x40,+0x44 AddOrErase evt/dir.  No "secondary vtable @0050f498", no
  "CreateTemporaryEventMappings" virtual (RP-name drift; swept incl CLASSMAP).
  L4 Enter/Exit rebuilt complete: StartMappableButtonsConfigure (the
  NonMapping 0x10000 <-> Mapping 0x8000 mode flip + the gauge's active-weapon
  latch) + the held-button re-arm + the 4 fire-button temp maps; only the RIO
  mapper implements the toggle (Thrustmaster no-ops = can't regroup).
- MechSubsystem +0xE8/+0xEC corrected to controlDestination/controlMessageID
  (ex hostEntity/subsystemId2 mislabel); MechWeapon ctor defaults the
  destination to &fireImpulse (@004b99a8).
- ConfigMapGauge state loop reconstructed (PE-recovered DAT_00518eb4 table;
  buttonGroup GetMapState sampler -- the "needs ModeManager" guard rationale
  was wrong).  Finding [T1]: the shipped binary NEVER enables this gauge (no
  SetColor caller) -- authentically dormant; BT_CONFIGMAP=1 is the dev enable.
- Dev harness: HOLD 'G' opens the session (BT_CONFIG_SLOT picks the weapon);
  BT_CONFIG_TEST scripts a headless verify.  @004afbc4 corrected to its real
  Fail-trap body (the guessed AddOrErase(NULL) body would corrupt groups).
- Bonus [T1]: the binary MechWeapon ATTRIBUTE table has ELEVEN entries
  (PercentDone..WeaponState) -- "TriggerState is the only one" was wrong.

TASK #11 -- the user-reported live-play regressions (both real bugs):
- PHANTOM FIRE: the mech4 bring-up shot block painted an explosion at the
  victim on its OWN 0.3s cadence whenever fire was held -- desynced once the
  authentic recharges landed.  Retired (the real impact visual flows from
  each discharge via the messmgr SubmitExplosion).
- WEAPON BRICKING (the "cuts out" bug): the Loading->Loaded snap window
  (+-0.01 around seekV) assumes the pod's LOCKED 60 fps; one port dt-spike
  jumps it, the byte-verified >1.0 clamp (_DAT_004ba830=0) zeroes readiness,
  and the weapon sticks in Loading at level ~10000 forever (observed live).
  Fixed with pod-frame (1/60s) sub-stepping of the binary's own Loading tick
  -- also fixes the I^2R integral over-heating generators on big steps.
  NEW GOTCHA CLASS recorded: reconstruction-gotchas #12 (frame-pacing trap).
- Duty-cycle measured (max-rate autofire): generators equilibrate in the
  degradation band (recharge stretch ~3.5-4.4x), the FailureHeat breaker
  never trips solo, PPCs sustain ~11.8-damage full-charge fire.  The slow
  kill pacing and recharge gaps are the AUTHENTIC heat economy; the pilot's
  counters (this regrouping UI, generator reassignment, coolant valves) are
  the reconstruction queue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:39:35 -05:00
arcattackandClaude Fable 5 4e63a7b6c3 Heat: THE AUTHENTIC ELECTRICAL MODEL -- weapons recharge from generators (task #10)
Task #10 set out to fix "scrambled linked-sink routing"; a [heat-link] attach
log proved the routing was NEVER scrambled (every subsystem links its authored
condenser exactly; the "pools in Condenser1" read was the diagnostic-sampler
aliasing trap).  The real defect: emitter.cpp's local FUN_00417ab4 stub
returned NULL, so the whole electrical model was inert and the E7 force-charge
recharged every emitter in ONE frame (~0.3s cycle, 1501 fires/90s, ~1.7e9
heat/s -- the "runaway").

Landed authentically [T1: disasm + byte-verified constants]:
- Emitter ctor @004bb120: seekVoltage = authored fraction x generator
  ratedVoltage (10000); EC = energyTotal/(seekV^2 x 0.5); voltageScale@0x310 =
  (RechargeRate / -ln(1 - 1e-4 x seekV)) / EC -- charge reaches seekV[rec] in
  EXACTLY the authored RechargeRate (PPC 5s, ERL 4s, SRM 3s, ERM 2s) cold.
  Owner-flags ctor gate (the usual gotcha; the this-flags read never armed).
- PoweredSubsystem::ChargeTimeScale (@004b0d50, was a =1.0 stub): voltageScale
  x (1 + thermalResistivity x srcTempRise) -- hot generators charge slower.
  ("voltageScale is never read back" was wrong; corrected + swept.)
- TrackSeekVoltage @004ba838: charging I^2R -> the GENERATOR's pendingHeat
  (~3.5e8/full PPC charge) -- generators self-heat, conduct to their authored
  condensers, and throttle further charging.  The feedback economy closes.
- FailureHeat consumers found: this+0x184 == 2 gates BOTH weapon families
  (@004baa88 emitter: reset firing + hold charge 0; @004bbd36 ballistic:
  recoil=rechargeRate + alarm 7).  Emitter::GetFaultState un-stubbed.
- ProjectileWeaponSimulation @004bbd04 opens with call 0x4b0bd0 (disasm) --
  launchers now run the powered/heat step (their firing heat previously
  accumulated in pendingHeat forever).
- heat.cpp: per-instance BT_HEAT_LOG census (the old shared-static 1-Hz
  sampler aliased); [heat-link] attach log; the stolen-else Verify restored.

Verified live (120s max-rate autofire): PPC ~470-500 (was 55,000), bank
plateaus ~600 and sheds to ambient, generators 1100-1400, ERMLaser
self-regulates at the authored 2000 failure threshold (shutdown-cool-resume).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 16:59:29 -05:00
arcattackandClaude Fable 5 4ed2bbc293 Heat: authentic 1e7-unit heat + the ambient radiator land (task #9)
- Emitters: heatPortion closed form = heatCostToFire x 1e7 x (charge/seekV)^2
  (PPC 1.1e8 -> +632K on its own sink); the missing projectile/missile heat
  adds (raw -- resources author pre-scaled 1e7 units).
- The bank's AMBIENT RADIATOR @4ae73c reconstructed (the system's ONLY heat
  exit; conductance x 0.1 x HeatSinkCount -- _DAT_004ae974 float80 = 0.1) +
  the link-attach guard corrected (skip = the 0xBBE bank, not Condenser; the
  inversion blocked condenser->bank links and closed the system).
- Constant corrections (all byte-verified float80s): coolant epsilons
  0.0025/0.003/1e-4 (was a single 1e-4 serving three sites);
  CoolantCapacityScale 0.05 (was 1.738).
- Verified: heat flows + exits; max-rate autofire overheats weapons into the
  authentic heatLoad range-cutout (thermal spam unsustainable by design).
- KNOWN REMAINING: linked-sink routing scrambled (heat pools in Condenser1;
  authored map says PPC->C4/C6) -- the heat-stream offset audit, filed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 16:21:37 -05:00
arcattackandClaude Fable 5 fd055281a8 Combat: THE AUTHENTIC DAMAGE ECONOMY -- authored per-weapon amounts through the real fire chain (task #8)
Three root causes, all fixed:
1. kDamageScale was 1.0 -- _DAT_004bafbc is an x87 float80 = 1e-7, cancelling
   the ctor's x1e7: damagePortion = authored DamageAmount x (charge/seekV)^2
   (closed form at fire time; madcat AC=25/LRM=50/ERLL=6, bhk1 PPC=12/SRM=35).
   The observed "0.25" was the degenerate EC=1 fallback, never authored data.
2. CheckFireEdge NaN latch: TriggerState carries ControlsButton INTS; the
   release value (-65) is a negative NaN.  The binary's x87 unordered compare
   read it as "released"; IEEE-correct float compares latched the edge
   detector shut after the first release -- the reason the emitter discharge
   chain NEVER fired in-game.  Fixed with bit-pattern sign compares.
3. The weapon-side submission LIVE: Emitter::FireWeapon fills damageData
   (amount + damageForce=target-muzzle [the gyro directional-bounce feed] +
   impact) -> MechWeapon::SendDamageMessage (@004b9728 real body) ->
   messmgr consolidation with per-weapon records + explosion bundling.
   The mech4 bring-up damage block + flat kShotDamage retired to diag hooks.
Bonus: LODReuseHysteresis 0.82 -> 0.33 (double misread).  Zone model
verified byte-exact (no change).  Solo end-to-end: 5-record volleys (2 PPC
+ 3 ERML with authored amounts), per-weapon zone granularity, explosions,
kill.  Heat stays bring-up scale pending the heat-calibration audit [T3].

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 15:32:24 -05:00
arcattackandClaude Fable 5 77190c93e5 Combat: the AUTHENTIC consolidated damage delivery -- TakeDamageStream through the message manager (task #7 tail)
ConsolidateAndSendDamage @0049b784 fully implemented: the beam path submits
into AddDamageMessage; each messmgr tick builds ONE Entity::
TakeDamageStreamMessage (id 0x13; wire-verified 0x34+4+Nx12) with the
common impact record + appended {type, amount, subsystemID} entries and
Dispatches it at the victim -- the T0 handler (ENTITY.cpp:817) re-splits
into per-record TakeDamage; replicants reroute cross-pod.  Real
ResolveExplosionID (firing weapon's +0x3E4, guarded) + SubmitExplosion via
the Explosion::Make port; TWO chain-purge bugs fixed (the "iterator dtor
clears it" assumption was wrong -- records re-applied every tick, observed
1->2->3->4 double-counting).  @004b9728 identity corrected (SendDamageMessage,
not DrawWeaponPip).  Verified solo: 25 clean single-record consolidations ->
kill -> death transition.  Residue: weapon-side submission awaits the
damage-economy reconciliation (authored 0.25-scale vs kShotDamage=12).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 14:30:30 -05:00
arcattackandClaude Fable 5 8ed6184d65 Combat: AUTHENTIC weapon groups -- streamed per-mech button bindings + the real fire chain (task #5)
The recovered system: fire channels = LBE4ControlsManager buttonGroups
(0x40/0x45/0x46/0x47); default groups = the per-mech type-6 controls-map
resource in BTL4.RES, installed by the T0 CreateStreamedMappings the port
already called -- it needed only the TriggerState attribute (id 0x13 PINNED
to the binary value; fireImpulse@0x31C is the binary's TriggerState) and an
input feed.  Keyboard/harness now push press/release edges into the button
groups; the gBT*Trigger bypasses, per-type keyboard split and 1,0 pulse
hack are retired -- weapons sharing a button fire TOGETHER (madcat Trigger
= 4 weapons).  Myomers @4b9550/@4b95b8 misattribution corrected (they are
MechWeapon ConfigureMappables/ChooseButton).  Verified 2-node: kill through
the authentic chain (12 hits vs ~36 pre-groups).  Config-mode session
(regrouping UI) = the remaining stage, KB-scoped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 13:06:29 -05:00
arcattackandClaude Fable 5 87c25b9206 Combat: critical-subsystem plugs BOUND -- zone destruction damages carried subsystems (task #2)
The binding was in the zone ctor all along: Ghidra dropped the two arg
pushes @0049d0e1 (Slot::AddImplementation(subsystemArray[streamedIndex])),
making it read as a bare Resolve().  DZSlot stand-in -> engine SlotOf<T>;
SendSubsystemDamage rewritten to the recovered @0049c9a8 body (allotment
into the subsystem's OWN private zone; vital -> graphicAlarm 9); CriticalHit
-> real ApplyDamageAndMeasure; parentArtifactZone.Add revived (LOD damage
averaging); videoObjectFlag renamed vitalSubsystem (+0xE4).  BT_CRIT_PROBE
diag added.  Verified: 66 plugs bound/mech; probe-destroyed zone -> crits
damaged/DESTROYED, statusAlarm + destroyed-skin chain fire; MP kill + solo
un-regressed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 12:10:43 -05:00
arcattackandClaude Fable 5 e8b9f71025 KB: task #1 landings -- update-record channel DONE, movementMode=simulationState identification swept
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 11:40:44 -05:00
arcattackandClaude Fable 5 3c34ae6de6 KB: full staleness audit + sweep -- 35 verified corrections across 16 topics, 15 new glossary terms
Adversarially-verified audit (18 agents) against the task #46-#56 landings:
MP/current-state understatements, superseded punch readings, retired env
gates, dead paths, line-cite drift; env-gate hub table completed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:56:51 -05:00
arcattackandClaude Fable 5 2abfd51350 KB: sweep stale MP open-questions -- cross-pod combat + replicant gait are DONE; the real gap is @0x4a0c2c
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:25:13 -05:00
arcattackandClaude Fable 5 8057c37e5b Cockpit: punch patches ARE the windows -- all 8 mechs authentic (task #55)
Resolved the non-Madcat blocked views. Rendering every *_COP shell offline
from its mech's AUTHORED eye (SKL [jointeye] translation -- incl. the Thor's
+1.13 offset cockpit) with faces coloured by punch flag showed the answer
plainly: the PUNCH-tagged patches sit exactly over the window apertures in
all 8 mechs. On these texture-less black-diffuse shells dpl_Punchize renders
the tagged geogroup as holes -- the punch patches are the transparent
WINDSHIELDS, and the visible frame is only the non-punch geometry.

The loader now drops the punch batches of _cop meshes (bgfload finish();
BT_COP_KEEPPUNCH=1 keeps them for diagnosis). In-game sweep on pure defaults:
- madcat: the dark dome frame (matches gameplay footage)
- thor / owens / sunder: authentic brace/dash/pillar remnants
- bhk1 / loki / vulture / avatar: authentically FRAMELESS (all-punch
  canopies -- the pod showed no frame for those mechs)

(An early-session test that "disproved" drop-punch ran with the broken
pre-inverse eye; its conclusion was wrong and is corrected in the KB.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 07:49:33 -05:00
arcattackandClaude Fable 5 946323b696 Gyro: damage fan-out FUN_004b2980 -- the cockpit hit-BOUNCE is live (task #56)
Re-disassembled the unexported gap 0x4b2980-0x4b2d8b byte-exact (capstone over
section_dump.txt) and reconstructed Gyroscope::ApplyDamageResponse:
- zero-damage + Collision(type 0) no-op; hit direction = Damage::damageForce
  or a RANDOM horizontal fallback when ~zero (per-component sign roll then
  value roll -- binary-legal until senders fill damageForce)
- direction rotated by the yaw-only torso-twist frame (placeRot=(0,twist,0)
  -> euler->matrix, the FUN_00408744 raw row-dot reproduced verbatim)
- per-type scaling amount / damageMultiplier[type] * damageResponse[type]
  .{trans,pitchRoll,yaw,vibration}; Explosive alone multiplies burstCount;
  each clamped at 1.3f
- four kicks: impulse(trans, dir), torque(pitchRoll, dir), impulse(vibration,
  vibrationDirection@0x390) and verticalImpulse(yaw, dir)

Layout corrections (byte-verified): gyro+0x390 is ONE Vector3D vibration axis
init (0,1,0) -- was mis-split as scalars animationOffset/Scale/Phase; mech
+0x5c4 is a FLOAT rumble-period countdown (gyroRumbleTimer) -- was mis-typed
int clipLoadGuard.

Call sites wired (binary-gated):
- take-damage hub: mech.cpp TakeDamageMessageHandler, FIRST action (@0x4a0264
  in hub FUN_004a0230) -- an invalid-zone hit still bounces
- crushable-icon crunch: mech4.cpp, torque 0.4 along the bounce delta-v + up
  impulse 0.2 (@4aa81e/@4aa86c)
- firing recoil: projweap.cpp FireWeapon, damage>3 -> impulse (0,0.6,-1.5)
  x damage/16 (@4bc136-4bc19c) via the new Mech::GetGyroSubsystem accessor

DEFERRED (recorded in KB with the byte recipe): the alternate-gait engage
jolt + 0.4s rumble (@4aa158-4aa365) -- its gates are [T3]-flagged and it
mutates the #49/#50-stabilized gait machine.

Verified live: [gyro-dmg] fires per hit with correct type scaling; eyePosition
shows the damped bounce (ramp to ~0.015u, oscillating Y, decay to zero); kill
chain un-regressed; no NaN.

Also recorded in KB: the OFFSET-COCKPIT verification -- Thor's authored
jointeye tranx=+1.13 matches its canopy X-center exactly AND our in-game eye
reproduces it to the hundredth (the placement model is correct per-mech); and
the playable roster is 8 models (strider/raptor/firstrtr/blkjack have assets
but no model resource -- BT_FORCE_MODEL with those names = NULL-resource AV,
cdb-verified, not a code bug).

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