b366da6f1ec43bf7d8d5d4b04f45e85e2c9b8563
85
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
07eb74d1f7 |
BT410 5.3.80: joint articulation is live -- 22 nodes, and the twist reaches the board
RecurseSKLFile now builds a joint renderable for any node whose page name resolves to a live skeleton Joint: HingeX/Y/Z -> HingeRenderable (watching Joint::GetHinge), Ball -> BallJointRenderable (watching GetEulerAngles), otherwise the static path. Each holds the rest offset in one DCS and the live rotation in a child DCS, and its Execute diffs the watched value and calls DPL_FLUSH_DCS -- the engine's own mechanism (L4VIDRND.CPP:1026+). The value comes from the mech's JointSubsystem via ResolveJoint, so sim and renderer read one source. Gated on BT_JOINTS while it proves out. [skl] video\max.skl -> 26 nodes, 1 objects, 1 eye, 22 articulated The bridge reported anim_abs=1 joints=0 twist=+0.00 before; it now reports joints=1 twist=-0.86, matching the game's [torso] twist=-0.856. With the mech stationary, frames that differed by 0.0% now differ by 62-80%. A crash it exposed: Mech::ResolveJoint passed segment->GetJointIndex() straight to GetJoint unchecked, and a segment with no joint reports -1 -- GetNthImplementation then indexes [base + -1*4] and dies (guest 00426A1D). Torso never hit it because it only asks for its own authored joint name; the walk asks for every page. Now bounds-checked against GetJointCount. Open: the canopy does not stay rigid in the view, though it and the eye hang off the same articulated node. Cancelling the bridge's cage compensation (CAGE_TWIST_SIGN=0) did not close it. Leading hypothesis: SetupCull builds worldToEyeMatrix from GetSegmentToWorld(siteeyepoint) -- the SIMULATION's segment transform -- independent of the render tree, so the canopy follows our render chain and the eye follows the sim's, and they diverge whenever one carries the twist and the other does not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3c289ff10a |
BT410 5.3.78: the torso twists from the button path; two buffer overruns fixed
BT_FORCE_TORSO sweeps the twist BUTTONS -- the members the streamed direct mappings write -- so the chain runs without a hand on the RIO. Live on the pod: twist sweeps -0.015 to -0.891 rad and back as cmdL/cmdR alternate, at the resource rate 0.873 rad/s, inside the authored +/-2.443 limits, with jointtorso resolved. End to end: attribute id -> direct-mapping destination -> TorsoSimulation integrator -> skeleton joint -> the canopy eye hanging off that chain. 5.3.68's table and 5.3.71's integrator both confirmed against real button semantics. Two buffer overruns, one of them mine. Reading TORSO.CPP back caught the ctor still zeroing dynamicsState[22] after I shrank the array to [16] by carving the command members out of its front: 24 bytes past the end of every Torso, on the heap, every mission, since 5.3.68. A sweep for the same shape across BT/BT_L4/MUNGA found a pre-existing one -- MECHMPPR.CPP zeroing reserved[22] against reserved[21]. Both now bound by ELEMENTS(). The rule for this tree: any array carved out of a reserve block must have its initialiser bound by ELEMENTS, because the carve is exactly the edit that silently invalidates a literal. Neither explains the residual host fault (it predates 5.3.68 at 3/3), but both were real corruption running in every mission -- and the Torso one was introduced by the very fix that cut the fault rate, which is worth remembering whenever a rate MOVES instead of going to zero. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bd1070110b |
BT410 5.3.71: torso button commands wired into the sim; residual fault rate measured
TorsoSimulation now carries the donor's digital command handling (binary @004b5cf0): elevate up/down, twist left/right with limit clamps, the centre-button recenter latch slewing home across frames, and the ramp machinery kept verbatim including the binary's punchline -- the shipped build unconditionally overwrites the ramp with 1.0f, authored dead weight preserved as the binary's shape. recenterActive carved from the dynamicsState reserve. With 5.3.68's table fix the RIO/TM twist buttons land in these members and move the torso, and the canopy eye rides the twist chain. Also corrected in the roadmap: the 5.3.70 TriggerState 'wart' was not a wart -- CheckFireEdge already compares fireImpulse's bit pattern as a signed int, sign-correct for button ints and floats alike, and the donor binds and types the member identically. Fault rate, measured: riostreak.sh ran four consecutive vRIO-live runs -- CLEAN/CLEAN/FAULT/CLEAN, faults only ever in the load window (ticks 42-53k), clean runs always past launch. Post-fix tally 8 runs / 2 faults (~25%; was 3/3 before the Torso fix). Our load window is ~60s where shipped's is ~15s: if the residual is a constant-rate hazard during load, shipped's expected per-run rate is only ~7% -- 'shipped survives' may be exposure luck, and the residual is most likely an emulator-fork serial/DPMI interaction rather than a game defect. Next probe: hook the fork's page-fault path on the fixed cr2 and dump recent serial-IRQ deliveries + guest cs:ip history, plus a shipped-exe streak for the baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8231bee58c |
BT410 5.3.68: THE LIVE-RIO FAULT IS FIXED -- it and the TM crash were one defect
The [map] audit named it in one run: subsystem 17 = Torso, attribute ids 12/13 unresolved. The donor's decompiled torso.hpp carries the authentic enum with binary offsets -- ids 3..15, including StickPosition(9), TorsoUp/Down/ Left/Right(10-13), TorsoCenter(14), MotionState(15). Our table stopped at seven entries with MotionState at id 9, on the strength of a comment claiming the rest were messages. The streamed control mappings bind BY ID as direct WRITE destinations, so the truncation produced two different crashes from one cause: in TM mode the button ids 12/13 resolved NULL (the boot write-fault the moment the joystick polled), and in RIO mode the analog id 9 resolved to our motionState StateIndicator -- every analog packet from a live vRIO wrote raw floats over a watcher-socketed object, and the corrupted chains walked into unmapped memory ~30-40s later. That was the 'intermittent' pod fault at the fixed DPMI-host address. vRIO down = no analog = no corruption, which is why the norio conf launched: every observation from the whole hunt drops out of this mechanism. Fix: the full 13-entry donor table; five int command members carved from the dynamicsState reserve (class size unchanged); ctor zeros them. Verified, two agreeing runs each way: TM mode launches with a clean audit and the Thrustmaster joystick driving the torso (stickY=0.907 -> torsoElev 0.349); RIO mode with vRIO STREAMING launches, zero faults, weapons cycling. pod_render_rec is no longer poisoned and the norio workaround is obsolete. The audit-before-the-archive-call pattern (BT_MAP_LOG walks the same streamed table CreateStreamedMappings consumes, naming what will not resolve) turned a two-day intermittent-corruption hunt into a one-run lookup. The streamed RES tables are binding contracts on our attribute enums. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4cf09917ce |
BT410 5.3.57: the crash is deterministic, and the real blocker is the load gate
The previous commit's attribution was wrong and is corrected in the roadmap. WRONG: 'the crash is the skeleton walk's' rested on 0 crashes in 2 runs with the walk disabled -- a 25% coin flip presented as evidence. The oldest preserved dump has no [skl] line and ends on the old 'couldn't figure out how to MakeEntityRenderables' fallback, so it predates the walk and faults identically. WRONG: 'it lands at different points each time'. Every dump carries the same numbers to the byte (cr2 7000FA64, EIP 66D9). What varies is how far the log gets, not where the fault is. Resolving the address through btl4opt.map names it exactly: EulerAngles::operator=(const LinearMatrix&)+0x19, a read through the matrix reference. A binary scan finds all three call sites of that operator pass a stack local, so the 1.79GB pointer still has no static explanation -- that is now its own open item rather than a guess. Two theories killed cleanly by the new BT_STACK_LOG probe and a binary scan: ESP drift (drift=0 over 2002 frames; exactly one callee-cleans function exists in the whole binary) and an undersized stack (our PE and the shipped one have identical 1MB/8K geometry). What the probe found matters more: the application is parked in state=2, which is LoadingMission, not WaitingForLaunch. Priority 0 is where the interest manager queues renderer events during load, the gate needs that priority empty, and it never empties -- so the mission never launches and the renderer holds a blank screen by design. The fault arrives ~30s into that wait. Runs that DO launch never print a single [launch] line. So 'crashes half the time' and 'hangs during load' are one event seen twice. A 15x disagreement between two clocks looked like a reconstruction slip -- BTL4.CPP passes GetTicksPerSecond() where ApplicationManager wants a frame rate. Checked against the shipped binary before touching it: same instruction sequence, same kind of static float pushed. Authentic. Documented so nobody 'fixes' it. Also swept every subsystem DefaultData against its real C++ base. Fifteen chain past their immediate parent, but fourteen skip only classes that add no handlers and no attributes, and no class's attribute-ID base disagrees with its index chain -- so there are no gap slots there. The one real defect: Generator is a HeatSink but chained to Subsystem::MessageHandlers, so it ignored every ToggleCooling message. Fixed (compiles next build). Tooling: podrun.sh stages the build over BTL4REC.EXE, exports the host-side VPX board env -- without which the run dies at the iserver handshake rather than merely rendering nothing -- and archives every run's log, marking it -CRASH when it faulted. Before this the only preserved dump was an accident, on a rig where each run costs four minutes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4168f1ad4d |
BT410 5.3.47: the whole authored watcher set is published -- cockpit renders in the pod
Ladder rungs, each run-verified on the pod rig: ReportLeak (HeatSink), GeneratorOn (Generator -- member existed, publication missing), ConfigureActivePress (MechSubsystem -- likewise), AmmoState + FireCountdownStarted + the rest of the AmmoBin table. THE PINNED RANGE IS NOW THE AUTHENTIC SHAPE. Publishing ReportLeak on HeatSink and ConfigureActivePress on MechSubsystem shifts the chain down two, and MechWeapon's bridge to its binary-pinned PercentDone (0x12) collapses from THREE guessed pads to ONE -- which is exactly the arithmetic the shipped string pool predicts (MechSubsystem 1, HeatableSubsystem 3, HeatSink 6, PoweredSubsystem 5). Three pads of guesswork replaced by a real attribute and a real base-class row. HeatableSubsystem and Torso rebase onto MechSubsystem::AttributeIndex; MechControlsMapper does not (it derives from Subsystem directly). Types were chosen deliberately this time, per the AudioWatcher families the engine instantiates (Motion / Hinge / Scalar / StateIndicator): every *State name resolves to a StateIndicator, ReportLeak is a Scalar. RESULT: the pod run now gets past every authored watcher and DRAWS THE FULL COCKPIT -- sensor cluster, myomers, cooling, the weapon panels, kills/deaths -- with the board booted and audio running. It then exits without flushing its redirected stdout, so the exit reason is not yet known; next step is a run with the redirect removed so the console is readable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
afb9e3f5d9 |
BT410 5.3.46: the Reservoir crash solved -- *State names must be StateIndicators
The linker map named the faulting function: the crash address minus the CODE base (0x410000) looked up in btl4opt.map's Publics-by-Value landed inside AudioStateWatcher::AudioStateWatcher +0x2D. AudioStateWatcher is AudioWatcherOf<StateIndicator> and its ctor immediately runs Cast_Object(StateIndicator*, attributePointer)->AddAudioWatcher(this). So every authored *State name must be published as a StateIndicator -- AlarmIndicator counts, it derives from one -- and pointing one at a plain int sends that member call through garbage. That explains both earlier failures: the AlarmIndicator attempt was the right type but was tested with other bugs still in the batch, and the plain-int attempt was simply the wrong type and crashed further along. Published as state objects: Reservoir/ReservoirState -> reservoirAlarm, Generator/GeneratorState -> stateAlarm, plus new StateIndicator members for Condenser/CondenserState and Torso/MotionState. StateIndicator has no Initialize(); the default ctor suffices because the watcher only needs the object to exist. Verified on the pod rig: no crash, ladder advanced to ReportLeak. Technique worth keeping: on an extender fault, subtract 0x410000 from the dumped address and look it up in btl4opt.map -- it names the engine function, and for this family of work that names the watcher class and hence the required member type. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0d97d8c38b |
BT410 5.3.45: six more watcher names published; the Reservoir one CRASHES (bisected)
Published and verified green on the pod rig: Condenser/CondenserState, Generator/GeneratorState, Emitter/LaserOn, ControlsMapper/TargetRangeExponent, plus the Torso and Myomers tables from the previous pass. Reservoir/ReservoirState CRASHES the pod on the DOS extender, and a bisect pins it to exactly that change: revert it and the run returns to a clean Fail, re-apply it alone and the crash returns. It is reverted; the tree is green and the ladder is blocked there. The important lesson is general: AttributeWatcherOf<T> does currentValue = *(T*)attributePointer AT CONSTRUCTION, so a published name is read the moment its watcher is built. My earlier staging note claimed the provisional types could not matter because nothing drives the values yet -- that is wrong, and this is the counterexample. Ruled out by measurement and recorded so they are not retried: the AlarmIndicator-vs-int type (a plain int got further, 232 -> 749 bytes of log, but still crashed), static-init order (reservr.obj sorts last, after heat), an id gap (contiguous at HeatSink::NextAttributeID), and the gauge rig (same binary runs clean there -- only the pod/arena context faults). Crash signature for whoever picks it up: 0044B4AD, mov eax,[edx+0x18] then call [eax+4], EAX=0x15, fault at 0x19 -- a small integer called through as an object, i.e. the AttributeIndexSet::Build uninitialised-slot pattern. Condenser is the control: same base class, plain int, no crash. Also fixed on the way: staged members must be appended at the END of a class (offset-sensitive readers exist -- condenserNumber is reached as master+0x1d4) and never added to a resource struct, which is a wire format. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
d422dfffc0 |
BT410 Phase 5.3.7: weapon view-fire gating + build header-stamp hardening
Completes the weapon-side half of the look commit: - MECHWEAP: real rearFiring/viewFireEnable members (binary @0x334/@0x3E0) + accessors. Ctor resolves REAR-FIRING authentically (@004b99a8 tail): the mount SEGMENT site name is tested for the 'b' (back) marker -- only the back gun ports (sitelbgunport/siterbgunport) carry it. Spawn = forward-armed. - MECH CommitLookState: re-arms every roster weapon per view (forward = non-rear, LOOK-BACK = rear-mounted, side/down = none) and flips the HUD reticle pip group with the authentic Reticle::Front/RearFiringWeaponsOn flags (BT411's raw "bits 1/2" resolved to their 1995 names). - MECHWEAP: defined MechWeapon::MessageHandlers (0-entry set inheriting the Subsystem chain) -- it was declared-but-undefined, and the surviving CODE PPC.CPP binds the inherited name into PPC::DefaultData; the zero-filled common block was a latent NULL deref (same trap as Emitter::AttributeIndex). Discovery: the TEST.EGG mech genuinely mounts TWO REAR LASERS (ERMLaser_2/3 on the b-ports). Verified on a full-clean baseline (engine 181 + bt 42, 0 fail): look-behind arms exactly the rear lasers, disarms the other five, pipMask=0xfffffffe; neutral run holds forward-armed; zero Fail. build410.sh HARDENED against the stale-obj layout-skew trap this wave exposed: cc() skipped recompiles on obj-vs-.cpp timestamps only, so the MECHWEAP.HPP member additions left emitter.obj on the OLD MechWeapon layout -- its chargeLevel=0.0f write landed exactly on the new rearFiring (a silent cross-TU struct skew, heisenbug-grade). cc() now honors header stamps: a source410 engine-header edit invalidates every bucket, a BT/BT_L4 header edit additionally invalidates the game buckets. CODE/ is immutable, no stamp needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
eb8bba3195 |
BT410 Phase 5.3.6: look-button state machine -- full eyepoint composition
Reconstructs the binary's five-state LOOK machine (controls mapper tail, part_013.c:396-459) as proper members/methods: - MECHMPPR: LookState enum (None/Left/Right/Behind/Down) + lookState/ previousLookState (out of reserved[24] -> [22]). InterpretControls picks the state from the look buttons each frame and on a CHANGE calls Mech::CommitLookState. BT_FORCE_LOOK=<1..4> dev hook. - MECH: authored look angles lookLeft/Right/Front/BackAngle (deg->rad from the GameModel resource, guarded) + lookPitch/lookYaw + CommitLookState(int): side looks yaw by the authored angle, look-behind = yaw pi + lookBackAngle pitch, look-down = lookFrontAngle pitch, forward = identity. The per-frame eyepoint compose in Simulate adds the live Torso elevation on top. (reservedState [208] -> [202].) - Deferred inside the commit (needs MechWeapon viewFireEnable/rearFiring): per-view weapon fire re-arm + HUD pip group-mask flip. Verified headlessly: model reads clean authored angles (L=60 R=-60 F=-10 B=0 deg -- ModelResource layout confirmed again); BT_FORCE_LOOK=3 fires ONE commit ([look] state=3 yaw=3.14159 pitch=0) and the per-frame compose holds eyeYaw=pi with eyePitch=0.349066 (lookBackAngle + the 20deg-clamped elevation). Zero Fail. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
87b1c048cf |
BT410 Phase 5.3.5: eyepointRotation -- torso elevation pitches the eye/aim-ray, not a joint
Investigated the next planned binding (elevation -> gun joints jointlgun/ jointrgun) before implementing it, and found it was the wrong target: BT411's own reverse-engineering history records that NO gun/arm elevation joint exists in the authentic engine. The pod's stick-Y aim pitches the COCKPIT EYE and the weapon boresight directly (mech4.cpp @~5219, pixel-calibrated against real screenshots) -- the torso geometry never tilts for elevation on this mech family. Reconstructing a gun-joint binding would have been invented behavior. - MECH.HPP: EulerAngles eyepointRotation member (out of reservedState, now [208]) + GetEyepointRotation() accessor. - MECH.CPP: composed each frame in Simulate -- eyepointRotation = EulerAngles(Radian(Normalize(lookPitch+elevation)), Radian(Normalize(lookYaw)), Radian(0)), reading Torso::CurrentElevation() via sinkSourceSubsystem. lookPitch/lookYaw are 0 until the look-button wave (BTCommitLookState) lands. Verified: BT_FORCE_ELEV=0.8 -> torsoElev and eyePitch both read 0.349066 (the authentic 20 degree resource limit) every frame, exact match; neutral -> 0. Zero Fail. Corrected TORSO.NOTES.md's prior (wrong) "next: gun joints" note. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9fff0e969e |
BT410 Phase 5.3.4: Mech::ResolveJoint + skeleton-joint binding (torso twist)
Reconstructs the shared skeleton-joint resolver and binds the first subsystem (Torso) to the live skeleton. - Mech::ResolveJoint(name) (mech.cpp @00424b60): GetSegment(name) -> segment joint index -> GetJointSubsystem()->GetJoint(idx). - Verified the JointedMover base streams the full skeleton headlessly: jointCount=19, 40 named segments (gun joints jointlgun/jointrgun, shoulders, hip, leg joints jointlthigh..jointrankle). BT_MECH_LOG [skel] summary; BT_SKEL_DUMP lists every segment/jointIdx. - Torso resolves + binds its twist joint (ResolveJoint(torsoHorizontalJoint)) and pushes currentTwist onto it via Joint::SetRotation (hinge->Radian, ball->EulerAngles yaw). The TEST.EGG mech has a fixed torso (horizJoint='', enabled=0) so its twist path is inert -- correct + guarded, zero Fail. Next joint piece: elevation -> gun joints (jointlgun/jointrgun, the fixed-torso aim mechanism) via weapon-joint binding; then gait leg animation. Both are VISIBLE only under the renderer; headlessly the joint transforms are loggable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3a0920acc0 |
BT410 Phase 5.3.3: torso weapon-elevation aim -- completes the control surface
Reconstructs Torso::TorsoSimulation (installed as the Torso's per-frame Performance) and wires the torso aim into MechControlsMapper::InterpretControls, so the control surface now covers aiming as well as locomotion. - TORSO: analogElevationAxis/analogTwistAxis + SetAnalog* setters, CurrentElevation()/GetHorizontalEnabled() accessors. TorsoSimulation slews currentElevation += analogElevationAxis*baseElevationRate*dt clamped to the vertical limits, and (if horizontalEnabled) currentTwist by the twist axis clamped to the horizontal limits (authentic torso.cpp core; the skeleton-joint application is deferred with the render wave). - InterpretControls: routes stick pitch (stick_y, squared) into the torso elevation every mode; Std/Vet route stick yaw into the twist. BT_FORCE_ELEV dev hook. Verified: BT_FORCE_ELEV=0.8 -> the elevation slews up and clamps at the authentic verticalLimitTop = 0.349 rad = 20deg; neutral -> 0; zero Fail. Deferred: skeleton-joint application (render wave), HUD free-aim slew, look/eyepoint commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0cc7713865 |
BT410 Phase 5.3.2e: source authentic per-mech locomotion params from GameModel resource
The Mech ctor now reads walkingTurnRate / runningTurnRate / maxAcceleration / throttleAdjustment from the GameModel resource (SearchList(GameModelResourceType) -> ModelResource), replacing the bring-up-default constants for those fields. Each read is sanity-guarded (out-of-band -> keep default) since BT411 flags parts of the ModelResource layout as mis-decoded -- but the live values came back clean and authentic: walkTR=75deg/s, runTR=50deg/s, maxAcc=30 u/s^2, throttleAdj=1.0 (walking turns faster than running; maxAcc matches the madcat note), confirming our reconstructed struct layout is correct for these fields. Verified: at walk speed the authentic 75deg/s tightens the turn circle to r~6.9 (=speed/turnRate); zero Fail. Still on defaults (pending LoadLocomotionClips): reverseStrideLength (top speed) / walkStrideLength / reverseSpeedMax -- measured from the walk/run animation clips. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ebf0d8c23f |
BT410 Phase 5.3.2: DRIVABLE mech -- authentic control interpretation + locomotion
The mech now drives from the authentic control chain (faithful route). Verified headlessly: full throttle -> walks straight at top speed (30 u/s) along heading; throttle+turn -> walks a circle (radius = speed/turnRate); neutral -> holds pose; zero Fail. MechControlsMapper::InterpretControls (mechmppr.cpp @004afd10) -- installed as the mapper's per-frame Performance (roster slot 0, ticks before the mech): - speedDemand = topSpeed*throttle*fwdScale (reverse inverts); soft stick (square) / pedal (cube) response; Basic=stick turn, Std/Vet=pedal turn; speed clamped while turning hard. Reads owner stride via Mech accessors. - BT_FORCE_THROTTLE/BT_FORCE_TURN dev hooks for headless verification. Mech::Simulate drive -- consumes the mapper demands: - accelerate currentBodySpeed toward speedDemand (maxBodyAcceleration); - authTurnRate = lerp(walkingTurnRate, runningTurnRate) by ground speed + over-run falloff (mech4.cpp master-perf @0x4aa3d3); - integrate heading via Quaternion::Add(prevPose, (0,turn*rate*dt,0)); - facing = world -Z basis (GetFromAxis(Z_Axis)); worldLinearVelocity = facing*spd; - integrate position (increment-1 core). MECH.HPP: 9 named locomotion members out of reservedState (now [211]) -- walking/runningTurnRate, reverse/walkStrideLength, reverseSpeedMax, forwardThrottleScale, maxBodyAcceleration, body/currentBodySpeed. BRING-UP DEFAULTS (authentic values come from the model resource + LoadLocomotionClips -- next refinement). Deferred: gait-clip-exact advance + leg anim (needs animation subsystem/renderer), model-resource sourcing, terrain drop, torso/free-look aim, telemetry filters. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3364b65ae8 |
BT410 Phase 5.3.1: Mech::Simulate motion core -- mech integrates motion per-frame
Installs Mech::Simulate as the mech's per-frame Performance (was DoNothingOnce). Reconstructs the load-bearing spine of the 1995 Simulate (mech4.cpp @004ab430): integrate the body velocity into localOrigin (linearPosition.AddScaled(.., worldLinearVelocity, dt)) and commit the Origin to the world transform with the engine idiom (localToWorld = localOrigin, per ENTITY.cpp:988 / MOVER.cpp:850). Uses the NAMED engine base Mover fields (localOrigin/localToWorld/ worldLinearVelocity) -- BT411's MechBaseLayoutCheck proved the WinTesla decomp's raw this+0x100/0x260 offsets actually stomp those base fields; the clean 1995 build addresses them by name. - MECH.HPP: Mech Performance typedef + SetPerformance + Simulate() decl. - MECH.CPP: SetPerformance(&Mech::Simulate) at ctor tail; Simulate body. Verified headlessly (BT_MECH_LOG "[sim] mech pos=" 1Hz): BT_DRIVE="5,0,10" advances the mech +5.0/s x, +10.0/s z (y fixed) = exactly the injected world velocity; no BT_DRIVE -> worldLinearVelocity is 0, mech holds spawn pose. Zero Fail/Exception either way. BT_DRIVE is a retained dev hook for headless motion. Deferred (locomotion wave): gait-cycle self-propulsion feeding worldLinearVelocity (IntegrateMotion->AdvanceBodyAnimation, steered by mapper throttle/turn), heading integration, terrain-height drop, cockpit telemetry filters. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3610ff1ecd |
BT410: entity simulation goes LIVE -- mission reaches RunningMission, mech + roster tick per-frame
The world now executes. Entity::Execute only PerformAndWatch's entities in application state RunningMission, reached by two RunMissionMessages (WaitingForLaunch->LaunchingMission->RunningMission). The 2nd is dispatched by Player::ManageApplicationStatus when the launch fade expires -- but that only runs inside PlayerSimulation, not the launch-phase HuntForDropZone. So the player must switch Performance onto PlayerSimulation after it spawns. - BTPLAYER.CPP DropZoneReplyMessageHandler: after CreatePlayerVehicle + InitializePlayerLink, choose the per-vehicle Performance by class -- Mech -> SetPerformance(PlayerSimulation)+SetScoringPlayerFlag; else CameraShipSimulation. - BTPLAYER.CPP PlayerSimulation: chains the authentic base Player::PlayerSimulation (CalcRanking + ManageApplicationStatus + vehicle-pos copy + status service); console SCORE-delta flush deferred to the scoring wave. - SENSOR.CPP SensorSimulation: minimal-safe partial (radarPercent = 1 - GetSubsystemDamageLevel, sensor healthy) so the roster tick path can't Fail once RunningMission engages. The heat/electrical gating awaits the power/heat sim wave (PoweredSubsystem/Generator/HeatSink/HeatWatcher *Simulation staged). Verified: BT_MECH_LOG run reaches "[tick] roster live (first Sensor frame)", two RunMissionHandler transitions, zero Fail/Exception. The mech is executed each frame; its BODY Performance is still DoNothingOnce (real Mech::Simulate = Phase 5.3, now the frontier). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
812ca99652 |
BT410: reconstructed source BOOTS into the live game loop (zero Fail)
Phase-5.1 crash solved + two BTPlayer bricks reconstructed. The tree now builds AND boots end-to-end with no staged Fail() reached, into the running per-frame game loop. - EMITTER.CPP: define Emitter::AttributePointers[]/AttributeIndex (ChargeLevel, chained to Subsystem::AttributeIndex) and point Emitter::DefaultData at it. EMITTER.HPP declared the index but the .cpp never defined it, so the surviving PPC.CPP (PPC::DefaultData binds inherited Emitter::AttributeIndex) and GAUSS.CPP (chains it) captured a NULL activeAttributeIndex -> GetAttributePointer faulted on the first PPC (roster slot 22). Fix clears all 33 roster slots and the control-mapping binding block. - BTPLAYER.CPP InitializePlayerLink: real body -- dispatch PlayerLinkMessage (our EntityID) to playerVehicle + replicants (mirrors engine CameraDirector::CreateCameraShip). No staging. - BTPLAYER.CPP VehicleDeadMessageHandler: non-death flavours (-2 drop-zone probe, >=0 respawn re-post) delegate to the authentic Player base handler; only the -1 death cycle stays staged (needs mech4 + scoring roles). Verified: 516-line smoke run, zero Fail/Exception/abort; boot reaches LBE4ControlsManager::Execute per-frame, waiting only on absent RIO hardware. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
11680bcb3e |
Phase 5.1: mapper attributes VERIFIED resolving (throttle/controlMode non-NULL)
Trace confirms the MechControlsMapper (slot 0) publishes its control attributes correctly: throttleAttr/controlModeAttr resolve to non-NULL pointers. So the mapper's AttributeIndex works. The post-construction crash (GetAttributePointer attr=0x13) is on a DIFFERENT object (EAX heap addr after the mapper) -- a streamed watcher targeting another simulation, not the mapper. Localizing next. (BT_MECH_LOG mapper trace retained.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1f2f8ac42a |
Phase 5.1: MechControlsMapper publishes its 20 control-input attributes
Reconstructed the MechControlsMapper attribute table (StickPosition..PilotArray, IDs 3-0x16 chained from Subsystem::NextAttributeID==2) + the real members (stickPosition ControlsJoystick, throttle/pedals/speed/turn Scalars, the look/ torso ControlsButtons, control/display/pilotArray ints) + AttributePointers[] + AttributeIndex (chains Subsystem::AttributeIndex). Fixed the mapper hierarchy statics in BTL4MPPR.CPP: L4/RIO/Thrustmaster ClassDerivations now chain correctly (L4 defined first for static-init order) and all use MechControlsMapper:: AttributeIndex so the mapper instance publishes the control attributes the streamed mappings bind to. Mech still constructs (33 subsystems). The post-construction crash is UNCHANGED (GetAttributePointer, attribute=0x13) -> so it's NOT the mapper; a streamed AttributeWatcher (numeric ID 19) resolves on another object via GetSimulation. Next diagnostic: which object/subsystemID the watcher targets. BT: 42 ok. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0248a78840 |
Phase-5 crash localized: control-mapping binding needs subsystem attribute tables
The Mech ctor fully constructs (33 subsystems / 7 weapons on real TEST.EGG data, trace-confirmed). The crash is the control-mapping resource binding in MakeViewpointEntity resolving subsystem attribute IDs via GetAttributePointer -- the subsystems publish none (reuse base AttributeIndex). Documented the precise phase-5 step-1 scope (per-subsystem AttributeIndex + the ID chain) in MECH.NOTES.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b8f5928887 |
Mech ctor PROVEN: constructs 33 subsystems + 7 weapons from real model data
Live trace (BT_MECH_LOG) on the pod TEST.EGG mech: '[mech] segment walk done: subsystemCount=33 weaponCount=7' -- the segment walk instantiates the ENTIRE real subsystem roster (31 subsystems + 2 sentinels, 7 weapons) from the actual streamed model, no crash. The structural reconstruction is proven end-to-end on real data. The crash is AFTER full construction, in MakeAndLinkViewpointEntity's attribute- watcher setup (an AttributeWatcher resolving a subsystem attribute via GetAttributePointer). Phase-5 frontier confirmed = per-subsystem AttributeIndex tables + watcher wiring. Trace gated behind BT_MECH_LOG. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |