Commit Graph
102 Commits
Author SHA1 Message Date
CydandClaude Fable 5 3ecd88aaef BT410 5.3.44: read the whole watcher list out of the resource; Torso MotionState
BTL4.RES stores each watcher as a (SUBSYSTEM, ATTRIBUTE) string pair, so the
complete list can be extracted directly instead of discovering one name per
6-minute run.  Banked the extraction method and the full normalised table.

Sixth rung climbed: Torso/MotionState (staged member, appended at the end of
Torso's own range so no downstream id moves).  LocalVelocity and
LocalAcceleration turn out to need nothing -- the engine's Mover already
publishes them.

Also banked, and deliberately NOT acted on: ReportLeak and
ConfigureActivePress are base-class attributes inside the pinned range, and
chasing them turned up what looks like the authentic attribute layout.  The
shipped pool gives MechSubsystem 1, HeatableSubsystem 3, HeatSink 6,
PoweredSubsystem 5 -- which counted from Subsystem::NextAttributeID=2 lands
MechWeapon's real ids on the binary-pinned 0x12 with EXACTLY ONE pad, where
our tree needs three.  That arithmetic is strong evidence for the original
layout, and reconciling to it would replace three guessed pads with the real
table.  But it moves ids underneath a cockpit currently verified at 98.9%
pixel-identical, so it wants a deliberate session with the A/B rig open
rather than a bulk edit at the end of a long one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 01:25:40 -05:00
CydandClaude Fable 5 14da9489c2 BT410 5.3.43: full pod rig -- geometry loads, ladder advances to the audio watchers
Under the documented rig (launch_pod.ps1 with the GL bridge up) the 'couldn't
load object' count went to ZERO.  .BGF loading was never a reconstruction
problem, only a missing bridge.

What blocks now is a series of authored AttributeWatchers: BTL4.RES binds
them BY NAME and the engine Fails outright on any that does not resolve, so
each is a name our subsystems must publish.  Five rungs climbed, each
run-verified: UnstablePercentage, SpeedEffect (Myomers table), AnimationState
+ CollisionState/CollisionNormal + ReduceButton (Mech), and the full Torso
table (all six authentic names mapped onto existing members).

The method that makes this cheap is banked: the shipped binary's string pool
carries each class's attribute names CONTIGUOUSLY in ID ORDER after the class
name, and in every case so far our member declarations sit in the same order
-- which confirms the layout and lets ids be pinned rather than guessed.
Intersecting those names with BTL4.RES gives the exact work list.

A THIRD miswired SharedData found on the way: Myomers carried Subsystem's
tables where it derives from PoweredSubsystem, the same defect as
MissileLauncher (5.3.33).  Worth a sweep across every subsystem.

Staged member TYPES are provisional and documented as such: AttributeWatcherOf
reads *(T*)attributePointer and the instantiation comes from the resource,
which the string pool does not reveal.  Nothing drives them yet, so settle the
types with the models that write them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 01:03:36 -05:00
CydandClaude Fable 5 91b043778e BT410 5.3.42: the 3-D pipeline runs to the geometry load -- two rungs climbed
RUNG 1: BTL4VideoRenderer::LoadMissionImplementation, Fail -> bring-up no-op.
Legal, not a cheat: the engine's own VideoRenderer version (VIDREND.CPP:259)
is a bare Tell and our working GaugeRenderer ships the same, so the renderer
comes up and runs its frame loop with an empty scene -- exactly what we want
to measure before writing content.  The authentic shape for the real body is
pinned by the surviving sibling header CODE/RP/RP_L4/RPL4VID.HPP (walk the
mission entities -> MakeEntityRenderables -> ReadSKLFile/RecurseSKLFile).

RUNG 2: Mech::EyepointRotation published.  The engine then Failed at
SetupCull, which fetches that attribute BY NAME off the viewpoint entity and
composes it with the siteeyepoint segment to build worldToEyeMatrix.  The
mech already HAD EulerAngles eyepointRotation with a getter -- only the
publication was missing.  One enum id, one table row.

The run now reaches the full mech build, constructs the renderer, boots the
board (~907K VPX wire transactions), loads the mission, runs the per-frame
cull and enters geometry loading with ZERO Fails and zero exceptions.  It
stops at 'couldn't load object buttee.bgf' / 'NULL instance' -- and that is
not a defect: the models are present and BTDPL.INI points at them, but .BGF
loading goes through the board to the external GL render bridge that the VPX
HLE tees over VPX_FIFOSOCK, and that bridge was not running.  'NULL instance'
comes from the prebuilt LIBDPL.LIB refusing to instance an object that never
loaded.

Next rung is the documented full rig (render-bridge/launch_pod.ps1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 00:18:10 -05:00
CydandClaude Fable 5 1b204de6d3 BT410: the 3-D path runs to mission load -- btl4vid's next brick is named
Run against the emulated Division card, our build reaches the mission scene
load and stops at the one method we stubbed:

  btl4vid.cpp(42): BTL4VideoRenderer::LoadMissionImplementation
                   -- btl4vid.cpp not yet reconstructed

That proves MakeVideoRenderer, the BTL4VideoRenderer ctor over the engine's
DPLRenderer, the board boot (transputer + i860 firmware, ~858K wire
transactions through the VPX HLE) and Renderer::LoadMission all work in our
build.  btl4vid is not a from-scratch climb; the engine half was always
linked and what is missing is the mission-load hook and its renderables.

Banked the reproduction rig (emulator/vidtest.conf + the host VPX env --
VPX_RESPOND alone is not enough, the iserver handshake dies without
VPX_RENDER), the authentic contract from CODE/RP/MUNGA/RENDERER.CPP:263, and
a note that the BT411 donor restructured this hook so the contract comes from
the 1995 engine and only the renderables come from the donor.

Also corrected today's earlier note: the transient 3-D frame seen during a
gauge run was NOT the dPL path.  MakeVideoRenderer returns NULL without
DPLARG and no gauge conf sets it, so no video renderer exists in those runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 23:32:06 -05:00
CydandClaude Fable 5 50341b4a66 BT410 5.3.41: the plane-write hunt -- two more hypotheses dead, defect bounded
PlayerStatus (dirty-gated, blits an 8-bit pixmap -- but the widget map for a
DIRTY run contains no playerStatus at all; it belongs to cameraInit and never
exists in a mech cockpit) and heat-driven redraw (a BT_FORCE_FIRE run came
back clean).  Seven hypotheses killed by measurement so far.

It is a heisenbug: dirty on ~3 runs in 25, and every run since instrumenting
has been clean.  The pattern tracks STARTUP LOG VOLUME -- the three dirty
runs all had the lighter conf, and a chatty trace slows init enough to
suppress it.  The blit trace is therefore narrowed to fire only for pixmaps
landing on a non-palette port, which is near-silent when nothing is wrong.

That narrowed trace caught real traffic: 17x17 PixelMap8 blits onto
Mfd1/2/3 -- 8-bit pixmaps on single-bit planes.  They render correctly, so
the library thresholds them; not the culprit but the right bug class.

Also banked a hunting recipe, because two detector traps cost runs: Eng1 == 0
means the cockpit is not up (not dirty), and an early capture during mission
start caught a full 3-D cockpit frame with the gauges at pod positions
(Eng1 62927) that did not recur over the next minute.  BTDPL.INI is in the
mount and DPLRenderer is linked, so the dPL path may emit a frame during
launch even with L4VIDEO=OFF -- first evidence the 3-D path produces
anything, and explicitly NOT a claim that btl4vid works.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 22:43:13 -05:00
CydandClaude Fable 5 90daf5e9b7 BT410: bank the plane-write hypothesis ledger and the instrument to use next
Four hypotheses tried and killed by measurement, recorded so the next session
does not repeat them: misrouted eng port (traced -- resolves correctly),
OneOfSeveral's DrawPixelMap8 branch (only OneOfSeveralPixInt uses it, and all
four instances are on the 6-bit colour port where it is correct), an 8-bit
value overflowing a 6-bit field (the bits are far too high), and a displaced
copy from elsewhere in the buffer (the rows appear nowhere else).

Observed structure: constant low byte, varying high byte -- one field written
correctly while another is garbage.

The next move is an instrument, not a fifth hypothesis: trace at the VIEW
layer and log every draw whose absolute position lands in the artifact rect,
with port, colour and call site.  The rect is tiny so the log is short.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 13:42:26 -05:00
CydandClaude Fable 5 c8350408fc BT410 5.3.40: correction -- the 'exact' heads are exact only on lucky runs
Same binary (md5 identical), different run: Eng1/Eng2/Comm read
17981/17981/15656 on some runs and 19011/19011/16909 on others.  The shipped
binary is STABLE across three runs, so this is ours.  Eleven consecutive
clean runs is what let it hide, and the dirty numbers are exactly the
pre-gate numbers from 5.3.32 -- the same artifact recurring.

Read off the raw words rather than inferred: in the artifact rect the shipped
binary writes ONE masked plane bit per pixel (0x4000 = Heat, 0x002D = sec),
while we write arbitrary 16-bit words (0x8BD6 / 0x8AD6 / 0x8AC0) that light
sec+overlay+Mfd1+Eng1+Eng2+Comm at once.  Raw values going into a
plane-packed buffer -- which is why one bad draw appears as extra pixels on
six heads and as 483 MISSING on Heat at the same time.

Intermittent because these widgets are change-driven: on runs where the
coolant value never moves after the first paint, the bad draw never happens.

The six fixes of 5.3.33-39 stand (each moved specific attributable pixels).
What must be restated is the claim that four heads are exact -- true only
when this defect does not fire.  The rig now needs a run-variance check
before any per-head number is believed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 13:39:11 -05:00
CydandClaude Fable 5 b6426388de BT410: correct what the sec head is -- the colour radar, not the 3-D scene
I had it recorded as 'the 3-D scene head, this is btl4vid'.  It is not.  Port
0 carries map / headingPointer / digitalClock / numericSpeed / messageBoard /
the armour colour-mappers / the four GeneratorClusters, and its plate shows
the radar scope, the SPEED / HEADING / MISSION TIME dials and the ARMOR
DAMAGE diagram -- the pod's colour screen, the one the VDB splits out
alongside the five mono MFDs.

So sec is ordinary gauge work and is now the largest single head delta.
btl4vid is a separate deliverable this rig cannot measure at all: the 3-D
main view never enters the gauge framebuffer -- it goes down the dPL path to
the render board, and these confs run L4VIDEO=OFF, so it is not even being
produced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 13:17:09 -05:00
CydandClaude Fable 5 540c41e497 BT410: correct the leak-gauge note -- both of my guesses about it were wrong
The CFG parameter order is fine (Make already maps third/frames by name,
with a comment saying so), and we do not paint nothing: cropping the plane
shows both binaries draw the hatch, ours about three columns narrower.  So
it is a level/frame-width detail, and most likely the same root as the
coolant numerals -- our coolant value differing by a step.  Folded the two
into one thread and pointed it at the value rather than the widget.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 13:00:06 -05:00
CydandClaude Fable 5 ce199103a9 BT410: session landing -- the cockpit at 98.9% identical, four heads exact
Final per-head state, the binding audit (31 CFG bindings resolve, zero NULL),
and the open list in priority order: btl4vid for the sec head, then the
leak-gauge hatch (with the CFG parameter-order question that probably
explains it), the coolant numerals, and the MFD remainders.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:58:33 -05:00
CydandClaude Fable 5 fc0cafe867 BT410 5.3.39: the Comm page goes exact -- the own row and its name raster
Two things, both read off the shipped framebuffer.

SLOT 0 IS THE OWN ROW.  Its layout entry is the odd one out -- {180,225} at
layout mode 0, KILLS/DEATHS at +0x95/+0xe8 instead of the strip boxes'
+0x48 -- and the shipped Comm page fills it with the LOCAL pilot while the
seven surrounding boxes stay empty in a solo mission.  So slot 0 takes the
mission player directly and only slots 1..7 come from the pod roster.  That
also explains the artifact this session opened with: the ungated walk was
feeding the mission player into the STRIP slots, which is why gating the
roster removed wrong pixels but left the centre row missing.

THE OWN ROW TAKES THE LARGE NAME RASTER.  Mission carries both; we asked for
the small one, so 'Aeolus' drew visibly undersized and low in the box.  The
strip slots keep the small raster -- their boxes are half the height.

  Comm   missing 2099 -> 733 -> 0     extra 0 -> 152 -> 0

The Comm head is now EXACT: 15656 pixels against the shipped binary's 15656.
Four heads exact (Eng1/2/3, Comm), the three MFDs within ~150px, and the
whole cockpit at 98.9% identical / 99% coverage -- from 89.7% / 92% when the
session started.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:55:21 -05:00
CydandClaude Fable 5 377aec92b0 BT410: the MFD wave landing report (5.3.33-38)
Where the cockpit stands per head, the five defects the rig found and the
order they fell, and the measurement lesson: a large overlapping error makes
a small one measure as already-optimal, which is exactly what I recorded for
the title banner before the strip art was fixed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:48:34 -05:00
CydandClaude Fable 5 44aa7a349c BT410 5.3.38: SegmentArc270 never applied its 270 degrees
The class computed segmentSpan = 0.75 in its ctor and nothing ever read it:
the engine SegmentArc base has no span member, and SegmentArc270 declared no
Execute, so the recharge dial lit all 20 segments at PercentDone 1.0 where
the shipped cockpit lights 15 and leaves the quarter gap.  That was the last
MFD delta -- four extra spokes at the upper-left of every weapon disc.

A derived Execute is the only place the 0.75 can reach the draw, which is the
same role SegmentArcRatio's Execute plays with its own copy.  It scales the
raw fraction, draws, then restores: the connection only rewrites currentValue
when its source changes, so scaling in place would compound frame after frame
and walk the dial to zero.

  head   missing        extra
  Mfd1    140 (=)       679 -> 7
  Mfd2     97 (=)       462 -> 14
  Mfd3     31 (=)       461 -> 13

Cockpit: 98.2% identical / 97% coverage.  The three MFD heads and the three
engineering heads are now within a few dozen pixels of the shipped binary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:47:48 -05:00
CydandClaude Fable 5 945812fef1 BT410 5.3.37: the panel title banner was off by exactly one row
y+0xb3 -> y+0xb4.  Earlier this session I measured 0xb3 as the optimum and
wrote that the banner was at most 1-2px off -- that reading was confounded:
the strip-art misplacement (5.3.35) was ten times larger and overlapped the
same bands, so it dominated the metric and hid the one-row error.  With the
strip art correct the title fringe isolated cleanly at 553/553 missing/extra
per head, and +1 collapsed it.

  head   missing         extra
  Mfd1   1107 ->  140    1646 ->  679
  Mfd2   1616 ->   97    1981 ->  462
  Mfd3    584 ->   31    1014 ->  461

Cockpit: 97.9% identical / 97% coverage, from 89.7%/92% at the start of the
session.  Method note for the worksheet: fix the largest error on a head
FIRST, then re-measure the small ones -- a big overlapping defect makes a
small one measure as noise, or as already-optimal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:42:18 -05:00
CydandClaude Fable 5 6a3cf06ece BT410 5.3.36: the cooling-loop lamp goes live (OFF -> the loop digit)
CoolingLoopConnection::Update wrote a constant 0 -- the documented inert stub
-- because HeatSink::coolantAvailable, HeatSink::linkedSinks and
Condenser::condenserNumber are protected with no accessor, so every MFD panel
showed OFF where the shipped cockpit shows the loop number.

heat.hpp now publishes GetCoolantAvailable(), ResolveCoolingMaster() and
GetCondenserNumber(), and the connection follows the binary: a sink with
coolant reports the loop number of the Condenser it is plumbed to, otherwise
frame 0.  Same shape as PowerSourceConnection beside it, which was already
live and already matched.

The box now reads 4 / A against the shipped binary's 4 / A, pixel for pixel.

  head   missing        extra
  Mfd1   1331 -> 1107   1776 -> 1646
  Mfd2   1908 -> 1616   2196 -> 1981
  Mfd3    730 ->  584   1119 -> 1014

Cockpit: 96.6% identical / 96% coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:39:07 -05:00
CydandClaude Fable 5 287695fd3b BT410 5.3.35: the MFD strip art belongs in the middle of the quadrant
image_names[auxScreenPlacement] (qblh0..qblh6) is the mech diagram with THIS
subsystem's segment lit, and SubsystemCluster drew it at the panel origin.
Shipped puts it in the quadrant's middle.

Solved from the framebuffer rather than guessed: our extra pixels clustered
at panel x 0..70 against shipped's missing at x 140..230, and a
cross-correlation of the two sets peaked at dx=+144 dy=-54 independently on
all three panels of the head.  +144 is 0x90 exactly; y is bottom-up in this
space, so up the screen is +0x36.

  head   missing         extra
  Mfd1   4908 -> 1331    3960 -> 1776
  Mfd2   6870 -> 1908    5280 -> 2196
  Mfd3   3091 ->  730    2565 -> 1119

~10.9K missing and ~6.7K extra pixels recovered.  The whole cockpit is now
96.4% pixel-identical / 95% coverage against the shipped binary, from
89.7%/92% at the start of the session -- and the MFD weapon panels are
visually indistinguishable apart from the state box, which still reads OFF
where shipped shows a digit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:34:37 -05:00
CydandClaude Fable 5 30e4b5885d BT410 5.3.34: the weapon ammo readout goes live (0024)
BallisticWeaponCluster's two round counters pointed at UnboundIntegerSource
because ProjectileWeapon::ammoBinLink is protected -- the documented inert
gap in the ctor ledger.  ProjectileWeapon now exposes GetAmmoCount() (the
resolved bin's rounds, -1 with no bin) and the cluster copies it into a live
cell each Execute, the same pattern it already uses for jammed and
reloadSeconds.  The binary pointed its NumericDisplayInteger straight at the
resolved bin's count.

The STREAK 6 panel now reads 0024 against the shipped binary's 0024, in the
same font, stencilled into the fire-ready disc the same way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:30:13 -05:00
CydandClaude Fable 5 341a503641 BT410 5.3.33: MissileLauncher was wired to the wrong shared-data tables
MissileLauncher::DefaultData passed Subsystem::MessageHandlers and
Subsystem::AttributeIndex where every sibling weapon passes MechWeapon's --
and a launcher publishes no tables of its own, so it must chain MechWeapon's
exactly as ProjectileWeapon (its own base) does.  Wired to Subsystem's, EVERY
weapon attribute on a missile launcher resolved to nothing and the gauge read
the unbound zero cell: PercentDone, TriggerState, WeaponState,
DistanceToTarget, RearFiring.

The cockpit is what found it.  The shipped binary lights a fire-ready disc on
the STREAK 6 panel (WeaponCluster gates it on PercentDone >= 0.99) and ours
never did -- while the launcher itself reported state=Loaded recoil=0 level=1
bin=24.  A healthy weapon behind a dead binding.

  head   missing before -> after      extra
  Mfd1        8807 -> 4991            4063 -> 4003
  Mfd3        6935 -> 3174            2659 -> 2608
  Mfd2        6882 -> 6870   (unchanged -- it hosts no missile panel)

~7.6K missing pixels recovered.  The disc, its rays and the stencilled ammo
cells now draw; the digits themselves stay blank pending the ammo feed, which
is the documented inert gap in the BallisticWeaponCluster ledger.

Traces added, all env-gated on BT_VIS_LOG and kept as tooling: [warn]
(cluster percentDone/warn state) and [proj-state] (launcher state, recoil,
recharge rate, bin count) -- the pair that separated "broken weapon" from
"broken binding" in one run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:25:53 -05:00
CydandClaude Fable 5 883315a647 BT410 5.3.32b: the weapon-panel work list, measured
With the engineering heads exact, the entire remaining cockpit difference is
the three MFD quadrant heads -- the SubsystemCluster panels, which the widget
map shows are built by vehicleSubSystems rather than by the CFG, so they are
our code end to end.

Banked with evidence: the ammo weapon's disc+rays are absent while both
energy weapons draw theirs; the ammo digits render as empty cells at exactly
the right position; the mech silhouette is misplaced low-left; the state box
reads OFF where shipped shows a digit.

Also settled by experiment that the title banner is NOT misplaced: 0xb3 is
the measured optimum (0xb6 and 0xb0 both score worse), and the equal
missing/extra counts are overlap fringes, not an offset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:14:48 -05:00
CydandClaude Fable 5 740ef2b0a7 BT410 Phase 5.3.32: the per-head A/B -- three engineering heads now exact
The gauge framebuffer is PLANE-PACKED: L4GAUGE.CFG gives each port a bit
mask, not a rectangle, so all six heads share the same x/y and are separated
only by bit -- the packing the VDB splits into the pod's physical screens.
Comparing RGB was therefore measuring nothing useful; the "magenta artifact"
just meant the wrong heads were lit at that pixel.

New instruments (emulator/render-bridge/gauge-ab/, with a README):
  planes.py  per-head scoreboard -- shipped vs ours, missing vs extra, per
             port mask, recovering the 16-bit word from DOSBox's RGB565
  BT_VIS_LOG a complete cockpit widget map from the single dispatch every
             gauge widget is built through (MethodDescription::Execute),
             printing name + port + authored position
  ab.sh      stages the fresh build over BTL4REC.EXE and kills any running
             DOSBox before launching -- see below

The fix: the Comm page's pilot roster is the POD roster (the viewpoint mech's
ControlsMapper pilot array, mapper attributes 15/16), not the mission's
player list.  It is zero in a solo mission, so the shipped page draws empty;
ours resolved the local player and painted its icon in colour 0xff.  The gate
belongs in the row source, not PilotList::Execute -- Execute's empty branch
still has to run, because erasing is what the shipped page draws.

  head    shipped    ours   missing   extra          (was)
  Eng1      17981   17981         0       0     (0 / 1030)
  Eng2      17981   17981         0       0
  Eng3      17981   17981         0       0
  Comm      15656   13557      2099       0     (2099 / 1253)
  Heat      21374   20913       566     105     (566 / 990)

Title-band magenta 404 -> 0.  The 2099 Comm pixels we are missing were
missing before the gate, so it is a strict improvement.

Method note, which cost more than the bug: the rig's confs run BTL4REC.EXE
out of the mount while the build writes build410/btl4opt.exe, and nothing
connected the two.  Three measurements ran an hour-old binary, so a change
that "did nothing" three times had never been tested -- and the correct first
hypothesis was discarded on that evidence.  ab.sh now re-stages every launch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:04:43 -05:00
CydandClaude Fable 5 c6cafadb28 BT410 Phase 5.3.31: traced the stray blocks -- PilotList, not PlayerStatus
Three rounds of source inspection produced three wrong suspects; one
instrumented run settled it:

    [pl] PilotList slot=0 at (180,225)      <- the only drawer
    (no [ps] lines at all)

So the magenta blocks over the top-left title are PilotList's slot-0 entry
(the local pilot's name art + mech icon) on the Comm port -- content that
SHOULD draw; what is wrong is the entry's coordinate convention inside that
port.  The port geometry itself is proven right: the Comm background
(btcomm.pcx, the KILLS/DEATHS columns) lands pixel-identical in both exes.

Second, separate finding: PlayerStatus never draws at all.  The cfg builds
8 of them (sec port, 4x2, players 1..8) but our roster resolve is 0-based
against 1-based cfg player numbers, so every panel stays unbound and its
Execute returns early.  Fixing that will add a whole scoreboard page, so it
belongs in an A/B session against the shipped reference rather than a blind
edit -- both leads are written up in GAUGE-BLOCK.NOTES.md.

Also recorded: the capture rig's occlusion failure mode (CopyFromScreen
grabs whatever is in front -- one run scored 77%/47% purely because an
editor covered half the window) and the PrintWindow fix for unattended runs.

Verified build, zero faults; last valid A/B measurement stands at 94.7%
identical / 92% coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 10:45:20 -05:00
CydandClaude Fable 5 10e1a70055 BT410 Phase 5.3.30: the pilot name resolves -- and the magenta boxes diagnosed
The name art was never missing.  The egg carries it as notation pages of hex
rows ([BitMap::Small::Aeolus]); the engine BitMap has a ctor that reads
exactly that format; and the AUTHENTIC Mission ctor already loads them into
smallNameBitmapChain (MISSION.CPP:525 -- implemented, called, working).  The
break was ours: btl4gau3's LookupPlayerNameBitmap was a bring-up stub
returning NULL.  It now uses the authentic API (GetCurrentMission()->
GetSmallNameBitmap(playerBitmapIndex)) and resolves live -- traced index=1
to a real BitMap, with the fallback-box branch provably never firing.
Coverage 91% -> 92%.

DIAGNOSIS CORRECTED for the stray magenta blocks over the top-left title:
they are NOT a missing-name placeholder.  They are the REAL name bitmap
drawn at the wrong slot coordinates -- PilotList lays its 8 roster slots
out from the PE-recovered DAT_0051af88 (x,y,mode) table, and ours puts them
top-left instead of the shipped exe's centre row.  Recovering that table is
the next cockpit thread; the egg-hex -> live-BitMap pipeline is verified end
to end, so what remains there is layout, not art or lookup.

(A duplicate loader written before finding the authentic one was removed --
CODE/RP/MUNGA/MISSION.CPP already does this job.)

Gauge fight 11/11, smoke and novice clean, zero faults.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 09:47:19 -05:00
CydandClaude Fable 5 42ff5c3856 BT410 Phase 5.3.29: the panel titles -- 94.8% identical to the shipped cockpit
Reading the art settled where the titles live: qsensors/qmyomers/qermlas/
qppc/qstrk6.pcc are 299x26 TITLE BANNERS, while the qblh0..7.pcc strings
the cfg hands vehicleSubSystems are 77x120 MECH ICONS (already lit by the
5.3.28 placement fix).  So the authored auxScreenLabel IS the panel title
-- it just belongs on the MFD panel, not only the ENG page.

SubsystemCluster now builds a titleBanner child from the cached label.
Placement was MEASURED against the shipped framebuffer through the A/B rig
(BackgroundBitmap places by left/BOTTOM): a bring-up BT_TITLE_DY knob, then
a green-pixel correlation over the title band, then baked at x+0x09,
y+0xb3 from the panel origin.  "SENSOR CLUSTER", "MYOMERS", "TYPE II 65
TONS" and both "ERMED LASER RANGE 500M" banners now render where the
shipped binary puts them.

Score progression vs the shipped cockpit on identical GAUGE content:
  5.3.26 birth            90.1% identical / 82% coverage
  5.3.28 authored screens 93.2% / 85%
  5.3.29 titles tuned     94.8% / 91%

Also: grab.ps1 now focuses the target window before capturing -- the
screen-copy grabbed whatever was in front, which cost two junk captures
(a white frame and an unrelated 3-D app window).

Gauge fight 11/11 rounds and smoke both clean, zero faults.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 23:09:15 -05:00
CydandClaude Fable 5 24ea06574c BT410 Phase 5.3.28: the visual A/B pass -- 93% pixel-identical to the shipped cockpit
Ran the reconstructed exe and the shipped ALPHA_1 binary against the SAME
GAUGE content, same mount, gauges on, and diffed the 640x480x16
framebuffers.  OUR EXE DRAWS THE COCKPIT: 90.1% pixel-identical on the
first run, and every difference traced to a single root cause.

The authored aux-screen block (screen number / background placement /
label) lives in the subsystem resource -- which is freed with the Mech
ctor's stream buffer (the 5.3.24 use-after-free landmine).  Both this tree
and the BT411 port had stubbed it, which is why BT411's displays show the
same artifacting.  PoweredSubsystem now caches the block at ctor time with
accessors, and btl4gau2 uses the AUTHORED screen instead of a roster-order
stand-in and builds the placement strip art.  Panels snapped onto their
authored screens -- weapon dials now land on the same panels as the
shipped exe, mech icons appear on the sensor/myomer panels, generator
letters correct -- taking the match to 93.2% identical / 85% coverage.

Remaining differences are donor-inherited stubs: the panel title art (now
proven to come from the q-strip statusImage path, not auxScreenLabel --
the label blit is wired and guarded regardless), the pilot name bitmap,
and some lamp frames.

The A/B rig is banked at emulator/render-bridge/gauge-ab/ (both confs +
the window-grab script) so the comparison is repeatable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 22:29:01 -05:00
CydandClaude Fable 5 d67f8d5eda BT410 Phase 5.3.27: the attribute wave -- 48/50 cockpit bindings go LIVE
The gauges now read the real simulation: heat/power/sensor/mech attribute
tables published under the 1995 L4GAUGE.CFG spellings the widgets already
bind by name.  Temperatures, coolant mass/capacity/leak rate, condenser
valve settings, generator voltages and numbers, radar percent, and the
mech's radar/speed rows all resolve to live members.

THE NUMBERING IS PINNED: the chain publishes 2..0x0E so
PoweredSubsystem::NextAttributeID lands on the authentic 0x0F -- the
surviving SENSOR.HPP numbers RadarPercent off it and MechWeapon's
binary-pinned table starts at 0x12, so its pads shrank 16 -> 3
(0x0F..0x11) exactly as the 5.3.x header comment predicted.  Sensor's
authentic enum finally has its table defined; MechWeapon/Sensor rechain to
PoweredSubsystem and Reservoir/AggregateHeatSink to HeatSink so the whole
family is visible where the cfg expects it.

Verified with BT_GAUGE_ATTR_LOG: 48 OK / 2 NULL (was 33/17, and 0/50 at
the block's birth).  The two remaining have no member to bind --
HeatSink/AmbientTemperature (a sim constant) and Searchlight/LightOn (a
memberless subclass) -- and fall to the documented zero cell.  Gauge
fight (15/15 rounds, 127 zone hits), smoke and novice all clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 20:09:08 -05:00
CydandClaude Fable 5 64e208f121 BT410 Phase 5.3.26: THE COCKPIT GAUGE BLOCK -- the first rendered milestone
Six TUs born in one wave (4-agent re-hosting workflow from the BT411 donors
+ inline integration): BTL4GAUG (16 widget primitives + 5 connections, the
TU anchor @004c3f6c), BTL4GAU2 (13 composite panel clusters), BTL4GAU3
(PilotList/PlayerStatus/SectorDisplay/MessageBoard/mapping group), BTL4RDR
(the MapDisplay radar vs the authentic surviving header), BTL4GRND (the
real renderer: L4LampManager + L4Warehouse + BuildConfigurationFile with
the 19-entry BT registry chained onto the engine table).  btl4.lib grows
7 -> 11 members; all 50 game TUs compile; link clean.

VERIFIED LIVE: with L4GAUGE=640x480x16 the interpreter parses the full
authentic L4GAUGE.CFG, Bhk1Init runs, ColorMapperArmor binds the live
mech's damage zones by name (cross-chassis names take the authentic inert
path), and the complete mutual missile fight runs with the cockpit stack
updating from real combat -- 16/16 rounds, 138 zone hits, zero faults.
Gauges-off regressions untouched.

Engine bring-up deviation (documented): ParseAttribute normalizes
unpublished attribute names to a shared writable zero cell -- the 1995
GaugeConnectionDirectOf ctor hard-derefs its source (first boot crashed
there); widgets on unpublished names draw static zeros and light up
automatically as the attribute waves publish the 1995 CFG spellings the
bindings already carry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 19:02:04 -05:00
CydandClaude Fable 5 f11cc0247e BT410: gauge worksheet donor path fixed (exact-byte repair, forward slashes)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 17:41:28 -05:00
CydandClaude Fable 5 b02ce77f06 BT410: byte-level fix for the gauge worksheet donor path (CR/BS escapes)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 17:40:26 -05:00
CydandClaude Fable 5 7bfbece12d BT410: fix a mangled donor path in the gauge worksheet header
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 17:39:36 -05:00
CydandClaude Fable 5 2e0c8a0681 BT410: the gauge-block worksheet -- per-class re-hosting dossiers banked
Three-dossier workflow output (65KB, verbatim): every class across the six
missing gauge TUs audited -- engine base, the L4GAUGE.CFG keyword + the
attribute each widget binds (and which reconstructed subsystem publishes
it), the port-shim -> 1995-native mapping, BC++4.52 hazards, and the
binary @ADDR anchors.  This is the implementing session's hot-start
worksheet for the first rendered milestone (see RENDER-ROADMAP.NOTES.md).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 17:39:16 -05:00
CydandClaude Fable 5 fdb1cfcf6c BT410: the render roadmap -- headless to visible, researched and mapped
Four-dossier workflow synthesis: the engine half of BOTH render paths
(DPLRenderer/libDPL for the division-card 3D view; the L4 gauge stack for
the cockpit instruments) ALREADY compiles and links into btl4opt.exe from
the CODE originals, and the emulator's VPX board-side device is
EXE-agnostic (pure wire-level).  What's missing is game-TU work with
complete BT411 donors: the 6 gauge TUs (~302 fns, the first rendered
milestone -- diffable against the shipped exe on the same GAUGE content)
and then btl4vid (the 3D world renderable factory).  Full protocol map,
env gates, and the staged plan in RENDER-ROADMAP.NOTES.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 17:28:54 -05:00
CydandClaude Fable 5 f65a3259ce BT410 Phase 5.3.25: scoring + the COMPLETE mission lifecycle -- egg to GAME-RC
Scoring roles bound (binary ctor tail @004c0bc8): the mission role dict ->
Player::scenarioRole; TEST.EGG's Role::Default = the baked dfltrole
(lives=1000, killBonus=500) -- authored data flowing.  The three score
handlers are live per the decomp derivations (@004c02e4 / @004c0200 /
@004c02a8 + CalcInflictedScore @004c052c; tonnage/bias staged); the
producers post damage scores per delivered hit and the kill credit from
the death transition with the latched killing-blow magnitude.  First live
kill: award=3.43, kills=1, score=123.6.

Out of lives -> the mission END: the binary's +10s review post sits in the
one decomp gap, so the staged stand-in enters the engine's own cascade
(StopMissionMessage +10s) -- EndingMission -> the 3s fade -> the second
Stop -> Application::Stop -> RunMissions exits.  The BTL4/L4 stop layers
run authentically (plasma score display off, egress lamps).

VERIFIED END-TO-END: a 2-lives egg (role-page return=2 override) -- death
#1 debits and respawns healed; death #2 is OUT OF LIVES; +10s later the
mission ends and BTL4OPT.EXE exits CLEANLY to DOS (GAME-RC prints).  The
full 1995 pod mission loop -- egg, boot, spawn, fight, score, die,
respawn, die, mission end, exit -- now runs from reconstructed source.
Zero faults; fight/smoke/novice regressions green.  Dev knob:
BT_PLAYER_PASSIVE=1 (piloted mechs hold force-fire).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 17:18:41 -05:00
CydandClaude Fable 5 623c872c92 BT410 Phase 5.3.24: the death -> respawn cycle -- mechs DIE and COME BACK
The full circuit, workflow-researched (5 parallel dossiers over BT411 + the
engine) and adversarially reviewed (3 lenses) before landing:

Mech side: the once-per-death transition in Simulate (freeze, motion kill,
DeathShutdown sweep, MASTER-only VehicleDead(-1) to the player link);
Mech::Reset @0049fb74 (reposition + heal every hull zone + the roster
DeathReset sweep + PreRun -- the reset-based respawn that REUSES the entity);
dead-owner terms in both weapon hard gates (wrecks fall silent).

Player side: the VehicleDead(-1) branch (deathPending dedup, deathCount
bump+stamp, scenario-role life debit, +5s re-post -> the engine drop-zone
hunt) and the DropZoneReply respawn branch (reset the dead mech at the
replied drop zone; probes on a live mech are moot).

Subsystem side: the engine base virtual DeathReset implemented across the
family -- MechSubsystem (zone heal + DestroyedState clear), MechWeapon (full
powered/thermal restore + fresh Loading cycle), AmmoBin (restock from the
ctor-cached count), HeatSink/HeatWatcher/PowerWatcher/Generator/Sensor/
MissileLauncher.

Review catches fixed before commit: AmmoBin restocked through the FREED
padBuffer pointer (use-after-free -> initialAmmoCount; MechSubsystem::
resource is now documented as never-deref-post-ctor); died-hot weapons
respawned at FailureHeat (now full thermal re-init); Generator tap counts
desynced across reset (preserved); Sensor skipped the base heal; PowerWatcher
inherited a statically-bound reset; cooling toggle + connect mode restored.

Verified: kill -> wreck (0 shots while dead) -> +5s -> drop-zone hunt ->
HEALED + placed at a real drop zone -> 134 shots after respawn incl. 12 SRM
(restock proof); unowned enemy wreck settles silently; fight/smoke/novice
regressions green, zero faults.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 16:35:05 -05:00
CydandClaude Fable 5 2f0d3b6239 BT410 Phase 5.3.23: the cylinder hit-location table -- unaimed hits land WHERE THEY STRIKE
DMGTABLE born (type-29 streams, BT411 byte-verified format): the height x
angle grid -- DamageLookupTable rows by height, PieSlice cells by angle
(rotate-with-torso rows add the live twist), DamageZonePercentTable = the
cumulative hit-distribution roll (first threshold above the roll -- the
BattleTech dice scatter).  Loaded in the Mech ctor by the DamageZoneStream
member's NAME (mech+0x444, resident); Mech::TakeDamageMessageHandler
resolves invalidDamageZone hits through it (binary @0x4a0264 tail).

The missile fuse becomes the authentic unaimed producer: zone -1 + the
round's world position as the impact point (ENTITY3.HPP: "damage zones are
only valid via reticle based weapons").

Fight-verified on the authentic 7-row bhk1 table: flat-flying SRMs strike
low -> row 0 -> FEET AND LEG zones, side-correct by impact angle (+x right,
-x left), identical geometry spread across the cell distribution by the
roll.  17/17 missile lifecycle, zero faults; smoke + novice green.  Also
fixed: the ctor's reservedState zero-loop counted past the shrunken array.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:46:39 -05:00
CydandClaude Fable 5 bfff7fe874 BT410 Phase 5.3.22: the cockpit weapon-button column -- generator panel LIVE
The full per-receiver message chain, contiguous ids 3..0xb: PoweredSubsystem
4-8 (SelectGeneratorA-D + ToggleGeneratorMode, binary table @0x50F4EC)
chained on HeatSink's ToggleCooling(3); MechWeapon 9/10 (config buttons,
staged latch bodies); Emitter 0xb ToggleSeekVoltage (@0x511DB8).

