530bbeb959e8405dd2fd0384390d579685d59200
101
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
da80727467 |
KB: autocannon jam/coolant -- SOLVED from decomp, QA behavior CONFIRMED faithful
Reproduced the QA report end-to-end from the decomp + raw-binary-verified constants. The coolant-priority effect is real but fire-rate-dependent: at the ~11s combat cadence, not-boosted jams (T=1058>1000) while boosting the AFC's condenser keeps it below the line (T=953, no jam) -- exactly as the QA described. Max-spam (8s) overheats regardless (why the earlier 'weak boost' read was wrong); 16s never jams. All AFC100 constants verified authentic vs BTL4.RES raw bytes (afc_dump.py). No bug, no stand-in. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
21abe2212e |
KB: autocannon jam/coolant-priority -- mechanism is FAITHFUL + COMPLETE (decomp hunt)
Two-agent decomp hunt on the QA report ('AFCs jam on 3rd shot if coolant priority
not boosted'). Conclusion: the whole coolant->jam chain is reconstructed end-to-end
and FAITHFUL -- the weapon's constant coolantFlowScale=1.0 is authentic (only
condensers' 0x15C is written from the valve, via RecomputeCondenserValves over the
condenser chain mech+0x7cc). Authentic mechanism is second-order-in-equation but
first-order-in-outcome: weapon -> its condenser -> bank; boosting a condenser's valve
share (~5x conductance swing) keeps its weapon below degradationTemp -> no jam.
Distinguished the sticky probabilistic jam (degrad 1000, JammedState 5) from the
self-clearing overheat lockout (failure 2000, weaponAlarm 7). Empirically the port is
directionally correct (~4th-shot probabilistic) but not the QA's deterministic 3rd
shot -- a calibration/scenario question pending QA clarification, NOT a stubbed path.
Do not tune the heat economy to force it without ground-truth.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
844edbd2aa |
KB: FX per-zone chain LIVENESS-VERIFIED (BT_CRIT_PROBE solo)
Closed the loop: BT_CRIT_PROBE=8 on solo DEV.EGG drove the full per-zone FX chain end-to-end -- [zonefx] entity 1:321 seg 12 psfx 12/13 -- proving TakeDamage -> mechdmg effect loop -> BTStartZoneEffect -> StartEntityEffectImplementation (segment world-pos resolve) -> BTStartPfxAttached fires live. The 2-node fight harness didn't engage (mechPicks=0, an MP-harness spawn/connect issue, not FX); BT_CRIT_PROBE is the reliable solo liveness path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a198e5fb4a |
KB: fix my botched FX entry -- the impact/destruction FX chain IS reconstructed
Reverting a wrong 'correction' I just made: I claimed the six L4VIDRND explosion renderables were STUBBED and 'never draw'. WRONG -- ScalingExplosionRenderable has a real ctor/Execute body, and the impact/fire/detonation FX were reconstructed in the Fire VISUALS ( |
||
|
|
31d338b6ff |
KB: correct stale StartEntityEffectImplementation entry (dispatcher DONE 2026-07-13)
The per-zone effect dispatcher was reconstructed + wired the same day the audit flagged it as 'RECOMMENDED NEXT TARGET' -- the entry was never updated. Correct it: dispatcher done (btl4vid.cpp:889, wired at mechdmg.cpp:1120); the real remaining FX gap is the six still-STUBBED L4VIDRND scaling-explosion renderable bodies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8f7b304069 |
KB: mark authentic per-mech turn-rate DONE (task #64b)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a83995a755 |
Locomotion: wire the AUTHENTIC per-mech turn-rate lerp (task #64b)
The master-perf disasm (0x4aa3d3-0x4aa4ff) computes the yaw rate as lerp(walkingTurnRate@0x574, runningTurnRate@0x578) by ground speed, not a constant. Wired it into the drive, replacing the bring-up constant kDriveTurnRate: - walkingTurnRate/runningTurnRate are now REAL named members. The ctor's `Wword(0x15d)/(0x15e) = model->...TurnRate * DegreesToRadians` writes were NO-OPS -- Wword() returns a shared static scratch cell, so the rates were silently discarded. Now stored + read by name. - The drive (mech4.cpp) computes authTurnRate: base = walkingTurnRate; above reverseSpeedMax(0x538) lerp walk->run across [walkStride 0x534 .. topSpeed 0x34c] with an over-run falloff runningTurnRate/t^2; clamp >=0; zeroed in the death/limbo/airborne leg states (1/2/3) or when deathAnimationLatched(0x650) is set. Falls back to kDriveTurnRate if the model gave no rates. The lerp is a RUN-speed refinement -- below reverseSpeedMax (walk / turn-in-place) the rate is the constant walkTR base; runningTurnRate < walkingTurnRate (a running mech is less maneuverable). Verified headless (DEV mech, BT_TURN_LOG): walkTR 1.309 rad/s (75 deg/s), runTR 0.873 (50 deg/s), turn-in-place + walk yaw at walkTR, no crash. [T1 logic / T2 runtime] Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
35c30bda67 |
Locomotion: replace task-#64 trn stand-ins with the AUTHENTIC dispatcher (disasm-decoded)
The turn-in-place dispatcher lives in the master-perf gap 0x4a9b5c-0x4ab188 that Ghidra never lifted. Disassembled it with objdump (scratchpad/masterperf.asm) and hand-decoded the real logic, replacing the two [T3] stand-ins I had invented: - Entry gate was `commandedSpeed < 0.25*standSpeed` (a near-zero guess). AUTHENTIC (0x4aa505): arm trn when TURNING (|angVel|>1e-4, port proxy = turnDemand deadband) AND speed in the FULL [0, standSpeed] sub-walk range AND turnCapable. trn is gated on TURNING, not on being slow. - Case-4 exit had a ×4 fast-forward + SetLegAnimation(5) invention. Restored the VERBATIM decompiled leg-SM exits (part_012.c:12013): standSpeed<spd or reverse -> Standing, else advance the pivot. Added the master-perf turn-STOP exit (0x4aa5c6): turn stopped -> Standing + legResetLatch=1 (folded into case 4 since the port has no separate master-perf frame). - Body case-4 twin mirrors the authentic exits (mj=0, tracks state for lockstep). The fast-forward was invented to dodge a turn->walk stutter that was ACTUALLY the body channel leaking joints (mj=1) -- already root-caused + fixed by mj=0. The trn->walk seam (legFrm7 cycle-end -> swr legFrm1) is the authentic pose-matched boundary and the same seam the fast-forward version hit, just at the correct release speed. Also decoded (documented, not yet wired) the authentic turn-RATE lerp (walkingTurnRate@0x574 -> runningTurnRate@0x578 by speed; angVel = turnDemand*rate); the bring-up drive still yaws at the constant kDriveTurnRate. Headless-verified (BT_AUTODRIVE/BT_FORCE_TURN/BT_WALK_DELAY/BT_GAIT_TRACE): turn-in- place enters state 4, ramp past standSpeed -> 4->stand->swr->walk, both channels in lockstep. Feel re-verification pending. [T1 logic / T2 runtime] Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bfdd41bb9d |
Gait: turn-then-walk stutter ROOT-CAUSED + FIXED -- one skeleton writer (task #64)
User repro (100%): turn in place, push forward before the turn stops -> the gait
skips/stutters + visibly reduced bob; standstill starts always clean. Three-layer
fix, user-verified:
1) THE DISPLAY BUG (the actual visible artifact): v5's "body advances first with
mj=1, leg overwrites last, so body drift can't show" was FALSE. Whenever the two
gait channels phase-split, the BODY channel's out-of-phase joint writes leaked
into the rendered skeleton (rhythmic leg skips, averaged-down bob) while every
leg-channel trace read clean -- the leg DATA was fine, the RENDERED pose wasn't
the leg's. v6: AdvanceBodyAnimation(dt, mj=0) -- the body still advances +
projects for replication (records/cycle speeds unchanged) but no longer touches
the skeleton. One writer, structurally; matches the binary's own observable
("body phase drift is locally INVISIBLE in the binary"). BT_BODY_MJ=1 = old A/B.
2) THE SPLIT SEED: the bring-up trn trigger armed only the LEG channel, so a
turn-in-place entry guaranteed the channels re-entered walk frames apart and the
walk cycles ran permanently out of phase. Lockstep: the Standing trn entry arms
BOTH channels the same frame (body case-4 twin added, same rate/keying), gated on
both Standing. The authentic dispatcher (un-decompiled master-perf gap 0x4a9b5c-
0x4ab188, the sole reader of turnDemand/turnCapable) armed both -- the body's
case-4 machinery is dead code otherwise [T1].
3) THE POSE-MATCH INVARIANT: the engine has NO pose blending; transitions avoid
pops purely by authored pose-matched boundaries. The old trn exits cut the pivot
clip MID-STEP (legFrm 7->1 teleport). Now: entry gated on near-zero speed
(turn-IN-place); on forward command the pivot FAST-FORWARDS to completion (x4)
and the authentic finish callback lands Standing at the stand pose -> the normal
pose-matched stand->walk runs. Decompiled reverse abort kept; the standSpeed
mid-clip abort subsumed (it WAS the pose cut). [T3: 0.25*standSpeed threshold +
4x rate stand in for the gap's constants.]
Harness: BT_FORCE_TURN now reaches gBTDrive.turn (was silently inert for the
gait), BT_WALK_DELAY=<s> holds forced throttle then ramps (the turn-first repro),
BT_GAIT_TRACE=1 per-frame gait trace. Regressions: standstill start, turn-entry,
pure pivot loops, run cycle -- all clean, bob full amplitude (1.33/1.32).
KB: locomotion.md v6 section + trn reconstruction + symptom-family closure.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
c0a0ec5e69 |
KB: document the searchlight fog-swap latent bug (task #63, left as-is by decision)
Investigation (workflow) found the searchlight-driven fog swap never worked in the ORIGINAL 1995 binary either -- it's a latent bug, not a port regression: SearchlightSimulation (@004b841c) reads requestedOn@0x1E0 (never written) while ToggleLamp (@004b860c) toggles commandedOn@0x1DC; no bridge exists, so lightState @0x1D8 is perpetually 0 (lamp never lights). The self-consistent sibling ThermalSight reads the field it toggles (0x1DC) -- the tell. The port reproduces the bug faithfully (searchlight.cpp:189). Additionally the port never constructs PullFogRenderable, so even absent the bug the swap wouldn't fire. Decision: LEAVE AS-IS + document (faithful to the buggy original); the port keeps the static lights-ON fog= values. A working swap would DEVIATE from the shipped binary (repair the sim to read commandedOn + construct PullFogRenderable at the btl4vid MakeMechRenderables inside pass + a toggle input) -- fully scoped in the KB if ever wanted. No code change this commit. Documented in rendering.md (fog section), open-questions.md (deferred subsystems), subsystems.md (Searchlight). Core per-map/time/weather fog (task #63) is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
225fa5951b |
Render: fog searchlight-swap verified INERT + BT_FOG_LOG diag (task #63)
Verifying the searchlight/nosearchlightfog swap (user request): it does NOT work. The arcade swaps active fog between fog= (headlight on) and nosearchlightfog= (off, darker/tighter) via PullFogRenderable::Execute (edge-triggered on the mech's light attrs -> SetFogStyle searchLightOn/Off). But PullFogRenderable is NEVER CONSTRUCTED in the port (grep: only the .h decl + .cpp def; no `new`), and searchLightOn/Off is called from nowhere else. Empirical confirmation via the new BT_FOG_LOG hook: over a 22s run SetFogStyle fired only style 0, zero style 2/3. So fog is static at the searchlight-ON (fog=) values -- normal lights-on play, so core per-map/time/weather fog is unaffected -- but the nosearchlightfog variant is dead. Corrects my earlier "the swap is wired end-to-end" claim (code paths exist, trigger never instantiated). KB rendering.md updated; wiring it back is a scoped reconstruction (bind the Searchlight subsys light state to a constructed PullFogRenderable at viewpoint setup). No behavior change this commit -- diag only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
083b148bb0 |
Render: FOG restored -- authentic per-map/time/weather fog now renders (task #63)
User: "aren't some levels supposed to have fog?" -- they were, and none showed. BTDPL.INI (the DPL env INI, L4DPLCFG) authors fog=near far r g b (+ often nosearchlightfog=) on EVERY map/time/WEATHER leaf; weather is a fixed egg field (clear/fog/soup). Shipped eggs pin cavern/night/clear -> page dsnitclear (near 90, far 1100, dark blue). The env pipeline resolved + pushed it to D3D, but it rendered INVISIBLE: D3DRS_FOGTABLEMODE=D3DFOG_LINEAR (table fog) derives its factor from the perspective-NONLINEAR z-buffer, so without WFOG the 90..1100 range collapsed to fog-factor ~1 (no fog everywhere). Fix (L4VIDEO.cpp world pass): auto-detect D3DPRASTERCAPS_WFOG -> per-pixel W-fog (smooth, == the arcade dpl_fog_type_pixel_lin); else VERTEX fog (eye-space but per-vertex -> splotchy on coarse terrain tris, so only a fallback). FOGENABLE + the fog mode are re-asserted each world-pass frame (the old one-time set got clobbered per-pass, which is why plain table fog showed nothing). Verified on this GPU: WFOG present -> per-pixel table fog, user-confirmed smooth. Also corrected task #20's false "shipped maps define no fog" comment (it checked .MAP/.RES, not BTDPL.INI). Env hooks: BT_FOGMODE=table|vertex|off, BT_WEATHER= clear|fog|soup, BT_FOG="near far r g b" (now honored on the authored branch too); [fog] resolved/model diagnostics. KB: rendering.md fog section added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a9c0d2c854 |
Combat: FIX splash mis-routed onto the direct victim via the msgmgr (task #62)
User: struck a mech ~4x, still standing -- but the [dmghit] trace showed the DIRECT victim taking 16 applications for 8 impacts while the intended splash bystander took 0. Root cause: BTApplySplashDamage delivered splash through the shooter's SubsystemMessageManager::AddDamageMessage, which CONSOLIDATES every damage message of a frame onto the FIRST hit entity (commonDamageInformation. entityHit, messmgr.cpp:279). The direct hit registered the primary as the common entity, so the bystander's splash was consolidated onto the primary too -- the excluded direct victim took 8 direct + 8 mis-routed splash = 2x, and the real bystander took nothing. Fix: deliver splash with a DIRECT e->Dispatch to each victim, matching the T0 source (EXPLODE.cpp:246 target_entity->Dispatch); Dispatch reroutes cross-pod for a replicant on its own. Verified (BT_DMG_LOG): 8 impacts -> 8 dmghit on the direct victim + 8 on the bystander (was 16/0); no death; per-hit 5.83. KB: combat-damage.md warns not to route splash through the consolidating msgmgr. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fa0b825b60 |
Combat: FIX missile direct damage 6x over-application -- damage once per salvo (task #62)
User live regression: missiles killed a mech in ~2 shots ("takes way more than 2
normally"). Root cause -- DamageZone::TakeDamage (arcade @0041e4e0 == WinTesla
DAMAGE.cpp:379) is `damageLevel += amount*scale` and IGNORES burstCount. So the
arcade's ONE cluster Missile per trigger lands its damageAmount EXACTLY ONCE; the
missileCount/burstCount split is cosmetic for zone damage (only gyro-bounce +
splash-falloff read burstCount). The port re-expresses that one cluster as N
flying rounds and was damaging on EVERY round = ~missileCount x too lethal on the
direct hit (mirrors the splash over-application fixed in
|
||
|
|
145a69f865 |
Combat: FIX missile splash over-application -- once per salvo, not per round (task #62)
Live regression: a clustered bystander died in ~2 missile salvos ("suddenly
lethal"). Root cause, not authentic: the arcade fires ONE cluster Missile per
trigger (burstCount=missileCount) doing ONE SplashDamage event with baseBurst =
missileCount, floored at 1 ONCE. The port re-expresses that cluster as N flying
BTProjectile rounds, and task #62 fired splash PER ROUND -- each baseBurst=1,
each floored at 1 -- so the distance floor was applied N times = ~missileCount x
too much splash.
Fix: BTProjectile.splashBurst tags ONLY the salvo-lead round. MissileLauncher::
FireWeapon passes nmiss (the cluster count) on i==0 and 0 on every other round;
the contact + world-impact splash hooks fire only when splashBurst>0, using it as
baseBurst. One splash event per salvo with baseBurst=missileCount -- matches the
single arcade cluster missile. Replicant mirror rounds carry 0 (damage 0 too) so
the master's cross-pod splash isn't doubled. AC unaffected (not a MissileLauncher;
splash_burst defaults 0).
Verified headless (BT_SPAWN_ENEMY=2 clustered rig, 45s): bystander now takes 6
splash events (1/salvo, baseBurst=6) and SURVIVES (was ~2 shots); primary dies to
direct hits in ~3 trigger pulls (36 missiles == single-enemy TTK); AC does not
splash; no crash. Also: BT_SPAWN_ENEMY read as a COUNT (=2 clusters a bystander
within SplashRadius) for eyeballing splash; entity-id logging on [enemy]/[splash].
KB: combat-damage.md documents the N-round cluster trap.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
12fbc023a8 |
Combat: MISSILE SPLASH DAMAGE -- Explosion::SplashDamage reconstructed (task #62)
Area-of-effect blast on missile detonation, from the T0 source EXPLODE.cpp:50-254 (@0042fad0) + the arcade decomp. Fires on ANY missile impact (world or mech) -- the +0x360 gate sits in the Missile::Perform collision branch (part_013.c:10097). Only missiles splash; the AC's tracer is not a Missile. Radius source CORRECTED: it is the ROUND's own GameModel (type-0xf) +0x50, seeded from the launcher's linked AmmoBin ammoModelFile @0x1e8 (part_013.c:8778) -- NOT the launcher's ExplosionModelFile (which resolved to 0). Live-resolved radius = 30 for both MP missile launchers; 0 for AC/Emitters. Burst falloff = baseBurst / dist^exp floored at 1 (arcade 1.25 = decomp 0x3ff40000; WinTesla EXPLODE.cpp:209 drifted to 1.2f). damageType/amount pass through unchanged; only burstCount, radial damageForce, and impactPoint are set. Excludes shooter + direct victim; dist>radius gated; delivered via msgmgr (cross-pod) or Dispatch. New: BTResolveSplashRadius / BTApplySplashDamage (mech4.cpp), hooked at both the world-impact and contact detonation paths; BTAmmoRoundModelResource bridge (ammobin). Env: BT_SPLASH_LOG, BT_SPLASH_TEST (synthetic near-miss), BT_AF_MISSILE (missile autofire). Verified: near-miss dist=15 -> 1 burst to a bystander; live missiles detonate + exclude the direct victim; AC does not splash; no crash. Deferred (T3): per-player enable sub-gate missile+0x360 = BTPlayer+0x264 (writers read as a per-frame toggle, not a config flag) -- port treats SplashRadius>0 as the enable. KB: combat-damage.md + open-questions.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4c54f7ef0c |
Ballistic FX: DAFC muzzle flash + autocannon fire replication (task #61)
The autocannon gains its authentic muzzle effect AND becomes visible when an enemy fires it -- the ballistic-FX gap behind "I only see the AC from my own view." MUZZLE FLASH (the genuine shipped effect, not the cut card): - MUZFLASH.BGF is an orphaned/cut asset (nothing references it) -- so it is NOT rendered. The shipped projectile-gun muzzle effect is DAFC.PFX, which BTDPL.INI documents as "the effect used on all projectile guns" (psfx 6 external / 14 internal): an orange fire-smoke blast (btfx:firesmoke1), maxIssue 25 over ~0.2s so the emitter auto-expires = one burst per shot. - BTFlashMuzzle (mech4.cpp) spawns it on the gun-port SEGMENT via the existing BTStartPfxAttached path; the segment frame sprays -Z out the barrel. Hooked at ProjectileWeapon::FireWeapon (the fire edge). Default ON (BT_MUZZLE=0 disables). AC only -- lasers show their beam, missiles their launch. ENEMY AC FIRE NOW REPLICATES (the real find): - ROOT CAUSE: the subsystem-record replication channel EXISTS and works (mech ticks subsystem->PerformAndWatch(update_stream); Entity::UpdateMessageHandler routes incoming records to GetSimulation(subsystemID-1)->ReadUpdateRecord). The emitter (beam) and MissileLauncher (salvo mirror) both call ForceUpdate() so their fire serializes -> enemy lasers + missiles ARE visible on the peer. The AUTOCANNON set only `simulationFlags |= 0x1` (the +0x28 instance flag, NOT the updateModel bit WriteSimulationUpdate walks) and had NO fire record, so its shot never crossed the wire -- the enemy's cannon was invisible. - FIX (the AC twin of the missile salvo mirror): ProjectileWeapon:: WriteUpdateRecord/ReadUpdateRecord (fire counter + aim) + ForceUpdate() in FireWeapon. The replicant edge-detects the counter and mirrors ONE visual round + DAFC muzzle flash from its own resolved muzzle; a null/untargeted aim streaks straight out the barrel (launchVelocity) instead of toward origin. - Verified live 2-node: the watching node logs REPLICANT AC shots + DAFC flashes at the enemy's gun-port (seg 7), aimed shots fly to the real target; no crashes. TRACER: all ACs author TracerInterval=1 (every round is a tracer); the existing amber streak is acceptable-authentic, so no tracer change was needed. KB: open-questions.md -- CORRECTED the (wrong) earlier note that claimed no subsystem-record channel exists; it does, the AC just wasn't using it. Logged the methodology lesson: the coverage audit finds UNWRITTEN functions, not "reconstructed but inert" ones (a function present but never called -- the AC record + the missile mirror both looked done); a LIVENESS audit would catch that class. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
267059ab88 |
Damage economy RECONCILED: 3 stand-ins fixed, 3 paths confirmed authentic (task #60)
Full 5-path audit of the damage economy vs the decomp (5-finder + adversarial-verify workflow), resolving the KB self-contradiction the binary-coverage audit flagged. CONFIRMED AUTHENTIC as-is (no change needed): - Energy beam (emitter.cpp): damagePortion = authored DamageAmount x (charge/seekV)^2; the ctor x1e7 and fire x1e-7 cancel (_DAT_004bafbc dumped from the exe = x87 80-bit 1e-7 exactly). - Autocannon (projweap.cpp): full authored DamageAmount from resource +0x19C delivered unmodified; the 0.0625 at :667 is the shooter's own gyro recoil, not the round. - Zone-armor BASE model: damageLevel += amount x damageScale[type] (engine DAMAGE.cpp:379, called mechdmg.cpp:427), legs x0.5, 1.0=dead. 3 STAND-INS FIXED (all byte-verified against the decomp): - A. mechdmg.cpp:451 -- read the phantom `stance` member (no binary offset, zero writers -> perma-0), so the leg-shot-out -> fall/death branch was DEAD. Now MovementMode() (mech+0x40, @part_012.c:6910). - B. mechdmg.cpp:458 -- guarded the leg partial-failure graphic on the always-0 IsAirborne() stub where the binary calls IsDisabled() (@0049fb54 = movementMode 2||9). On a wreck the binary SUPPRESSES the write; the stub let it corrupt graphicAlarm 9->4/3 -- the task-#52 wreck-graphic bug, now fixed AT SOURCE (was only masked by the IsMechDestroyed latch). - C. mech4.cpp:1551 -- flat kShotDamage=12 fed as the kill-score damageAmount (the KB self-contradiction: task #8 claimed it retired, but it was live). The score handler @0x4c02e4 derives the whole kill award from it, so every kill scored identically regardless of weapon. Now lastInflictingDamage -- the real killing-blow magnitude, latched in TakeDamageMessageHandler (mech.cpp:624), mirroring the per-hit path (mech4.cpp:1207). The phantom `int stance` slot is reused for the new Scalar member (size-neutral, no layout shift); init 0 in the ctor. DEFERRED (task #60-D, documented): the missile CLUSTER model -- the port fires N flying rounds (net armor total authentic) vs the binary's ONE missile with a random burstCount cluster roll (loses cluster variance + single-zone concentration). Blocked on an OPEN decomp semantic (does burstCount multiply armor or only the gyro kick? settle at FUN_004bef78 -> FUN_004be078 -> EXPLODE.cpp:209-210). VERIFIED live: clean build; 2-node fight -> clean center-mass kill (no crash, kills 0->1); [zone-armor] dump confirms per-zone armor 50-140 + legs x0.5. NB the displayed POINTS score still reads 0 -- a SEPARATE open gap (scoreAward + role/team/tonnage multipliers unwired); fix C corrected the damage INPUT to that formula. KB swept: open-questions.md (self-contradiction resolved + task #60 summary + deferred missile item), combat-damage.md (damageScale is type-indexed not even/odd; task-#52 source fix; kill-score section), RECONCILE.md (missile = ONE spawn not N), stale comments in mechweap.cpp (SendDamageMessage is LIVE), mislanch.hpp, mechdmg.cpp (FUN_0049fb54 = IsDisabled). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2e9c78d604 |
Gait: the AUTHENTIC walk lean -- interior/exterior clip sets, level cockpit + leaning mechs (task #59)
The MadCat (and every leg-mech) leans -8deg into a walk / -11deg into a run --
an authored jointhip (hingex) pose in the EXTERIOR gait clips that the walk/run
CYCLE clips never rebind, so it HOLDS. The 1995 game keeps a separate INTERIOR
('i'-suffix) clip set for the mech you PILOT: those shake jointshakey (cockpit
rattle) and OMIT jointhip, so your own view stays level while everyone else sees
you lean. The port had this entirely dead -- the local cockpit mech pitched
-8deg into the ground (user-reported "staring at the ground").
ROOT CAUSE (two stacked bugs):
- The authentic ctor clip-set gate (@part_012.c:10308-10320) was reconstructed
as no-op stubs: LoadLowDetailBody/LoadHighDetailBody MISLABELED the
FUN_004a80d4/86c8 GAIT-CLIP loader addresses as a "body LOD" pair, so the gate
did nothing and a separate unconditional LoadLocomotionClips always loaded
EXTERIOR clips.
- LoadLocomotionClipsExt (the interior loader) was a stub aliased to the
exterior loader.
FIX:
- Reconstructed the real 4-char INTERIOR loader (byte-exact vs @004a86c8; the
'i'-suffix table dumped from the exe @0x10d74d: swri/wwri/wwli/...; all 18
base clips confirmed present in BTL4.RES for mad/blh/ava).
- Fixed the ctor gate: getenv("L4VIEWEXT") || (instanceFlags&0xC)==4 -> exterior
(replicant / forced external view); else -> interior (local cockpit master).
Removed the mislabeling no-op stubs.
- PORT ADAPTATION (MaintainViewClipSet, per-frame in PerformAndWatch): the port
never sets the replicant COPY bit (all MP mechs build as local masters --
heat-sim/scoring/torso-watcher-connect all run on both, by design), so the
ctor gate lands everyone on interior. Pick the set by VIEWPOINT instead: the
mech you pilot keeps interior (level cockpit), every other mech flips once to
exterior (the lean). Reloads only on a viewpoint-status change; the sets
share stride data so the swap is seamless. Model pointers stashed at ctor.
VERIFIED live (BT_HIP_LOG probe): your walking mech writes jointhip=0 (level);
the peer's replica in the other pod's view leans at exactly -8.0/-11.1deg --
the authored clip values. A/B control: L4VIEWEXT=1 forces the walking master
back to 74 sustained -8/-11 writes.
KB: locomotion.md ("NO walk lean" audit conclusion CORRECTED -- the lean is
authored + sustained + clip-set-specific), asset-formats.md (.ANI parses
pitch/yaw/roll for all joint types; KeyJointPos translation; cycle clips carry
no pitch), open-questions.md (item closed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
02cdfd6576 |
Torso: the TWIST goes LIVE -- electrical watchdog chain, centered crosshair, coherent controls (task #57/#58)
The MadCat torso twists, the view turns with it, and targeting follows. Three reconstruction fronts closed: THE ELECTRICAL WATCHDOG CHAIN (why the torso never powered up): - PowerWatcher::UpdateWatch reconstructed (@004b181c, the REAL registered Performance -- PTR @0050f5fc; Ghidra missed the fn start): the watchdog MIRRORS the watched subsystem's electrical level (+0x278), brownout downgrade when gen output <= minVoltage% x rated. @004b1804 relabeled ResetToInitialState (slot 10) -- the old "Simulation" tag was wrong. - The factory watcher-CONNECT pass reconstructed (vtable slot +0x38, @004aee2c/@004b1a40 byte-identical, recovered from raw exe bytes): watchedLink.Add(roster[watchedSubsystem]) on the master node. Was the SubProxy::Start() no-op -- every watchdog sat at 0 forever. - MinVoltageScale = 0.01 (a 10-byte x87 literal @0x4b1924; was 1.0f = permanent brownout) and PowerWatcher's Derivation chains its REAL base HeatWatcher (the HeatableSubsystem stand-in broke IsDerivedFrom for the whole Torso/Searchlight/ThermalSight family). - KB correction swept: derivation tag 0x50e604 = HEATWATCHER (not "HeatSink"); the btl4gaug heat-widget gate now tests it via the BTIsHeatWatcher bridge. THE CROSSHAIR (task #58 forensics, 6-agent workflow + live probes): - The VIEW is TORSO-MOUNTED: jointtorso -> jointeye -> siteeyepoint in every twist-capable .SKL; the camera + canopy ride the same hinge subtree through HingeRenderable's live matrix-stack compose -- ALREADY WORKING in the port. The crosshair stays screen-centered (center IS the boresight); the twist reads on the tape carets/compass/radar. - The real bug was the port's gBTAimX = tan(twist) slew (the falsified "body-mounted view" model): the camera already carried the twist, so the crosshair counter-slid to hull-forward and the fire ray with it. Deleted; the pick ray inherits the twist from the yawing eye basis. - Two instrumentation traps documented (chase-eye-as-default-camera, BT_FORCE_TORSO clobbering real joints -> the hook now only fills unresolved ones); an over-correcting explicit eye compose was added on those false readings and retired the same day. CONTROLS + REPLICATION: - Q/E spring-center on release (the axis is a twist-RATE demand; the old hold-deflection model drifted forever); X also zeroes the axis and pulses the authentic torso Recenter (@004b6918). M cycles control mode via the real CycleControlMode body. - Torso update-record DIRECTION fixed: engine truth is Write=serialize / Read=apply; @004b6a78 is the READ (was mislabeled Write) and the missing WRITE @004b6a1c recovered from raw disasm (recordLength 0x1C, twist/vel/rate at +0x10/14/18) -- kills the replicant's 0xCDCDCDCD -140-degree ghost twist. - Marching-ghost desync: 4 Standing-case guards zero stale reverse cycleSpeed (negative cadence passed the <= ZeroSpeed stop gate). - Kill credit rerouted to the OBSERVED killer (lastInflictingID -> killer's player link) -- kills count, target K/D populates. KB: subsystems.md (watcher chain), multiplayer.md (record direction), combat-damage.md + gauges-hud.md + cockpit-view.md (torso-mounted view re-correction), decomp-reference.md (new addresses + tag fix), open-questions.md (dead capability-roster loops 2-4, snapshot CD read). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
065c114590 |
Impact-FX FORENSICS wave: the i860 specialfx engine, per-round detonations, the ram economy closed
The "we're off the rails" reinvestigation -- a 5-thread evidence workflow
(logs / PFX data / RES model lists / decomp / port audit) + the i860 firmware
decode, then surgical fixes. Every claim tiered; the wrong turns are on the
record in the KB alongside the corrections.
THE OLD-STYLE SPECIALFX ENGINE [T1, firmware-decoded from VREND.MNG]:
rebuilt scratchpad/i860dis.py (binutils opcode table); mapped the dispatch
(data+0xdd0c; sfx trigger 0xf040cda0, install 0xf040cdc0, step ~0xf0413698,
instance init 0xf04128d8); decoded the heat model EXACTLY: per 30Hz board
frame h *= cool_a; RGB_ch = h_old*(h_new*cook_ch - 0.25) + 0.25 (K=0.25 =
the ember floor @VA 0xF080; kill at h <= 1e-4 @0xF0A0); alpha fades cool_b
x RAW dt (PER-SECOND -- the x30 scaling made laser hits invisible); 7s cap;
y_off = the kill plane; "variance" is DEAD DATA (binary reads "variance",
INI authors "varience"). The 13 descriptors (PPCHit/LaserHit/MissileHit/
Chunks/Sparks/Fireball...) parse from BTDPL.INI and render via the BTPfx
layer -- heat bursts draw the FIERY sheet (brightness over fire), .PFX keeps
GRAYSCALE (authored colours: DNBOOM orange, DDAM gray).
HIT-PACKAGE CENSUS [T1, RES byte-verified]: ppchit=[8] lzrhit=[9] mghit=[7,11]
canhit=[11] mslhit=[10,12,1023] explode=[6]; all 8 mech death lists identical.
Effect routing corrected in BOTH consumers: <100 = specialfx, >=1000 = psfx.
"SILVER MIST x5" ROOT CAUSE: SHKWAVE.PFX (mslhit's 1023) authors maxIssue=5
relPeriod=0.2 rate=1 -- the ONE file where rate contradicts the window; the
emitter trusted rate -> 5 shells at exactly 1Hz. Emission rate now always
maxIssue/releasePeriod.
PER-ROUND DETONATIONS [T1 @004bef78]: every missile round spawns its own
ExplosionModelFile at ITS impact (hull + terrain) -- a volley ripples 12
fireballs like the demos; rack-tube launch spread [T3] (GUIDED rounds only --
the slot-0 deflection sent every AFC100 shell 3.4deg left/2.5deg down: the
phantom "4th gold beam"); replicant salvos detonate too (launcher index rides
the visual push). Missile damage bundles through the shooter's messmgr with
the launcher's subsystemID (mslhit fires at the consolidated point; the
binary's dedup CONFIRMED [T1 @0049b784] -- a workflow agent's per-record
claim REFUTED by direct decomp read).
DAMAGE-BAND SEMANTICS [T1]: MechDeathHandler fires the CURRENT band effect on
any damage rise (the binary's changed-flag coalescing) -- a mauled mech under
fire smokes/burns per hit; bands 3/4 are authored fire plumes.
THE RAM ECONOMY -- CLOSED (3 layers, measured live):
1. armor = POINTS (every zone: damageScale = 1/armorPoints, armor 50-140;
the [zone-armor] BT_DMG_LOG dump);
2. StaticBounce prices rams with authored moverMass ~1.3e6 -> ~59,000 pts
@10m/s; the binary dispatches it RAW but pod MP dropped it on the local
replicant (MECH.CPP:986 warns) -- ram damage was NETWORK-INERT; our
task-#47 replicant forwarding surfaced it as a one-shot. Port
normalizes x1e-3 to the point economy [T3];
3. contact EDGE (ramLastVictim/ramContactLinger): the binary's bounce made
separation implicit; our gait-derived velocity re-priced full rams at
60Hz (the respawn explode-loop). One bump = one hit; pressed = BLOCK.
PLUS: sfx size x0.5 + 1.25m visibility floor + hot-phase occlusion [T3];
particle pool 2048->8192 (missile traffic starved laser bursts -- the
"intermittent effects"); replicant beam aging (a lost beam-END record pinned
a stale beam on forever); UV-variant noise stamps (mask-safe mirror/swap);
no scroll on particle stamps (material-path only); BT_FX_TEST/fxshot.py
self-verification harness ([beam-draw]/[zone-armor]/[collide-tx] telemetry).
KB: rendering.md (specialfx engine + census + closeout), combat-damage.md
(ram economy + band semantics).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
48c9c8444f |
Fire VISUALS wave: the authored firesmoke sheet, vertex-alpha effect cards, the case-4 wreck dressing
The "fireballs like the demo vids" arc, decomp/content-grounded end to end,
plus the live-play UX batch verified over the same sessions:
FIRESMOKE SHEET (the PFX fireball fix): every firesmokeN_scr_tex in BTFX.VMF
maps the SAME 64x64 tileable noise image bintA (variants differ only in SCROLL
rate) and firesmoke1_mtl colours it through the "fiery" ramp (0.3,0.1,0.1)->
(0.9,0.7,0.3). The particle layer now bakes ramp(lum(bintA)) as its sprite
colour (noise detail in alpha) and SCROLLS it at firesmoke1's authored rate
via a texture-transform; the port's radial soft-edge mask moved to a second
CLAMPed stage so the WRAPPED scroll rolls flame through the sprite without
scrolling the edge away. Old grit x radial bake kept as the no-BINTA fallback.
Impact hits, damage-band smoke and death booms all ride this layer.
AUTHORED TEXTURE SCROLL in the model path: the BMF TEXTURE records carry
SPECIAL " SCROLL u0 v0 du dv" (tag 0x2037); the draw path always supported
per-op scrolling (SetTextureScrolling) but the BGF loader never parsed it, so
every scrolling material rendered frozen. Wired TexRef -> MatInfo -> batch ->
L4TEXOP.doScroll: the flame cards (flamebig/fire5) now roll fire noise.
VERTEX-ALPHA EFFECT CARDS (the "twisted drill bit of fire" fix): FLAMEBIG's
verts carry authored float RGBA -- white-hot base (1.0,0.99,0.97) -> dark-red
tip fading to alpha -0.2 (the DPL clamp convention). The loader kept a flat
batch colour and drew it OPAQUE = a solid orange spike. Corpus sweep: exactly
14 shipped BGFs carry vertex alpha, ALL effect cards (flames, MUZFLASH,
EXDISK_A/B/C, TMST_A/B/C, beam models, DECLOUDS). Such batches now keep the
authored per-vertex gradient and route to the alpha-blend pass, unlit,
colour = texture x gradient, alpha = the vertex fade; sky objects excluded
(drawAsSky + alphaTest passes NEITHER pass filter -- DECLOUDS stays in the
sky pass). MUZFLASH/EXDISK render correctly for free when the muzzle-model
work lands.
WRECK DRESSING (the 1996 ExplosionScripts case-4 transcription): pieces spawn
HIDDEN and reveal 0.25s after the boom (the InstanceSwitch delay, behind the
dnboom flash); flamebig hangs over the pile, Y-BILLBOARDED at the camera
(SetOffsetYaw + a camera-pos getter -- the dpl_SetDCSReorientAxes analog);
the MakeDCSFall settle arms at the reveal with the two authored rates (hulk/
debris -0.025 t^2, fires -0.01 t^2 -- the flames ride above the sinking pile
and die with it at burial). EMPTY-PLACEHOLDER hulk guard: THRDBR.BGF is a
153-byte zero-geometry stub that "loads fine" -- vertex-count check now routes
it to the gendbr fallback (a Thor wreck was invisible). Hulk content census
recorded: AVADBR==MADDBR==VULDBR geometry (palette-only prefix diffs),
RAPDBR==SNDDBR==STIDBR byte-identical -- wreck variety is materials + the
dressing, not unique piles.
LIVE-PLAY BATCH: muzzle resolve uses the named segmentIndex (raw +0xdc read
was layout garbage); forward launch frame (authored MuzzleVelocity +Z vs the
mech's -Z facing); dock-bottom single window (gauge strip appended below the
world viewport, 1100x600 default, BT_DEV_GAUGES_WINDOW=1 restores the separate
window); portrait sec surface unrotated CW; ammo counters live via typed
bridges (BTAmmoBinCountPtr/BTAmmoBinFeeding/BTWeaponAmmoBin -- raw bin+0x180
and a hand-rolled link walk were garbage); fourth fire key ('4' = Pinky);
panel/arc probes de-aliased (%61 prime).
KB: rendering.md (vertex-alpha card family + scroll), combat-damage.md (hulk
census + THRDBR stub), gauges-hud.md (ammo bridges).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
bb795e2805 |
MP live-play wave: collision economy, missiles, radar transform, panel polarity, comm ticker
The interactive 2-node playtest wave -- every fix decomp-grounded and live-verified: COLLISION ECONOMY (the ram one-shot): StaticBounce mutates worldLinearVelocity per contact and ProcessCollisionList walks EVERY touched solid per frame; with 2007 terrain-as-solids the reflections compounded x4-x40 within one frame and a walking bump one-shot a pristine mech for 112,375 pts (62-pt authentic economy). Fix: frameEntryWorldVelocity restore per contact (damage always priced at the real approach speed -- all the binary's physics ever saw); Mech::Reset zeroes the mover motion (respawn = teleport); [collide-tx]/[mp-hdlr] telemetry. Gotcha #16 (engine-facility drift class). MISSILES: peer-visible salvos (the launcher record extension carries a salvo counter + aim point; ForceUpdate actually enqueues it -- the dirty flag alone never serialized), the authentic arc (authored MuzzleVelocity vector + the Seeker's 200m/0.1/300 loft + gain-4 steering, decoded from @004beae4/@004bef78), world-impact bursts (rounds detonate on cave geometry instead of phasing through), contact-only damage (flight-cap expiry = fizzle, no more teleport damage), live re-lead, and ballistic (unguided) shells for autocannons. projweap's stale BTPushProjectile extern (the /FORCE signature trap, gotcha #6 corollary) crashed the Avatar's first AFC100 shot -- fixed + sweep rule recorded. RADAR: two transcription bugs made the scope permanently empty -- FUN_0040b244 is the affine INVERSE (not a copy) and FUN_0040adec writes ONLY the 3x3 rotation (never the translation); worldToView now Invert(view) built rotation-first. CulturalIcons sorted out of the moving grid (the phantom red pips were map props), visible-radius culls on all three draw passes, live pip verified at |delta| x ppm px. Gotcha #17 (verify the FUN_ body, not its call shape). WEAPON PANELS (the frozen-dial hunt): the binary's *(subsystem+0x40) means FAILED -- the recon's 'operating' name was backwards, inverting the destroyed-X lamps, the panel look, the children enable and the ready-lamp gate (which had NEVER executed). Polarity chain corrected end-to-end (failedState, fed by real damage saturation). Root cause of the freezes: MFD page-mode gating -- the dev composite shows ALL pages at once, so off-page dials legitimately stopped; under BT_DEV_GAUGES the 15 page-plane bits stay active (the exclusive secondary trio untouched). The SEH gauge guard now names its kills; repaint-heal resets the incremental arc after panel repaints; [panel]/[arc] probes added. COMM/SCORE: MessageBoard LIVE (the engine already shipped the whole Player__StatusMessage queue; wired the binary's one producer -- the kill branch, victim's name, 6s -- plus the consumer bridge and a lazy source bind); MP DEATHS counted via the observed-death tally (each node scores every pilot from locally observed events, the same model as the KILLS credit) and the -2/-1 engine seed clamped for display. DEV UX: node-tagged window titles (-net port), gauge panel reworked (1320x480, true 4:3 MFD cells, the portrait secondary UNROTATED upright, linear filtering, BT_GAUGE_SCALE), fixed close spawns via BT_SPAWN_XZ, Boreas flies an Avatar (first second-chassis live outing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
dd27238ceb |
Gauges: the CLOSEOUT wave -- radar pips, Myomers dedup, attribute parity, ledger sweep (tasks #14-#17)
TASK #17 -- AUTHENTIC RADAR SYMBOLOGY (the cross-blip stand-in retired). The recon proved the 'missing pip raster set' never existed as BT game code: pips are the ENGINE's L4GaugeImage vector-stroke system (T0 source in tree, L4GAUIMA.cpp == FUN_0046f0c0 line-for-line), the 'pip table' is L4Warehouse:: gaugeImageBin keyed by Entity::resourceID, and BTL4.RES ships 110 type-0x12 shapes (every mech/vehicle/building/tree). The six btl4rdr stubs are wired to the real facilities: contacts draw their authentic model silhouettes with LOD selection, the target gets the binary's 4px-inflated box highlight, and player-name labels resolve via Mission::GetSmallNameBitmap (the prebuilt 64x16 egg rasters keyed by the player's bitmapindex). DECODED: the invented 'VideoObject' was Entity::owningPlayer all along (+0x190; nameID = playerBitmapIndex@0x1E0, target = BTPlayer::objectiveMech@0x284 -- new bridge BTPlayerObjectiveMechOf); the 'LabelledEntity' class is Landmark (cultural.h; label path dormant -- no landmark content ships, no runtime landmarkID writer). The L4GREND BT_DEV_GAUGES warehouse guard is removed (its AV had a different culprit, below); resource type 18 corrected to GaugeImageStream in decomp-reference (was mislabeled 'ModelList'). TASK #14 -- the Myomers ODR duplicate ELIMINATED: the powersub.cpp/hpp 'Myomers' (classID 0xBC3 -- actually Sensor) is retired whole; it duplicated ?DefaultData@Myomers@@ against the real class (dumpbin-verified) and /FORCE picked the winner by link order. The real Myomers (0xBC6) now chains PoweredSubsystem's handler set (ids 4-8) and publishes its SEVEN binary attributes (@00511588: SpeedEffect/Current/Recommended/Min/MaxSeekVoltage- Index/SeekVoltage/OutputVoltage) -- the old empty unchained index starved the Myomer engineering panel of every resolve. TASK #16 -- attribute parity: MechWeapon publishes the FULL binary table @0x511890 (11 entries; ids renumbered to binary truth -- the port aliases had squatted the binary's DistanceToTarget/TargetWithinRange ids; the streamed TriggerState 0x13 binding unchanged). Binary names resolved two TODO members: pipState -> estimatedReadyTime (attr 0x1A), and the EXT-model flag is the binary's RearFiring (0x1B). ThermalSight LightState published. HUD (offset conflicts) + missile-side tables (id encoding suspect) documented for a re-dump instead of publishing blind. THE CRASH THIS EXPOSED [T2, cdb-verified]: gotcha #11's dense-table gap is a LATENT AV, not a guaranteed one -- the old table's 0x0D..0x12 gap (task #5) survived on heap luck; the renumber reshuffled allocations and Find() AV'd on a garbage entryName in WeaponCluster's PercentDone resolve. Fixed with five named PAD entries (the mech.cpp attrPad idiom) + a static_assert locking the pad base to PoweredSubsystem::NextAttributeID. Gotcha #11 amended with the proof. TASK #15 -- stale-ledger sweep: GAUGE_COMPOSITE ('composite not yet built', Reservoir shadow, PlayerStatus/vehicleSubSystems 'remaining', valve-dormant- until-0xBD3, sensor guard, the superseded 'Heat MFD near-static' reframe -- all banner-corrected), gauges-hud frontmatter, L4VB16 + powersub comments. Verified live: 50/50 config attribute bindings resolve, 0 NULLs, 0 parse skips, mech spawns and simulates 31/31 subsystems, no cross-blip fallbacks, the pip cache fills through entity registration without the old guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
80cadc98c7 |
Cockpit: the COOLANT VALVE control -- the heat-management triad completes (task #13)
The pilot's third heat tool: Condenser::MoveValve (id 4, the Condenser handler table @0x50E52C -- PE-verified, exactly one entry) registered via the order-proof function-local accessor and GUARDED by the real FUN_004ac9c8 (task #12's BTPlayerRoleLocksAdvanced rookie-role bridge -- the old deferral note blamed the 0xBD3 messmgr, a stale misattribution, swept). Each press cycles the valve 1 -> 5 -> 50 -> 0 -> 1 and RecomputeCondenserValves re-shares the fixed total coolant flow across all six condensers -- opening one valve wide starves the others: the pilot's cooling-triage lever. Desktop: 'C' cycles the selected condenser's valve (BT_VALVE_SLOT picks the roster slot; default Condenser1). BT_VALVE_TEST scripts a headless press. Verified live: press -> "[valve] condenser#1 valveState=5 flow=0.5 (total=10)" -- exactly the authored redistribution math (5/(5+5x1)), through the real role gate. The heat-management triad is now complete and all-authentic: G + fire buttons = regroup weapons across buttons (task #6) F5-F8 / F9 = reassign weapon generators / mode (task #12) C = coolant valve triage (task #13) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d7b900d108 |
Cockpit: GENERATOR POWER ROUTING -- assign weapons to generators (task #12)
The pilot's second heat-management tool: the PoweredSubsystem message table
@0x50F4EC (ids 4-8) registered and implemented -- SelectGeneratorA-D
(@004b099c..@004b0a74: FindGeneratorByNumber roster walk on generatorNumber
@0x1E0 -> AttachToVoltageSource re-tap with tap accounting -> modeAlarm
Connected) and ToggleGeneratorMode (@004b0abc: Manual -> Auto -> detach+Manual
cycle). Weapons inherit via the MechWeapon handler chain. Desktop: F5-F8
assign the selected weapon (BT_CONFIG_SLOT) to Generator A-D, F9 toggles
reconnect mode; BT_GENSEL_TEST scripts a headless verify.
Verified live end-to-end: dispatch -> handler -> re-tap ("PPC_1 ->
GeneratorD (tapped)") -> the charging I^2R physically moved (GenD cold ~90K
baseline -> ~1570K carrying the PPC; GenA relieved), stable over a sustained
autofire soak with thermal-breaker trips.
FOUR defects found and fixed on the way [all T1/T2]:
- THE e17 HEAT EXPLOSION: Generator::SourceLevel misread *(this[0x38]+0x158)
as linkedSinks->heatEnergy; it is the engine-base DamageZone @0xE0 ->
damageLevel [0..1] (the same named-member pattern as the bank radiator's
zone read). A breaker-restarting generator emitted (1 - 4e8) x 10000
volts; squared through the customers' I^2R feed, one restart blew the
whole thermal network to e17. Authentic: a damaged generator yields
proportionally less voltage.
- FUN_004ac9c8 is NOT "IsDamaged": raw body = owner -> mech+0x190 player ->
roleClassIndex(+0x274) == 0 -- the ROOKIE-role lockout for advanced
cockpit systems. New bridge BTPlayerRoleLocksAdvanced (NULL player =
unlocked [T3]; bring-up role 2 = unlocked). The old stand-in gated the
handlers off permanently (healthy subsystems have simulationState==1).
- MESSAGE_ENTRY tables must be FUNCTION-LOCAL statics inside the accessor:
as namespace-scope arrays they are read by other TUs' static-init chains
before their own initializers run -- Build copies zeros and every id in
the table is silently dropped (ids 9/10 only worked by TU-order luck;
both tables relocated; gotcha recorded, reconstruction-gotchas #9).
- The AutoConnect hunt scanned GetSegment() -- the raw @004b0bd0 walks the
subsystem ROSTER (+0x124/+0x128); EntitySegments were being cast to
Subsystems (the classic +0x128 gotcha).
Also: the dense handler table's GAP slots (skipped ids) are uninitialized
heap -- the name-based Find strcmp-walks them and AVs; diagnostic probes use
the id-based Find only (the 1995 binary's own tables carry the same holes).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
092408041c |
Cockpit: the CONFIG-MODE weapon-regrouping session + the live-play fire fixes (tasks #6+#11)
TASK #6 -- the pod's in-cockpit weapon regrouping, fully reconstructed and live-verified (hold-configure -> tap fire button -> toggle membership -> release-commit): - MechWeapon handlers id 9 ConfigureMappables @004b9550 / id 10 ChooseButton @004b95b8 (binary table @0x511860; the old "Myomers fns" mislabel swept) + the GetMessageHandlers() accessor chain through Emitter/ProjectileWeapon/ MissileLauncher/GAUSS/PPC (empty per-class sets swallowed dispatch). - Mapper vtable truth: +0x38 EnterConfiguration / +0x3C ExitConfiguration / +0x40,+0x44 AddOrErase evt/dir. No "secondary vtable @0050f498", no "CreateTemporaryEventMappings" virtual (RP-name drift; swept incl CLASSMAP). L4 Enter/Exit rebuilt complete: StartMappableButtonsConfigure (the NonMapping 0x10000 <-> Mapping 0x8000 mode flip + the gauge's active-weapon latch) + the held-button re-arm + the 4 fire-button temp maps; only the RIO mapper implements the toggle (Thrustmaster no-ops = can't regroup). - MechSubsystem +0xE8/+0xEC corrected to controlDestination/controlMessageID (ex hostEntity/subsystemId2 mislabel); MechWeapon ctor defaults the destination to &fireImpulse (@004b99a8). - ConfigMapGauge state loop reconstructed (PE-recovered DAT_00518eb4 table; buttonGroup GetMapState sampler -- the "needs ModeManager" guard rationale was wrong). Finding [T1]: the shipped binary NEVER enables this gauge (no SetColor caller) -- authentically dormant; BT_CONFIGMAP=1 is the dev enable. - Dev harness: HOLD 'G' opens the session (BT_CONFIG_SLOT picks the weapon); BT_CONFIG_TEST scripts a headless verify. @004afbc4 corrected to its real Fail-trap body (the guessed AddOrErase(NULL) body would corrupt groups). - Bonus [T1]: the binary MechWeapon ATTRIBUTE table has ELEVEN entries (PercentDone..WeaponState) -- "TriggerState is the only one" was wrong. TASK #11 -- the user-reported live-play regressions (both real bugs): - PHANTOM FIRE: the mech4 bring-up shot block painted an explosion at the victim on its OWN 0.3s cadence whenever fire was held -- desynced once the authentic recharges landed. Retired (the real impact visual flows from each discharge via the messmgr SubmitExplosion). - WEAPON BRICKING (the "cuts out" bug): the Loading->Loaded snap window (+-0.01 around seekV) assumes the pod's LOCKED 60 fps; one port dt-spike jumps it, the byte-verified >1.0 clamp (_DAT_004ba830=0) zeroes readiness, and the weapon sticks in Loading at level ~10000 forever (observed live). Fixed with pod-frame (1/60s) sub-stepping of the binary's own Loading tick -- also fixes the I^2R integral over-heating generators on big steps. NEW GOTCHA CLASS recorded: reconstruction-gotchas #12 (frame-pacing trap). - Duty-cycle measured (max-rate autofire): generators equilibrate in the degradation band (recharge stretch ~3.5-4.4x), the FailureHeat breaker never trips solo, PPCs sustain ~11.8-damage full-charge fire. The slow kill pacing and recharge gaps are the AUTHENTIC heat economy; the pilot's counters (this regrouping UI, generator reassignment, coolant valves) are the reconstruction queue. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4e63a7b6c3 |
Heat: THE AUTHENTIC ELECTRICAL MODEL -- weapons recharge from generators (task #10)
Task #10 set out to fix "scrambled linked-sink routing"; a [heat-link] attach log proved the routing was NEVER scrambled (every subsystem links its authored condenser exactly; the "pools in Condenser1" read was the diagnostic-sampler aliasing trap). The real defect: emitter.cpp's local FUN_00417ab4 stub returned NULL, so the whole electrical model was inert and the E7 force-charge recharged every emitter in ONE frame (~0.3s cycle, 1501 fires/90s, ~1.7e9 heat/s -- the "runaway"). Landed authentically [T1: disasm + byte-verified constants]: - Emitter ctor @004bb120: seekVoltage = authored fraction x generator ratedVoltage (10000); EC = energyTotal/(seekV^2 x 0.5); voltageScale@0x310 = (RechargeRate / -ln(1 - 1e-4 x seekV)) / EC -- charge reaches seekV[rec] in EXACTLY the authored RechargeRate (PPC 5s, ERL 4s, SRM 3s, ERM 2s) cold. Owner-flags ctor gate (the usual gotcha; the this-flags read never armed). - PoweredSubsystem::ChargeTimeScale (@004b0d50, was a =1.0 stub): voltageScale x (1 + thermalResistivity x srcTempRise) -- hot generators charge slower. ("voltageScale is never read back" was wrong; corrected + swept.) - TrackSeekVoltage @004ba838: charging I^2R -> the GENERATOR's pendingHeat (~3.5e8/full PPC charge) -- generators self-heat, conduct to their authored condensers, and throttle further charging. The feedback economy closes. - FailureHeat consumers found: this+0x184 == 2 gates BOTH weapon families (@004baa88 emitter: reset firing + hold charge 0; @004bbd36 ballistic: recoil=rechargeRate + alarm 7). Emitter::GetFaultState un-stubbed. - ProjectileWeaponSimulation @004bbd04 opens with call 0x4b0bd0 (disasm) -- launchers now run the powered/heat step (their firing heat previously accumulated in pendingHeat forever). - heat.cpp: per-instance BT_HEAT_LOG census (the old shared-static 1-Hz sampler aliased); [heat-link] attach log; the stolen-else Verify restored. Verified live (120s max-rate autofire): PPC ~470-500 (was 55,000), bank plateaus ~600 and sheds to ambient, generators 1100-1400, ERMLaser self-regulates at the authored 2000 failure threshold (shutdown-cool-resume). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4ed2bbc293 |
Heat: authentic 1e7-unit heat + the ambient radiator land (task #9)
- Emitters: heatPortion closed form = heatCostToFire x 1e7 x (charge/seekV)^2 (PPC 1.1e8 -> +632K on its own sink); the missing projectile/missile heat adds (raw -- resources author pre-scaled 1e7 units). - The bank's AMBIENT RADIATOR @4ae73c reconstructed (the system's ONLY heat exit; conductance x 0.1 x HeatSinkCount -- _DAT_004ae974 float80 = 0.1) + the link-attach guard corrected (skip = the 0xBBE bank, not Condenser; the inversion blocked condenser->bank links and closed the system). - Constant corrections (all byte-verified float80s): coolant epsilons 0.0025/0.003/1e-4 (was a single 1e-4 serving three sites); CoolantCapacityScale 0.05 (was 1.738). - Verified: heat flows + exits; max-rate autofire overheats weapons into the authentic heatLoad range-cutout (thermal spam unsustainable by design). - KNOWN REMAINING: linked-sink routing scrambled (heat pools in Condenser1; authored map says PPC->C4/C6) -- the heat-stream offset audit, filed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fd055281a8 |
Combat: THE AUTHENTIC DAMAGE ECONOMY -- authored per-weapon amounts through the real fire chain (task #8)
Three root causes, all fixed: 1. kDamageScale was 1.0 -- _DAT_004bafbc is an x87 float80 = 1e-7, cancelling the ctor's x1e7: damagePortion = authored DamageAmount x (charge/seekV)^2 (closed form at fire time; madcat AC=25/LRM=50/ERLL=6, bhk1 PPC=12/SRM=35). The observed "0.25" was the degenerate EC=1 fallback, never authored data. 2. CheckFireEdge NaN latch: TriggerState carries ControlsButton INTS; the release value (-65) is a negative NaN. The binary's x87 unordered compare read it as "released"; IEEE-correct float compares latched the edge detector shut after the first release -- the reason the emitter discharge chain NEVER fired in-game. Fixed with bit-pattern sign compares. 3. The weapon-side submission LIVE: Emitter::FireWeapon fills damageData (amount + damageForce=target-muzzle [the gyro directional-bounce feed] + impact) -> MechWeapon::SendDamageMessage (@004b9728 real body) -> messmgr consolidation with per-weapon records + explosion bundling. The mech4 bring-up damage block + flat kShotDamage retired to diag hooks. Bonus: LODReuseHysteresis 0.82 -> 0.33 (double misread). Zone model verified byte-exact (no change). Solo end-to-end: 5-record volleys (2 PPC + 3 ERML with authored amounts), per-weapon zone granularity, explosions, kill. Heat stays bring-up scale pending the heat-calibration audit [T3]. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
77190c93e5 |
Combat: the AUTHENTIC consolidated damage delivery -- TakeDamageStream through the message manager (task #7 tail)
ConsolidateAndSendDamage @0049b784 fully implemented: the beam path submits
into AddDamageMessage; each messmgr tick builds ONE Entity::
TakeDamageStreamMessage (id 0x13; wire-verified 0x34+4+Nx12) with the
common impact record + appended {type, amount, subsystemID} entries and
Dispatches it at the victim -- the T0 handler (ENTITY.cpp:817) re-splits
into per-record TakeDamage; replicants reroute cross-pod. Real
ResolveExplosionID (firing weapon's +0x3E4, guarded) + SubmitExplosion via
the Explosion::Make port; TWO chain-purge bugs fixed (the "iterator dtor
clears it" assumption was wrong -- records re-applied every tick, observed
1->2->3->4 double-counting). @004b9728 identity corrected (SendDamageMessage,
not DrawWeaponPip). Verified solo: 25 clean single-record consolidations ->
kill -> death transition. Residue: weapon-side submission awaits the
damage-economy reconciliation (authored 0.25-scale vs kShotDamage=12).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
8ed6184d65 |
Combat: AUTHENTIC weapon groups -- streamed per-mech button bindings + the real fire chain (task #5)
The recovered system: fire channels = LBE4ControlsManager buttonGroups (0x40/0x45/0x46/0x47); default groups = the per-mech type-6 controls-map resource in BTL4.RES, installed by the T0 CreateStreamedMappings the port already called -- it needed only the TriggerState attribute (id 0x13 PINNED to the binary value; fireImpulse@0x31C is the binary's TriggerState) and an input feed. Keyboard/harness now push press/release edges into the button groups; the gBT*Trigger bypasses, per-type keyboard split and 1,0 pulse hack are retired -- weapons sharing a button fire TOGETHER (madcat Trigger = 4 weapons). Myomers @4b9550/@4b95b8 misattribution corrected (they are MechWeapon ConfigureMappables/ChooseButton). Verified 2-node: kill through the authentic chain (12 hits vs ~36 pre-groups). Config-mode session (regrouping UI) = the remaining stage, KB-scoped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4554ea543a |
KB: locomotion turn-rate note -- authored rates stream correctly post task #4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
63206d7f19 |
Model record: the authoritative 200-byte overlay -- struct skew fixed, floor-25 era retired (task #4)
Two size errors (animationPrefix char[12] vs 4 bytes; a FICTITIOUS 64-byte skeletonName block -- the record has no skl name) skewed every member after word 0x10 by +2/+15 words and pushed the fieldXX block past the record end. Full writer map recovered (FUN_004a2da8 + Mover FUN_004238bc + Reticle FUN_00435ac8 chains) + runtime reader map (ctor pass @0x4a24d1-0x4a2968, capstone-verified). Struct rewritten as the exact overlay (static_assert- locked); all Pass-2 consumers renamed to real fields; retired: ctor + replicant + master-gait floor-25 hacks, the throttle guard, the fictitious classID stamp. Verified: accel=30 superStop=10 throttleAdj=1 (was ~1/OOB/0), deadbands byte-identical via named fields, full walk->run lifecycle clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
001ca16af9 |
KB: crit-propagation landing recorded (task #2)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
87c25b9206 |
Combat: critical-subsystem plugs BOUND -- zone destruction damages carried subsystems (task #2)
The binding was in the zone ctor all along: Ghidra dropped the two arg pushes @0049d0e1 (Slot::AddImplementation(subsystemArray[streamedIndex])), making it read as a bare Resolve(). DZSlot stand-in -> engine SlotOf<T>; SendSubsystemDamage rewritten to the recovered @0049c9a8 body (allotment into the subsystem's OWN private zone; vital -> graphicAlarm 9); CriticalHit -> real ApplyDamageAndMeasure; parentArtifactZone.Add revived (LOD damage averaging); videoObjectFlag renamed vitalSubsystem (+0xE4). BT_CRIT_PROBE diag added. Verified: 66 plugs bound/mech; probe-destroyed zone -> crits damaged/DESTROYED, statusAlarm + destroyed-skin chain fire; MP kill + solo un-regressed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e8b9f71025 |
KB: task #1 landings -- update-record channel DONE, movementMode=simulationState identification swept
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3c34ae6de6 |
KB: full staleness audit + sweep -- 35 verified corrections across 16 topics, 15 new glossary terms
Adversarially-verified audit (18 agents) against the task #46-#56 landings: MP/current-state understatements, superseded punch readings, retired env gates, dead paths, line-cite drift; env-gate hub table completed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2abfd51350 |
KB: sweep stale MP open-questions -- cross-pod combat + replicant gait are DONE; the real gap is @0x4a0c2c
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1ace2e3cb4 |
Gyro: BT_GYRO_TRACE per-frame integrator trace -- oscillation verified vs authored constants (task #56)
25-hit MP autofire trace: kick -> damped oscillation -> settle, peaks well inside clamps, no NaN/drift; measured Y:X frequency ratio 7.75x matches sqrt(springK.y/springK.x) exactly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
27959990ea |
View: the INI viewangle is the HORIZONTAL field -- authentic 60x47 frustum (task #55)
Community reports said the camera looked too far back and the cockpit frames were never this revealed; the user's hypothesis (our projection widens the view) was exactly right. The original Tesla projection call, preserved in a comment at DPLRenderer::SetView, is dpl_SetViewProjection(view, -1, -aspect, +1, +aspect, 1/tan(viewangle/2)) i.e. BTDPL.INI's viewangle=60.0 is the HORIZONTAL field of view with the vertical derived by aspect: the pod's frustum was 60 x 46.8 deg at 4:3. The port fed the 60 to D3DXMatrixPerspectiveFovRH as the VERTICAL fov -> 75 x 60 at 4:3 (worse on widescreen): the world rendered ~25% farther away and far more canopy was visible than the pod ever showed. Fix: BTFovYFromHorizontal(viewangle, aspect) at all five projection sites (main, per-frame resize, sky pass, WM_SIZE rebuild, fallback) -- the horizontal field stays the authentic 60 deg at any window shape. The aim boresight reads the live projection (gBTAimP22) and adapts; combat re-verified (kill chain, no NaN). Cockpits now show the frames at the pod's framing: viewports dominant, frames at the periphery. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
96b8892e7c |
Cockpit: the authentic punch STENCIL-CUT -- all 8 cockpits real (task #55)
The user caught the inversion (blocked viewports, see-through frames); the
answer came from the rasterizer board itself: an i860 disassembly of the
VREND.MNG firmware decoded the damageize handler (@0xf040f6f8) -- cmd 0x20
(vr_damage_action) writes the dpl_Punchize token triple onto the punch
geogroup's first three geometry chunks IN FILE ORDER:
chunk1 = the detailed window-aperture shapes -> an invisible per-pixel MASK
chunk2 = the coarse hull -> the ONLY colour-drawn geometry (where not masked)
chunk3 = byte-identical hull twin -> the damage-reset record, never visible
Visible canopy = hull-minus-apertures. Both earlier readings were wrong ways
around the same three chunks: dropping all punch left floating fragments (no
hull); hiding the duplicate pair hid the HULL and drew the MASK -- the exact
inversion observed.
Port: bgfload tags per punch patch {non-paired pmesh = mask (role 1),
first twin = hull (role 2), second twin = skip}; L4D3D DrawMesh runs
mask->stencil-1 (no colour/z-write, z-tested), hull->stencil-NOTEQUAL,
mask->stencil-0; device D24X8 -> D24S8 + stencil clear. The cut never
touches z, so later-drawn entities show through the apertures.
BT_COP_PLATES=1 disables (diag).
In-game, all 8 mechs on pure defaults: connected dark frames with clear
viewports -- thor (the footage mech) shows the central viewport + surrounding
frame exactly as filmed. Combat regression clean (kill + no NaN).
Arena textured punch uses the same firmware kit; the black-texel alpha path
there is a working approximation, flagged in the KB.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
9414268138 |
KB: sweep the superseded punch-patch claims (task #55 resolution)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8057c37e5b |
Cockpit: punch patches ARE the windows -- all 8 mechs authentic (task #55)
Resolved the non-Madcat blocked views. Rendering every *_COP shell offline from its mech's AUTHORED eye (SKL [jointeye] translation -- incl. the Thor's +1.13 offset cockpit) with faces coloured by punch flag showed the answer plainly: the PUNCH-tagged patches sit exactly over the window apertures in all 8 mechs. On these texture-less black-diffuse shells dpl_Punchize renders the tagged geogroup as holes -- the punch patches are the transparent WINDSHIELDS, and the visible frame is only the non-punch geometry. The loader now drops the punch batches of _cop meshes (bgfload finish(); BT_COP_KEEPPUNCH=1 keeps them for diagnosis). In-game sweep on pure defaults: - madcat: the dark dome frame (matches gameplay footage) - thor / owens / sunder: authentic brace/dash/pillar remnants - bhk1 / loki / vulture / avatar: authentically FRAMELESS (all-punch canopies -- the pod showed no frame for those mechs) (An early-session test that "disproved" drop-punch ran with the broken pre-inverse eye; its conclusion was wrong and is corrected in the KB.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
946323b696 |
Gyro: damage fan-out FUN_004b2980 -- the cockpit hit-BOUNCE is live (task #56)
Re-disassembled the unexported gap 0x4b2980-0x4b2d8b byte-exact (capstone over
section_dump.txt) and reconstructed Gyroscope::ApplyDamageResponse:
- zero-damage + Collision(type 0) no-op; hit direction = Damage::damageForce
or a RANDOM horizontal fallback when ~zero (per-component sign roll then
value roll -- binary-legal until senders fill damageForce)
- direction rotated by the yaw-only torso-twist frame (placeRot=(0,twist,0)
-> euler->matrix, the FUN_00408744 raw row-dot reproduced verbatim)
- per-type scaling amount / damageMultiplier[type] * damageResponse[type]
.{trans,pitchRoll,yaw,vibration}; Explosive alone multiplies burstCount;
each clamped at 1.3f
- four kicks: impulse(trans, dir), torque(pitchRoll, dir), impulse(vibration,
vibrationDirection@0x390) and verticalImpulse(yaw, dir)
Layout corrections (byte-verified): gyro+0x390 is ONE Vector3D vibration axis
init (0,1,0) -- was mis-split as scalars animationOffset/Scale/Phase; mech
+0x5c4 is a FLOAT rumble-period countdown (gyroRumbleTimer) -- was mis-typed
int clipLoadGuard.
Call sites wired (binary-gated):
- take-damage hub: mech.cpp TakeDamageMessageHandler, FIRST action (@0x4a0264
in hub FUN_004a0230) -- an invalid-zone hit still bounces
- crushable-icon crunch: mech4.cpp, torque 0.4 along the bounce delta-v + up
impulse 0.2 (@4aa81e/@4aa86c)
- firing recoil: projweap.cpp FireWeapon, damage>3 -> impulse (0,0.6,-1.5)
x damage/16 (@4bc136-4bc19c) via the new Mech::GetGyroSubsystem accessor
DEFERRED (recorded in KB with the byte recipe): the alternate-gait engage
jolt + 0.4s rumble (@4aa158-4aa365) -- its gates are [T3]-flagged and it
mutates the #49/#50-stabilized gait machine.
Verified live: [gyro-dmg] fires per hit with correct type scaling; eyePosition
shows the damped bounce (ramp to ~0.015u, oscillating Y, decay to zero); kill
chain un-regressed; no NaN.
Also recorded in KB: the OFFSET-COCKPIT verification -- Thor's authored
jointeye tranx=+1.13 matches its canopy X-center exactly AND our in-game eye
reproduces it to the hundredth (the placement model is correct per-mech); and
the playable roster is 8 models (strider/raptor/firstrtr/blkjack have assets
but no model resource -- BT_FORCE_MODEL with those names = NULL-resource AV,
cdb-verified, not a code bug).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
cb85517ede |
Gyro: byte-exact re-enable -- ctor map, integrators, jointeye writers (task #56)
The Gyroscope subsystem is LIVE again (was a stub since the NaN revert).
Reconstructed byte-exact from @004b3778 / @004b2ec0 / @004b30ec / @004b33e0 /
@004b34ec + the byte-verified dispatch in the unexported Mech performance
FUN_004a9b5c (calls @0x4aaf74/0x4aaf83):
- ctor field map corrected: springConstant@0x1E8 / dampingConstant@0x1F4 were
mislabelled (the old 'eyeOrientation = r->springConstant' poison line), the
0x254-0x2B3 block (externalPitchPtr, work matrix, placement) was missing so
everything after was mis-offset, and clamps/accumulators were 0xCD fill --
every field now initialised per the binary; 33 offsets static_assert-locked,
sizeof == 0x3D0 exact
- integrators: state-minus-target displacements, componentwise damping
OVERWRITE (FUN_004086d0 is a component multiply, not a cross), position step
without dt, clamp to [posSpring, negSpring]; IntegrateBody's X/Z-crossed
force + integration terms
- writers: WriteMechJoint drives ONE node ('jointeye', type 5): TRANSLATION =
eyePosition spring, ROTATION = bodyOrientation spring; WriteEyeJoint is a
multiplicative sway attenuator on 'jointlocal' (post-anim only)
- dispatch moved out of GyroscopeSimulation into the Mech performance tail
(GyroFrameJointWrite bridge, mech4.cpp) with the binary gates; gyro<->torso
pitch link wired (gyro+0x258 = &torso currentTwist) via complete-type-TU
bridges, retiring the SubProxy::linkTarget landmine
- Mech ctor: deathAnimationLatched/legResetLatch were never initialised --
0xCDCDCDCD gated the writers off silently
Runtime-verified: joints resolve (both BallTranslation), Performance installs,
WriteMechJoint finite from frame 0 (no NaN), Madcat cockpit un-regressed,
combat targeting healthy. Empirical: spring targets are SYMMETRIC -> eye
equilibrium (0,0,0), clamps +/-0.1-0.15u -- the gyro is the hit-BOUNCE
mechanism, not a steady eye offset (hypothesis disproven, recorded in KB).
Pending (task #56 tail): the damage->gyro fan-out FUN_004b2980 (unexported
gap) so hits actually kick the springs; the mech+0x3F0 overspeed sway model;
the torso-pitch EyepointRotation writer (FUN_004b66b4) + glance-look states.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
7b0b4f64ab |
Diag: add BT_SHOT backbuffer dump + correct/park the -net render glitch (task #53)
Scouted the "-net node render glitch". Two prior framings were wrong and are corrected in the KB: it is NOT device contention (retracted earlier), and it is NOT a from-load glitch -- the trigger is a RESPAWN (user correction). It's a post-respawn render-rebuild fault on the MP path. Could not reproduce in 9 automated 2-node loads + a forced death/respawn (all clean). Debug uninit is a deterministic 0xCDCDCDCD, so the intermittency implies a race -- and the task #52 respawn fixes (LoadMission re-entry, viewpoint relink, death-latch) plausibly already closed it. Parked as WATCH; re-open if it recurs. Tooling (general, off by default): BT_SHOT=<path> dumps a per-instance backbuffer PNG from the world-render path (L4VIDEO.cpp) every 90 frames -- lets us capture a specific node's frame without foregrounding the window. Used with scratchpad/hunt53.sh (N-run divergence hunter) to verify a fix if the glitch returns. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
efa7aeb683 |
KB: retract the GPU-contention claim -- the -net node glitch is MP-path-specific (two solo instances both clean)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |