Commit Graph
763 Commits
Author SHA1 Message Date
Joe DiPrima 9493fba04d handoff: #124 twist-sign verified correct 2026-08-04 15:50:17 -05:00
Joe DiPrimaandClaude Fable 5 1f995ee35a #124 twist-sign VERIFIED correct (live missile bench) + the rig to do it
The frame-adapter's inferred twist-sign flip (SelectSlice theta -= twist,
vs the binary's += pre-reflection) was the last unverified half of #124 --
every earlier probe ran at twist 0.  Bench: stationary madcat target with
the torso PINNED at 0 / +140 / -140 deg, LRM salvos from a fixed shooter
(missiles = the authentic cylinder path; the binary DROPPED zone -1 beam
damage).  Result: slice picks track the physically-facing flank in BOTH
directions (twist-left -> right-family zones for left-flank impacts,
twist-right -> left-family), deterministic, wrong-sign outcome (slice 7
vs observed slice 1) clearly excluded.  No game-code change needed.

Instrumentation added (all env-gated):
- torso.cpp BT_FORCE_TWIST=<-1..1>: HOLD the sim's analogTwistAxis (the
  input-level pin was dead -- live input rides the CONTROLS.MAP device
  push, and Basic mode auto-centers; sim-level is plumbing-independent).
- dmgtable.cpp [slice] line: rot flag, live twist, thetaIn/thetaAdj,
  chosen slice -- the weighted leaf roll made zone-only logs ambiguous.
- [dmgresolve] now names the zone (BTMechZoneSegAndName).
- mech4.cpp BT_FORCE_TWIST input pin + one-shot mode cycle (kept as doc
  of the dead path), mechmppr [mppr] mode probe.
- scratchpad/night11/twistsign.sh: the 3-config bench.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 15:49:14 -05:00
Joe DiPrima 952f10b18d handoff: #92 already fixed (zone-walk receipts) + night11 benches 2026-08-04 14:31:13 -05:00
Joe DiPrimaandClaude Fable 5 375fa28fef KB: #91 inside-view roster claim upgraded to T1 (FUN_004cef28 decomp anchor)
The binary's inside renderable build sets type A (=4, part_014.c:5077) and
its per-segment loop looks up EVERY segment's type-A mesh (FUN_00424084,
:5570) with no torso/cop filter -- the arcade drew all authored own-body
meshes (thor pod, owens legs). No opt-out exists in the binary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 14:17:21 -05:00
Joe DiPrima ac3729b41b handoff: #91 decode (own missile pod, blakskn material-key fix) 2026-08-04 12:44:18 -05:00
Joe DiPrimaandClaude Fable 5 6180a44c64 #91 thor black rectangle: it's the OWN missile pod -- blakskn now material-keyed
Three testers reported a black rectangle swaying with the footsteps in the
thor cockpit (Summoner = the thr1 label, same THX canopy -- no isolation).
Decode: the inside view is a PER-MECH AUTHORED type-A set, not the cop
alone (no fallback in EntitySegment::GetVideoObjectName -- authored data):
madcat/vulture/bhk1 = cop; sunder/loki/avatar = +tor; thor = +tor +MSL
(the shoulder pod, the reported rectangle); owens = +both legs +tshd.
The pod/leg pilot-facing surfaces use the SAME "<pfx>skin:blakskn_dz_*"
interior-structure material as the canopy frame, but the unlit frame
constant was keyed on the _cop FILENAME -- identical material rendered
(0.13,0.12,0.15) on the canopy and pure (0,0,0) on the pod/legs
([matlog]: owx_cop blakskn vcol=FF211F26 vs owx_lule vcol=FF000000).

Fix (bgfload.cpp): the frame-constant treatment keys on meshIsCop OR
material contains "skin:blakskn_dz_". mechfx:blakskn_mtl (tshd shadow
quads) deliberately excluded. Verified: zero pure-black px in the lower
view band across walk captures, the pod plate renders frame-toned and
blends with the bar at rest (the reported anomaly dissolves), owens legs
read as coherent structure, canopy/terrain un-regressed.

Diags added: BT_MAT_LOG=<stem> per-batch material routing dump (bgfload),
BT_HIDE_INSIDE_SEG=<substr> inside-mesh hide (btl4vid), [view] per-segment
inside-roster names. KB: cockpit-view.md exactly-one claim corrected +
the #91 section; bench scratchpad/night11/thorrect.sh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 12:43:36 -05:00
Joe DiPrima dcd8583449 handoff: gate rejections are player-visible now 2026-08-04 11:42:51 -05:00
Joe DiPrimaandClaude Fable 5 648f6b1675 Steam gate: rejected players now get a MESSAGE BOX, not a silent quit
A failed join returns 1 to the FE, which QUITS the exe -- so every
rejection so far was a log line plus "the game just closed" (#68's exact
complaint). New LobbyNotice() = same text in the day log (flattened, still
greppable) + blocking MessageBox. Wired to every join-side bail:

- BUILD MISMATCH: when the version-filtered search is empty, probe once
  without the version filter; if a lobby IS up, the box names the host's
  build vs ours (lobby data rides the list result -- no join needed).
  Post-entry verify mismatch gets the same box.
- NO LOBBY FOUND: probe empty too -> plain no-lobby box naming our build.
- STEAM UNAVAILABLE: transport install failed.
- LEFT BEHIND: host launched without us (no token in btl4map).

Old exes still exit silently on rejection -- nothing shipped today can
add text to a binary players already have; the host's roster marker +
REJECT log line remain the operator's view of those.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 11:42:01 -05:00
Joe DiPrima edff8fdb96 handoff: Steam build gate shipped (both halves) + LAN caveat 2026-08-04 11:36:28 -05:00
Joe DiPrimaandClaude Fable 5 d050db5cae Steam BUILD GATE, host half: reject OLD exes at GO (the real Conn Man case)
The joiner-side filter only runs on builds that HAVE it -- a stale-zip
player runs an old exe with no filter, finds the lobby, and joins anyway.
The host must do the rejecting, and the lever already ships in every old
build: a member omitted from btl4map hits its own "the host's map is
missing us" path and fails the join cleanly.

1. PublishSelf stamps per-member data bv=BT_VERSION_STRING; an old exe
   cannot fake a key it never sets.
2. Host GO mint: any non-self member with absent/mismatched bv gets NO
   token -- omitted from the map, loud REJECT LobbyLog with both builds.
3. Room screen: mismatched members show [WRONG BUILD -- WILL NOT LAUNCH]
   so the host sees who's stale BEFORE pressing GO, not after the match
   starts short-handed.