SelectGenerator = release the old tap -> roster walk by generatorNumber ->
AttachToVoltageSource -> Connected; mode toggle cycles Manual->Auto->
(detach)->Manual; the seek dial steps the authored voltage ladder with wrap
(a step up re-enters Loading, charge persists).  All novice-locked.
Generator::UntapVoltageSource + PoweredSubsystem::DetachFromVoltageSource
added.  Dev harness: BT_PRESS_GEN=1..4 / BT_PRESS_SEEK.

Verified: PPC_1 retaps GeneratorB (tapped); seek index 2->3, target 9900V
(authored fraction x rated); novice presses INERT; missile fight 14/14 and
smoke green, zero faults.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:36:32 -05:00
CydandClaude Fable 5 1a01aa6f43 BT410 Phase 5.3.21: the muzzle wave -- rounds leave FROM THE GUN
MechWeapon::GetMuzzlePoint (binary @004b9948): the mount segment's world
frame via GetSegmentToWorld (segmentToEntity x localToWorld), translation
extracted by transforming the segment-space origin.  The missile spawn now
launches from the rack mount -- verified live: SRM6_1/SRM6_2 leave from
distinct world positions 4.0 units above the hull origin, laterally offset
per rack and rotated through the mech's yaw, on BOTH mechs.  19/19
launch-detonate over 100s, zero faults; smoke green.

