Newer
Older
rpg / packages / engine / src / anim / __tests__ / clip.test.ts
import { describe, expect, it } from 'vitest';
import { stepClip, type ClipDef } from '../clip';

const loop: ClipDef = { fps: 10, loop: 'loop' };
const once: ClipDef = { fps: 10, loop: 'once' };
const ping: ClipDef = { fps: 10, loop: 'pingpong' };

describe('stepClip (время в шагах: fps=10 → 1 сек = 10 шагов)', () => {
    it('loop: шаги идут по кругу, time не ограничено', () => {
        let time = 0;
        const steps: number[] = [];
        for (let i = 0; i < 25; i++) {
            const r = stepClip(4, loop, time, 0.1);
            time = r.time;
            steps.push(r.tick.step);
        }
        expect(steps).toEqual([1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1]);
        expect(time).toBeCloseTo(25, 6); // время в шагах: 25 × (0.1 с × 10 fps)
    });

    it('loop: отрицательный dt ходит назад без ухода в минус', () => {
        const r = stepClip(4, loop, 0, -0.1);
        expect(r.tick.step).toBe(3);
    });

    it('once: стоп на последнем кадре, finished один раз', () => {
        let time = 0;
        const finished: boolean[] = [];
        for (let i = 0; i < 5; i++) {
            const r = stepClip(3, once, time, 0.1);
            time = r.time;
            finished.push(r.tick.finished);
        }
        // 3 кадра → конец после 2-го продвижения; дальше шаг держится на 2.
        expect(finished).toEqual([false, true, false, false, false]);
        let last: number | null = null;
        for (let i = 0; i < 3; i++) last = stepClip(3, once, time, 0.1).tick.step;
        expect(last).toBe(2);
    });

    it('pingpong: туда-обратно без дубля крайних кадров', () => {
        let time = 0;
        const steps: number[] = [];
        for (let i = 0; i < 8; i++) {
            const r = stepClip(3, ping, time, 0.1);
            time = r.time;
            steps.push(r.tick.step);
        }
        // Период 2*(3-1)=4: 0,1,2,1 | 0,1,2,1
        expect(steps).toEqual([1, 2, 1, 0, 1, 2, 1, 0]);
    });

    it('speedScale замедляет и ускоряет клип', () => {
        // 0.1 сек при fps 10 = 1 шаг; с speedScale 0.5 — полшага.
        expect(stepClip(4, { ...loop, speedScale: 0.5 }, 0, 0.1).tick.step).toBe(0);
        expect(stepClip(4, { ...loop, speedScale: 2 }, 0, 0.1).tick.step).toBe(2);
    });

    it('dt больше периода: сообщается только финальный шаг', () => {
        const r = stepClip(4, loop, 0, 10); // 10 сек = 100 шагов вперёд
        expect(r.tick.step).toBe(0); // 100 % 4 = 0
        expect(r.tick.prevStep).toBe(0);
    });

    it('prevStep отражает шаг до продвижения', () => {
        const r = stepClip(4, loop, 1.05, 0.1);
        expect(r.tick.prevStep).toBe(1);
        expect(r.tick.step).toBe(2);
    });
});