diff --git a/context/open-questions.md b/context/open-questions.md index cc39a20..744d563 100644 --- a/context/open-questions.md +++ b/context/open-questions.md @@ -161,8 +161,13 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR (task #8 "landed" vs the kShotDamage=12 bring-up residue) — audit before reworking. ## Deferred subsystems / feeds (authentic path scoped, marked in code) -- **Myomer seek "SUPERCHARGE" — 4.0-manual vs 4.10-binary, AWAITING THE PLAYER VERSION-PROBE - (2026-07-31).** The 1995 manual (p20/p22 + stat sheets) says seek changes TOP SPEED (gear 4 = +- **~~Myomer seek "SUPERCHARGE"~~ — ✅ RESOLVED same day: the players' testimony (Oracle: + seek-4 humanoid = 182 kph, overheat = freeze) prompted a re-audit that found the coupling in + the un-exported master-perf gap (@0x4a9cf2). The manual was right, 4.10 HAS supercharge, and + the port now implements it byte-grounded ([[subsystems]] WAVE 6 correction). LESSON: a + "no callers in the decomp" claim is only as good as the export coverage — sweep the raw image + for the operand pattern before declaring a data path dead ([[reconstruction-gotchas]]). + Original entry below for the record:** The 1995 manual (p20/p22 + stat sheets) says seek changes TOP SPEED (gear 4 = "Supercharge", expert-only; Loki/Thor print Normal 143 / Super Charged 182 / Gimped 40 kph). The 4.10 binary provably has NO runtime myomer→speed coupling (full data-flow closure [T1]: demand fn @004afd10 all branches, all speedDemand writers, AvailableOutput's 2 callers, the diff --git a/context/pod-hardware.md b/context/pod-hardware.md index 3f83c74..562c6c4 100644 --- a/context/pod-hardware.md +++ b/context/pod-hardware.md @@ -208,7 +208,15 @@ every checked control behavior: side button columns + **EJECT button** beside the joystick + per-mech COOLANT LOOP tables (weapons+generators per loop 1-6) and stat sheets (tonnage/armor/reservoir liters/heat sinks/top speeds incl. "Super Charged"). -- **MYOMER SEEK / "SUPER CHARGE" — a documented 4.0→4.10 DRIFT (2026-07-31) [T1 both sides].** +- **MYOMER SEEK / "SUPER CHARGE" — ⚠ the "4.0→4.10 drift" verdict below was WRONG and is + RETRACTED (same day).** 4.10 HAS the seek→speed coupling — in the un-exported master-perf + gap (@0x4a9cf2: `speedDemand *= max myomer speedEffect`, turn-freeze at dead drive; see + [[subsystems]] WAVE 6 correction). The manual is ACCURATE for 4.10; Oracle's pod memory + (seek-4 humanoid = 182 kph = the printed figure) confirmed it and prompted the re-audit. + The paragraph below is retained as the record of the error (its BINARY facts — the two + exported AvailableOutput callers, the single attr ref — were correct but incomplete: + export-gap functions are invisible to decomp text sweeps). + ~~ORIGINAL (WRONG) VERDICT:~~ Manual p20/p22: seek "translates to a lower or higher top speed", gear 4 is named **Supercharge** (screenshot caption "Seek Level Is Set to Supercharge"), expert-only ("in standard simulation mode, you are not able to supercharge"), and "overheated myomers can diff --git a/context/reconstruction-gotchas.md b/context/reconstruction-gotchas.md index 91ce9ef..b0a8751 100644 --- a/context/reconstruction-gotchas.md +++ b/context/reconstruction-gotchas.md @@ -700,3 +700,20 @@ damage isn't only cross-TU reads (gotcha #23) — it is **which half rides the w **Tool:** scope the trap. `StateIndicator`/`Simulation` state traps drown in subsystem churn — filter to one watched object (`g_btGimpWatchMech`) and print the caller module-relative (`btl4+0x…`) so `tools/symcrash.py` names it. That turned a two-hour guess into one line. + + +## §21 — Export-gap blindness: "no callers in the decomp" is NOT "no callers in the binary" +**(2026-07-31, the myomer seek retraction — the costliest wrong verdict to date.)** The Ghidra +export has GAPS (gitea #60): whole functions absent from `reference/decomp/all/*.c` — the +master-perf region 0x4a9770-0x4ab188, the 0x4a03xx crit caller, @004b838c, and others. A text +sweep of the decomp for callers/readers of a symbol **cannot see a consumer that lives in a +gap**. The myomer `speedEffect@0x31C` demand feed was declared dead on exactly such a sweep +("two AvailableOutput callers, one attribute reference, no raw readers") and an already-correct +port mechanism was deleted on that basis — while the real consumer sat un-exported at +@0x4a9cf2 (`speedDemand *= max myomer speedEffect` + the dead-drive turn freeze). Field +testimony (Oracle: pod seek-4 = 182 kph, the manual's printed figure) forced the re-audit. +**RULE: before declaring a data path dead, sweep the RAW IMAGE for the operand pattern** — +capstone over the CODE section for the addressing form (e.g. FPU reads of `[reg+0x31C]`, SIB +`[reg+reg*4+0x330]`). Minutes of scanning; it found in one pass what three decomp sweeps +missed. Corollary: when the decomp and a PRIMARY SOURCE (the manual, a pod veteran) disagree, +treat the disagreement as a hole in YOUR evidence first, not in theirs. diff --git a/context/subsystems.md b/context/subsystems.md index 5076441..b8cf988 100644 --- a/context/subsystems.md +++ b/context/subsystems.md @@ -70,10 +70,20 @@ Making a base byte-exact GROWS every subclass — they must be re-based TOGETHER was built on a misattribution.** `@004b9550/@004b95b8` (the "ConnectToMover/DisconnectFromMover" bodies) are **MechWeapon::ConfigureMappables/ChooseButton** — their `+0x31C` is the weapon TriggerState and `**(mech+0x128)` is the CONTROLS MAPPER's button roster (proven by the - MECHWEAP.CPP assert string + the trigger-edge detector @004b9608 adjacent). **The binary has NO - dynamic myomer→speed feed**: `AvailableOutput` has exactly TWO callers in the whole image — - `RegisterMaxOutput` (assembly) and the SeekVoltageGraph sampler — and `speedEffect@0x31C` is a - published GAUGE attribute. The authentic myomer system, all live in the port now: + MECHWEAP.CPP assert string + the trigger-edge detector @004b9608 adjacent). + **⚠ SAME-DAY CORRECTION (the seek audit): the binary DOES have a dynamic myomer→speed feed — + it lives in the un-exported master-perf gap** (@0x4a9cf2-0x4a9da4, raw capstone; the same gap + that hid the #93 crash block). Per frame: MAX `speedEffect@0x31C` over the myomer chain + (+0x7AC) → `mech+0x79C` → **`mapper->speedDemand@0x128 *= it`**, and at |drive| ≤ 1e-4 + (@0x4ab16c) the mapper's `turnDemand@0x12C` is ZEROED — dead/overheated myomers freeze speed + AND turn. The morning's "no feed" verdict was export-gap blindness (text sweeps cannot see + un-exported functions; the decisive tool was a raw-image capstone scan for FPU reads of + +0x31C). Field truth agrees: Oracle (pod veteran) — seek-4 humanoids ran 182 kph (the + manual's printed Super Charged figure), overheat = "freezing up". The port restored the + demand scale byte-grounded (mechmppr.cpp): MAX over the chain, UNCLAMPED (gear 4 rides ~1.43× + demand into the RegisterMaxOutput cap = SUPERCHARGE), turn-freeze included; damage slows + (speedEffect carries 1−damage — gitea #75's original premise was RIGHT). + The rest of the myomer system, all live in the port: 1. **Heat** — the drive-heat integrator (#85, below). 2. **Vital death** — myomers are the ONLY `vital=1` subsystem in the authored records (BTL4.RES `res+0x48` scan): destroying them KILLS the mech (the #80 vital-crit path). This @@ -95,12 +105,9 @@ Making a base byte-exact GROWS every subclass — they must be re-based TOGETHER on a damaged generator freezes the pilot's AIM until they downshift. On a healthy mech, gears 1-3 are literally identical (heat ratio floors at 1, no speed coupling); gear 4 = double heat + a tighter dropout threshold for nothing but the taller graph — a trap. - **Deliberately REMOVED (was an invention): the `speedDemand *= speedEffect` demand multiplier** - (mechmppr.cpp) and `BTMyomersDriveOf`. Damaged-but-alive myomers heat faster and drop the - power curve but do NOT slow the mech — gitea #75's premise is falsified by the binary; the - mech's damage slowdown is the LEG-damage gimp gait, a separate live mechanism. Seek gears do - NOT change speed (heat + sourcing + graph only). Verified live: a mech with dmg=0.234 - (speedEffect attr 0.766) beelines at full commanded speed with dmgGain=1.234 heat. + (A `speedDemand *= speedEffect` multiplier was removed in the morning commit and RESTORED, + byte-grounded and improved, the same day — see the correction above. Verified live: healthy + drive=1.0, collision-rattled myomers drive 0.89→0.74 with demand scaling in lockstep.) **✅ MYOMER DRIVE HEAT LIVE (#85, 2026-07-31) [T2].** The five Mech motion operands of the drive-heat integrator `@004b8d18` were `return 0.0f` cross-family shims, so the (fully reconstructed) integrator accumulated `ratio² · damageGain · 0` every tick — myomers ice cold at diff --git a/game/reconstructed/mechmppr.cpp b/game/reconstructed/mechmppr.cpp index 1581a77..e8e5773 100644 --- a/game/reconstructed/mechmppr.cpp +++ b/game/reconstructed/mechmppr.cpp @@ -969,19 +969,49 @@ void speedDemand = -mech->reverseStrideLength * throttlePosition; } - // (The myomer `speedDemand *= speedEffect` DRIVE SCALE is GONE -- myomer- - // system completion, 2026-07-31. It was an invention built on the - // ConnectToMover misattribution: the binary has NO dynamic myomer->speed - // coupling. AvailableOutput's only callers are RegisterMaxOutput (the - // assembly-time gait cap, now live via MyomersSimulation) and the ENG-page - // graph sampler; damaged-but-alive myomers heat faster and drop the power - // curve but do NOT slow the mech, and DESTROYED myomers KILL it (the - // authored vital flag + the #80 vital-crit death). GIMP slowdown: the - // binary's real mechanism is the gimp gait machines -- the LIVE demand is - // clamped to the wg clip's speed and the limp cadence enforces it; - // BT_GIMP_SPEED remains as an optional override multiplier (unset = 1.0 = - // authentic).) + // (History note: this block was briefly REMOVED on the morning of + // 2026-07-31 on the claim "the binary has NO dynamic myomer->speed + // coupling" -- an export-gap-blind conclusion, retracted the same day + // when the pod veterans testified seek 4 = 182 kph and the raw-image + // float-read sweep found the consumer inside the un-exported master-perf + // region. The restored form below is now BYTE-GROUNDED, and better than + // the pre-removal draft: MAX over the myomer chain (not first-found), NO + // 1.0 clamp (overdrive is real), and the turn-freeze at dead drive.) { + // THE MYOMER DRIVE SCALE -- RESTORED + BYTE-GROUNDED (2026-07-31, the + // seek-audit correction). The morning's removal ("the binary has NO + // dynamic myomer->speed coupling") was EXPORT-GAP BLINDNESS: the + // master perf's un-exported region (@0x4a9cf2-0x4a9da4, raw capstone + // -- the same gap that hid the #93 crash block) walks the mech's + // MYOMER capability chain (+0x7AC) every frame, keeps the MAX + // speedEffect@0x31C in mech+0x79C, and MULTIPLIES the mapper's + // speedDemand@0x128 by it in place; when |max| <= 1e-4 (@0x4ab16c) + // it ALSO zeroes the mapper's turnDemand@0x12C -- dead/overheated + // myomers freeze BOTH speed and turn ("less than a minute at seek 4 + // before overheating and freezing up" -- Oracle, who put pod seek-4 + // humanoids at 182 kph: the manual's printed Super Charged figure). + // speedEffect = (gear/recGear) x heatFactor x (1 - damage), NO 1.0 + // clamp: gear 4 rides ~1.43x demand into the RegisterMaxOutput gait + // cap (base x 1.4284) = SUPERCHARGE. [T1 disasm] + Scalar drive = 0.0f; // mech+0x79C accumulator + int myomers = 0; + extern Scalar BTMyomersSpeedEffectOf(void *subsystem); // myomers.cpp (-1 = not a Myomers) + int n = mech->GetSubsystemCount(); // chain-walk emulation (the + for (int i = 2; i < n; ++i) // +0x7AC fill is the SubProxy stub) + { + Scalar f = BTMyomersSpeedEffectOf(mech->GetSubsystem(i)); + if (f >= -0.5f) // a Myomers (value may be 0) + { + ++myomers; + if (f > drive) drive = f; // MAX over the chain (@0x4a9d20) + } + } + if (myomers > 0) + { + speedDemand *= drive; // @0x4a9d63: in-place demand scale + if (fabsf(drive) <= 1.0e-4f) // @0x4a9d89 vs _DAT_004ab16c + turnDemand = 0.0f; // @0x4a9d9e: mapper+0x12C -- the FREEZE + } extern int BTMechGimpLevel(void *mech_v); // mechdmg.cpp (the TU-safe read) int mm = BTMechGimpLevel(mech); // the gimp cell (see above) if (mm == 3 || mm == 4) @@ -1001,7 +1031,8 @@ void if (s_dAcc >= 1.0f) { s_dAcc = 0.0f; - DEBUG_STREAM << "[drive] mm=" << mm << " dmd=" << speedDemand + DEBUG_STREAM << "[drive] drive=" << drive << " nMyo=" << myomers + << " mm=" << mm << " dmd=" << speedDemand << " mech=" << (int)mech->GetEntityID() << " @" << (void *)mech << "\n" << std::flush; } diff --git a/game/reconstructed/myomers.cpp b/game/reconstructed/myomers.cpp index 01e2e13..8536a65 100644 --- a/game/reconstructed/myomers.cpp +++ b/game/reconstructed/myomers.cpp @@ -414,13 +414,25 @@ Scalar Myomers::SeekVoltageResponse(Scalar input_voltage) // (see emitter.cpp BTSeekVoltageSample). Guarded: a non-Myomers subsystem // samples 0 (only emitter/myomer pages own a graph in the shipped config). //*************************************************************************** -// (BTMyomersDriveOf is DELETED -- myomer-system completion, 2026-07-31. The -// demand-side `speedDemand *= speedEffect` coupling it fed (mechmppr.cpp) was -// an invention built on the ConnectToMover misattribution. The binary's only -// runtime AvailableOutput consumers are RegisterMaxOutput (assembly) and the -// SeekVoltageGraph sampler; myomer damage does NOT slow a live mech -- a -// DESTROYED myomers KILLS it, via the authored vital flag (the only vital=1 -// subsystem in the RES) + the #80 vital-crit death path.) +//*************************************************************************** +// BTMyomersSpeedEffectOf -- the myomer drive fraction for the master perf's +// demand scale (mechmppr.cpp). RESTORED 2026-07-31 (the seek audit): the +// morning's deletion of the predecessor (BTMyomersDriveOf) rested on an +// export-gap-blind sweep -- the REAL consumer lives in the un-exported +// master-perf region @0x4a9cf2-0x4a9da4: MAX speedEffect over the myomer +// chain -> mech+0x79C -> `mapper->speedDemand *= it` (+ turn freeze at +// |drive| <= 1e-4). UNCLAMPED (the old bridge's f>1 -> 1 clamp ate the +// gear-4 overdrive): speedEffect legitimately rides to ~1.43 at seek 4. +// Returns -1 for a non-Myomers subsystem. [T1 disasm] +//*************************************************************************** +Scalar BTMyomersSpeedEffectOf(void *subsystem) +{ + Entity *entity = (Entity *)subsystem; + if (entity == 0 || !entity->IsDerivedFrom(Myomers::ClassDerivations)) + return -1.0f; + Scalar f = ((Myomers *)entity)->SpeedEffect(); + return (f > 0.0f) ? f : 0.0f; +} Scalar BTMyomersSeekSample(void *subsystem, Scalar voltage) {