'use strict';

/* ────────────────────────────────────────────────────────────────
   GameLoop — requestAnimationFrame loop with fixed & variable timestep
──────────────────────────────────────────────────────────────── */
class GameLoop {
    constructor(callbacks) {
        this.callbacks = callbacks;
        this._running = false;
        this._rafId = 0;
        this._lastTime = 0;
        this._accumulator = 0;
        this._frameCount = 0;
        this._fpsTime = 0;
        this._fps = 0;
        this._timeScale = 1;
        this.fixedTimestep = 1 / 60; // 60 Hz fixed update
        this.maxDelta = 0.1; // clamp to prevent spiral of death
        /* ── Internal ───────────────────────────────────────────────── */
        this.tick = (nowMs) => {
            if (!this._running)
                return;
            this._rafId = requestAnimationFrame(this.tick);
            const now = nowMs / 1000;
            let dt = (now - this._lastTime) * this._timeScale;
            this._lastTime = now;
            // Clamp large deltas (e.g. tab was in background)
            if (dt > this.maxDelta)
                dt = this.maxDelta;
            // Fixed timestep accumulator
            this._accumulator += dt;
            while (this._accumulator >= this.fixedTimestep) {
                this.callbacks.fixedUpdate(this.fixedTimestep);
                this._accumulator -= this.fixedTimestep;
            }
            // Variable update
            this.callbacks.update(dt);
            // Render with interpolation factor
            const interpolation = this._accumulator / this.fixedTimestep;
            this.callbacks.render(interpolation);
            // FPS counter
            this._frameCount++;
            if (now - this._fpsTime >= 1) {
                this._fps = this._frameCount / (now - this._fpsTime);
                this._frameCount = 0;
                this._fpsTime = now;
            }
        };
    }
    /* ── Public API ─────────────────────────────────────────────── */
    get running() {
        return this._running;
    }
    get fps() {
        return this._fps;
    }
    get frameCount() {
        return this._frameCount;
    }
    get timeScale() {
        return this._timeScale;
    }
    set timeScale(v) {
        this._timeScale = Math.max(0, v);
    }
    start() {
        if (this._running)
            return;
        this._running = true;
        this._lastTime = performance.now() / 1000;
        this._accumulator = 0;
        this._fpsTime = this._lastTime;
        this._frameCount = 0;
        this._rafId = requestAnimationFrame(this.tick);
    }
    stop() {
        this._running = false;
        if (this._rafId) {
            cancelAnimationFrame(this._rafId);
            this._rafId = 0;
        }
    }
    /** Run a single frame manually (for step-debug) */
    stepFrame(dt = this.fixedTimestep) {
        this.callbacks.fixedUpdate(dt);
        this.callbacks.update(dt);
        this.callbacks.render(1);
        this._frameCount++;
    }
}

exports.GameLoop = GameLoop;
//# sourceMappingURL=GameLoop.js.map
