Restores the PPC's authentic secondary-display effect: an EnergyDamageType hit
scrambles every secondary cockpit display for 0.8s while the main out-the-window
view stays clean. Was fully specced (phase-14) with the engine half already
present under the original VWE names; only the trigger and the visual were
missing (the visual STUBBED since 2007).
A -- TRIGGER (game/reconstructed/mech.cpp): in TakeDamageMessageHandler, at the
binary's @0x4a03f3 position (after the cylinder resolve, before the burst
loop, so ONCE per damage message) fire
GetGaugeRenderer()->SpecialEffect(scrambleVideo, damageType*0.2f) on
EnergyDamageType (==4). That type is authored on exactly the 14 PPC/ERPPC
records, so the branch is structurally PPC-exclusive -- no weapon-class check.
Duration DERIVED from the ordinal (4*0.2==0.8s), not a literal. BT_DMG_LOG
prints [ppc-scramble].
B -- VISUAL (SVGA16::FunkyVideo, was the 2007 stub): FunkyVideo now arms
scrambleActive; new SVGA16::ScrambleRowShift is a per-source-row horizontal
shear+roll, read by BOTH DrawDevSurface (surround/dock) and ExpandPlaneToBGRA
(glass windows, native + rotated radar), so all secondary surfaces shear
together in source space and the main 3D view (separate timing chain) is
untouched -- the modern stand-in for the VGA CRTC Horizontal-Total detune.
Tunable: BT_SCRAMBLE_SHEAR (px/line, def 4), BT_SCRAMBLE_ROLL (px/sec, def
220); BT_SCRAMBLE_TEST=1 forces it on for tuning by eye.
C -- NON-STACKING LATCH (L4GaugeRenderer::SpecialEffect): ignore the re-arm while
scrambleVideoFlag is set (matches the binary's `modified` latch) -- a second
PPC during the window no longer extends it. Was a divergence.
Verified: Release links clean; surround boots + runs with the effect forced on,
every secondary MFD + the radar shear while the out-the-window view stays clean,
no crash (screenshot). Open (for the playtesters who filed the report): live
PPC-fire confirmation + by-eye shear tuning -- k/roll are not recoverable from
the binary.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Disassembly of BTL4OPT.EXE (2026-08-06) recovered the PPC's authentic
secondary-display effect: an EnergyDamageType (==4) hit calls the gauge
renderer's SpecialEffect(scrambleVideo, damageType*0.2f), which detunes the
VGA CRTC Horizontal Total by -9 for 0.8s -- every secondary cockpit display
loses horizontal sync ("looks like the CRTs are being degaussed"), the main
VPX view is untouched. damageType 4 is authored on exactly the 14 PPC/ERPPC
records, so the branch is structurally PPC-exclusive.
- phases/phase-14-ppc-sync-distortion.md: the port spec (trigger + visual +
fidelity constraints + verification).
- context/gauges-hud.md: full disasm chain + the FlashPalette non-confusion.
- context/combat-damage.md: the damageType==4 branch in the damage handler.
NOT YET IMPLEMENTED -- this commit is the spec; the effect is the next work.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements what the pod actually does (pod-hardware.md THE RGB SPLIT): a VGA
port's R/G/B lines each drive a separate mono MFD monitor, so a window is not
one MFD -- it is one PORT carrying up to three. BT_POD_RGB=1 collapses the
five MFD windows into the two ports the cab drives (Port A: Comm=red,
Mfd2=green, Heat=blue; Port B: Mfd1=red, Mfd3=green) and composites each
group's planes into the colour channels, leaving the radar on its own
full-colour port. Channel comes from the live port (GetEnableID), never a
hardcoded table, so an Eng-page swap follows automatically; BlankColor planes
contribute nothing, exactly as on the pod. Implies BT_POD_SURFACES (bare
640x480 pictures -- the cab's buttons are physical).
Verified locally (bare windows, "RGB COMPOSITE of 2 plane(s): Comm Mfd2") and
LIVE ON NICK'S CRASH CART over the tailnet: Port A -> DISPLAY4, Port B ->
DISPLAY2, radar -> DISPLAY1, all exact-fit. Pod scratch kit included (ssh
helper, layout cfgs for both modes, launcher; the mission-egg launcher fix --
a bare MP.EGG exits the mission loop with no relay, and BT_FE_SOLO parks at
the menu, so neither lights the panels).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Answering 'how do the panels split RGB into 3 monitors' from primary sources
rather than inference:
- content/GAUGE/L4GAUGE.CFG (the authentic 1996 pod config) configures each
gauge port with a bit-plane mask AND A COLOUR CHANNEL: Comm=red,
Mfd2=green, Heat=blue on clut2 (the upper row); Mfd1=red, Mfd3=green on
clut1 (the lower row, blue spare); sec/radar = full rgb, rotation 270 (the
portrait CRT). Eng1/2/3 are the engineering-page twins on the same
monitors, swapped in/out via reconfigure() with 'blank'.
- L4GraphicsPort::BuildSecondaryColor (L4VB16.cpp) proves the mechanism at
T0: it walks the palette entries owned by the port's bit group and writes
exactly ONE component (RedChannel->Red, GreenChannel->Green,
BlueChannel->Blue, AllChannels->whole triplet); BlankColor blanks the
group. So one palettized framebuffer emits three independent pictures on
the R/G/B analog lines, and the splitter feeds each line to its own mono
monitor -- which is also what the '1280x480 horizontally spanned' MFD
surface actually is: two VGA outputs x three channels.
Port consequence recorded: the per-panel window path (BT_POD_SURFACES) is
right for per-panel outputs but WRONG for splitter-wired glass, which needs a
channel-composite mode (three planes -> one RGB image, pure primary tints).
ExpandPlaneToBGRA already does the per-plane half. Open: how Nick's cart is
actually wired. Also lands the pod bring-up scratch (ssh helper, layout cfg,
launcher, firestorm repo browser).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
OpenSSH server + a single authorized key + a private/domain-only firewall
rule, so bring-up can run over a tailnet instead of by hand through Chrome
Remote Desktop (CRD paints a canvas -- unreadable to tooling; text and logs
need a real shell). Handles the Windows administrators_authorized_keys ACL
quirk, refuses to run on pre-Win10 (the period-pod case, which stays on the
clipboard/probe route), never opens the public profile, and prints the exact
ssh line including the Tailscale address. Undo steps in the header.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The crash cart may BE the period pod PC (that is where NVIDIA Horizontal Span
still exists), in which case the first question is not the display map but
whether btl4.exe can launch at all -- a modern MSVC toolset needs Win7 SP1+.
The probe now states the verdict outright, and podprobe.bat covers the case
where PowerShell/.NET is not present to run the probe in the first place
(wmic + dxdiag, both XP-era tools).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nick's crash cart is the first shot at driving the real MFD panels, so the
pieces the cab needs that the desktop glass path lacked:
- BT_POD_SURFACES=1 (L4GLASSWIN): crop every display window to its SURFACE
(MFDs exactly 640x480, radar 480x640 portrait), drop the on-screen RIO
button banks -- the cab's buttons are PHYSICAL, so drawing fake ones onto a
real panel is exactly wrong -- go frameless, and skip the Flight Controls
pad entirely (6 windows, not 7). Per-window ",bare" in glass_layout.cfg
for mixed rigs (idempotent with the global mode).
- Placement receipts: each window logs which PHYSICAL monitor it landed on
("[glasswin] 'Heat MFD' surface=Heat at X,Y 640x480 bare -> monitor
\.\DISPLAYn (...)"). Nobody can see 7 surfaces at once on a cab, and over
Chrome Remote Desktop you cannot see the panels at all -- the log is the
only confirmation the map is right.
- tools/podprobe.ps1: run-on-the-pod topology probe (no install/admin) --
GPUs, every monitor's virtual-desktop rect, EDID make/model, serial ports
(the RIO board), session type, plus a PROPOSED glass_layout.cfg assigning
the six surfaces to the non-primary monitors. Self-tested here (correctly
reports a single-monitor laptop and declines to map).
Local verify: 5 MFDs at 640x480 bare + portrait radar + Flight Controls
dropped, receipts printed. KB: pod-hardware §MFD PANELS ON REAL HARDWARE
(incl. WHY the 1995 path is not the route -- NVIDIA Horizontal Span is gone
from post-XP drivers and per-adapter exclusive fullscreen is fragile over a
remote session) + glass-cockpit env table.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- CLAUDE.md front + project-overview: census/re-export DONE (93.5% coverage,
41KB dark), polish list refreshed; the citation-policy warning rides the
front matter so it is unmissable.
- locomotion §CROUCH addendum: the re-export CONFIRMS the hand transcription
field-for-field, documents that Ghidra renders members as int-ARRAY indices
([0xfe] == byte 0x3f8 -- why offset-string greps of the export miss them),
and records the AIRBORNE AUTO-RISE branch the raw pass missed (+ its fix).
- decomp-reference §7: citation policy (@ADDR, never part/LINE -- old
citations resolve only against archive_2025export/), the array-index
gotcha, and the full re-export toolchain (ghidra_reexport.sh incl. the 8.3
short-path requirement, gapcensus, gapdiff).
- open-questions: the leads the re-export produced -- @0x4c0904 is the MASTER
BTPlayer Performance (team-by-name resolve + EndMission post + score
heartbeat; our @0x4c083c attribution needs a re-check) and the ~9KB
l4splr|btmssn cluster that stayed dark THROUGH the fill (no call/data
reference reaches it -- jump-table entry suspected).
- glossary: dark-region.
Section ordering fixed (addenda above Key Relationships); checkctx CLEAN.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Installed JDK 21 + Ghidra 12.1.2 (no admin, %LOCALAPPDATA%\bt411-tools beside
DXSDK/cmake; runner uses 8.3 SHORT paths because Ghidra's .bat expands
%JAVA_HOME% unquoted and the profile has a space).
New tooling: reference/ghidra_scripts/ExportGaps.java -- ExportAll's exact
output contract PLUS a gap-fill pass (force disassembly + createFunction at
E8 call targets outside functions, data->code pointers at a plausible
prologue, and the census's discovered starts; iterated to a fixpoint,
logged to gapfill_report.tsv). tools/ghidra_reexport.sh (headless runner,
'reprocess' mode) and tools/gapdiff.py (score two censused exports);
gapcensus.py now censuses any export dir.
Results: 6267 -> 6472 functions (+205 created in 2 rounds: 195 census
starts, 6 call targets, 4 data pointers; 56.1KB newly covered), ZERO
decompile failures. Dark real code 90.4 -> 40.8 KB (54.8% recovered);
game-side dark 53.1 -> 21.1 KB; regions 428 -> 321. EVERY historically
dark function now has pseudocode -- including @0x4c05c4 VehicleDead, the
absence that opened this issue.
VALIDATION: the new pseudocode confirms this week's hand reconstruction of
the crouch field-for-field (mapPosture/duckState/squatCapable/myomerEff/
novice gate/SetLegAnimation/ForceUpdate/stability alarm) -- and exposed one
branch the raw pass missed: AIRBORNE AUTO-RISE (mode 3|4 && legState 1 ->
forced squ), now implemented in mech4.cpp and re-benched un-regressed.
PROMOTION: the re-export is canonical reference/decomp/; the previous export
is preserved at reference/decomp/archive_2025export/ so old
`part_0NN.c:LINE` citations still resolve (addresses are stable across both;
line/shard membership is NOT -- cite @ADDR).
New lead recorded: @0x4c0904 is the MASTER BTPlayer Performance (team
resolution, EndMission console post, score heartbeat) -- our @0x4c083c
PlayerSimulation attribution needs a re-check. KB: source-completeness,
gotcha #20 (the rule is cheap now -- look it up), CLAUDE.md router/layout.
Log: phases/phase-04-gap-census.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tools/gapcensus.py (deterministic): index-vs-.text interval math, export
cross-check, function-start discovery inside dark regions (E8 call targets +
data-section code pointers), pad exclusion, TU attribution, repo-citation
flags. Output: reference/decomp/GAP_CENSUS.md + gap_census.tsv.
Headline: .text 892KB, index covers 87.3%; 428 dark regions = 90KB REAL code
(indexed-but-unexported = 0 -- the gap class is purely 'never indexed').
Game-side dark ~54KB: 66 regions visited by past digs, 159 NEVER TOUCHED.
Validation: all six historically-bitten dark addresses (VehicleDead,
ToggleLamp, death tail, master-perf, myomer integrator, duck consumer) land
inside census regions; the two most-cited regions are the two that produced
the most reconstructions.
Top uncharted leads (spot-checked real code): the ~9KB l4splr|btmssn cluster
(dispatch-table state machine -- likely BTMission's unexported heart); the
613B btplayer hole before the ctor (mission-review id-0x18 sender suspect);
btl4app tails; heat|mechmppr + mechweap|btplayer boundaries. Full log:
phases/phase-04-gap-census.md. Re-export half deferred (no local Ghidra;
scripts ready). KB: source-completeness census section + CLAUDE.md lookup
row + decomp-reference tools entry; consult the census BEFORE any 'absent
from the export' claim (gotcha #20).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
crouch gate to the LIVE drive value
The myomer system was ALREADY COMPLETE (2026-07-31 seek audit): Performance
wrapper @004b8b9c, AvailableOutput @004b8ac0 (gear clamp x quadratic heat
degrade x (1 - zone damage)), and the master-perf chain walk + speedDemand
scale + turn freeze in mechmppr.cpp:990 -- the same @0x4a9cf2-0x4a9da4 bytes
the crouch dig re-decoded. The 2026-08-05 banners calling the feeder dark
were an export-gap-blind grep (named members, not offsets). Fixes:
mechmppr publishes the chain MAX into mech->myomerEffectiveness (the
binary's +0x79C home) so the crouch posture gate reads the live factor
(dead/overheated myomers now genuinely refuse squat/rise -- previously the
gate read a neutral 1.0 and never fired); the duplicate speedDemand multiply
in the posture block is removed (mechmppr's is the one application); banners
and locomotion.md corrected. Squat re-benched green on the live wiring.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The WIP's 'pose does not hold' was a chain of bench-instrument errors, not a
code bug: every capture ran in COCKPIT view (the pilot cannot see their own
legs; the eye-height residual masked as reversion). Joint probes prove the
park holds indefinitely (knee 1.138, root -2.219 steady); the 2-node bench
shows the observer's replicant fully crouched and held (duckmpA_031 -- the
type-3 state record carries it with zero new replication code); the second
scripted press (new BT_BTNTEST2 env) verifies RISE -> standing zeros.
MP button delivery confirmed mode-mask-clean (the one miss was round-start
jitter). Diags added, all BT_DUCK_LOG-gated: SetLegAnimation re-arm tracer,
1 Hz joint probe, RIO press mode-mask, BT_TREE_LOG topology dump, and the
squat-park log. RESIDUAL filed: pilot's own cockpit eye does not ride the
root drop (DPLEyeRenderable chain composition; cosmetic).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Raw-disasm of the dark master-perf region (@0x4a9cf0-0x4aa0af): the myomer
effectiveness factor (+0x79c, MAX over heatables' +0x31c, scales speedDemand
-- feeder @004b8be3 unreconstructed, neutral 1.0 [T3]), the posture selector
(+0x3f8: mode/novice/leg-state/myomer gates -- novices cannot crouch), and
the DuckRequest consumer (standing -> SetLegAnimation(2) 'sqd', ducked ->
SetLegAnimation(3) 'squ', ForceUpdate 8+1 ships the type-3 state record,
stability alarm flips, request consumed). +0x1DC = mountSegment... er, the
searchlight learned that one; here: mapPosture @0x3f8 + myomerEffectiveness
@0x79c members land; value-space note (port normal mode == 1, binary 0).
VERIFIED: request->consumer chain fires ([duck] SQUAT), the sqd clip plays
(22kf/7joint, ends root -2.22 crouched -- clip data parsed from BTL4.RES,
squ is its exact mirror; loader slot map re-verified byte-exact). OPEN: the
parked crouch pose does not HOLD on screen (reverts ~1 frame after clip end
with NO SetLegAnimation re-arm logged) -- the hold's render path is the
remaining dig; [duck] re-arm tracer left in SetLegAnimation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SPOT.BGF decoded: a 7-vert cone from the mount, ~50u forward and ~35deg DOWN
(a ground-pool lamp, not an air beam), verts tinted cyan-white, material
class 'brighten' smuggling its additive factor in DIFFUSE.r (0.25) with a
warm emissive on the night page. The loader had never met the class -- it
drew as an opaque dark-red blob. Now: brightenFactor parsed (name-gated to
brighten*), batch -> L4DRAWOP.brightenAlpha, drawn in the blend pass as an
additive veil (dest += vertexRGB x factor), unlit. [T3] tint compose
(vertex cyan vs night emissive warm) noted in the draw branch -- field
eyeball accepted the current look.
BT_SPOT_SELF=1 (bench-only): builds the cone on the own-cockpit tree so the
BT_CAM=face view can inspect it solo. KB: view toggle is BACKTICK ('V' is
the rear-view hold since #68 -- the toggle skips bound keys); stale V-toggle
claims swept.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The subsystem (sim/toggle/attribute/replication) was already complete; this
lands the missing VISUALS, decoded from MakeMechRenderables @004cef28 case
0xbd8 (raw pseudocode part_014):
- COCKPIT: the 1995 searchlight is a FOG SWAP -- the @00456778/@00456814
watcher switches DPLRenderer::SetFogStyle between the authored fog= (lit)
and nosearchlightfog= (dark) sets per map/time page in BTDPL.INI. The
engine kept the whole system under its real names; completed the stubbed
plane application (currentFogNear/Far) and transcribed the watcher (with
its inverted-cache seed) into TickSearchlight. CONSEQUENCE: night now
STARTS on the authentic dark set (near-plane 5u on arena pages) -- our
builds had rendered the searchlight-ON fog permanently.
- EXTERNAL: spot.bgf beam cone hung on the searchlight SITE joint, shown/
hidden from the replicated LightOn attribute (@0045612c watcher). Site
segments now build geometry-less DCS children (posed + parentable, as the
1995 graph did) -- previously they were skipped entirely.
- searchlight.hpp: commandedOn @0x1DC identified as mountSegment (resource
segmentIndex; the cone's mount joint).
Benches: searchfog.sh (solo cockpit: first-tick dark sync, F5/0x14 press ->
SetFogStyle(2), red-fog probe end-to-end), spotcone.sh (2-node: B's button ->
lightState replication -> A logs "[spot] cone SHOWN (seg 20)"). Cone look
(size/aim on the mount) pending an eyeball pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reconstructs the dark-gap tail of Mech::TakeDamageMessageHandler
(@0x4a02f4-0x4a0890, raw disasm): the three id-0x16 score reports (kill to
the shooter's player / type-0 wire-fidelity / received to the victim's
player) and the BT 0x38-byte VehicleDeadMessage extension {killed-by player,
kill zone} dispatched from the death tail. Retires BTPostDamageScore /
BTPostKillScore and the per-hit inflicted credit (never existed in 1995:
@0x4c0200 is bound in no handler-table entry -- byte-scan receipt in
decomp-reference). Suicides now dispatch and the handler negates the award
(the #134 panic penalty). Collision divert falls through to the death tail
per @0x4a0375 (wall deaths respawn + blast; no score). ScoreMessage fields
renamed to decoded truth (vitalHit/zoneIndex/subsysID) + wire asserts;
console VTVDamaged points_transfered corrected (Round(award), not Now()).
Benches: scorekill.sh cross-node kill (kills=1 award=4.88, killedBy=2:1
zone=3, single death cycle), scoreself.sh suicide (type=2 award=-39.00
kills=0), deathblast2.sh re-verified (72 bursts at ~9u).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The missing half of Advanced Damage, found by call-scanning
Explosion::SplashDamage @0042fad0: TWO callers, not one -- Missile::Perform
(the known #62 path) and 0x4a0bda, the UN-EXPORTED tail of
Mech::TakeDamageMessageHandler itself. Raw disasm @0x4a07b8-0x4a0bda:
when the victim ENTERS dead(9)/eject(10) during the applications, the
binary sets the wreck burning (id 0x17, deferred -- handler not yet
reconstructed), spawns the death Explosion (model 0x31 -- our death-list
visuals stand in), and SPLASHES:
gates : owning player's advancedDamageOn (+0x264) AND NOT
suppressConsole (+0x258 -- eject sets it: PUNCH-OUTS NEVER
BLAST, the authentic anti-suicide-bomb rule)
damage: type 2 Explosive, amount = deathSplashDamage (mech+0x520),
bursts = round(0.001 * moverMass * 15.0) -- scales with tonnage
radius: deathSplashRadius (mech+0x524); per-victim falloff
bursts/dist^1.25 in the shared core
Draco's collision-divert suspicion is settled: the blast is TYPE 2, the
divert never touched it -- the tail was simply never reconstructed.
Port: deathSplashDamage/Radius PROMOTED from the Wword scratch bank to
named Mech members (the bank is one GLOBAL array -- authored per-chassis
values were clobbered to the last-loaded mech); BTSplashCore split out of
the #62 weapon splash and shared; BTApplyDeathSplash + the death-edge arm
in the handler tail; BTPlayerConsoleSuppressed bridge (friend).
Bench (2-node, B parked 8.9u from a self-destructing A): blast fired with
authored madcat data (radius=50, amount=5, mass=75000 -> 1125 base
bursts), B took 73 bursts (falloff exact: 1125/8.91^1.25), cross-pod
delivery + cylinder spray verified on B's own log ([dmghit] type=2
burst=73 across zones). ~365 damage at 9u -- Draco's 'double kills on
drops' economy restored.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Provocations run under the verified [skate] detector: sustained healthy
walking, 7x respawn-while-moving, gimp onset at speed, ~21s of sustained
GIMPED walking (aimed-leg self-damage: BT_SELF_DAMAGE_ZONE=dz_ldleg),
leg-destruction death (authentic: lvl 1.0 -> leg gone -> fall/death; the
mid-session "died of the gimp edge" reading was a capped-print artifact,
retracted), and respawn. ZERO skate anywhere. Peer gimp replication
VERIFIED live: observer reads gl=3 + sim=3 with the gimp bodyStates
cycling for the whole master limp window (#82 remains fixed). Conclusion:
the field skating does not reproduce at lab scale; the detector + SKATE
matchlog record ship with the next cut and the field names the failing
case. Bench scripts archived (skatebench3-7; 7 is the clean-room one --
the sed-derived chains dropped envs twice).
Also: BT_LAMP_LOG=1 joins the field bats (#135 -- lamp/annunciator edge
forensics; near-zero noise, answers leak-no-flash reports in one grep).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The night-12 field logs eliminated record starvation (zero [ghost] during
three observed skating windows), so the bug lives in gait APPLICATION on
peers. This adds the [skate] detector to the death-handler tick: a
replicant moving >0.08 u/frame for 90+ frames with BOTH animation
channels idle (legCycleSpeed + bodyCycleSpeed ~ 0) logs one line per
episode + a SKATE matchlog record carrying the discriminating inputs
(legCyc/bodyCyc/cmdSpd/destroyed/mode).
Honest history: the first build keyed on legCycleSpeed alone and
false-fired on every healthy movement phase -- the current peer
architecture poses joints from the BODY channel (s_peerLegCh=0,
AdvanceBodyAnimation mj=1), so legCycleSpeed==0 is NORMAL there. Caught
same-session by the [gimpfeed] silence (AdvanceLegAnimation never runs
on peers); corrected to channel-agnostic before anything shipped.
Bench (skatebench2.sh, 2-node, autodrive walker + kill every ~40s):
7 death/respawn cycles, ZERO skate hits either side -- no false fires,
and light local conditions do NOT reproduce the field skating. Next
provocations: leg-GIMPED walker (the #82 family transition) and 6-player
load; otherwise the detector rides the next cut and the field names the
failing case for us.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Night-12 field report (Ronin/Conn Man/Oracle, blackhawk-correlated): lock
ring lit with the reticle visibly off the mech + no-reg complaints. Root
cause: TWO port stand-ins answered where the 1995 card (which cast against
the DRAWN geometry) would miss -- the pick's any-object sphere fallback and
the caller's whole-mech AABB fallback. The regime that exposes them: a
LEVEL boresight over a SHORT mech -- the blackhawk's mesh tops out below
eye-ray height, so the ray clears every triangle but pierces the fat cull
spheres; the ring lights with the reticle above the mech's head (the
operator watched exactly this on the sweep bench). Careful aimed-down fire
rides triangles, which is why Oracle's per-panel audit passed on the same
build.
Fix: MechSegmentPick returns 1=drawn-geometry hit / 0=TRUE MISS / -1=no
render tree; the sphere may answer ONLY for a mesh the reader cannot parse
(pm==0 -- currently none exist: counters objs/invFail/noTri all clean);
the AABB survives ONLY as the pre-tree replicant grace. A readable mesh
the ray misses is a MISS -- no lock.
Verified (2-node vs bhk1 at 100u, all runs on force-relinked string-
verified exes after today's stale-link flake):
- LEVEL lock-sweep: 0 locks all run (pre-fix: lock band from 168 sphere
answers; picksrc tri=0 sphereFB=168).
- DOWN-PITCHED sweep: locks return 100%% tri-sourced (tri=158 sphereFB=0),
landing on real parts (rarm/ldleg/rdleg) with honest gaps.
- Full zone-walk matrix: tri=18874 sphereFB=0 box=0; victim took 156 hits
across 16 zones incl. both side torsos -- combat un-regressed.
New instruments (all env-gated): BT_LOCK_SWEEP=<axis> torso pan (the
operator-visible lock-envelope bench), [locksweep] transition log,
BT_LOCK_ENVELOPE synthetic unit-sweep probe, [picksrc]/[pickbox] source
telemetry with objs/invFail/noTri localization counters.
Bench: scratchpad/night12/zonewalk_bhk.sh.
NOTE for the field: locking is now strictly TIGHTER (ring = reticle truly
on the machine). If era testers feel the pods were more forgiving,
Draco's "slight lock linger" memory becomes a deliberate investigation
(sourced hysteresis), not an accidental sphere halo.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The desktop plasma window (L4PLASMAWIN, "BattleTech - Plasma") is created by a
different TU than the per-display panels, so it couldn't be a gWins[] entry and
sat outside the sticky-layout / ,noframe system. Add a tiny extern-window
registry to L4GLASSWIN so it rides the same glass_layout.cfg:
- BTGlassLayout_QueryWindow(title, rect, noframe) -- read a title's saved rect
and ,noframe flag (used by the plasma on creation, BEFORE sizing, since
WS_POPUP's frame extent differs from the framed tool window).
- BTGlassLayout_RegisterExtern(hwnd, title) -- register an externally-created
window so SaveLayout writes its line (with a last-known-rect cache so a
teardown before the save still preserves the line).
- BTGlassLayout_Save() -- public save trigger the plasma WndProc calls on
WM_EXITSIZEMOVE / teardown.
L4PLASMAWIN now reads its saved rect+flag on create (WS_POPUP when ,noframe,
position restored), registers itself, and saves on finished-drag/teardown. All
BT_GLASS-gated; the pod is untouched. The plasma blits directly every frame, so
it has no WM_TIMER focus-throttle.
Verified: Release links clean; drag wrote "BattleTech - Plasma=321,222,528,167",
reload with ,noframe brought it up WS_POPUP (WS_CAPTION absent) at 321,222.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two glass-panel indicator fixes reported by playtesters.
1. FLASH SLOW UNLESS FOCUSED. The per-display panel windows repaint off a
62ms WM_TIMER, and Windows coalesces/throttles timer + paint messages for a
window that is in the BACKGROUND (unfocused) -- so with the game window
holding focus the panels' lamp flash (a repaint-driven animation) crawled,
and giving a panel focus un-throttled it. (My earlier surround fix drew in
the main D3D frame and never touched these separate windows.) New
BTGlassPanels_Tick(), called once per frame from the main render loop
(L4VIDEO), drives a synchronous InvalidateRect+UpdateWindow at the ~16Hz
flash cadence -- the main loop runs every frame regardless of which window
has focus, and the forced paint bypasses the throttled timer-message path.
The WM_TIMER stays for its one-shot re-snap.
2. RED PANIC/EJECT. 0x3D is the Panic/Eject button; on the blue flight panel
it was just another blue block. The shared flight-grid geometry (L4RIOBANK)
now tags 0x3D as colorClass 0 (red); L4GLASSWIN's AdoptBank maps colorClass
0 -> ClrRed explicitly (the flight bank's ClrBlue default no longer swallows
it), and the surround (L4VB16) already renders non-blue/yellow as red -- so
the eject stands out red in both the surround and the exploded window.
Verified: Release links clean (40 tolerated /FORCE externals only); exploded
panels boot + run 13s no crash under the per-frame repaint pump; riobank dump
confirms 0x3d class=0 (red), neighbours class=2 (blue).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 08-03 zone rule (struck SEGMENT's SKL dzone, ALWAYS -> every aimed
torso hit = center torso) was wrong, and era players' pushback caught it.
Byte-level proof: MAD_TOR.BGF zone-tags the hull PER PANEL (dz_utorso x36,
dz_ltorso/rtorso x18, dz_dtorso x16, all four rear panels, searchlight);
the dpl hit result kept GEOGROUP granularity (dplHitGeoGroup, T0); and the
binary's segment->zone map @49db20 has NO runtime caller (raw call-scan:
sole caller = CreateStreamedDamageZone, load time) -- no segment-level
collapse mechanism exists. Oracle's night-10 'only LCT gets hits' audit
was the BUG's fingerprint, not the pod's design.
Fix: MechSegmentPick attributes the struck triangle to its draw op (index
range) and takes the op's .DZM-bound zone -- the #87 armour-darkening
bindings, the same authored patch->zone mapping that already paints the
panels -- with the segment dzone as the untagged fallback. ZoneAimPoint
now aims hull zones at their patch CENTROIDS (all hull zones previously
shared the chest cull-center), which also upgrades the zone walker.
Bench (2-node zone-walk vs spinning madcat, zonewalk_madcat.sh): every
hull panel resolves individually -- utorso 11/12 in-zone, no L/R
mirroring, all four rear panels register; misses are the panel facing the
shooter mid-spin (correct geometry, not misattribution). Victim applied
254 hits spread across every panel family. Was 102/102 hull aims ->
dtorso before the fix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The night-11 instrumentation that makes the next ghost/K-D report
diagnosable instead of anecdotal:
- UNGATED [ghost] stale-replicant detector: ReadUpdateRecord stamps every
applied record; the death-handler tick logs ONE line per starvation
episode (>600 frames, unburied) with entity id + mode + last position,
plus a GHOST matchlog record. Verified both ways: zero false positives
on a healthy 2-node session; fires on both nodes at frame 601 after a
mid-session relay kill.
- Entity IDs on the render forensics (MakeMechRenderables / RemakeEntity /
wreck-swap fallbacks) and the replicant un-wreck line un-gated -- ghost
triage no longer needs players to set envs.
- players/*.bat (steam + both joins): BT_MATCHLOG/BT_SCORE_LOG/BT_DEATH_LOG
on for every field session.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Operator reports audited vs the binary (full matrix in RESPAWN_REARM_PLAN
addendum):
- VALVES (real gap): Condenser reset @004ae534 was missing from the decomp
export -- raw disasm shows it chains HEATSINK (coolant refill runs; the
old body chained HeatableSubsystem per the stale TCP shard) then, respawn-
side, resets valveState to detent 1 and restores massScale from
refrigerationFactor. Mech::Reset now also runs the binary's tail call
(@0049f788 BTRecomputeCondenserValves) so flow fractions rebuild from the
reset detents. Bench: detent 5 -> death -> "[respawn] Condenser1 valve
detent 5 -> 1".
- #129 SMOKE (real gap, peers-only): the replicant un-wreck edge rebuilt
the model without the @004d0c14 per-entity effect cleanup, so the
observer's last 10s wreck-plume window rode the teleport onto the fresh
mech. BTStopEntityPfx now runs on the edge; bench shows no plume line
after any un-wreck until the next death.
- AUTHENTIC (no fix): weapon->generator taps persist (@004b0e6c only
resolves the link) and MFD display/control modes persist (mapper vtables
0050f45c/0051e440 slots 8-11 = plain root bodies, read from the exe).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>