#96: TRACE THE CLOCK ORIGIN -- the pod ran at 28 fps (byte-proven); myomer heat re-bracketed to 14 Hz effective

epilectrik: "the issue seems to be related to a tick rate that advances the
heat accumulation... i need to trace out what the clock origin was."  Traced.

THE CLOCK ORIGIN [T1]: 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)
The DOS main pushes it straight into the ApplicationManager ctor (0x401189:
push [0x52140c]), and ~90 sites across the image fmul/fdiv by it -- it is THE
per-frame<->per-second conversion scalar of the whole 1995 engine.  The engine
executes every interesting master every frame and every executable subsystem
every entity tick (UPDATE.cpp / ENTITY.cpp, T0 -- no throttle at any layer),
so subsystem Performance cadence == this rate: the pod's myomer tick was
NOMINALLY 28 Hz.  My previous 30 Hz reference (the i860 BOARD frame) was
wrong by 7%, not the 2x the veterans hear.

THE RESIDUAL IS EFFECTIVE RATE, NOT NOMINAL: Oracle, on the halved build,
still asks "If you could please halve the myomer heat rate again.  We will
bracket to something reasonable."  A 486 host missing beats halves the real
Performance cadence without changing any constant -- and the 18.2 fallback
existing at all says slow paths were expected.  Statics cannot settle the
in-pod effective rate, so his bracket is the right instrument:

    effective default = 28 [T1 nominal] x 0.5 [T3 testimony calib] = 14 Hz
    BT_MYO_HZ=<hz> still overrides absolutely for bracketing
    the resolved rate now logs under BT_MYO_LOG, tier tags inline

Measured (Owens, seek 4, flat out): peak T 1007 -> ~640, degradation never
reached -- the halving Oracle requested, delivered as one auditable knob
instead of a silent constant edit.

