22 Commits
Author SHA1 Message Date
arcattackandClaude Fable 5 abaa145b6f Cockpit: authentic eyepoint + canopy lattice render (task #55)
The first-person cockpit now shows by default and matches gameplay footage
(verified on the Madcat). Two decomp-verified reconstructions:

EYEPOINT (FUN_004579a8 + caller part_014.c:5525, FUN_004c22c4):
- eye offset = siteeyepoint GetBaseOffset (not GetSegmentToEntity, not the
  upright hack), parented on the PARENT segment DCS (btl4vid.cpp)
- live view = affine INVERSE of the eye world matrix (L4VIDRND Execute) --
  replaces the hand-rolled LookAtRH whose +Z-forward/+Y-up row guess aimed
  some mechs into the canopy; combine order fixed to baseOffset * R
- chase camera basis + aim boresight re-expressed for the inverse convention
  (row2 = back); boresight now derives from the view matrix (the stale rows
  aimed the pick ray at the sky = no target, no discharge)

CANOPY (*_COP.BGF, dpl_Punchize geogroups):
- every shell is an open strut lattice (38-59% boundary edges, all 12 mechs);
  rendered single-sided with per-face INWARD winding (l/r torso patches are
  mirrored, so no global winding works) the struts are the dark frame and the
  openings show the world
- frame colour = the 'softer' ramp near its dark end (texture-less unlit ramp;
  BLXSKIN.BMF has no texture -- the old "punch texels" theory was wrong)
- scoped by filename (meshIsCop); wrong drop-punch + view-cut paths removed
- BT_FORCE_MODEL=<name> forces the player mech for per-mech bring-up

KB: new context/cockpit-view.md topic; punch split corrected in bgf-format;
gotchas 14 (LookAt axis guess) + 15 (per-patch edge namespaces); env gates +
eye/punch addresses in decomp-reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 06:46:54 -05:00
arcattackandClaude Opus 4.8 0bfb3d4ab3 Warp: restore the translocation-vortex look + fix texture-scroll precision collapse (task #52)
The death/respawn "blue whirlwind" (tsphere) now matches the original cabinet
photo (capture.png): a smooth spinning lavender vortex with a bright core.

Root cause of the long-standing "radial spokes" artifact was NOT the warp code
but a general engine bug: L4D3D::SetTextureScrolling computed its texture-matrix
offset as scrollDelta * absolute_time, which grows unbounded and collapses UV
float precision -> a smooth scrolled cloud shatters into grainy radial steps.
Wrapped with fmodf(..., 1.0f) (identical under REPEAT tiling, full precision).
This also cleans the scrolling bexp beam grit and any other SCROLL material.

Visual reconstruction (verified against the real 45-vtx TSPHERE.BGF bicone,
offline-rasterized then ported):
  - view ON-AXIS (eye centred on the throat) + spin in place -> concentric rings
    (decomp FUN_00453dc4 does spin-about-local-Z + submit; the port had stubbed it)
  - bintA cloud through a WIDE lavender ramp at full contrast (drawn as SKY);
    no geometry "bands", no log-polar twist, no off-axis tornado (all discarded)
  - tessellate the 12-facet bicone smooth; isotropic + trilinear; ramp baked into
    the texture and drawn SELECTARG1(TEXTURE) to avoid double-tinting

Env knobs (BT_WARP_*) default to the verified values; BT_WARP_SELFTEST/SELFSHOT
are an off-by-default visual-verification harness (backbuffer frame dump).

Docs: new context/translocation-warp.md (geometry/material/visual/lifecycle/env);
reconstruction-gotchas.md gains the accumulated-time precision-collapse bug class;
rendering.md / multiplayer.md / decomp-reference.md / CLAUDE.md cross-linked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 14:53:50 -05:00
arcattackandClaude Opus 4.8 0929d87321 CLAUDE.md: add convention — correct (+ sweep) context-system errors when ground truth contradicts them
The context system is a reconstruction; some claims are wrong. When the decomp/binary/engine
source contradicts a context/glossary/docs claim, FIX it + grep-sweep the same claim across the
other topic files + re-run checkctx.py -- don't just work around it. Archetype cited: the 0xBD3
'gates the valve/message routes' correction from this session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 23:03:12 -05:00
arcattackandClaude Opus 4.8 f914fc040a context-system: complete migration -> CLAUDE.md is now a 160-line router (zero context lost)
Full migration of the 2236-line monolithic CLAUDE.md into the progressive-context
knowledge graph (per spark-lesson / expert-seed.md), so the deep RE knowledge loads
on-demand instead of every session.

ZERO CONTEXT LOST:
- docs/PROGRESS_LOG.md = the complete old CLAUDE.md, VERBATIM (byte-identical) -- the
  lossless safety net + the "full detail" quick-lookup fallback.
- 18 context/*.md topic files (1343 lines) digest every section (§1-3 -> project-overview,
  §4 -> content-archives, §5 -> asset-formats/bgf-format, §5a -> source-completeness,
  §5b/§8 -> wintesla-port, §7/§10 -> locomotion, §10a -> build-and-run, §10b ->
  reconstruction-method, §10c -> combat-damage + reconstruction-gotchas, §10d -> subsystems,
  render notes -> rendering, gauges -> gauges-hud, MP -> multiplayer, §9 -> open-questions).
- reference/glossary.yaml (53 terms). decomp-reference.md = the offsets/ClassIDs/addresses hub.

CLAUDE.md (160 lines) = router: identity, answer/reason protocols, quick-lookup table,
evidence tiers (T0 engine-truth / T1 decompiled+verified / T2 reconstructed+runtime /
T3 guarded / T4 hypothesis), conventions + DO-NOT (the systemic bug classes), structure.
Retains the load-bearing work directives (build recipe pointer, "keep current" mandate).

Knowledge graph validates CLEAN (scratchpad/checkctx.py -- all [[links]] + quick-lookup +
docs refs resolve; [[name]] -> topic file or glossary term). docs/*.md ledgers stay as the
detailed logs; context/*.md are the curated digests that route into them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 22:19:50 -05:00
arcattackandClaude Opus 4.8 401b290949 CLAUDE.md §8: gauge system COMPLETE — 0 NULL bindings + 0 parse-skips (aggregate bank, valve, SectorDisplay/PrepEngr/MessageBoard)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 21:44:06 -05:00
arcattackandClaude Opus 4.8 f52bf057e6 WAVE 7 Phase B: the Mad Cat's LRMs LAUNCH flying missiles that fly + damage (autocannon too)
The byte-exact WORLD-ENTITY reconstruction (Projectile @4be1bc 0x340 / Missile @4bf5b4 0x368)
is infeasible on the 2007 engine: measured sizeof(engine Entity)=0x1BC vs the 1995 binary's
0x300, so the reconstruction's raw base-offset reads (velocity@0x1dc, roster@0x124, motion@0x250)
read GARBAGE on the engine (the Mech 0x638-vs-0x854 gap, but the entity integrator DEPENDS on
those offsets).  So -- like the mech drive and the beam renderer -- flying projectiles are a PORT
reconstruction (BTPushProjectile/BTUpdateProjectiles in mech4.cpp, a static array + stack
messages, ZERO heap ops): seeded from the launcher's fire with the decomp's real muzzle
(GetMuzzlePoint) / launch speed (|launchVelocity|) / per-shot damage (damageData, split across
missileCount), they fly to the target (tracer via BTPushBeam) and deliver the weapon's damage on
impact through the SAME Entity::TakeDamage path as the beam (aim Mech::FirstVitalZone()).

THREE bring-up fixes were needed to make a projectile weapon fire at all (found by tracing):
  1. TRIGGER: fireImpulse was only driven for the Emitter; ProjectileWeaponSimulation now sets
     fireImpulse = gBTWeaponTrigger too (else CheckFireEdge never sees an edge).
  2. AMMO BIN: OwnerSubsystemCount/OwnerSubsystem were stubbed ->0, so ammoBinLink never resolved
     and ConsumeRound always failed; redirected to the real roster (owner->GetSubsystemCount()/
     GetSubsystem(i) -- the AmmoBin 0xBCB constructs before the weapons 0xBCD/0xBD0).
  3. JAM ROLL: UniformRandom() was stubbed `return 0.0f`, so CheckForJam's `0 < jamChance` ALWAYS
     jammed (a projectile weapon could NEVER fire); replaced with a real LCG [0,1) (fires
     ~1-jamChance of the time -- authentic occasional jams).
Also: the mech's own target slot (owner+0x388) isn't populated in bring-up (the visible fire
targets the gEnemyMech global), so BTPushProjectile falls back to gEnemyMech.

Verified: Mad Cat PUSH=62 / IMPACT=31 (LRM missiles 3.33 dmg each split across the salvo + AFC100
autocannon 25 dmg), TARGET DESTROYED, 0 crashes, construction heapcheck-clean; BLH un-regressed
(also fires its ballistic weapon now).  Diagnostics BT_PROJ_LOG ([projectile] PUSH/IMPACT).

REMAINING (deferred): the byte-exact world-entity Missile (Projectile : Mover, MP-replicable via
Registry::MakeEntity) -- the port projectile is master-local only (no MP replication); the real
per-weapon fire-rate/heat wiring off the subsystem sim (mech4 beam path is still the bring-up harness).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 14:19:38 -05:00
arcattackandClaude Opus 4.8 2bcca26cea WAVE 7 Phase A: wire the projectile/missile weapon SUBSYSTEMS byte-exact (un-mislabeled)
An 8-agent decomp-mapping workflow mapped every weapon-family ctor + fire/spawn path
(scratchpad/wave7_maps.txt).  KEY IDENTITY CORRECTION via VDATA.h enum (base 3000=0xBB8):
the factory ctor-address comments were right but the built CLASS names were stubs/base --
    0xBCD == ProjectileWeapon  (was building the base MechWeapon stub)
    0xBCE == GaussRifle         (NOT MissileLauncher -- was a MissileLauncher stub)
    0xBD0 == MissileLauncher    (NOT BallisticWeapon -- was a BallisticWeapon stub)
Un-mislabeled + wired via Create<Class>Subsystem bridges (mirroring CreateEmitterSubsystem),
each static_assert-locked byte-exact on the now-locked MechWeapon(0x3F0)/Emitter(0x478) bases:

  * ProjectileWeapon (0xBCD, @4bc3fc : MechWeapon, sizeof 0x448)
      FIX: AmmoBinConnection was an empty 1-byte struct -> retyped to the binary's 0xC
           SharedData::Connection (was making the object 8 bytes short, sliding
           MissileLauncher's missileCount off 0x448).
  * MissileLauncher (0xBD0, @4bcff0 : ProjectileWeapon, sizeof 0x44C)
      FIX: deleted muzzleVelocity (ALIAS of inherited ProjectileWeapon launchVelocity@0x410)
           and salvoCount (SHADOW of inherited MechWeapon damageData.burstCount@0x3d4),
           keeping the single own field missileCount@0x448.
  * GaussRifle (0xBCE, @4bdcb4 : Emitter, sizeof 0x484 = Emitter + Vector3D muzzleVelocity)
      DEDICATED bridge (not CreateEmitter): its FireWeapon is a no-op (mov [eax+0x414],0) --
      a non-functional weapon in this 1995 build.  Also fixed GAUSS.CPP's discharge write
      rechargeLevel -> currentLevel@0x414 (the binary writes this+0x414).

Phase A safe-stubs FireWeapon (consume round + recoil, NO spawn): the reconstructed
Projectile (@4be1bc, 0x340) + Missile (@4bf5b4, 0x368) ENTITY classes are NOT spawn-ready
(Entity-base phantom members overflow their New() alloc -> heap corruption; New() takes
(int)this instead of the 0xD4 descriptor; Entity base size unconfirmed 2007-vs-1995, the
Mech 0x638-vs-0x854 problem).  The workflow's adversarial verify flagged the live spawn as a
heap-overflow hazard and recommended exactly this phasing.  Phase B = the entity byte-exactness
+ descriptor build + New(MakeMessage*) so a fired shot becomes a flying entity that damages.

Factory now 18 of 20 cases wired to real ticking classes (remaining: SubsystemMessageManager
0xBD3 [WAVE 8], Gyroscope 0xBC4 [deferred]).  Verified: Mad Cat (LRM/ballistic) + BLH construct
the real weapon classes + tick authentic charge/ammo/heat/recoil, DESTROYED-in-8, 0 crashes,
heapcheck-clean through construction.  Energy weapons already damage via the mech4 beam path,
so combat is unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 13:35:43 -05:00
arcattackandClaude Opus 4.8 9d82be46a1 P7: subsystem-tree alarm unification -- the whole PoweredSubsystem weapon/power subtree is byte-exact
The reconstruction modeled the binary's 0x54 AlarmIndicator (FUN_0041b9ec) with undersized
stand-ins (AlarmIndicator==ReconAlarm==4B; HeatAlarm==8B) across the entire subsystem tree, so
every field above an alarm sat at the wrong compiled offset.  Retype every such stand-in to
GaugeAlarm54(0x54) and de-phantom each class against its ctor, so the whole PoweredSubsystem
subtree becomes byte-exact.  An 8-agent read-only decomp-mapping workflow decoded every ctor
first; then hands-on implementation.  static_assert-locked chain (verified vs the raw ctors):

  HeatSink 0x1D0
   -> PoweredSubsystem 0x31C   retype electricalStateAlarm@0x264 + modeAlarm@0x2B8   @004b0f74
   -> MechWeapon      0x3F0   retype weaponAlarm@0x350; delete 5 phantom tail fields  @004b99a8
   -> Emitter         0x478   delete outputVoltage/beamLengthRatio/firingArmed aliases
                              + beamHit*/beamColor/beamHitData/energyRampTime phantoms;
                              retype beamOrientation EulerAngles->Quaternion(16B)       @004bb120
   -> PPC             0x478   (no own fields)                                           @004bb888
  PoweredSubsystem -> Sensor  0x328   (no alarm; 3 own fields)                          @004b1d18
  PoweredSubsystem -> Myomers 0x358   (no alarm)                                        @004b8fec

New systemic bug-class instances fixed (added to the CLAUDE.md checklist):
  * alias field    - Emitter outputVoltage==inherited rechargeLevel@0x320; beamLengthRatio==
                     beamScale.z@0x434; firingArmed==inherited useConfiguredPip@0x3E0
  * phantom field  - MechWeapon segmentReference/pipSegment/hasTarget/targetPoint/muzzlePoint
                     (past 0x3F0); Emitter beamHitPoint/beamImpact/beamImpactScalar/beamColor/
                     beamHitData/energyRampTime (binary writes inherited damageData/voltageScale
                     or the value is a method local)

Non-layout fixes required in the same wave:
  * outputVoltage->rechargeLevel also in the compiled GAUSS.CPP:74/93 (external readers of the
    removed Emitter field -- must grep EVERY TU, not just the class's own .cpp)
  * MechWeapon::GetMuzzlePoint reimplemented faithfully (removed muzzlePoint collided with
    Emitter's own fields at 0x3F0) via a BTResolveWeaponMuzzle void* bridge in mech4.cpp,
    resolving the weapon's mount segment (inherited this+0xdc) through the owner segment table
  * DetachFromVoltageSource fixed to set electricalStateAlarm not modeAlarm (raw @004b0e30
    writes the 0x264 alarm)

The vehicleSubSystems aux-screen gauge raw reads (btl4gau2.cpp:868/952 at subsystem+0x2b8/+0x278)
and the Sensor RadarPercent path now read the CORRECT byte offsets (garbage under the short layout).

Verified: combat DESTROYED-in-8, 28 shots, 0 crashes, heat heatEnergy=1.34e7, every static_assert
lock passes, heapcheck-clean through construction (the phase the isolated PoweredSubsystem retype
had overflowed).  LESSON: a factory-bridge runtime Check(sizeof<=alloc) does NOT fail the build --
only a static_assert sizeof lock catches alloc overflow at compile time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:49:58 -05:00
arcattackandClaude Opus 4.8 1356870e56 P7: byte-exact re-base of the CORE heat leaf (HeatSink/Condenser/Reservoir/Generator/Myomers)
The reconstruction modeled the binary's shared alarm/connection types with undersized
stand-ins, sliding every field above them low (the 72-byte auxScreenNumber gap). Fix the
foundational heat-leaf classes byte-exact + static_assert-lock them, from the ctor decomp:

Shared types corrected:
  * SubsystemConnection 4 -> 0xC  (binary link node FUN_004af9cf; FUN_00417ab4 derefs +8)
  * GaugeAlarm54 = 0x54           (real AlarmIndicator FUN_0041b9ec; STATUS level at +0x14,
                                   so subsystem+0x184 == heatAlarm+0x14 == GetLevel())
    WatcherGaugeAlarm now typedefs GaugeAlarm54 (Watcher branch locks stay valid).

Byte-exact + locked (ctor-verified):
  * HeatSink   heatEnergy@0x158 linkedSinks@0x164 heatAlarm@0x170 resource@0x1C4
               pendingHeat@0x1C8, sizeof 0x1D0  (@004adda0)
  * Condenser  valveState@0x1D0 condenserAlarm@0x1DC  (@004ae568)
  * Reservoir  reservoirAlarm@0x1D0 ... squirtEfficiency@0x22C, sizeof 0x230  (@4aef78)
  * Generator  stateAlarm@0x1FC, sizeof 0x250  (@004b225c)
  * Myomers    phantom moverConnection tail removed (fits 0x358)

Three systemic bug classes fixed (added to the checklist in CLAUDE.md / HARD_PROBLEMS.md):
  * alias field    - a subclass member re-declaring an inherited slot the ctor reuses
                     (Condenser refrigerationOutput==massScale@0x160; Reservoir
                     coolantCapacity==thermalCapacity@0x128) -> use the inherited name
  * alarm-interior - a value read at alarm+0x14 modeled as a separate member
                     (HeatSink heatState@0x184, Reservoir injectActive@0x1e4)
                     -> route to alarm.GetLevel()
  * phantom field  - a member past the object end (Generator shortFlag@0x25C is really
                     *(owner+0x190)+0x25c the msg-manager, @004b0efc; Myomers
                     moverConnection@0x110 a write-only base slot) -> remove it

Heat conduction now reads the REAL heatEnergy=1.34e7 (not garbage); combat DESTROYED-in-8,
0 crashes, heapcheck-clean through construction.

REMAINING (measured; a distinct larger task): making PoweredSubsystem byte-exact grows it
+0x98 and cascades into MechWeapon/Emitter/PPC/Sensor/Myomers -- all model the 0x54
AlarmIndicator with 4-byte ReconAlarm / 8-byte HeatAlarm stand-ins and are short +
phantom-tailed; retyping without byte-exacting them overflows the Emitter alloc (heap
corruption). PoweredSubsystem kept on HeatAlarm(8) stand-ins (marked) pending a
subsystem-tree ALARM UNIFICATION. See docs/HARD_PROBLEMS.md P7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 09:47:37 -05:00
arcattackandClaude Opus 4.8 f4ad0f4340 docs: vehicleSubSystems DONE + render-verified
Mark the engineering-screen cluster-panel system complete in the spec + CLAUDE.md
§10.  Render-verified (BT_DEV_GAUGES_DOCK): 7 authentic panels build + draw --
SENSOR CLUSTER, MYOMERS, ER MED LASER RANGE 500M x3, PPC RANGE 500M x2 -- with bar
gauges, recharge dials, and lamps; 0 crashes, combat un-regressed.  Records that
the aux-screen blocker resolved cleanly via the PoweredSubsystem bridge (the fields
were already populated; no MechSubsystem core re-base was needed) and lists the
non-blocking follow-ups.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 07:17:27 -05:00
arcattackandClaude Opus 4.8 a5fa9f1c79 vehicleSubSystems: full reverse-engineering + reconstruction spec (blocked)
Reverse-engineered the vehicleSubSystems config primitive end to end (Ghidra
headless, all ~28 functions).  It is NOT a widget: it is the engineering-screen
(MFD) subsystem-panel system.  Its Make (FUN_004cbaf0) is a per-subsystem factory
that builds a SubsystemCluster-family status panel onto one of 12 auxiliary MFD
positions, dispatching on subsystem classID (HeatSink/Myomer/Energy/Ballistic
clusters).  The whole cluster family (base + 5 subclasses) + 4 btl4gau2 sub-gauges
(CoolingLoop/PowerSource/ScalarBarGauge/ConfigMapGauge) are declared in btl4gau2.hpp
but not reconstructed.

BLOCKER: the Make reads base subsystem fields subsystem[0x1dc] (aux-screen position),
[0x1e4]/[0x1e0]/[0x224] that our MechSubsystem reconstruction (ends 0x114) does not
have or populate -- so the panels render nothing until the core subsystem layout is
extended + populated from the resource parse, which touches the working combat/heat
subsystem code (regression risk).  Checkpointed at full spec pending go/no-go on the
large core-touching implementation.

- docs/VEHICLE_SUBSYSTEMS.md: complete reconstruction spec (dispatch table, geometry
  table, class family map, sub-gauge inventory, engine-primitive reuse, the blocker,
  the Phase-1/Phase-2 plan).
- reference/ghidra_scripts/DecompVSS.java: headless address-list decompiler (reusable
  for any function the assert-anchored exporter skipped).
- CLAUDE.md: record the finding in the gauge-widget notes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 23:55:56 -05:00
arcattackandClaude Opus 4.8 ab039bcc1e docs: map radar contacts now render (grid population + projection fix)
Update GAUGE_COMPOSITE.md + CLAUDE.md: the radar plots the enemy contact inside
the FOV wedge.  Record the two fixes (engine RebuildEntityGrid populating the
dropped entity feed; the delta-relative blip projection replacing the wrong
camera-matrix point transform) and narrow the remaining map work to the authentic
pip/name infrastructure + SetTargetRange zoom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 22:23:39 -05:00
arcattackandClaude Opus 4.8 1746c5bb1f docs: record the map (radar) gauge as done + its follow-ups
GAUGE_COMPOSITE.md + CLAUDE.md: add the map/MapDisplay radar (increment 6, the
view wedge renders) to the done list; note the systemic pattern (a newly-consumed
reconstructed gauge is the first reader of stubbed helpers -- the map exposed 3);
narrow the remaining gauge widgets to PlayerStatus + vehicleSubSystems, and list
the map follow-ups (contact classification / stubbed pip+name infra, SetTargetRange
zoom).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 21:17:52 -05:00
arcattackandClaude Opus 4.8 eae631ba04 docs: record oneOfSeveralPixInt (increment 5) + update remaining-widget list
GAUGE_COMPOSITE.md + CLAUDE.md: add the OneOfSeveral/oneOfSeveralPixInt button-
lamp reconstruction to the done list; narrow the remaining widgets to map (radar
marquee), PlayerStatus, vehicleSubSystems, plus the follow-ups to make the
duck/searchlight buttons dynamic (extend the Mech table to 0x37 + a Searchlight
table).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 19:34:58 -05:00
arcattackandClaude Opus 4.8 c2c4ab1f67 docs: record the gauge attribute-wave (mechanism + 4 increments) + durable RE tools
Document the cockpit-gauge attribute-pointer system in GAUGE_COMPOSITE.md
(§Attribute wave) and CLAUDE.md §10: how a Subsystem/Attribute or bare-Attribute
binding resolves (ParseAttribute -> FindSubsystem/GetName -> GetAttributePointer
-> activeAttributeIndex), what a class must publish (AttributeID enum +
AttributePointers[] + GetAttributeIndex chained + DefaultData wiring), the
DENSE-TABLE HAZARD (Build leaves gap slots uninitialized, Find strcmps every
slot -> tables must be a dense prefix), and the base-primitive vs BT-specific
widget split.  Records the 4 committed increments (HeatSink table, Mech table,
vertBar, segmentArcRatio) and the follow-ups (oneOfSeveralPixInt/map/PlayerStatus/
vehicleSubSystems, the #if0'd HeatSink-bank AmbientTemperature, the Reservoir
coolantCapacity shadow).

Promote the two reverse-engineering helpers from the session scratchpad into
tools/ (durable): disas2.py (capstone + PE parse -- recovers x87 float math
Ghidra drops; FUN_004dcd94=round, FUN_004dcd00=fabs) and vtdump.py (dump a class
vtable to find an override the assert-anchored decomp never exported, e.g.
SegmentArcRatio::Execute).  Doc references updated scratchpad/ -> tools/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 18:52:53 -05:00
arcattackandClaude Opus 4.8 b526e92cad gauges: all 6 cockpit MFD surfaces in a SEPARATE dev window (Milestone C)
Generalizes the single-'sec'-inset (Milestone B) into a full 6-surface
compositor presented in its own top-level window -- the default under
BT_DEV_GAUGES (BT_DEV_GAUGES_DOCK=1 docks it into the main window instead).
A 960x384 window tiles all six pod instrument screens: Heat (COOLANT/BALANCE/
RES + condenser gauges), Comm (KILLS/DEATHS/SELECT TARGET), Mfd1/2/3 (DISPLAY/
PROGRAM/NEAREST frames), and the color radar (SCALE grid + dials + ARMOR
DAMAGE). Main 800x600 3D view is un-occluded; default DEV un-regressed
(TARGET DESTROYED after 8 hits, 0 crashes).

Design (mapped by the mfd-multisurface-map workflow):
- All 6 surfaces are bit-plane MASKS over the ONE shared SVGA16/pixelBuffer
  (sec=palette low byte; Heat=0x4000 UL, Mfd2=0x0400 UC, Comm=0x8000 UR,
  Mfd1=0x0100 LL, Mfd3=0x1000 LR), so the compositor reaches the SVGA16 once
  and extracts each by mask. No port-name reconcile needed on the dev path --
  fetch the BT names directly; the RP aux* names only matter to the pod's own
  SVGA16::Update demux (a deferred pod-only fallback).
- SVGA16::DrawDevSurface: two kernels -- palette-LUT (sec) + mono bit-plane->
  tint ((word&mask)?tint:0). BTDrawGaugeSurfaces iterates a 6-entry table.
- Separate window = one CreateAdditionalSwapChain on the existing device (no
  2nd D3D device). BTGaugeWindowRenderAndPresent (after the main EndScene):
  SetRenderTarget -> SetDepthStencilSurface(NULL) -> Clear -> BeginScene ->
  6 tiles -> EndScene -> restore -> swap->Present.
- KEY BUG fixed: the main 800x600 depth surface stays bound when rendering to
  the 960x384 gauge backbuffer; a bound depth smaller than the RT is invalid
  -> all draws silently fail (clear color, no geometry). Unbind depth (Z is
  off), restore after.

Pod SVGA16::Update/BuildWindows path byte-unchanged; all gated BT_DEV_GAUGES.
Content is still the authored cockpit frames + base-table gauges -- the live
MFD widgets need the BTL4MethodDescription reconstruction (this window is now
the live viewer for that work). Details: docs/GAUGE_COMPOSITE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 11:43:31 -05:00
arcattackandClaude Opus 4.8 a256813395 gauges: secondary/radar MFD composites live on a dev box (Milestone B)
The pod's secondary/radar cockpit surface now renders as an inset in the
800x600 dev window under BT_DEV_GAUGES: radar/tactical grid, SPEED/HEADING/
MAGNETIC/PROP dials, and the color-coded ARMOR DAMAGE mech schematic -- real
gauge content (nzSec=27247), composited from the shared CPU pixelBuffer.

Composite pass (engine): SVGA16::DrawDevInset palette-expands the secondary
plane into a MANAGED texture on the MAIN device and draws an XYZRHW inset quad;
BTDrawGaugeInset() reaches the gauge renderer's 'sec' port and is called from
DPLRenderer::ExecuteImplementation as the last draw before EndScene.

Three reconstruction bugs fixed to get real content:
1. BTL4Application::MakeGaugeRenderer signature mismatch (REAL fix): the
   reconstructed no-arg override only HID the 2007-engine's widened 3-arg
   virtual MakeGaugeRenderer(int*,int*,int*), so the engine built a base
   L4GaugeRenderer whose ctor never parsed gauge/l4gauge.cfg -> empty symbol
   table -> "undefined label 'bhk1Init'" -> no ports/gauges. Matched the 3-arg
   signature so it truly overrides (args ignored; BT is fullscreen). Same bug
   class as the BTL4GaugeRenderer(false,NULL,NULL,NULL) ctor fix.
2. Parse hung on undefined primitives (gated dev accommodation): the BT gauge
   primitive table (BTL4MethodDescription) is still a stub, so an unknown
   primitive -> ReportParsingError -> Fail() -> a MODAL dialog freezing the
   parse. Under BT_DEV_GAUGES, skip the unknown primitive's params so labels
   register and the base "configure" primitive builds the ports.
3. Gauge widgets AV on NULL data bindings (gated): NumericDisplayScalar's NULL
   value_pointer -> GaugeConnectionDirectOf ctor deref (bind NULL->static zero);
   RankAndScore::Execute derefs unreconstructed game state (Gauge::GuardedExecute
   SEH wrapper -> Disable(True) on first fault).

All guards gated on BT_DEV_GAUGES: default DEV un-regressed (TARGET DESTROYED,
0 crashes) and the pod path is byte-unchanged (strict Fail, no guards).

Remaining (docs/GAUGE_COMPOSITE.md): the real gauge WIDGET reconstruction
(BTL4MethodDescription method table + gauge->game-state data bindings), then
Step 2 (RP<->BT MFD port-name reconcile) + Step 3 (2-window layout).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 10:59:45 -05:00
arcattackandClaude Opus 4.8 c13e72d0ba platform profile + gauge dev-composite (Milestone A: gauge renderer woken)
PLATFORM PROFILE (-platform pod|dev / BT_PLATFORM / run.cmd pod; default dev):
an env-preset selector, NOT a code fork -- it picks which environment the
existing video/gauge/input code reads.  dev = single 800x600 window + keyboard
(the working build); pod = RIO input + (on real hardware) the multi-surface
gauges/MFDs, mirroring content/SETENV.BAT (the pod path -- FindBestAdapterIndices
/ SVGA16 -- is left untouched).  The multi-surface needs the pod's 2 video cards,
so -platform pod does NOT auto-enable L4GAUGE on a dev box (stays bootable,
single-window).  Also fixes a real engine bug: SVGA16::BuildWindows
PostQuitMessage'd on a CreateDevice failure but fell through to a null-device
deref (segfault) -- now breaks (inert on the pod).

GAUGE DEV-COMPOSITE, Milestone A (option B; opt-in BT_DEV_GAUGES, default OFF):
wake the (dormant) gauge renderer so its CPU-rastered pixelBuffer can later be
composited into the dev window, WITHOUT touching the pod SVGA16 output path.
SVGA16 does no per-surface D3D in this mode (a DevGaugeComposite() gate forces
the no-surface path + short-circuits Update/Refresh).  Waking the never-exercised
gauge subsystem exposed 4 latent reconstruction bugs, each guarded:
SVGA16::Update / Refresh (empty mSurfaces[]), LBE4ControlsManager::MakeLinkedLamp
(NULL lamp manager), L4GaugeRenderer::NotifyOfNewInterestingEntity (garbage
warehouse chain).  It now boots STABLY; the gauge reconstruction is incomplete
(shadowed lamp-manager + warehouse members) -- Milestone B (the composite pass)
will reveal whether the content is real.  See docs/GAUGE_COMPOSITE.md.

Verified: default DEV un-regressed (combat DESTROYED, 0 crashes); pod path
untouched; BT_DEV_GAUGES boots stable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 10:18:40 -05:00
arcattackandClaude Opus 4.8 8b36440d05 subsystems: un-stub Wave 4 readouts + Wave 6 Myomers (10->15 factory cases)
WAVE 4 (standalone readouts) -- Sensor(0xBC3)/Searchlight(0xBD8)/
ThermalSight(0xBDE)/AmmoBin(0xBCB) un-stubbed via Create<Class>Subsystem
bridges + Torso-style de-shim (drop cross-family shadow fields, redirect
accessors to the real inherited base, static_assert layout locks).
- FIX: Searchlight/ThermalSight ctors gated their Performance on the shadow
  segmentFlags(=0) so it NEVER installed; switched to owner->simulationFlags.
- AmmoBin: retype ammoAlarm HeatAlarm->WatcherGaugeAlarm(0x54) + drop the
  statusState@0x40 shadow -> exact 0x22C layout.
- Guard HeatWatcher::WatchSimulation against the unresolved watchedLink
  (null-deref exposed once these sims run; faithful fix = resolve the link).
- Heat-leaf branch (Sensor) is not byte-exact -> overflow-lock only.

WAVE 6 (Myomers 0xBC6, mover-coupled) -- structural un-stub, gated
BT_MYOMERS (default on; =0 -> Actuator stub). Wired INERT: MyomersSimulation
early-returns (advanced-damage gate stubbed) + no-op mover feed, so the live
JointedMover is untouched and the gait cannot regress. De-shim drops the
owner*/segmentFlags shims to fit the exact-0x358 alloc. Authentic mover/heat
coupling deferred (needs messmgr 0xBD3 + reconciling the mover feed with the
gait cutover).

Verified: BLH tick 20->27, Mad Cat 24, combat DESTROYED un-regressed,
locomotion un-regressed, 0 crashes, 0 heap detections under BT_HEAPCHECK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 23:01:31 -05:00
arcattackandClaude Opus 4.8 cc4819e59e CLAUDE.md: fix the §10 intro build path to bt411
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:10:33 -05:00
arcattackandClaude Opus 4.8 cf695d9d67 CLAUDE.md: adapt operational sections to the bt411 tree
Rewrite the build/run/debug recipe (§10a) and the "where inputs live" section
(§10a-bis) for the consolidated bt411 layout; remap the reconstruction-ledger
citations (btbuild/*.md -> docs/*.md); flag §6's port/ viewer as archive-only.
The deep decomp-history sections still cite the original nick-games paths as
provenance -- the orientation banner at the top maps them onto the new tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:10:08 -05:00
arcattackandClaude Opus 4.8 7b7d465e5e Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.

Layout:
  engine/   MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
            work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
            models) + image codec; the minimal rp/ headers the audio HAL needs
  game/     reconstructed BT logic + surviving-original BT source + fwd shims
            + WinMain launcher
  content/  full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
  docs/     format specs + reconstruction ledgers
  reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
  tools/    MP console emulator + map/resource scanners

One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:03:40 -05:00