Files
healer-man/src/scene/frameRateGovernor.test.ts
T
2026-08-30 14:16:56 -04:00

65 lines
2.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
createFrameRateGovernor,
resetFrameRateGovernor,
stepFrameRateGovernor,
} from "./frameRateGovernor";
function renderCount(targetFps: 30 | 60, sourceFps: number, durationMs = 1_000): number {
let state = createFrameRateGovernor(targetFps);
let renders = 0;
for (let timestamp = 0; timestamp <= durationMs; timestamp += 1_000 / sourceFps) {
const step = stepFrameRateGovernor(state, timestamp);
state = step.state;
if (step.shouldRender) renders += 1;
}
return renders;
}
describe("frame-rate governor", () => {
it("holds capped modes near their target on high-refresh displays", () => {
expect(renderCount(30, 144)).toBeGreaterThanOrEqual(30);
expect(renderCount(30, 144)).toBeLessThanOrEqual(31);
expect(renderCount(60, 240)).toBeGreaterThanOrEqual(60);
expect(renderCount(60, 240)).toBeLessThanOrEqual(61);
});
it("renders every source callback in display mode", () => {
let state = createFrameRateGovernor("display", 4);
for (const timestamp of [1_000, 1_008, 1_016]) {
const step = stepFrameRateGovernor(state, timestamp);
state = step.state;
expect(step.shouldRender).toBe(true);
}
expect(state.elapsedSeconds).toBeCloseTo(4.016);
});
it("skips missed deadlines without emitting a catch-up burst", () => {
let state = createFrameRateGovernor(60);
state = stepFrameRateGovernor(state, 0).state;
const delayed = stepFrameRateGovernor(state, 100);
expect(delayed.shouldRender).toBe(true);
expect(delayed.state.nextDeadlineMs).toBeGreaterThan(100);
const sameTimestamp = stepFrameRateGovernor(delayed.state, 100);
expect(sameTimestamp.shouldRender).toBe(false);
});
it("preserves elapsed time while resetting the wall-clock baseline", () => {
let state = createFrameRateGovernor(30, 2);
state = stepFrameRateGovernor(state, 1_000).state;
state = stepFrameRateGovernor(state, 1_034).state;
const reset = resetFrameRateGovernor(state, 60);
const resumed = stepFrameRateGovernor(reset, 90_000);
expect(resumed.shouldRender).toBe(true);
expect(resumed.elapsedSeconds).toBeCloseTo(state.elapsedSeconds);
});
it("never moves elapsed time backwards", () => {
let state = createFrameRateGovernor(60);
state = stepFrameRateGovernor(state, 20).state;
state = stepFrameRateGovernor(state, 40).state;
const backwards = stepFrameRateGovernor(state, 10);
expect(backwards.elapsedSeconds).toBe(state.elapsedSeconds);
});
});