import { describe, expect, it } from 'vitest'; import { computeRangedTarget } from '$lib/engine/ranged'; import { fkBasis } from '$lib/engine/derived'; import { newCharacter } from '$lib/characters/default'; import type { ExtraModifierId } from '$lib/rules/modifiers-ranged'; const fixedId = '00000000-0000-4000-8000-000000000099'; function charWithBogen(taw: number) { const c = newCharacter({ name: 'Archer', id: fixedId }); const kt = c.kampftalente.find((k) => k.id === 'bogen'); if (kt) kt.taw = taw; return c; } const baseSel = { range: 'near' as const, /** Zielgröße „groß“ hat Modifikator 0 in den Tabellen */ targetSize: 'large' as const, visibility: 'clear' as const, movement: 'slow' as const, extras: [] as ExtraModifierId[], aimTurns: 0, targetBuild: 'biped' as const }; describe('ranged', () => { it('computes FK target with zero modifiers', () => { const c = charWithBogen(4); const fkBase = fkBasis(c); const r = computeRangedTarget(c, 'shortbow', { ...baseSel, reloadState: undefined }); expect(r.fkBase).toBe(fkBase); expect(r.baseTarget).toBe(fkBase + 4); expect(r.finalTarget).toBe(fkBase + 4); }); it('applies distance modifier (mittel = +4 EW)', () => { const c = charWithBogen(0); const r = computeRangedTarget( c, 'shortbow', { ...baseSel, range: 'medium', reloadState: 'regular_shot' } ); // +4 Entfernung, +1 normaler Schuss, +0 Zielgröße „groß“ expect(r.totalModifier).toBe(5); expect(r.finalTarget).toBe(r.baseTarget - 5); }); it('halves TaW for aimed shot without expertise', () => { const c = charWithBogen(10); const r = computeRangedTarget( c, 'shortbow', { ...baseSel, reloadState: 'aimed_shot' } ); expect(r.effectiveTaW).toBe(5); expect(r.baseTarget).toBe(r.fkBase + 5); }); it('applies matching weapon specialization (+2 FK)', () => { const c = charWithBogen(0); c.abilities = [{ id: 'sf1', defId: 'specialize_shortbow' }]; const base = computeRangedTarget(c, 'shortbow', baseSel); const plain = computeRangedTarget(charWithBogen(0), 'shortbow', baseSel); expect(base.finalTarget).toBe(plain.finalTarget + 2); }); it('ignores specialization for a different weapon', () => { const c = charWithBogen(0); c.abilities = [{ id: 'sf1', defId: 'specialize_longbow' }]; const r = computeRangedTarget(c, 'shortbow', baseSel); const plain = computeRangedTarget(charWithBogen(0), 'shortbow', baseSel); expect(r.finalTarget).toBe(plain.finalTarget); }); it('derives expert from Scharfschütze ability (TaW/2 -2 for aimed shot)', () => { const c = charWithBogen(10); c.abilities = [{ id: 'sf1', defId: 'sniper' }]; const r = computeRangedTarget(c, 'shortbow', { ...baseSel, reloadState: 'aimed_shot' }); expect(r.effectiveTaW).toBe(3); }); it('derives master from Meisterschütze ability (full TaW for aimed shot)', () => { const c = charWithBogen(10); c.abilities = [{ id: 'sf1', defId: 'marksman' }]; const r = computeRangedTarget(c, 'shortbow', { ...baseSel, reloadState: 'aimed_shot' }); expect(r.effectiveTaW).toBe(10); }); });