Verified in the deployed exe by string scan (btl4ver, REJECT-at-GO,
BUILD MISMATCH ascii + WRONG BUILD utf16 all present). Field behavior to
verify on the next Steam night.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 11:33:53 -05:00
Joe DiPrimaandClaude Fable 5 22f732fcfb Steam BUILD GATE: same-zip lobbies only (#108 confound killer)
Mixed-build lobbies silently corrupt raw-struct replication (night-9: one
stale-zip player, one desynced stream, ghost mechs + K/D doubt). Three
additive edits in btl4lobby.cpp:

1. HOST stamps the lobby with its exact build (btl4ver = BT_VERSION_STRING).
2. JOINER's lobby search filters on build equality -- a stale-zip player
   simply finds no lobby, and the "no lobby found" log line NAMES the local
   build so the report is self-diagnosing (#68's silent-exit lesson).
3. Post-entry verify (covers invites/direct joins + unstamped older hosts):
   mismatch -> log both versions loudly, LeaveLobby, fail the join.

LAN/relay (join.bat) handshake remains a separate 717 item -- this covers
the Steam path the operator asked about.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 11:24:05 -05:00
Joe DiPrimaandClaude Fable 5 15a1cf5d32 handoff: #108 version-skew confound + build-handshake item
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 11:12:27 -05:00
Joe DiPrimaandClaude Fable 5 24b0d2eaf7 handoff: day-2 addendum -- eject epilogue, stale-tracker sweep, #108/K-D instrumentation plan
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 10:24:54 -05:00
Joe DiPrimaandClaude Fable 5 42b0691592 #119 leak-voice stutter SETTLED: authentic -- the wooHoo latch is authored OFF
Deterministic leak bench (BT_KILL_SUBSYS partial form, "Condenser4=0.5":
zone level without the crit -- a clean leak source) + the aud-tail receipts
decode the voice stutter end to end: the warning is a phrase-sequenced
voice patch (Warnings01 zones as notes), and every techstat leak-bit edge
restarts/stops the sequence mid-phrase (authored zero-release = hard cut).
At a drained tank the draw HUNTS the authored 0.0025/0.003 band -> edge
streams -> progressive clipping as more systems hunt.

The anti-spam wooHoo latch would bound exactly this -- but no armer exists
anywhere in the flat export, and the authored tuning is minDur=0 range=0
chance=0 (dumped live): DORMANT BY AUTHORING. The clipping is the 1995
experience; the port's one real bug here was the 2x hunt cadence, already
fixed by the 28 Hz filter (#119, 1df2c57). No code change warranted.

Bench additions: the partial-damage killsub form + the authored-tuning
dump (BT_LAMP_LOG).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 09:44:42 -05:00
Joe DiPrimaandClaude Fable 5 a728fa6de9 #118 alarm dig complete: the TechStatus bit model -- MarkGeneratorOut bridge removed
The gauge-alarm "condition" is a STATUS-FLAG BIT INDEX edge-scanned by
MechTech (bit 0/1 structure, 2 leak, 3 heat, 4 AmmoBurning, 5 Jammed,
6 !HasVoltage). Conditions 4/5 -> the engEject flash = the AMMO purge/unjam
invite (flashing the very key whose streamed function is EjectAmmo) --
never pilot eject; cond 6 -> the bus-switch invite; the PANIC lamp is the
sole pilot-eject indicator.

The destruction->stateAlarm(4) bridge is REMOVED as unfounded: alarms never
read stateAlarm; state 4 is the THERMAL BREAKER state produced by
GeneratorSimulation itself (byte-matched vs FUN_004b1f7c). And the binary's
crit distributor (@0049c9a8, read raw) touches nothing electrical -- a
generator destroyed in place keeps stale Ready voltage until any transition
recomputes output via (1 - damage) x rated. The port now matches that
subtlety exactly (verified: single-gen force-kill -> no bus invite, no arm,
silent unarmed keypad -- all authentic).

KB: the full bit table + invite semantics + electrical subtlety recorded in
decomp-reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 03:18:50 -05:00
Joe DiPrimaandClaude Fable 5 609dde8051 #118 strict: REMOVE the coolant-clause hysteresis -- the flap is authentic
The operator's causality challenge held up: the hysteresis did NOT fix the
audio tick (the tick persisted past it and was the autofire scalpel's
20/sec jam clicks -- no game bug; control run without autofire is clean).
With the dead-code claim corrected, the boundary flap is AUTHENTIC: the
arcade evaluated the identical plain < 0.05 compare per frame, so a pod
hovering at the line flapped the same way -- cosmetic mode/lamp churn,
crash-free across hundreds of bench transitions. Deviation unjustified;
the binary's compare is restored.

Verified on the reverted build: armed PANIC press -> PUNCH-OUT (1); audio
profile clean (sparse explosions/warnings only).

Eject deviation ledger now: ONE item -- the Panic-button->pilot-keypad
desktop wire (hardware emulation, not behavior).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 02:57:03 -05:00
Joe DiPrimaandClaude Fable 5 d86b65d7aa #118 STRICT: remove the eng-page eject-key hook -- soft keys never pilot-eject
Operator called binary-faithful; the trace agrees. The hook (bfc072b/
fa3d634) fired ALONGSIDE the key's authored per-page function, so on a
weapon eng page an armed pilot pressing UNJAM/EJECT to clear a jam would
self-destruct -- a destructive hijack of a real control. Removed; the
authored press model stands alone: pilot KEYPAD bank (desktop numpad) or
the PANIC key while armed. The flashing engEject cell is the invite lamp.

Regressions: armed PANIC press -> PUNCH-OUT (1); armed soft-key 0x0B press
-> no eject (0); streamed ammo-eject route untouched (the hook was purely
additive).

Remaining marked deviations, both flagged in code and handoff: the coolant
arm hysteresis (0.05/0.06) and the Panic-button->keypad desktop wire (the
pod's physical matrix emulated).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 02:47:34 -05:00
Joe DiPrimaandClaude Fable 5 675fe68cb0 #118 correction sweep: the ARCADE's eject was LIVE -- 'dead code in 4.10' was wrong
The absolute-pointer scan missed the E8-relative call; the later byte-scan
found it (FUN_004a9b5c+0x10 -- the master performance evaluates eject
permission per frame in the shipped binary). Under deadline pressure the
disproven 'unfinished/dead code' claim leaked back into three comments and
the handoff; swept per the correction mandate.

Settled press model, now stated correctly everywhere: pilot eject = the
pilot KEYPAD bank while armed (+ the PANIC key reporting through that
matrix). The MFD soft keys NEVER pilot-eject in the binary -- every page
routes them to authored functions; the flashing engEject cell is the
INVITE LAMP. The port's page-gated eng-key eject is a MARKED CONVENIENCE
deviation (operator-requested), and the coolant hysteresis is a MARKED
smoothing deviation for a degenerate boundary oscillation -- not
completions of unfinished code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 02:40:39 -05:00
Joe DiPrimaandClaude Fable 5 38872ac726 #118: coolant-clause hysteresis kills the panic-arm flap (10 Hz mode churn)
Post-respawn under sustained fire the bank fraction hovers at the 0.05 arm
threshold and the plain compare flipped ejectPermitted EVERY FRAME --
hundreds of mode ON/off pairs churning the mask, the lamp, and the arm cue.
The evaluator was dead code in shipped 4.10, so the boundary flap is a
first-ever-exercised condition; completed with the engine's own idiom
(HeatSink coolantActive dual thresholds): ARM below 0.05 (the binary
constant), release above 0.06, no new members.  Stress bench: 1 arm
transition in 3 minutes (was: hundreds).

NOTE: the operator's ticking SOUND persists past this fix -- it is the
separate, pre-existing night-10 "audio stutter on coolant leak" family;
hunt continues at the audio-cue chokepoint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 02:18:58 -05:00
Joe DiPrimaandClaude Fable 5 5a4ee55e4e #118: the eject slot graphic returns -- vtbl+0x58 is DrawBitMapOpaque [T0]
The proper RE the corruption arc demanded: counting GRAPH2D.h's declared
virtual order, GraphicsView vtbl+0x18 = SetColor and +0x24 = MoveToAbsolute
land exactly -- and +0x58 is NOT DrawBitMap (+0x54) but the NEXT virtual,
DrawBitMapOpaque(background, rotation, bmp, sx1, sy1, sx2, sy2).  The wipe
is a two-span OPAQUE draw with INVERTED fg/bg pairs -- the literal "inverse
wipe" -- and FUN_004c5fb8 transcribes byte-exactly with the real signature
(the ++level between spans is inclusive-bounds bookkeeping, no gap).  The
base ctor fields were already right (colors +0x94/+0x98, bitmap Size.x/y at
+0x9C/+0xA0, decomp @004c5e84).

BallisticWeaponCluster::ejectWipe re-wired (bteejtm.pcc <- PercentOfEject).
Stress-verified: 4 gens dead + autofire jams + leak warning, weapon pages
pristine end to end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 23:56:53 -05:00
Joe DiPrimaandClaude Fable 5 5cd1947b65 handoff: evening eject sprint + MFD corruption arc addendum
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 21:07:31 -05:00
Joe DiPrimaandClaude Fable 5 0535a7ab74 KB: gotcha #27 -- drawing against compositor invariants (the #118 MFD corruption class)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 21:06:55 -05:00
Joe DiPrimaandClaude Fable 5 78722542fd #118: REMOVE the full-face flash overlays -- the MFD corruption, root-caused
The operator's three captures decode as ONE mistake drawn two ways: the
cockpit compositor deliberately draws the side buttons as BIG rects tucked
UNDER the display surfaces (step 2), with only the protruding edge visible
-- the edge IS the port's lamp light (the pod's backlit keys sat beside the
CRT; there is no in-display face to light).  My on-top "flash overlay"
(86a6bbb L4VB16, 5f79dce L4GLASSWIN) violated that design: drawn after
DrawDevSurface without resetting the bound texture state it rendered the
gauge atlas as green striped garbage at the button rects; with the state
reset it drew honest full faces -- solid flashing red covering that whole
portion of the MFD.  Both overlays removed; the original step-2 rendering
(which already tick-animates lamp shades) is the whole story.

Pixel-verified through the full stress (4 gens dead, autofire jams, leak
warning): weapon pages pristine end to end, and the 0x27 edge lamp
square-waves 34.0 <-> 28.9 across consecutive captures -- the invite
flashes at its authentic scale.

(The eject-wipe gauge -- the OTHER corrupting draw, visible in the earliest
sighting -- stays re-stubbed pending a real RE of FUN_004c5fb8's draw op.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 21:05:28 -05:00
Joe DiPrimaandClaude Fable 5 3ec30ee28a #118 fix: eject-wipe DrawBitMap arg smeared the weapon MFDs (capture.png)
The BitMapInverseWipeScalar Execute passed the colour constants as
DrawBitMap's first argument; the sibling wipe always passes 0 there (colour
selection is SetColor only). The nonzero arg smeared striped garbage across
the weapon MFD pages from the gauge's first draw (operator's capture.png).
Pixel-verified clean after the fix (weapon pages pristine, eject graphic
path unchanged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 20:50:50 -05:00
Joe DiPrimaandClaude Fable 5 86a6bbb9cb #118: the flash overlay lands in the REAL compositor (L4VB16) + [ckflash] probe
The first visibility fix painted L4GLASSWIN -- a window the operator's
single-window layout never shows. The main-window cockpit compositor is
BTDrawCockpitPanels (L4VB16): same under-the-surface masking, so the RIO
flash was again invisible. The 3b overlay now re-draws flashing lamps
(state & 3) above the surfaces there, tick-phased; [ckflash] (BT_LAMP_LOG)
logs each animated draw. Verified in-engine: addr 0x27 state 0x37
alternating shade 1 -> 3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 20:46:43 -05:00
Joe DiPrimaandClaude Fable 5 5f79dcee3b #118: flashing lamps draw ON TOP of the MFD imagery -- the invite is visible
The glass windows paint buttons UNDER the display surface (only a sliver of
edge protrudes), so the RIO flash commands were arriving ([lamp] 0xb <- 0x37
FLASHING in every trace) while the operator "never saw it flash" -- the
blink was a few masked pixels.  Lamps carrying RIO flash bits (state & 3)
now re-draw their full face above the surface with the tick-phased shade:
the gotoEngineering / engEject invites read like the pod's backlit keys.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 20:36:57 -05:00
Joe DiPrimaandClaude Fable 5 fa3d634ed3 #118 crash fix: the eng-eject page gate moves into the game-thread RIO drain
The page-gated eject query ran in the window-click thread (PadRIO
EmitButton) and dereferenced the TU-local application global -- NULL there
under the /FORCE duplicate-symbol layout -> c0000005 at mode-manager +0x50
the moment the operator pressed the eng eject key (field crash,
live.log [crash] record). Moved into LBE4ControlsManager::ProcessRIOEvent's
ButtonPressedEvent branch, where mode_mask is already resolved on the game
thread; PadRIO now only queues events. Same gate semantics (armed + that
bank's ENG-page mode bits); verified no-crash + no-eject on the quad page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 20:33:32 -05:00
Joe DiPrimaandClaude Fable 5 bfc072b56f #118: the ENG-PAGE EJECT KEY ejects -- page-gated intent completion
The decode that settles it: the eng-page side-key functions align key-for-
key with the engEject lamp map (selectGenA-D = 0xF-0xC, EJECT = 0x0B,
GEN-MODE = 0xA, COOLING = 0x9, BACK = 0x8; banks 2/3 at 0x27-/0x7-), and
the streamed weapon pages route the same key to EjectAmmo -- so 0x0B/0x23/
0x03 ARE the authored EJECT position. On generator pages the shipped
stream leaves the key with NO live route (msg 9, no handler anywhere):
eject was UNFINISHED in 4.10 (the evaluator was dead code -- the arcade
never armed panic at all). Intent-completion [T3, marked]: while PANIC is
armed AND that bank displays an ENGINEERING page (kBTEngModeMask bits --
the same gating the alarm lamps use), the flashing EJECT key fires the
pilot-keypad eject. Any other page: authored function untouched (the two
field regressions came from skipping the page gate).

Verified: armed + QUAD page + 0x0B press -> no eject (gate holds); panic /
numpad / Backspace unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 20:29:16 -05:00
Joe DiPrimaandClaude Fable 5 aa54655980 #118: the FLASHING eject invite fires -- generator OUT state + pilot keypad
The last hop lands: a destroyed Generator now reaches its OUT display state
(stateAlarm 4 -- enum renamed from the placeholder "GeneratorRecovered";
[T3 inferred link, both endpoints byte-verified: the eject evaluator's
state==4 dead clause @0049fa1c and the authored alarm streams' conditions
4/5 -> gotoEngineering 0x80 + engEject 0x85]). Wired from the one crit
chokepoint (MechSubsystem::ForceCriticalFailure -> BTGeneratorMarkOut
bridge). Verified live: gen kill -> the quad-select columns FLASH
(RIO 0x37 fast-flash) -- the pod's guided "punch out now" trail.

Plus the authentic panel eject input: CONTROLS.MAP binds the desktop
NUMPAD to the pilot keypad (keypad pilot 0-9) -- while armed, any pilot-
keypad key fires the eject (the binary's only armed binding); un-armed
they feed program entry.

The full 1995 eject experience is now live: alarm -> flashing invite trail
-> lit Panic button -> press (Panic / numpad / Backspace) -> punch-out ->
death without honor -> respawn.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 20:11:05 -05:00
Joe DiPrimaandClaude Fable 5 417008a6d4 #118 faithful settlement: MFD soft keys NEVER eject -- panic/keypad only
The streamed .CTL dump (BT_CTRLMAP_LOG, 109 records on the madcat) settles
the eject-input question from CONTENT: no 0x200000 record exists -- the MFD
soft keys carry their authored per-page routes unconditionally (elem 0xB on
a weapon eng page = msg 0xB EjectAmmo, the round eject), so they must never
route to pilot-eject. The invite-cell routing (b9430cc) hijacked exactly
that key -- the operator's "bottom left button self destructed" -- and is
REMOVED.

The faithful model, now fully grounded: the flashing engEject soft-key LAMP
is the INVITE indicator; the eject PRESS is the guarded PANIC key (no
button-space consumer in the image -> it reported through the pilot keypad
matrix; the 0x3D->keypad wire stays) or any pilot-keypad key while armed.

Verified: armed + soft-key 0x0B press -> NO punch-out (authored function
preserved); panic path unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 19:15:45 -05:00
Joe DiPrimaandClaude Fable 5 b4b98a64c0 #118 fix-the-fix: armed eject routes ONLY the invite cells + Panic
Field-caught by the operator within minutes: the blanket MFD key-space
routing (0x00-0x2F) made the DISPLAY navigation key eject the mech
mid-page-switch while armed. Narrowed to exactly the guarded PANIC button
(0x3D) and the three eng-page EJECT INVITE cells (0x0B/0x23/0x03, the
addresses the engEject 0x85 alarm lamp flashes on). Navigation is safe
while armed; the flashing cell still punches out (re-verified through the
click seam: armed -> press 0x0B -> PUNCH-OUT).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 19:07:38 -05:00
Joe DiPrimaandClaude Fable 5 b9430cc5a7 #118: the MFD eject cell ejects -- all pilot-keypad-space keys route while armed
The operator's pod flow ("the engineering panel's bottom-left button flashes
EJECT, you push it") is input #3 of 3. The eng-page eject cell resolves to
MFD side-column addresses 0x0B/0x23/0x03 (kBTEngBankTop - 4 per bank), and
the binary's ONLY armed eject binding is the KeyboardPilot group (0x200000
-> mech msg 0x19; exhaustive 0x200000 scan: no other consumer) -- so on the
pod those keys report through the pilot keypad matrix. Desktop mirror: MFD
key-space presses (0x00-0x2F) and Panic (0x3D) also land as pilot-keypad
keys. Outside panic mode the binding is mode-masked dead -- normal MFD
operation untouched; armed, the flashing cell (or any keypad key -- the pod
semantic) punches out.

Verified through the click seam: gens dead -> armed -> press 0x0B ->
PUNCH-OUT.

All three eject inputs now live: Panic button, Backspace/pad LeftThumb,
and the flashing MFD eject cell.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 19:02:21 -05:00
Joe DiPrimaandClaude Fable 5 0bd9bb6f51 #118: the FLASHING eject invite is authored -- engEject alarm items found
BT_LAMP_LOG now dumps each gauge-alarm stream item as it reads
([galm-item] sub/cond/itemCond/lampCode). First dump against the shipped
content settles the operator's "shouldn't something flash?" question:

  GeneratorA itemCond=4 -> lampCode 0x85 (engEject) + 0x80 (gotoEngineering)
  GeneratorA itemCond=5 -> lampCode 0x85 + 0x80

The 1995 pod FLASHES "GOTO ENGINEERING" and the engineering page's EJECT
cell when a generator goes OUT (conditions 4/5) -- the authored eject
invite. The Panic button lamp itself is solid by authoring ([lamp] 0x3d <-
0x3c, no flash bits) -- both halves of the field observation are genuine.

Remaining: the BT_KILL_SUBSYS scalpel skips the authentic state-4
transition, so the flash did not fire in the bench; verify a genuinely
damaged/overheated generator reaches state 4/5 and lights the invite
(the #118 "Generator Out vs !=4 clause" tail).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 18:49:36 -05:00
Joe DiPrimaandClaude Fable 5 751b1159b5 #118: the eject slot graphic lands -- BitMapInverseWipeScalar reconstructed
The operator's field report ("eject slot flashing but no graphic in it") was
the tracked-NULL bring-up stub in BallisticWeaponCluster. Reconstructed the
missing gauge class from the binary:

- BitMapInverseWipeScalar @004c61c8 (vtable 0x518a14; base @004c5e84,
  Execute @004c5fb8, BecameActive @004c5fa4): a bitmap COLUMN SWEEP whose
  level tracks a live Scalar -- two spans with INVERTED colour pairs,
  [0..level] fg colorA / bg colorB, remainder the inverse.
- Wired per the binary call site (part_014.c:2312): bteejtm.pcc at (0xF,0)
  on the weapon's eng port, colours 0/0xFF, watching the weapon's
  PercentOfEject (@0x3F8, the round-eject 0..1 progress) via a new
  complete-type bridge BTWeaponPercentOfEjectPtr (databinding rule).

At idle (level 0) the EJECT graphic now renders in the slot; during a
round-eject cycle it sweeps with the countdown. BTEEJTM.PCC ships in
content/GAUGE (the loose gauge bitmap set). Smoke-tested: constructs clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 17:53:33 -05:00
Joe DiPrimaandClaude Fable 5 611b1a8bbd #118: the PANIC BUTTON ejects -- keypad-bank routing + the 0x17/0x19 id bug
Two finds close the press path the operator field-tested:

1. The pod's guarded PANIC key reports through the PILOT KEYPAD bank -- the
   binary's ONLY panic press binding is keyboardGroup[KeyboardPilot].Add(
   mode 0x200000, mech, 0x19) (FUN_004d266c); there is NO buttonGroup[0x3d]
   consumer (0x3d is the lamp address). The desktop Panic click now ALSO
   lands as a pilot-keypad key (PadRIO::EmitButton -> EmitKeypad), so the
   armed-mode binding fires; a healthy press stays a mode-masked no-op,
   exactly the pod behaviour.

2. KeypressMessageID resolved to 0x17 via enum-chain arithmetic; the binary
   passes LITERAL 0x19 (two mapper ids are unreconstructed). The armed
   keypad send arrived at the Mech as msg 0x17 -- no handler, silently
   swallowed. Pinned to 0x19 (Mech::EjectPilot's id on the OWNER receiver --
   the collision is the design).

Verified headless through the REAL click seam (BT_BTNTEST=61): generators
killed -> panic-arm ON -> screen-click Panic -> [lbe4key] unit 0 mode
0x650421 -> PUNCH-OUT. Also: IsDisabled eject refusals now log; [padkey]/
[lbe4key] seam tracing under BT_PAD_LOG.

Still open (cosmetic): the flashing eject slot on the engineering MFD shows
no graphic -- the streamed page element's pixmap is unreconstructed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 17:31:16 -05:00
Joe DiPrimaandClaude Fable 5 0089b3284f Eject firsthand prep: G = coolant Flush binding + BT_FLUSH_HOLD scalpel
The Flush ACTION existed in the input engine but no key was bound in the
shipped CONTROLS.MAP -- desktop players could never hold the flush. G (free)
now maps to it. BT_FLUSH_HOLD=<frames> extends the BT_FLUSH_TEST scalpel
past its 60-frame edge test for tank-dry benches.

Dry-run finding (fl.log): a full flush drains the tank to 0/14 in ~30 s,
but does NOT arm eject by itself -- InjectCoolant moves tank coolant INTO
the loops (the bank RISES), and idle heat draw is too slow to burn the
loops down to the 5% arm threshold in minutes. Firsthand arming needs
sustained fire after tank-dry (or coolant-loop leak damage / generator
loss). Matches the binary's intent: eject arms when LEAKED nearly dry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 14:10:43 -05:00
Joe DiPrimaandClaude Fable 5 8165cee9da #118: the eject option comes alive -- per-frame arm chain closed (FUN_004a9b5c+0x10)
The evaluator's "unexported caller" found by E8-scan: the Mech MASTER
PERFORMANCE (FUN_004a9b5c, in the 004a977x..004ab188 export hole) calls
EvaluateEjectPermission as its FIRST act every frame. Without that call the
port's ejectPermitted stayed at ctor-0 forever -- the panic-armed mode never
raised, so the PANIC lamp never lit and testers "never saw the option for
eject come alive". Restored at the top of Mech::PerformAndWatch (masters
only, like the binary).

Mislabel swept: mech+0x414 is ejectPermitted, NOT "missionReviewMode" -- the
mapper's @004d196c edge-watch arms PANIC mode 0x200000 from it (the real
review mode is the GLOBAL DAT_004fd550; btl4pb now reads that global, the
GetMissionReviewMode stub is retired, mapper member renamed
previousEjectPermitted).

Verified live: kill 4 generators -> "[eject] panic-arm mode ON" -> the
miniconsole Panic button lights dark->bright (pixel captures; the pad panel
shades it from the same PadRIO lamp state the pod's physical button uses) ->
BT_EJECT_AT press -> PUNCH-OUT -> respawn heals -> mode disarms -> healthy
presses REFUSED. BT_EJECT_LOG=1 logs the arm edges.

Note: no shipped gauge rides mode 0x200000 -- the 1995 eject indication is
the button lamp; the weapon-eng MFD "UNJAM/EJECT" is the ammo-jam indicator.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 13:53:09 -05:00
Joe DiPrimaandClaude Fable 5 440cee1e4f #87 dig closure: host response is linear (raw decomp re-read); the pixel curve was div-card firmware
Re-read the material damage watcher RAW (@004573e4 ctor / @00457784
change-push): 16 floats snapshotted, damaged = pristine x 0.1 (literal at
the MakeMechRenderables call site, watching zone+0x158), linear lerp, 13
floats written back, then FUN_0048d4d4 -- which is "flush_material": a dpl
command-stream marshal (opcode 100) to the i860 division card. The
material->ramp->texel response curve was CARD firmware and is not in
BTL4OPT.EXE; logged as an open question (ask: firmware/DIV docs/period
screenshots).

Field reconciliation, measured live: one 25-pt hit (ERLG class) snaps a
77-pt arm 0 -> 0.3247 in a frame -- "a single missile leaves visible
armor damage" holds under the linear curve for heavy hits; 2-pt ERS creep
(the leg-audit weapon) is the invisible case.

Bench additions: BT_WALK_ZONES=<name-list> walker filter (concentrated
per-region audits, scratchpad/night10/legwalk.sh) + BindArmourDamage
ORPHAN inventory under BT_ARMOR_LOG (proved every drawn op is
zone-claimed on the full tree).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 13:10:00 -05:00
Joe DiPrimaandClaude Fable 5 fe48accb6b #87 root cause: the level-crossing zone-record send was missing -- observers never saw damage
The binary's effect watcher (FUN_0042aa2c) raises
ForceUpdate(DamageZoneUpdateModelFlag) when a zone's damageLevel CROSSES a
band-descriptor threshold (FUN_0042a5f4, master-gated on entity+0x28 & 0xc)
-- that send is how every other pod's replicant learns zone levels
mid-fight. The port's band hub kept only the graphic-state branch, so an
observer's copy sat at 0.0 until destruction: no enemy hull darkening, no
doll movement, ever (the night-10 "no armour discoloration" report).

Restored the level branch with the already-reconstructed DescriptorCrossed
(@0042a5f4) + the master gate on both branches (mesh swap still runs on
every instance). 2-node verified: A-side armour watcher pushed 114 level
changes; every replicant peak matched the master's finals to 4 decimals
(dtorso 0.9321 == 0.9321).

Render path cleared separately (gotcha #23 discharged): BT_ARMOR_FORCE 0/1
A/B captures prove the tint renders (hull -> charcoal at 1.0). Solo
perception is the authentic economy: 2-25 pt lasers vs 68-185 pt pools.

Bench: zonewalk.sh node A now carries BT_ARMOR_LOG + BT_SHOT_EVERY (the
standing zone-replication receipt) + digest_walk.py; armorpx.sh is the
force-pair pixel rig.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 12:24:36 -05:00
Joe DiPrimaandClaude Fable 5 cb5426cc7f KB: #124 close-out -- triangle pick + the authored one-hull-segment zone rule
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 11:32:26 -05:00
Joe DiPrimaandClaude Fable 5 00dfbf8f9f #124 RESOLVED against the authored skeleton: the pick's zone is the
struck drawn segment's dzone, ALWAYS -- the cylinder embellishment removed

The open question ('what zone did 1995 designate for hull hits') was
already answered in the KB's #73 dig [T0+T1]: no software pick writer
exists -- the pod's pick was a dpl scene intersection, struck triangle
-> DCS segment -> its SKL-authored dzone; the cylinder lottery was only
ever the UNAIMED path (missiles/splash/rams). The live segpick dump
completes it with the authored data: the madcat's pickable skeleton is
18 segments -- hip, shoulders, guns, thighs, knees, ankles+toes, and
ONE hull piece (jointshakey -> dtorso). No ltorso/rtorso/utorso/rear/
door/searchlight segments exist. Aimed fire at any torso panel
authentically designates CENTER TORSO; the side/rear/upper panels strip
via the unaimed path.

Changes:
* the shared-carrier -> cylinder override (yesterday's embellishment)
  is REMOVED: triangle hit -> that segment's dzone, period;
* SegAimPoint: the walker's hull anchor moves from the joint ORIGIN
  (crotch gap -- rays sailed between the legs to the terrain sentinel)
  to the carrier segment's cull center (the chest);
* [picktri]/[pickwin] diagnostics grew tri/seg/zone fields -- three
  consecutive 'mystery' regressions in this stretch were the STALE-EXE
  trap (CWD-relative cmake --build from content/ resolved to a
  nonexistent dir and the old binary kept running) -- the harness doc's
  warning, hit again.

Final zone-walk matrix (spinning target, real MP, full cycles):
limbs 55/70 in-zone (legs/feet 6/6; arm strays = hull/occlusion);
hull-family aims -> dtorso 56/69; victim consumption matches. The
night-10 field table now reads as this model's fingerprint: 'only LCT
gets hits' from aimed energy IS the pod -- one hull piece, dtorso.
What was actually broken and is now fixed: the sphere mis-picks
(rgun-from-a-dtorso-aim class), the unaimed path's 180-degree frame,
and the vertical scatter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 11:31:45 -05:00
Joe DiPrimaandClaude Fable 5 1a3c268278 #124: the aimed pick now intersects the DRAWN GEOMETRY -- sphere
approximation retired; shared-hull hits route through the cylinder

The zone-walk matrix caught the pick red-handed: with the servo
verifiably holding the reticle on the dtorso segment, the per-segment
BOUNDING-SPHERE pick returned rgun/ruleg -- the gun pods and legs
thread the ray before the torso from most angles (its own comments
admitted a foot could be unhittable behind its own knee). The 1995
pick was a dpl scene intersection against the drawn geometry.

Restored that semantic:
* BTGetPickMesh caches each segment d3d_OBJECT's triangles CPU-side
  once (its own BGF VB/IB, managed-pool locks); MechSegmentPick now
  runs sphere PRE-FILTER -> Moller-Trumbore nearest-hit across the
  threaded segments' posed meshes; the old smallest-sphere selection
  survives only as the no-triangle fallback.
* Segments claimed as CARRIER by 2+ zones (the shared hull: madcat
  seg 4 carries dtorso+ltorso+rtorso+utorso+rears) cannot resolve one
  zone from geometry -- those hits return zone -1 WITH the accurate
  triangle point, and the victim's bit-verified (frame-fixed) cylinder
  assigns the panel by band/wedge. Unique carriers (legs, feet, arms,
  gun pods) keep the direct zone.
* ZoneAimPoint: the walker aims at a zone's VISUAL center (largest
  pick object's cull-center) instead of the segment origin -- joint
  origins made feet/lower legs strike the part above.
* Walker upgrades from live operation: 3-column truth (aim/pick/land),
  engage gate, damped servo with polarity watchdog, approach port.

Zone-walk verdict (full cycles, spinning target, real MP): limbs 5-6/6
direct in-zone; every hull panel routes CYL; the victim's landed zones
now include ltorso/rtorso/reardtorso/rearutorso/rearrtorso -- the
panels night-10 reported unhittable. Residuals tracked on #124:
rtorso/utorso aim-anchor placement, doors/searchlight small-zone
sample, twisted-torso twist-sign verify.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 10:38:24 -05:00
Joe DiPrimaandClaude Fable 5 16e75d6e1a #124 bench: the ZONE-WALK MATRIX -- systematic per-panel targeting, operator-watchable
The precision rig the operator specified: two nodes, target visible,
every zone aimed at deliberately through the REAL reticle/pick path.

* BT_SPIN_SELF=<deg/s> (target node): rotates the node's own viewpoint
  mech in place (the BT_SPAWN_AT write pattern per frame) so every
  aspect passes the shooter's boresight; the operator watches this
  node's paper doll take the hits.
* BT_ZONE_WALK=<secs/zone> (shooter node): walks the target's damage
  zones in order, resolves each zone's carrier segment's live world
  position (BTResolveSegmentWorld + the new BTMechZoneSegAndName
  bridge), SERVOS the torso twist + aim elevation until the centered
  reticle ray (BTGetAimRay) points at the segment, fires 3 laser
  pulses, advances. [walk] ZONE/FIRE/HOLD on A pairs with [dmghit]
  zone/level lines on B.
* scratchpad/night10/zonewalk.sh: launch both + relay, map sed'd to
  grass/day, NO kill timer -- the session stays up for observation.

Hard-won servo constraints (documented in test-harness.md so they are
never relearned): the ENGAGE GATE (ray live + range<150 + bearing<1.1;
outside it RELAX the twist -- servoing at the twist limit while the
goto marches is a limit-clamp fight that visibly shakes the mech, and
a 0.55 gate deadlocks against the goto's ~0.55 resting bearing); YAW
POLARITY -1 (the twist cell's angular sense is opposite atan2(x,-z)
world yaw -- operator-observed live, the SECOND witness for the #124
SelectSlice twist-sign flip) with a divergence watchdog that
self-flips; damped correction (gain .40, cap .025/frame -- the aim ray
lags the twist write a frame); and a one-shot approach PORT to 100u
off the target instead of a cross-map march.

First live run: walker cycles zones, settles, fires; the target's
luleg climbed 0.458->0.523 under its own aimed pulses -- aimed shot,
correct panel, damage consumed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 10:04:03 -05:00
Joe DiPrimaandClaude Fable 5 b0b7adc4ea #124 CORRECTION: the frame adapter is a Z-REFLECTION, not a pi rotation --
proven by the mech's own pods

The first fix (c5dfb00) negated x AND z. The operator challenged it:
'they couldn't hit rear panels AT ALL -- a 180 rotation couldn't explain
that' -- and a rotation would also have relocated rear damage, not
erased it, while the bench premise (the dummy faces its attacker) was
assumed, never measured.

The zero-premise probe (BT_ASPECT_TEST): four self-impacts at known
WORLD cardinals + one at each weapon's PHYSICAL MUZZLE position -- the
mech's own asymmetric geometry as the left/right anchor. Measured:

* WorldToLocal is a clean R(yaw), no hidden terms (yaws -30/150/40 deg
  all consistent);
* our frame: RIGHT = +X -- SAME as the 1995 ring (W0/W7 = Right*) --
  and FORWARD = -Z, flipped vs the ring's Front*-in-+Z-arc;
* under the pi rotation the pods CROSSED (left muzzle -> rtorso);
  under z-negation every anchor lands its own side.

So: negate local z only, and -- since a reflection reverses angular
direction -- the SelectSlice torso-twist term flips sign with it
(twist was 0 in all probes; twisted-torso verify tracked on #124).

Anchor acceptance (fresh exe, third yaw): LRM15_1 left pod -> ltorso,
LRM15_2 -> right wedges, left/right arm muzzles -> larm/rarm, nose ->
front cells, tail -> rear cells, flank cardinals -> rgun / left wedges.

The BT_ASPECT_TEST probe + BTWeaponMuzzleWorld bridge stay as bench
scalpels.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 09:05:35 -05:00
Joe DiPrimaandClaude Fable 5 c5dfb002f3 #124: hit-location ASPECT was rotated 180 degrees -- the resolver now speaks the 1995 frame
Night-10's systematic targeting audit (rear panels unhittable from
direct fire, Owens left torso hitting the RIGHT weapon pod and vice
versa, missiles favoring the far side) measured down to one geometric
fact: the authored wedge ring puts the Front* cells in the +Z arc
(slice-name audit, bit-verified tables), i.e. the 1995 resolver frame
has mech-forward = +Z -- while OUR engine's entity frame carries
forward along -Z. Bench proof: a victim facing its attacker took
frontal beams at local z = -4..-10, theta ~270 deg, resolving the
REAR-named cells (zones 12/14/15) every time.

The two frames differ by a pi ROTATION about Y -- rotation, not
reflection, because the field flank observations CROSS (left->right)
rather than persist. ResolveHit now rotates the local impact into the
cylinder's frame (negate x and z; y untouched; handedness preserved so
the SelectSlice torso-twist add keeps its sign).

A/B (172 resolves, live missiles+beams on a facing victim): before
theta~4.7 -> rear zones only; after theta~1.42-1.59 -> utorso/dtorso/
ltorso/rtorso/arm -- the front-authored cells, with the per-burst
re-roll scatter intact.

NOT this bug: the "one band LOW" Y-axis half of the night-10 table --
that is #16 (pick-ray boresight parallax, code-confirmed, still open).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 01:12:32 -05:00
Joe DiPrimaandClaude Fable 5 788031bc36 gitignore: tester notes + lastrun dumps join the field-log privacy rule
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 00:41:29 -05:00
Joe DiPrimaandClaude Fable 5 1df2c571ee #119: heatLoad filter restored to the POD's 28 Hz sample cadence
The clipping coolant-leak voice ("warning... war... warning coo...")
measured to a ReportLeak relaxation oscillation at a drained tank:
draw = zoneDamage x heatLoad hunts across the authored 0.0025/0.003
hysteresis band (constants byte-verified) as the dry sink's conduction
collapses and the tank trickle refills it. State machine, constants,
refill dynamics and the sequencer's chase-and-cut stop (T0 AUDSEQ.cpp)
are all the binary's own -- the ONE divergence was the 15-sample
heatLoad filter: per-Perform in the binary = a 0.536 s window at the
pod's 28 Hz, but only 0.25 s at the port's ~59 fps -- heatLoad twice as
twitchy, the flap at double the pod cadence (the myomer-kinetic dt-less
class, third instance).

UpdateHeatLoad now accrues real time and samples at 28 Hz on any
machine (catch-up capped at the 15-sample window). Per-instance clock
in a static map -- the heat-family layouts are factory-size-locked
(sizeof(Myomers) == 0x358 exact), no new members. Ctor/reset prime
calls sample unconditionally.

A/B (260 s leak-to-empty bench): trigger fires 12 -> 8, transitions
11 -> 7. The residual slow restart at a bone-dry tank is authentic 1995
behavior (same engine sequencer chase-cut); a minimum-retrigger
interval would be an opt-in deviation for the operator to decide.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 20:05:06 -05:00
Joe DiPrimaandClaude Fable 5 1efe8efc68 #118 tails: death score cost + the DeathWithoutHonor console notice
* SpecialCaseDeathPenalty restored to the engine ScenarioRole (SCNROLE.h/
  .cpp) -- the 4.10-only role field the 2007 WinTesla source dropped
  (record slot [1] after KillBonus, fail-hard read; role+0x20; the
  DefaultRendererRate pattern). Its consumer decoded from the @004c05c4
  export gap (@004c07cd, missing from the #52 reconstruction): every
  death with advancedDamageOn hands the ENGINE base ScoreMessageHandler
  (@0042da20 == PLAYER.cpp:138, a DIRECT call bypassing the BT type
  Verifies) a type-1 award of -penalty. Wired verbatim. Shipped content
  authors the key nowhere -> cost 0 in the field. NUANCE [T4]: the 1995
  engine adds at Player+0x1c8, which no BT scoreboard reads (+0x278) --
  the pod's cost may never have displayed; our single-cell port shows it.

* The eject console notice: FUN_004c198c IS the
  ConsolePlayerMechDeathWithoutHonorMessage ctor (T0 BTCNSL.CPP survives
  and is compiled) -- "death without honor" is the punch-out record.
  BTPlayerEjectBookkeeping now sends it to the console host through the
  same ConsoleClientID path as the VTVDamaged notify, then latches
  suppressConsole: the eject notice REPLACES the death notify. Verified
  two-node: "notice -> console host 1" fires on the ejecting node; the
  relay-side delivery of client-5 traffic is a pre-existing question
  shared with the never-observed VTVDamaged sibling (operator-console
  workstream).

* Eject alarm-10 audio: resolved NEGATIVE -- the five Eject*.wav ship in
  AUDIO/ but BTL4.RES references them nowhere (orphaned like the SQUAT
  clips); wiring them would be a stand-in. Operator-optional deviation.

* RIO 0x38 panic control: deferred with the plumbing documented (the
  glass Flight-Controls window already renders the 0x38 bank; consumer =
  read buttonGroup[ButtonPanic] alongside the binding-engine edge) --
  pod-hardware-only, untestable on this bench.

Two-node verify (fresh-exe rerun after a stale-exe trap): REFUSED
healthy, PUNCH-OUT after generator kill, death + wreck swap + smoke +
un-wreck warp all replicated on the observer, respawned mech refuses
again, console notice fired.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:12:48 -05:00
Joe DiPrimaandClaude Fable 5 e996be249d EJECT/PANIC wired: Mech::EjectPilot (id 0x19 @0049f854) + the crippled-mech gate
The button died twice before reaching game logic: no handler (0x19 was
unregistered) and no sender (the pod's panic was a control bit, not a
mappable). Both halves reconstructed from raw disasm -- the handler AND
its permission evaluator sat in export gaps.

@0049f854 EjectPilot: press-only; gated on ejectPermitted (@0x414) and
!IsDisabled; console eject notice (relay wire = tracked tail; the
suppressConsole@0x258 latch that prevents the death double-notify IS
wired); graphicAlarm -> 10, which kills via the >=9 predicate; then a
self TakeDamage {inflicting=SELF, zone -1, Explosive, amount =
ScenarioRole::killBonus}. Role layout byte-settled via the role reader
@00429bec dest offsets + the ctor record copy: +0x1c IS killBonus, +0x20
is the 4.10-only SpecialCaseDeathPenalty, +0x28 returnFromDeath (all
prior citations reconciled). KillBonus authors NOWHERE in shipped
content -> the charge is 0 and the ALARM does the killing: our bench
outcome is the pod outcome.

@0049fa1c EvaluateEjectPermission: eject only from a CRIPPLED mech --
bank coolant fraction < 0.05 | zero live generators | live weapons
below mech+0x448 (no exported writer: zero, clause inert) | leg-gimped
novice. A healthy mech REFUSES the button; no free resets.

Input: binding-engine "Eject" action -- Backspace / pad LeftThumb
(default profile + shipped CONTROLS.MAP). Bridges per the databinding
rule: weapon/generator/bank/player reads land in their complete TUs;
MechSubsystem gains the both-cells destroyed accessors (gotcha #22).
Bench scalpels: BT_EJECT_AT=<frame> (path-identical synthetic press),
BT_KILL_SUBSYS now takes a comma list.

Verified single-node: healthy press REFUSED (x2), four generators
killed, next press PUNCH-OUT -> death, wreck smoke, respawn; the
respawned mech refuses again (permission re-evaluates after Reset).
Death rides the normal damage/death chain, so MP replication is the
proven path. Tails tracked: console relay notice, RIO 0x38 panic
control, alarm-10 eject audio/canopy, SpecialCaseDeathPenalty consumer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 16:45:54 -05:00
Joe DiPrimaandClaude Fable 5 e1ae92264a #96: the sim TIME MODEL closed from bytes -- myomer kinetic term restored to the faithful 28 Hz
The open question under every heat bracket was what the 1995 sim does with
time. Answered at the byte level:

* FUN_0041c018 IS the arcade Simulation::PerformAndWatch: slice =
  (till.ticks - last.ticks) / DAT_0052140c, stored, Perform(slice) --
  VARIABLE-STEP in real SECONDS, line-for-line the WinTesla SIMULATE.cpp
  body. Every dt-carrying reconstruction is unit-faithful; no tick/second
  gap exists anywhere.
* The myomer heat term's ONE caller is the unexported MyomersSimulation
  body @0x4b8b9a (E8-scan + raw disasm; export gap): once per Perform, raw
  slice, gated measuredVoltage>0 only. The dt-less kinetic pulse therefore
  fires at the FRAME cadence -- 28 Hz nominal, sagging under load
  (emergent hardware, not a code constant).
* Mover velocity is u/s (dead-reckoner @0x421f7c: pos += vel x ticksD/28),
  so the v operand is unit-faithful too.

The 0.5 "loaded-rate" witness bracket is RETIRED -- it modeled the sag as
a constant, wrong in exactly the reported scenario (open-field cruising is
a light scene; the pod held ~28 there). kSeekHeatCalib = 1.0.

Measured at the faithful rate (madcat cruise benches): recommended gear
equilibrates ~1450 -- inside the authored degradation band as a SOFT 20%
output governor (heatFactor = 1 - d^2/range^2), weapons untouched at
~110-150, ZERO jams, zero bay fires from movement alone; supercharge
~1770 (deep governor -- the manual's own expert-mode warning). That is
the authored system: degradeT=1000 is governor onset, failT=2000 the
cliff, and the coolant-loop controls are the player's lever.

New leads filed in open-questions: the climb term's gravity operand
samples 0 (bridge points at a dead cell); the engine Mover's own gravity
subtraction is per-frame dt-less (port impact unread); gait-noise phantom
acceleration feeds the accel term.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 16:01:04 -05:00