joeylib2/tests/host/millisHost.c

101 lines
3.8 KiB
C

// millisHost.c - Host-cc harness for jlpGenericMillisElapsed (the
// IIgs/Amiga millisecond clock). Drives simulated frame-count sequences,
// including the 16-bit wrap at 65536 frames, and asserts the millisecond
// clock never runs backward.
//
// Built by scripts/check-millis.sh with -DJL_HAS_FRAME_COUNT and
// -DJL_HAS_FRAME_HZ so port.h routes jlpFrameCount/jlpFrameHz to the mock
// implementations below instead of the generic stubs.
//
// TODAY this harness FAILS: jlpGenericMillisElapsed is a pure function of
// the wrapping uint16_t frame count (PERF-AUDIT.md finding #23), so the
// clock jumps backward every 65536 frames (~18 min at 60Hz). The Phase 1
// fix makes it accumulate deltas; this harness must then PASS and becomes
// the regression gate for it.
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "port.h"
static uint16_t gMockFrame = 0;
static uint16_t gMockHz = 60;
// Prototypes (harness-local, alphabetical).
static bool runScenario(const char *name, uint16_t hz, const uint32_t *frames, int count);
uint16_t jlpFrameCount(void) {
return gMockFrame;
}
uint16_t jlpFrameHz(void) {
return gMockHz;
}
// Walk an absolute-frame sequence (values may exceed 65535 to express
// real elapsed frames; the mock truncates to uint16_t exactly like real
// hardware counters wrap). Asserts millis is non-decreasing and lands
// within 1 frame-period of the true elapsed time at the end.
static bool runScenario(const char *name, uint16_t hz, const uint32_t *frames, int count) {
uint32_t prevMs;
uint32_t ms;
uint32_t expectMs;
uint32_t tolerance;
bool ok;
int i;
gMockHz = hz;
ok = true;
prevMs = 0;
ms = 0;
for (i = 0; i < count; i++) {
gMockFrame = (uint16_t)frames[i];
ms = jlpGenericMillisElapsed();
if (ms < prevMs) {
printf(" %s: BACKWARD at step %d (frame %lu): %lu ms -> %lu ms\n",
name, i, (unsigned long)frames[i],
(unsigned long)prevMs, (unsigned long)ms);
ok = false;
}
prevMs = ms;
}
expectMs = (uint32_t)(((uint64_t)frames[count - 1] * 1000u) / hz);
tolerance = (uint32_t)(1000u / hz + 1u);
if (ok && (ms + tolerance < expectMs || ms > expectMs + tolerance)) {
printf(" %s: DRIFT: got %lu ms, expected ~%lu ms (+/- %lu)\n",
name, (unsigned long)ms, (unsigned long)expectMs,
(unsigned long)tolerance);
ok = false;
}
printf(" %s: %s\n", name, ok ? "PASS" : "FAIL");
return ok;
}
int main(void) {
// Sequences are ABSOLUTE frame counts; steps stay under 65535 frames
// apart (the documented poll-at-least-once-per-wrap requirement).
static const uint32_t simple60[] = { 0, 60, 600, 3600, 36000 };
static const uint32_t wrap60[] = { 0, 30000, 65000, 70000, 131000, 190000, 200000 };
static const uint32_t wrap50[] = { 0, 40000, 65500, 66000, 131000 };
bool ok = true;
printf("millisHost: jlpGenericMillisElapsed scenarios\n");
// Scenario order alternates Hz deliberately: the accumulator's
// statics persist across scenarios, and a frame-rate change is the
// documented re-base point -- alternating gives each scenario a
// fresh epoch instead of inheriting the previous one's baseline.
ok &= runScenario("simple-60hz", 60, simple60, (int)(sizeof(simple60) / sizeof(simple60[0])));
ok &= runScenario("wrap-50hz", 50, wrap50, (int)(sizeof(wrap50) / sizeof(wrap50[0])));
ok &= runScenario("wrap-60hz", 60, wrap60, (int)(sizeof(wrap60) / sizeof(wrap60[0])));
if (!ok) {
printf("millisHost: FAIL (finding #23 wrap bug present -- expected until Phase 1 lands)\n");
return 1;
}
printf("millisHost: PASS\n");
return 0;
}