(The authored MuzzleVelocity up-tilt composed in the mount frame + the mech
velocity term remain with the beam/render wave -- the seeker converges from
the aimed initial velocity either way.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:24:02 -05:00
CydandClaude Fable 5 76acc516e4 BT410 Phase 5.3.20: missiles fly on the AUTHENTIC 'mslhit' family
The explosionModelFile decode: SRM6's streamed value 17 is a real resource
FAMILY id -- 'mslhit', 3 members: VideoModel(225), AudioStreamList(227) and
an 8-byte GameModel(228) = { int 498, maxTimeOfFlight 5.0 }.  The round now
spawns on that family and reads the AUTHORED 5.0s lifetime (range 800 at
~160 u/s checks out); the owner-family fallback stays for content without a
projectile family (sanity band keeps the default there).

Two engine truths closed the wave: (1) collision volumes are the Mover
DEFAULT -- a projectile spawn MUST pass NoCollisionVolumeFlag or the ctor
demands a BoxedSolidStream member the family doesn't carry (SearchList
NULL -> Lock() page fault -- the crash that originally masqueraded as a
"model-list index"); (2) the @0x414938 SearchList crash is the
missing-MEMBER case (Check compiled out) -- probe families manually before
handing them to engine paths.  Resource type numbering mapped and recorded
(15=GameModel, 10=VideoModel, 20=DamageZoneStream, ...).  BT_MODEL_DUMP=1
keeps the raw family/record dump as a decode bench.

