From e1ae92264a27ca1a66b29a3ab2328eecc0b3b79b Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Sun, 2 Aug 2026 16:01:04 -0500 Subject: [PATCH] #96: the sim TIME MODEL closed from bytes -- myomer kinetic term restored to the faithful 28 Hz The open question under every heat bracket was what the 1995 sim does with time. Answered at the byte level: * FUN_0041c018 IS the arcade Simulation::PerformAndWatch: slice = (till.ticks - last.ticks) / DAT_0052140c, stored, Perform(slice) -- VARIABLE-STEP in real SECONDS, line-for-line the WinTesla SIMULATE.cpp body. Every dt-carrying reconstruction is unit-faithful; no tick/second gap exists anywhere. * The myomer heat term's ONE caller is the unexported MyomersSimulation body @0x4b8b9a (E8-scan + raw disasm; export gap): once per Perform, raw slice, gated measuredVoltage>0 only. The dt-less kinetic pulse therefore fires at the FRAME cadence -- 28 Hz nominal, sagging under load (emergent hardware, not a code constant). * Mover velocity is u/s (dead-reckoner @0x421f7c: pos += vel x ticksD/28), so the v operand is unit-faithful too. The 0.5 "loaded-rate" witness bracket is RETIRED -- it modeled the sag as a constant, wrong in exactly the reported scenario (open-field cruising is a light scene; the pod held ~28 there). kSeekHeatCalib = 1.0. Measured at the faithful rate (madcat cruise benches): recommended gear equilibrates ~1450 -- inside the authored degradation band as a SOFT 20% output governor (heatFactor = 1 - d^2/range^2), weapons untouched at ~110-150, ZERO jams, zero bay fires from movement alone; supercharge ~1770 (deep governor -- the manual's own expert-mode warning). That is the authored system: degradeT=1000 is governor onset, failT=2000 the cliff, and the coolant-loop controls are the player's lever. New leads filed in open-questions: the climb term's gravity operand samples 0 (bridge points at a dead cell); the engine Mover's own gravity subtraction is per-frame dt-less (port impact unread); gait-noise phantom acceleration feeds the accel term. Co-Authored-By: Claude Fable 5 --- context/decomp-reference.md | 16 ++++++++++ context/open-questions.md | 16 ++++++++++ game/reconstructed/myomers.cpp | 57 +++++++++++++++++----------------- 3 files changed, 61 insertions(+), 28 deletions(-) diff --git a/context/decomp-reference.md b/context/decomp-reference.md index f9a40ec..f5def02 100644 --- a/context/decomp-reference.md +++ b/context/decomp-reference.md @@ -319,6 +319,22 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a Loaded ("full == the gear's seek voltage" is the arcade's own discharge algebra). Repro + verify: BT_SEEKTEST seek-abuse -- pre-fix deadlocks at pct=0/alarm=3; post-fix 38 rescues, ends Loaded/pct=1. `[seek]` log prints the per-gear table + rescue events. +- **THE SIM TIME MODEL — CLOSED 2026-08-02 (issue #96) [T1]:** the arcade + `Simulation::PerformAndWatch` is **`FUN_0041c018`** (part_002.c:5101): + `slice = (till.ticks − this[4].ticks) / DAT_0052140c; this[4] = till; + (*this[7])(this, slice)` then watchers (`FUN_0041c08c`) + WriteSimulationUpdate + (`FUN_0041bd98`) — **VARIABLE-STEP, slice in real SECONDS**, line-for-line the + WinTesla SIMULATE.cpp body. Corollaries: every Performance's `time_slice` is + seconds in BOTH binary and port (no hidden tick/second unit gap); a dt-LESS + per-frame term (myomer kinetic @4b8d18's middle term; the Mover gravity + `-= **(mover+0x250)` @0x421e77) fires at the FRAME cadence = the tick rate, + 28 Hz nominal, sagging under load. Mover velocity is u/s (the replicant + dead-reckoner @0x421f7c does `pos += vel × ticksΔ/DAT_0052140c`). The myomer + heat term's ONE caller is the **unexported MyomersSimulation body + @0x4b8b9a-0x4b8d0d** (E8-scan + raw disasm; the export gap hid it): calls + @4b8d18 once per Perform, raw slice, gated `measuredVoltage > 0` only — plus + a −0.011/Perform repair trickle at alarm 1 and `speedEffect(+0x31c) = + AvailableOutput(min(measuredV, seek gear, cap)) / owner->runSpeedBase(+0x34c)`. - **AMMO COOK-OFF cluster DECODED 2026-07-25 (issue #46, raw disasm `scratchpad/disammo.py`) [T1]:** `@004bd394` AmmoBinSimulation (arm on heatAlarm FAILURE / detonate / cancel-on-Empty); `@004bdb94` HandleMessage(1) = crit-induced arm; the FUSE = `Now().ticks + diff --git a/context/open-questions.md b/context/open-questions.md index f3606ca..be3b5f9 100644 --- a/context/open-questions.md +++ b/context/open-questions.md @@ -694,6 +694,22 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR destroyed-subsystem dark-panel look. ## Locomotion / combat polish (non-gating) +- **Myomer climb term is DEAD in the port: the gravity operand samples 0** (found in the #96 + cruise benches, `[myoheat] g=0` every line — `BTMechMyomerMotionSample` reads + `GetEnvironment()->gravityConstant` and gets 0; the binary reads `**(mover+0x250)`, a POINTER + the Mover holds to the live gravity cell). Harmless on flat ground (|vy|≈0 anyway) but the + climb-work heat never accrues on slopes. Find where our Environment gravity actually lives + and re-point the bridge. [T2 measured] +- **Engine Mover gravity is dt-LESS per-frame in the binary** (`@0x421e77: vy -= **(+0x250)`, + raw, once per Perform — same class as the myomer kinetic term). If our linked WinTesla + MOVER.cpp kept that form, gravity acceleration in the port scales with OUR ~59Hz frame rate + (≈2.1× the pod's 28) — jump arcs, falls, missile droop all stiffer than the pod. CHECK the + WinTesla source's gravity line; if per-frame, it needs the same 28Hz reference-rate + normalization the myomer kinetic term got. [T1 binary side / T4 port impact until read] +- **Gait-noise phantom acceleration feeds the myomer accel term** (`[myoheat] a=4..33` at + steady cruise — our per-frame velocity re-derivation jitters; the binary read the same + `AccelerationLastFrame` cell off ITS OWN gait, noise level unknown). Bounded contributor + (~25-40% of cruise heat-in); revisit only if the 28Hz-faithful feel is contested. [T3] - **✅ Interior vs exterior gait CLIP SET — DONE 2026-07-13 (task #59) [T2].** The local cockpit mech was leaning −8° into every walk because the authentic ctor clip-set gate (@part_012.c:10308-10320) was a no-op (`LoadLowDetailBody`/`LoadHighDetailBody` mislabeled the diff --git a/game/reconstructed/myomers.cpp b/game/reconstructed/myomers.cpp index 6fbccf4..3a13ddf 100644 --- a/game/reconstructed/myomers.cpp +++ b/game/reconstructed/myomers.cpp @@ -762,35 +762,36 @@ void Myomers::MyomersDriveHeat(Scalar time_slice) // exists because the binary's per-tick value depends on the pod's frame // rate, which our port does not run at. // - // THE CLOCK ORIGIN, traced 2026-08-02 (was the open question here): - // the DOS binary keeps its engine frame rate in the global DAT_0052140c, - // set once at startup -- - // 0x401ace: mov [0x52140c], 0x41E00000 = 28.0f (nominal) - // 0x401ada: mov [0x52140c], 0x4191A6E0 = 18.2065f (BIOS-tick fallback) - // -- and consumed by ~90 fmul/fdiv sites across the image as THE - // per-frame<->per-second conversion. The DOS main passes it straight into - // the ApplicationManager ctor (0x401189: push [0x52140c]). So the pod's - // nominal Performance cadence was 28 Hz, byte-proven. [T1] + // THE CLOCK -- CLOSED 2026-08-02, all [T1] from bytes: + // * DAT_0052140c = SystemClock ticksPerSecond, 28.0 nominal (0x401ace) / + // 18.2065 BIOS fallback (0x401ada). + // * The arcade Simulation::PerformAndWatch is FUN_0041c018: + // slice = (till.ticks - lastPerformance.ticks) / DAT_0052140c; + // lastPerformance = till; (*performance)(this, slice); + // -- VARIABLE-STEP, slice in real SECONDS, line-for-line the WinTesla + // SIMULATE.cpp body. Every dt-carrying term is frame-rate independent + // in BOTH the binary and the port; no unit conversion is missing. + // * This term's ONE caller is the unexported MyomersSimulation body + // (0x4b8b9a-0x4b8d0d, raw disasm 2026-08-02 -- the export gap hid it): + // it calls 0x4b8d18 once per Perform with the RAW slice, gated only on + // measuredVoltage > 0. So the kinetic energy pulse fires once per + // FRAME, and the pod's frame cadence is its tick rate: 28 Hz when + // holding the deadline, sagging under load (fewer pulses/s -- an + // EMERGENT hardware mercy, not a constant in the code). // - // CALIBRATION FACTOR 0.5 -- the pod's EFFECTIVE rate under load, now - // corroborated from three directions (2026-08-02): - // * Oracle (pod veteran), asked if the frame rate was rock solid: - // "I think I could do as well with a flipbook"; heavy fights "would - // turn to a slide show". - // * Lynx (original-era): "RP had a target frame rate of 30FPS. It - // wasn't hard coded or locked. BT never had a locked frame rate in - // 1st release testing... coming from 3.0, where 20FPS was a miracle." - // * The engine's own pacer DESIGN: end_of_frame = Now() + frameDuration - // with background fill and NO overrun catch-up (APPMGR.cpp, T0) -- - // "target 28 but allowed to slip" is the construction, and the - // 18.2065 BIOS fallback anticipated degraded timing. - // Corollary: a choking 486 generated LESS heat/s exactly during the - // biggest fights -- veteran memory of seek-4 endurance encodes the LOADED - // rate, so bracketing by their consensus is the correct instrument. - // Effective default 28 * 0.5 = 14 Hz [T2 multi-witness]; BT_MYO_HZ= - // overrides absolutely for bracketing. + // The faithful reference is therefore the binary's own nominal cadence, + // 28 Hz, full stop. The old 0.5 "loaded-rate" calibration [T3 witness + // bracket] is RETIRED: it modeled the sag as a constant, which is wrong in + // exactly the reported scenario (open-field cruising is a LIGHT scene -- + // the pod held ~28 there). Measured at 28 Hz (cruise benches 2026-08-02): + // recommended gear equilibrates ~1450 (soft 20% output governor inside the + // authored degradation band, weapons unaffected, zero jams); supercharge + // ~1770 (deep governor, the manual's own warning). That IS the authored + // system: degradeT=1000 is a governor onset, failT=2000 the cliff, and the + // pod's coolant-loop controls (flush/balance, Myomers on loop 5) are the + // player's lever. BT_MYO_HZ= remains as the bracketing override. static const Scalar kPodFrameHz = 28.0f; // byte-proven nominal [T1] - static const Scalar kSeekHeatCalib = 0.5f; // Oracle bracket 2026-08-02 [T3] + static const Scalar kSeekHeatCalib = 1.0f; // faithful (0.5 bracket retired 2026-08-02) static Scalar s_hz = -1.0f; if (s_hz < 0.0f) { @@ -800,7 +801,7 @@ void Myomers::MyomersDriveHeat(Scalar time_slice) if (getenv("BT_MYO_LOG")) DEBUG_STREAM << "[myoheat] kinetic-term reference rate: " << s_hz << " Hz (pod nominal 28 [T1] x calib " << kSeekHeatCalib - << " [T3]" << (hv ? ", ENV OVERRIDE" : "") << ")" << std::endl; + << (hv ? ", ENV OVERRIDE" : "") << ")" << std::endl; } Scalar termClimb = velComplement * vy * mass * gravity * time_slice;