Newer
Older
rpg / v2 / packages / engine / src / render / __tests__ / daynight.test.ts
/** Тесты чистой математики суток (перенос релевантных проверок v1 lightSim). */
import { describe, expect, it } from 'vitest';
import {
    DUSK_PEAK, DEFAULT_DUSK_COLOR, dayNightAmbient, dayNightFactor,
    dimColor, flickerFactor, lerpAmbient, lerpColor,
} from '../daynight';

describe('lerpColor/lerpAmbient', () => {
    it('краи и середина по каналам', () => {
        expect(lerpColor(0x000000, 0xffffff, 0)).toBe(0x000000);
        expect(lerpColor(0x000000, 0xffffff, 1)).toBe(0xffffff);
        expect(lerpAmbient(0x102030, 0x304050, 0.5)).toBe(0x203040);
    });
});

describe('dayNightAmbient (золотой час)', () => {
    it('краи: k=0 — день, k=1 — ночь', () => {
        expect(dayNightAmbient(0xf7f4ea, 0x3e4658, 0)).toBe(0xf7f4ea);
        expect(dayNightAmbient(0xf7f4ea, 0x3e4658, 1)).toBe(0x3e4658);
    });
    it('на пике (k = DUSK_PEAK) ambient равен закатному тону', () => {
        expect(dayNightAmbient(0xf7f4ea, 0x3e4658, DUSK_PEAK)).toBe(DEFAULT_DUSK_COLOR);
        expect(dayNightAmbient(0xf7f4ea, 0x3e4658, DUSK_PEAK, 0xffcc88)).toBe(0xffcc88);
    });
    it('до пика — лерп день→закат, после — закат→ночь', () => {
        expect(dayNightAmbient(0xf7f4ea, 0x3e4658, DUSK_PEAK / 2)).toBe(
            lerpAmbient(0xf7f4ea, DEFAULT_DUSK_COLOR, 0.5),
        );
        expect(dayNightAmbient(0xf7f4ea, 0x3e4658, DUSK_PEAK + (1 - DUSK_PEAK) / 2)).toBe(
            lerpAmbient(DEFAULT_DUSK_COLOR, 0x3e4658, 0.5),
        );
    });
    it('непрерывен и монотонен по k (день светлее ночи по R)', () => {
        let prevR = 256;
        for (let k = 0; k <= 1.0001; k += 0.01) {
            const r = (dayNightAmbient(0xf7f4ea, 0x3e4658, k) >> 16) & 0xff;
            expect(r).toBeLessThanOrEqual(prevR);
            prevR = r;
        }
        expect(dayNightAmbient(0xf7f4ea, 0x3e4658, DUSK_PEAK - 0.001)).toBeCloseTo(
            dayNightAmbient(0xf7f4ea, 0x3e4658, DUSK_PEAK + 0.001), 1,
        );
    });
});

describe('dayNightFactor', () => {
    it('день (8..18) — 0, ночь (20..6) — 1', () => {
        expect(dayNightFactor(12)).toBe(0);
        expect(dayNightFactor(0)).toBe(1);
        expect(dayNightFactor(23)).toBe(1);
        expect(dayNightFactor(5.5)).toBe(1);
    });
    it('переходы линейны: 7:00 — половина рассвета, 19:00 — половина заката', () => {
        expect(dayNightFactor(7)).toBeCloseTo(0.5);
        expect(dayNightFactor(19)).toBeCloseTo(0.5);
        expect(dayNightFactor(18.5)).toBeCloseTo(0.25);
    });
    it('непрерывен на стыке 24/0', () => {
        expect(dayNightFactor(24)).toBe(dayNightFactor(0));
    });
});

describe('dimColor', () => {
    it('краи: полная яркость и ноль', () => {
        expect(dimColor(0x54586a, 1)).toBe(0x54586a);
        expect(dimColor(0x54586a, 0)).toBe(0x000000);
    });
});

describe('flickerFactor', () => {
    it('детерминирован: одинаковые (time, seed) — одинаковый результат', () => {
        expect(flickerFactor(2.5, 0.3, 0.4, 0.9)).toBe(flickerFactor(2.5, 0.3, 0.4, 0.9));
    });
    it('разные seed дают разные значения', () => {
        expect(flickerFactor(1, 0.1, 0.5, 0.9)).not.toBe(flickerFactor(1, 0.9, 0.5, 0.9));
    });
    it('в диапазоне [1-amount, 1] на сетке времени', () => {
        for (let t = 0; t < 10; t += 0.05) {
            const f = flickerFactor(t, 0.42, 0.35, 0.9);
            expect(f).toBeLessThanOrEqual(1);
            expect(f).toBeGreaterThanOrEqual(1 - 0.35 - 1e-9);
        }
    });
    it('amount = 0 — ровный свет', () => {
        expect(flickerFactor(3.7, 0.5, 0, 0.9)).toBe(1);
    });
});