Verified: 20 launches -> 20 detonations on the mslhit family over 110s,
zero faults; smoke + novice green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:17:04 -05:00
CydandClaude Fable 5 fdf26d6e55 BT410 Phase 5.3.19: teardown SOLVED -- flying missiles are the DEFAULT
The death-row page fault was the THIRD strike of the uninitialized
zone-array trap: the staged spawn rides the mech family's resourceID, whose
DamageZoneStream makes the Entity base ctor allocate the 20-pointer
damageZones[] array with UNINITIALIZED slots -- the Missile never fills it,
and ~Entity's teardown deleted 20 garbage pointers through trash vtables
(the dtor-chain bisect showed every dtor completing, then EIP-in-heap).
Fix: the Projectile ctor NULLs every inherited zone slot.  RULE, now
three-crashes-proven: any entity subclass that does not FILL the allocated
zone array must NULL it -- the engine never initializes the slots, on
construction OR destruction.

Missiles are now the default SRM delivery (BT_MISSILE_INSTANT=1 = the
5.3.18a instant-hit as an A/B opt-out).  Verified: 20 launches -> 20
detonations -> 20 clean death-row teardowns over a 110s mutual fight, zero
faults; smoke + novice regressions green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 13:39:18 -05:00
CydandClaude Fable 5 110ecec2ca BT410 Phase 5.3.18: flying missiles -- spawn/home/detonate LIVE, teardown open
The Missile entity family is reconstructed: Projectile : Mover gets a real
make-path ctor; Missile hosts the Seeker + MissleThruster roster (the
AUTHENTIC surviving SEEKER.HPP / MISTHRST.HPP interfaces -- the 1995
misspelling included) and flies the binary's guided integrator (@004bef78):
age/lifetime, seeker re-lead with the decoded loft constants
(200/50/300/0.1), thruster burn, steering (gain 4.0, deadband 1e-4), and
the proximity fuse delivering the cluster damage ONCE with the launcher as
inflictor.  Live-verified end-to-end: LAUNCH -> flight telemetry (thruster
visibly accelerating) -> DETONATE on the target ~0.9s out.