KB: decomp-reference.md gains the DAT_0052140c section (~90 consumer sites --
whenever the decomp shows an unexplained mul/div by _DAT_0052140c it is
per-second<->per-frame at 28); btl4main.cpp's "30 = pod authentic" pacer note
corrected (30 kept deliberately for display smoothness, the divergence now
documented).  Candidate follow-up recorded: the frame-rate-dependent particle
trail density (rendering.md) should normalize against the same 28, which may
bear on Ronin's smoke-density report (#114).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-02 09:39:00 -05:00
co-authored by Claude Opus 5
parent c791fae0f8
commit c28555bdc9
3 changed files with 58 additions and 14 deletions
+20
View File
@@ -548,6 +548,26 @@ Respawn SIM path: `DropZoneReply` `FUN_004bffd0` → `Mech::Reset` @0x4009fb74;
---
## THE ENGINE FRAME-RATE GLOBAL — `DAT_0052140c` = 28.0f (2026-08-02) [T1]
The DOS binary's per-frame<->per-second conversion scalar, set ONCE at startup:
0x401ace: mov [0x52140c], 0x41E00000 ; 28.0f (nominal rate)
0x401ada: mov [0x52140c], 0x4191A6E0 ; 18.2065f (BIOS-tick fallback, PIT/65536)
and passed straight into the ApplicationManager ctor by the DOS main
(0x401189: `push [0x52140c]`). ~90 code sites `fmul`/`fdiv` this global —
whenever the decomp shows an unexplained multiply/divide by `_DAT_0052140c`,
it is per-second <-> per-frame conversion at the pod's 28 Hz.
**So the pod's nominal simulation cadence was 28 fps, not the i860 board's
30 Hz.** Every "per tick with no dt" accumulation in the binary (the myomer
kinetic heat term; candidate: particle trail density) was calibrated against
28 nominal — and less under load (the 18.2 fallback existing at all says slow
paths were expected; the EFFECTIVE in-pod rate is an open question, see
[[open-questions]]). Our port's frame pacer note in btl4main.cpp claimed 30
as "the pod's authentic rate" — corrected to 28.
## Key Relationships
- Feeds: [[subsystems]] (factory + ClassIDs), [[combat-damage]] (damage delivery), [[gauges-hud]] (attribute binding).
- Verified against: [[reconstruction-method]] (the decomp loop), [[reconstruction-gotchas]] (why raw offsets fail).
+8 -3
View File
@@ -1512,9 +1512,14 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
// (end_of_frame = now + 1/frameRate) degenerated and the game ran at a
// timer-quantized ~15 FPS (66-70ms frames measured) on ANY hardware.
// Default 60; env TARGETFPS still overrides (clamped to sanity).
int target_fps = 30; // the pod's authentic rate; frame work (~20-40ms in
// this build) misses 60Hz beats -> jitter, but holds
// 30 rock-steady. TARGETFPS env still overrides.
int target_fps = 30; // NOTE: the pod's byte-proven NOMINAL rate is 28
// (DAT_0052140c = 28.0f @0x401ace; 18.2065 BIOS
// fallback @0x401ada -- decomp-reference.md). We
// keep 30 for display smoothness; anything that
// must match the pod's per-tick math normalizes
// itself (e.g. the myomer kinetic term). Frame
// work (~20-40ms) misses 60Hz beats -> jitter,
// holds 30 steady. TARGETFPS env overrides.
{
const char *tf = getenv("TARGETFPS");
if (tf != 0)
+30 -11
View File
@@ -757,22 +757,41 @@ void Myomers::MyomersDriveHeat(Scalar time_slice)
// cruising; at the pod's documented 30Hz board frame that is ~54s.
//
// So the term is rescaled to the reference frame rate: multiply by
// dt * kPodFrameHz, which is 1.0 exactly when dt == 1/30 and makes the heat
// identical on every machine. This is the ONE deliberate divergence from a
// verbatim transcription of @004b8d18, and it exists because the binary's
// value depends on a constant our port does not have.
// dt * hz, making the heat identical on every machine. This is the ONE
// deliberate divergence from a verbatim transcription of @004b8d18, and it
// exists because the binary's per-tick value depends on the pod's frame
// rate, which our port does not run at.
//
// ⚠ 30Hz is the i860 board frame ([T1], rendering.md) -- the HOST simulation
// rate is not separately established, so treat this as the reference, not as
// proven. BT_MYO_HZ overrides it without a rebuild so the pod veterans can
// calibrate it by feel.
static const Scalar kPodFrameHz = 30.0f;
// 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]
//
// CALIBRATION FACTOR 0.5, testimony (Oracle, 2026-08-02): at the byte rate
// the veterans still report ~2x too much myomer heat -- "If you could
// please halve the myomer heat rate again. We will bracket to something
// reasonable." The plausible physical story is the pod's EFFECTIVE rate
// under load (a 486 host missing beats; the 18.2 fallback existing at all
// says slow paths were expected), which statics cannot settle. Effective
// default therefore 28 * 0.5 = 14 Hz, explicitly marked testimony-
// calibrated [T3]; BT_MYO_HZ=<hz> overrides absolutely for bracketing.
static const Scalar kPodFrameHz = 28.0f; // byte-proven nominal [T1]
static const Scalar kSeekHeatCalib = 0.5f; // Oracle bracket 2026-08-02 [T3]
static Scalar s_hz = -1.0f;
if (s_hz < 0.0f)
{
const char *hv = getenv("BT_MYO_HZ");
s_hz = (hv && *hv) ? (Scalar)atof(hv) : kPodFrameHz;
if (s_hz <= 0.0f) s_hz = kPodFrameHz;
s_hz = (hv && *hv) ? (Scalar)atof(hv) : (kPodFrameHz * kSeekHeatCalib);
if (s_hz <= 0.0f) s_hz = kPodFrameHz * kSeekHeatCalib;
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;
}
Scalar termClimb = velComplement * vy * mass * gravity * time_slice;