// 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 #include #include #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; }