OPEN: the first ~Missile off the death row completes, then the frame
page-faults with EIP in the heap -- the first entity/subsystem teardown the
reconstructed sim has ever run.  The spawn path is therefore OPT-IN
(BT_MISSILE_FLIGHT=1); default SRM delivery stays the 5.3.18a instant-hit
cluster (verified 1:1, zero exceptions).  Forensics + engine truths
(SearchList crashes on non-directory ids; MakeMessages need FAMILY resource
ids; explosionModelFile is a model-list INDEX) in MISSILE.NOTES.md.

Also fixed on the way: the Subsystem NAME ctor left damageZone
UNINITIALIZED (latent garbage for every name-built subsystem).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 11:58:40 -05:00
CydandClaude Fable 5 f2424e9fdc BT410 Phase 5.3.18a: salvo economy corrected -- one cluster hit per trigger
BT411 task-#62 truth: the arcade fires ONE cluster Missile per salvo and its
zone damage lands EXACTLY ONCE (burstCount feeds only the gyro bounce +
splash falloff, never the zone armor formula). The 5.3.16 per-missile
delivery loop was ~6x too lethal. MISLANCH now sends a single salvo-split
damage message (verified 1:1 FIRED-to-damage in the mutual fight; zero
exceptions). The cluster splash joins the Missile entity wave.

