Newer
Older
rpg / apps / game / src / systems / __tests__ / InteractRouting.test.ts
import { describe, expect, it } from 'vitest';
import { inInteractRangePos, pickInteract, type InteractCandidate } from '../InteractRouting';

describe('inInteractRangePos — истинная евклидова линейка (юнит = тайл)', () => {
    // Центр тайла (5,5) = мир (5.5, 5.5): NPC/объект стоят в центре своего тайла.

    it('соседний тайл по оси — в радиусе (дистанция 1.0)', () => {
        expect(inInteractRangePos({ x: 6.5, y: 5.5 }, 5, 5)).toBe(true);
    });

    it('диагональный сосед — в радиусе (дистанция 1.414 < 1.5)', () => {
        // findPathToNeighbor может привести героя в диагонального соседа цели.
        expect(inInteractRangePos({ x: 6.5, y: 6.5 }, 5, 5)).toBe(true);
    });

    it('два тайла — вне радиуса (дистанция 2.0): дальний клик не срабатывает', () => {
        // Регресс бага: проекционная линейка давала «радиус» в 2-3 тайла.
        expect(inInteractRangePos({ x: 7.5, y: 5.5 }, 5, 5)).toBe(false);
    });

    it('2 тайла вдоль мировой диагонали (проекция была бы «ближе») — вне радиуса', () => {
        expect(inInteractRangePos({ x: 6.5, y: 6.5 }, 4, 4)).toBe(false);
    });
});

describe('pickInteract — выбор цели кнопки действия', () => {
    /** Кандидат-заглушка: тайл задаёт дистанцию (герой в (0,0)). */
    function cand(
        kind: InteractCandidate['kind'],
        tile: { x: number; y: number },
        label = 'цель'
    ): InteractCandidate {
        return { kind, tile, dist: Math.hypot(tile.x, tile.y), label };
    }

    it('ближайший кандидат в радиусе — он', () => {
        const c = pickInteract([cand('npc', { x: 2, y: 0 }), cand('interact', { x: 1, y: 0 })]);
        expect(c?.label).toBe('цель');
        expect(c?.kind).toBe('interact');
        expect(c?.tile).toEqual({ x: 1, y: 0 });
    });

    it('вне радиуса (два тайла и дальше) — не выбирается', () => {
        expect(pickInteract([cand('npc', { x: 2, y: 0 })])).toBeNull();
        expect(pickInteract([])).toBeNull();
    });

    it('ничья по дистанции — приоритет NPC выше интерактива', () => {
        const c = pickInteract([cand('interact', { x: 1, y: 0 }), cand('npc', { x: 0, y: 1 })]);
        expect(c?.kind).toBe('npc');
    });

    it('почти-ничья (в эпсилоне) — приоритет: переход выше цветка', () => {
        const c = pickInteract([cand('flower', { x: 1, y: 1 }), cand('transition', { x: 1.04, y: 0 })]);
        expect(c?.kind).toBe('transition');
    });

    it('заметно ближе — дистанция побеждает приоритет', () => {
        const c = pickInteract([cand('npc', { x: 1, y: 0 }), cand('flower', { x: 0.5, y: 0 })]);
        expect(c?.kind).toBe('flower');
    });

    it('заперто — кандидат locked с текстом', () => {
        const c: InteractCandidate = {
            kind: 'locked',
            tile: { x: 1, y: 0 },
            dist: 1,
            label: 'дверь',
            text: 'Заперто.'
        };
        expect(pickInteract([c])?.text).toBe('Заперто.');
    });
});