Newer
Older
rpg / v1 / packages / engine / src / render / __tests__ / shadowSim.test.ts
import { describe, expect, it } from 'vitest';
import { blobPlacement, sunShadow } from '../shadowSim';

describe('sunShadow', () => {
    it('ночью тени нет', () => {
        expect(sunShadow(2)).toBeNull();
        expect(sunShadow(23)).toBeNull();
        expect(sunShadow(5.5)).toBeNull();
    });

    it('на границах дня альфа нулевая, в разгар дня максимальная', () => {
        expect(sunShadow(6)!.alpha).toBeCloseTo(0);
        expect(sunShadow(19.9)!.alpha).toBeCloseTo(0); // к закату гаснет (в 20 — null)
        const noon = sunShadow(13)!.alpha;
        expect(noon).toBeGreaterThan(0.3);
        const morning = sunShadow(7)!.alpha; // ещё в разгоне (dawn..day = 6..8)
        expect(morning).toBeGreaterThan(0);
        expect(morning).toBeLessThan(noon);
    });

    it('тень утром на запад экрана, вечером на восток', () => {
        expect(sunShadow(7)!.dirX).toBeLessThan(-0.5);
        expect(sunShadow(19)!.dirX).toBeGreaterThan(0.5);
        expect(sunShadow(13)!.dirX).toBeCloseTo(0); // полдень — почти под объектом
    });

    it('длина максимальна утром/вечером, минимальна в полдень', () => {
        const noon = sunShadow(13)!.len;
        const morning = sunShadow(7)!.len;
        const evening = sunShadow(19)!.len;
        expect(noon).toBeLessThan(morning);
        expect(noon).toBeLessThan(evening);
        expect(morning).toBeCloseTo(evening, 1); // симметричный день
    });

    it('вектор направления единичный', () => {
        for (let h = 6; h < 20; h += 0.5) {
            const s = sunShadow(h)!;
            expect(Math.hypot(s.dirX, s.dirY)).toBeCloseTo(1);
        }
    });
});

describe('blobPlacement', () => {
    it('масса эллипса сидит у базы: ближний край заходит за базу, вылет — вперёд', () => {
        const sun = { dirX: 1, dirY: 0, len: 1, alpha: 0.3 };
        const b = blobPlacement(10, 40, sun);
        const center = 10 * 0.3 + 40 * 0.2; // 11 — центр массы от базы
        expect(b.x).toBeCloseTo(center);
        expect(b.y).toBe(0);
        expect(b.scaleX).toBeCloseTo(center + 5.5 + 8.8); // полуось вдоль тени
        expect(b.scaleY).toBeCloseTo(10); // 0.75 + 0.25·min(1,len) = 1 при len=1
        expect(b.alpha).toBeCloseTo(0.3);
        expect(b.rotation).toBeCloseTo(0);
        // Ближний край эллипса за базой (тень «приклеена» к объекту)…
        expect(b.x - b.scaleX).toBeLessThan(0);
        // …а дальний край вылетает вперёд по тени.
        expect(b.x + b.scaleX).toBeGreaterThan(0);
    });

    it('длинная утренняя тень дальше, чем короткая полуденная', () => {
        const morning = blobPlacement(10, 40, { dirX: -1, dirY: 0, len: 1.5, alpha: 0.3 });
        const noon = blobPlacement(10, 40, { dirX: 0, dirY: 1, len: 0.6, alpha: 0.3 });
        expect(Math.hypot(morning.x, morning.y)).toBeGreaterThan(Math.hypot(noon.x, noon.y));
    });

    it('поворот соответствует направлению тени', () => {
        const b = blobPlacement(10, 40, { dirX: 0, dirY: 1, len: 1, alpha: 0.3 });
        expect(b.rotation).toBeCloseTo(Math.PI / 2);
    });

    it('центр массы близко к базе даже при длинной тени (объект не «висит»)', () => {
        const long = blobPlacement(9, 12, { dirX: -0.9, dirY: 0.45, len: 1.6, alpha: 0.3 });
        expect(Math.hypot(long.x, long.y)).toBeLessThan(9); // герой: центр ~5-6 px от ног
    });
});