Also: BT_FORCE_ZONE=n dev hook (pin every hit -- the cascade verification
knob) landed with 5.3.17's runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 11:19:27 -05:00
CydandClaude Fable 5 d4f0aef6e1 BT410 Phase 5.3.17: the destruction cascade -- zone deaths DESTROY things
Mech::DamageZone::TakeDamage is the full binary cascade (@0049c690): LOD
artifact router with same-attacker clustering (@0049c40c, hysteresis 0.33),
artifact parent level = mean of children, Energy-hit generator shorts
through the crit plug binding (novice-exempt), weighted CriticalHit
(@0049ccc4), zone-death allotment push (SendSubsystemDamage @0049c9a8) and
the segment-table destruction descent (RecurseSegmentTable @0049cad4 --
SIBS + DESCEND via the engine EntitySegment iterators).

Supporting bricks: Mech's OWN handler table with the TakeDamage override
(latches lastInflictingID @0x43c; gyro feed staged; cylinder table
deferred); friend class Mech__DamageZone (authentic); subsystem private
zones ARMED (structureReference + armorByFacing[5] normalized -- crits are
measurable); ApplyDamageAndMeasure; ForceCriticalFailure sets
DestroyedState so the weapon FSMs' GetSimulationState()==1 hard gate went
live; Generator::ForceShort + PoweredSubsystem::ForceShortRecovery.

Fight-verified (mutual BT_SPAWN_ENEMY + BT_FORCE_ZONE=8 concentrated fire):
arm zone to 1.0 -> sibling searchlight zone dies (Searchlight2/ThermalSight
crits destroyed, HUD degrading) -> DESCEND kills the gun zone -> PPC_2 +
ERMLaser_2 + Condenser6 DESTROYED and FALL SILENT (0 shots after death;
undamaged PPC_1 keeps firing); vital-zone hit = MECH KILL; PPC hit shorted
GeneratorD. This art has zero artifact zones (redirect=0 across all 20 --
router verified dormant-correct). Smoke + novice regressions clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 11:13:28 -05:00
CydandClaude Fable 5 76cfc64353 BT410 Phase 5.3.16: targeting + damage -- two-mech fights are LIVE
The simulation is now a FIGHT. Mech target slot (+0x388), authentic
Loaded->Firing gate (viewFireEnable && HasActiveTarget), UpdateTargeting
range refresh, and SendDamage via the engine TakeDamageMessage.

MECHDMG.CPP is BORN against the AUTHENTIC surviving CODE/BT/BT/MECHDMG.HPP:
the Mech ctor's Pass-3 hull zone fill constructs Mech__DamageZone per zone
(the Entity base only allocates the raw pointer array -- unfilled slots were
crash #1; a base-class fill parses each record short and skews the stream --
crash #2). Streamed ctor parses the full BT tail (flags / Scalar-weighted
criticals with the Master+Dynamic plug gate / LOD redirect table) and
normalizes the armor economy: scale[type]=1/(raw x armorPoints), legs
halved -- BT411-verified @0049ce50.

Live two-mech verification (BT_SPAWN_ENEMY harness in DropZoneReply):
20 named hull zones stream on both mechs; PPC lands 11.77/hit (12 x
chargeRatio^2, type 4), ER-M laser 3.43 (type 3), SRM6 salvos 5.83 x 6
rounds on independent zones (type 2, one message per missile -- burstCount
is NOT in the zone formula); repeat hits climb linearly, leg zones absorb
at half scale, zones reach 1.0 Burning/Destroyed. No-target and novice
regressions hold (weapons load but never fire without a target).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 10:21:43 -05:00
CydandClaude Fable 5 b772e716fe BT410 Phase 5.3.15: the electrical charge model -- real charge, closed feedback loop
The emitter charge cycle is now the authentic electrical model end to end.

- WIRE-VERIFIED resource fix: the Emitter block is graphicLength /
  dischargeTime / seekVoltage[5] (FRACTIONS of the generator's rated voltage,
  -1 sentinel) / seekVoltageRecommendedIndex. The old two-field guess read
  seekVoltage[3]=0.99 as "dischargeTime 0.99s" -- the true PPC discharge is
  0.2s, and the calibration reads seekV={6000,7000,8000,9900} x rated 10000,
  recommended gear 2: the documented curve exactly.
- Ctor calibration (@004bb120): energyTotal=(dmg+heat)x1e7; EC pins
  E=0.5V^2EC to energyTotal at the recommended gear; voltageScale calibrates
  the exponential charge to reach seekV[rec] in the authored RechargeRate
  seconds cold; damageFraction = the damage share.
- TrackSeekVoltage (@004ba838) + ChargeTimeScale (@004b0d50): the charge
  integrates from the generator through the heat-stretched time scale, and
  the I2R loss heats the GENERATOR -- verified GeneratorA at 477.9K charging
  its PPC (BT411 predicted ~+480K) while the avionics generator idles at 77.
  Hot generators charge slower: the heat/firepower feedback loop is CLOSED.
