import type { Invariant } from './types';
/**
* Чистые хелперы инвариантов: сцены и системы собирают нарушения через них,
* мост мержит и отдаёт списком. Без зависимостей — тестируется в Vitest.
*/
function inv(id: string, severity: 'error' | 'warn', message: string, where?: string): Invariant {
return where ? { id, severity, message, where } : { id, severity, message };
}
/** NaN/Infinity в числовых значениях (позиции, hp и т.п.) — почти всегда сломанный мир. */
export function checkFinite(values: Record<string, number>, where: string): Invariant[] {
const out: Invariant[] = [];
for (const [name, v] of Object.entries(values)) {
if (!Number.isFinite(v)) {
out.push(inv('pos-nan', 'error', `${name} = ${v} (не конечное число)`, where));
}
}
return out;
}
/** Число вне диапазона (hp < 0, скорость < 0, ...). */
export function checkRange(
name: string,
v: number,
min: number,
max: number,
where: string
): Invariant | null {
if (!Number.isFinite(v)) {
return inv('pos-nan', 'error', `${name} = ${v} (не конечное число)`, where);
}
if (v < min || v > max) {
return inv('range', 'error', `${name} = ${v} вне [${min}, ${max}]`, where);
}
return null;
}
/** Тайл в границах карты. */
export function checkBounds(
name: string,
tile: { x: number; y: number },
w: number,
h: number,
where: string
): Invariant | null {
if (!Number.isInteger(tile.x) || !Number.isInteger(tile.y) || tile.x < 0 || tile.y < 0
|| tile.x >= w || tile.y >= h) {
return inv('out-of-bounds', 'error', `${name}: тайл (${tile.x},${tile.y}) вне карты ${w}×${h}`, where);
}
return null;
}
/** Тайл проходим (герой/NPC не должны стоять в стене). */
export function checkWalkable(
name: string,
tile: { x: number; y: number },
map: { isWalkable(x: number, y: number): boolean },
where: string
): Invariant | null {
if (!map.isWalkable(tile.x, tile.y)) {
return inv('in-wall', 'error', `${name} стоит в непроходимом тайле (${tile.x},${tile.y})`, where);
}
return null;
}
/** Мерж групп: null/false/undefined отбрасываются, группы — разворачиваются. */
export function mergeInvariants(
...groups: (Invariant | null | undefined | false | Invariant[])[]
): Invariant[] {
const out: Invariant[] = [];
for (const g of groups) {
if (!g) continue;
if (Array.isArray(g)) out.push(...g);
else out.push(g);
}
return out;
}