- Emitter::ComputeOutputVoltage override (now virtual, as in the binary
  vtable): dial = level/seekV[gear], 0.01 snap, over-1 clamp.
- Sub-stepped Loading (1/60s slices -- the BT411 weapon-brick fix) + the
  documented-divergence overcharge rescue. VERIFIED: the PPC loads at level
  ~7920, inside the binary's exact [7920,8080] snap window.
- FireWeapon energy algebra (@004bace8): damage/heat = energy shares x
  chargeRatio^2. VERIFIED dmg=11.78 / heat=1.079e8 at ratio 0.9906. The
  heatCost x 1e7 partial is retired -- heat now comes from the energy.

Zero Fail; jams/shutdowns regressions stay live under spam.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 09:14:56 -05:00
CydandClaude Fable 5 6451a0a38d BT410 Phase 5.3.14: AggregateHeatSink -- the bank + the ambient radiator (the heat EXIT)
The central heat bank is now its real class: the binary's 0x0BBE
AggregateHeatSink (the value our VDATA enum named HeatSinkClassID -- there is
no streamed plain HeatSink; the segment walk now builds the bank).

- Ctor (@4ae8d0): heatSinkCount from res +0xFC (bhk1 = 6, matching its six
  condensers); thermalConductance x 0.1 x count (231000 -> 138600); ambient
  setpoint 300 (the mission [mission] temperature overwrite joins the
  Mech-PlayerLink wave).
- RadiatorSimulation (@4ae73c) replaces the base heat step on the bank -- THE
  system's only heat exit: relax toward the ambient target with rate
  k = conductance x (1-damage) x (coolant/capacity) x flowScale / mass; tail
  tops the bank's coolant from the attached store via the DrawCoolant virtual
  (base 0 until the reservoir-attach wave). VERIFIED signed-correct: the bank
  warms 77 -> 300 from the cold start, then flips to actively radiating
  (-48K..-167K/step) once fired heat pushes it past ambient. Until now heat
  only ever POOLED in the central sink; the mech now genuinely sheds it.
- Reservoir master path: capacity = 0.05 x bankCount x streamed = 6 (the
  authentic tank), refilled.

Zero Fail; the expert economy regression stays green (202 heat events under
forced spam).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 08:54:21 -05:00
CydandClaude Fable 5 c01e57ab22 BT410 Phase 5.3.13: cockpit-button message layer -- valves, cooling, flush LIVE
The subsystem-family cockpit buttons now work through the authentic Receiver
dispatch -> per-class handler-table path. The id space decodes cleanly:
Receiver::NextMessageID == 3, so ToggleCooling = 3 on the HeatSink chain and
the per-class id 4 is MoveValve on a Condenser / InjectCoolant on the
Reservoir -- same number, different class, the binary's per-receiver-class
convention.

- HeatSink::ToggleCoolingMessageHandler (@004ad6f8, id 3): novice-locked,
  press-only; toggles coolantAvailable + coolantFlowScale together.
- Condenser::MoveValveMessageHandler (@4ae464, id 4): novice-locked; cycles
  the valve 1->5->50->0->1 and calls Condenser::RecomputeValves (@0049f788):
  every condenser's coolantFlowScale = valve / sum-of-valves. The ctor now
  streams the AUTHENTIC flowScale=0; the Mech ctor seeds equal shares once at
  spawn -- the flowScale=1 interim is retired.
- Reservoir::InjectCoolantMessageHandler (@4aee70, id 4): novice-locked;
  press arms the flush when the tank holds charge, release drops it.
- MechSubsystem::NoviceLockout() (@4ac9c8): owner -> playerLink -> experience
  == novice; unlinked mechs read unlocked.
- DEV harness BT_PRESS_VALVE / BT_PRESS_FLUSH: dispatch the REAL messages ~6s
  into the mission from Mech::Simulate.

VERIFIED: spawn shares 6 x 0.166667; one MoveValve press -> Condenser1
valve=5 flow=0.5, others 0.1 (valve/sum exact); flush arms via the real
button message; NOVICE locks both presses (valve lines stay spawn-only, zero
flush). Zero Fail throughout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 08:43:01 -05:00
CydandClaude Fable 5 e1e5a9a6db BT410 Phase 5.3.12: experience gates + coolant system -- novice/expert modes REAL
The player-experience system now gates the entire heat/jam economy, verified in
BOTH directions with a one-token egg edit (TESTNOV.EGG, experience=novice):
NOVICE = 212 fire cycles all pinned at T=77, zero jams, zero shutdowns (the
heat model authentically absent); EXPERT = the full economy (duty cycles, 119
shutdowns + 2 authentic jams under forced fire). Zero Fail in both.

- BTPLAYER: the flag block renamed to its TRUE semantics (simLive @0x25c,
  heatModelOn @0x260 -- the FUN_004ad7d4 master switch, advancedDamageOn pair,
  levelFlag26c/270, experienceLevel); the ctor rows were already
  binary-accurate (nov 0000 / std 1011 / vet 1111 / exp 1101); accessors +
  [exp] sentinel.
- HeatSink::HeatModelActive() + ProjectileWeapon::LiveFireEnabled(): owner
  mech -> Entity::GetPlayerLink() -> the BTPlayer flags, NULL-permissive.
  Gates: both HeatSinkSimulation phases, every weapon fire-heat dump, and
  CheckForJam is now the AUTHENTIC form (LiveFireEnabled + heatLoad<=0
  early-outs + the minJamChance floor; the interim heat-degraded gate retired).

- THE LOAD-BEARING FIX: Mech ctor SetValidFlag(). Every 1995 entity ctor tail
  marks itself valid; ours didn't -- Entity::Dispatch routes messages to an
  INVALID entity into the deferred event queue, so the PlayerLink bind (and
  every directly-dispatched mech message) silently never landed and the gates
  read a NULL player forever.

- THE COOLANT SYSTEM (authentic bodies, byte-verified constants: HeatLoadScale
  0.002 -> heatLoad now in [0,1]; equalize eps 1e-4; draw floor/ON
  0.0025/0.003): UpdateCoolant (damage-scaled draw -- an undamaged mech leaks
  nothing), BalanceCoolant (full clamp chain from ConductHeat), DrawCoolant
  base=0 with the RESERVOIR override as THE SOURCE; Reservoir reconstructed
  (capacity overlays thermalCapacity, CoolantSimulation + the full
  InjectCoolant flush distribution, BT_FORCE_FLUSH dev hook); Condenser
  RefrigerationSimulation (massScale = (1-damage)*refrigerationFactor >= 1 --
  the heat pump that chills the bank; valveState inits 1; digit-suffix number
  fix). Deferred: AggregateHeatSink family, cockpit-button handlers
  (MoveValve/ToggleCooling/InjectCoolant), TrackSeekVoltage charge model.

Research driven by a 4-agent workflow dossier over the BT411 RE (verbatim
bodies for every function above + the egg experience plumbing).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 22:15:07 -05:00
CydandClaude Fable 5 b1cbbe1c9b BT410 Phase 5.3.11: weapon heat consequences -- jams + thermal shutdowns LIVE
The heat economy now bites back, verified under sustained fire:

- Ballistic gate 1 (@4bbd36): destroyed-state or own-sink FailureHeat pins
  full recoil + the unavailable alarm (the NoAmmo roach-motel re-asserts).
- CheckForJam (@4bbfcc): p = 0.41*T/failT clamped [minJamChance, 1.0], rolled
  per granted shot against the MUNGA uniform Random. Interim heat-degraded
  gate stands in for the deferred LiveFireEnabled novice switch (the exact
  spurious-cold-jam trap the BT411 port documented). VERIFIED: SRM6s jam at
  T~1200-1320 after riding past the authored 1000-degree threshold, and stay
  jammed (mission-reset recovery only) -- authentic.
- Emitter hard gate (@4baab9): FailureHeat drops the beam state + charge each
  frame; SELF-RECOVERING once conduction cools the sink below failure.
  VERIFIED: the PPC settles into an emergent thermal duty cycle -- fire at
  ~1930K, spike to 2562K, shutdown, cool, refire -- firing exactly as fast as
  its sink sheds heat; the lasers oscillate around ~2200K.

Zero Fail. Deferred: LiveFireEnabled/HeatModelOff experience gates,
mech-disabled gate halves, coolant depletion, jam recovery via mission reset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 21:20:37 -05:00
CydandClaude Fable 5 57507cb15c BT410 Phase 5.3.10: power/heat wave -- the thermal + electrical economy is LIVE
Weapons dump firing heat into their own sinks, sinks conduct through the
Condenser bank into the central HeatSink, temperatures drive the degradation/
failure alarms, and every powered subsystem tracks its generator through the
electrical state machine. Verified: the authentic fire-discipline game -- a PPC
spikes 77->709K per shot, relaxes to 441K across its 5s reload, and sustained
fire climbs 441->674->838->960K, brushing the authored 1000K degradation
threshold. All calibration values match the BT411 audit exactly (central bank
1.39e6, PPC sink 174000, thresholds 77/1000/2000).

- HEAT: HeatSinkSimulation (@004ad924 absorb->T->load->conduct->alarm),
  ConductHeat/ComputeHeatFlow (@004ad8ac/@004ad9ec two-body equilibrium
  relaxation; flow==0 exactly at uniform T), UpdateHeatLoad (15-sample filter);
  heat-state enum + accessors; installed by the HeatSink ctor.
- WIRE-VERIFIED: HeatSink resource gains linkedSinkIndex -- THE missing
  ancestry int that shifted every descendant block +1 (voltageSourceIndex had
  been reading the linked-sink index: "power sources" appeared to be
  Condensers). Conduction topology wired from it at construction:
  weapons->Condensers1-6->central; Generators->Condensers; Reservoir->central.
- MECHWEAP resource: authentic pip tail (pipPosition int + pipColor 3 floats +
  pipExtendedRange int) per the BT411 verified overlay; with linkedSinkIndex
  this closes the whole +3 alignment mystery (ProjectileWeapon pad deleted).
  True bhk1 reads: PPC recharge 5.0s (not 1.0), discharge 0.99s, range 900.
- POWERSUB: ctor resolves voltageSourceIndex -> GeneratorA-D (wire-verified),
  taps via Generator::TapVoltageSource (-1 when full); PoweredSubsystemSimulation
  (@004b0bd0) electrical FSM (Starting/NoVoltage/Shorted/GeneratorOff/Ready);
  GeneratorSimulation partial (start/short-recovery timers).
- Weapons: power step at sim head; Loading recharge gated on electrical Ready
  (authentic @4bbdf5); FireWeapon dumps firing heat. HEAT UNITS: the chain is
  1e7-native with TWO authoring conventions -- energy weapons store small
  (PPC=11, x1e7 from the energy algebra), ballistics store native (SRM6=5.06e7,
  dumped raw; double-scaling it was the bring-up runaway-temperature bug).
- SENSOR: authentic gating (@004b1c4c) -- power step, electrical-Ready gate,
  heat-state switch (Degradation x0.5 / Failure 0). Verified Ready + 100%.

Deferred: coolant depletion/venting, the novice HeatModelOff gate, the charge
model (TrackSeekVoltage/voltage sag/I2R), weapon gate 1 + jam roll, the central
sink's forward-linked drain. Zero Fail across fire + neutral runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 23:55:48 -05:00
CydandClaude Fable 5 ab7191dc38 BT410 Phase 5.3.9: ballistic fire path -- SRM launchers fire salvos + run dry
The SRM6 MissileLaunchers now run the authentic ballistic fire cycle against
their linked AmmoBins, wire-verified end to end.

- PROJWEAP ProjectileWeaponSimulation (PARTIAL, binary @004bbd04 -- fully
  recovered in the BT411 RE): one trigger-edge sample; the dry-bin gate pins
  NoAmmo(7) every frame; Loaded(2) -- the AMMO PULL LIVES IN THE CALLER (a
  denied shot does the 4->2 denial blip with NO ammo pulled -- early-returning
  gates from FireWeapon while the caller cycles the alarm is the 1995 "denied
  shot fakes a full firing cycle" defect class); granted -> Firing ->
  FireWeapon -> Loading (or NoAmmo on the emptying pull), recoil=rechargeRate;
  Loading bleeds recoil -> Loaded when a round is chambered, dial animates;
  Jammed(5) held; NoAmmo(7) = the roach-motel (re-asserts unconditionally).
  Deferred: electrical step, gate 1, eject, jam roll, HasActiveTarget.
- AMMOBIN: FeedAmmo() + GetAmmoState() (1 round-ready / 2 EMPTY).
- ammoBinLink wired from the resource's ammoBinIndex (the bin's roster slot).
- WIRE-VERIFIED RESOURCE ALIGNMENT: a raw-stream dump pinned the
  ProjectileWeapon field block +3 ints past our staged struct (the MechWeapon
  resource ancestry runs 3 ints short -- the pip-family fields). Fixed with
  resourceAlignPad[3]; phantom muzzleVelocity/missileCount tail dropped.
  Confirmed on-wire: ammoBinIndex=27/29 (the exact bin slots), tracerInterval=1,
  minTOF=0.5, minVolt%=0.3, minJam=0.05, missileCount=6 (an SRM6!).
- MECHWEAP: weapon-state enum extended to the full 1995 set (DryTrigger 1,
  Jammed 5, TriggerDuringJam 6, NoAmmo 7; count 8).
- MISLANCH FireWeapon PARTIAL (@004bcc60: heat + salvo spawn only -- spawn
  needs the entity/targeting waves).

VERIFIED headlessly (BT_FORCE_FIRE): bins resolve with the authored 24 rounds;
SRM6s fire 6-missile salvos at the authored 1s cadence, rounds 24->0; after
the 24th salvo the launcher goes DRY and stays silent across ~3,000 subsequent
fire events while the energy weapons keep cycling. Zero Fail.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 23:18:39 -05:00
CydandClaude Fable 5 896bbbed4d BT410 Phase 5.3.8: weapon fire path -- authentic attribute table + fire state machine
The energy weapons now run a live fire cycle, and the REAL trigger wiring is in
place: the 1995 MechWeapon attribute table (binary @0x511890) is published with
PINNED IDs, so the streamed per-mech fire-button mappings bind TriggerState
(0x13) -> fireImpulse and the controls push writes the trigger into the weapon.

- MECHWEAP: attribute enum + table (PercentDone 0x12, TriggerState 0x13,
  DistanceToTarget 0x14, TargetWithinRange 0x15, WeaponRange 0x16,
  EstimatedReadyTime 0x1A, RearFiring 0x1B, WeaponState 0x1C). Pads bridge the
  chain gap 2..0x11 -- LOAD-BEARING: AttributeIndexSet::Build leaves uncovered
  gap slots as uninitialized garbage, so every ID up to the max must be covered
  (they shrink to the authentic 0x0F..0x11 when the parent attribute waves land;
  SENSOR.HPP pins PoweredSubsystem::NextAttributeID at 0x0F).
- MECHWEAP: fire-machine members (fireImpulse/previousFireImpulse,
  rechargeLevel, rangeToTarget, effectiveRange, estimatedReadyTime, recoil,
  weaponAlarm 0/2/3/4); CheckFireEdge (@004b9608, BIT-PATTERN int compare --
  TriggerState carries raw ControlsButton ints whose release value is a float
  NaN; IEEE compare would latch the detector shut); ComputeOutputVoltage
  (@004b9c9c recharge dial).
- EMITTER: EmitterSimulation (PARTIAL @004baa88) -- Firing/Loaded/Loading FSM
  on the weapon alarm, viewFireEnable gate at Loaded->Firing, recoil-decay
  recharge over the authored RechargeRate seconds; FireWeapon (PARTIAL
  @004bace8) discharge bookkeeping; ChargeLevel re-pinned to the authentic
  0x1D; index re-chained to MechWeapon's. Deferred: electrical charge model
  (TrackSeekVoltage), heat dump, HasActiveTarget gate, beam/damage submission.
- BT_FORCE_FIRE dev hook: trigger pulse whenever Loaded (auto-fire cadence).

VERIFIED headlessly: forward view -- PPC_1/2 + ERMLaser_1 cycle FIRED->LOADED
at the authored 0.6s/1s cadence, rear lasers silent; BT_FORCE_LOOK=3 -- ONLY
the rear lasers (ERMLaser_2/3) fire. The view-fire x fire-FSM interlock holds
both directions. Zero Fail. (SRM6 ballistic FSM = a later increment.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 23:01:57 -05:00