Files
SpaceWanderer/lib/game/skills.test.ts
T
kamilkrzywda 7f2121a717
CI / build + unit + e2e (pull_request) Successful in 2m53s
feat(game): skills.ts — XP, levels, power bands, titles (#45)
2026-10-04 01:33:05 +00:00

51 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest'
import type { SkillName } from './crew-costs'
import { grantXp, levelFromXp, powerForLevel, titleFor, type SkillRow } from './skills'
const pilot = { name: 'BEN OKONKWO', role: 'PILOT' }
const row = (crewId: string, skill: SkillName, level: number, xp: number): SkillRow =>
({ saveId: 's1', crewId, skill, level, xp })
const XP: [number, number][] = [[4, 0], [5, 1], [10, 2], [15, 3], [25, 5], [100, 5]]
describe('levelFromXp (thresholds [5,10,15,20,25], cap 5)', () => {
it('maps cumulative xp → highest level reached', () => {
for (const [xp, lvl] of XP) expect(levelFromXp(xp), `xp ${xp}`).toBe(lvl)
})
})
const POWER: [number, number][] = [[0, 1], [2, 1], [3, 2], [4, 2], [5, 3]]
describe('powerForLevel (bands [1,1,1,2,2,3])', () => {
it('L0–2 → 1 · L3–4 → 2 · L5 → 3', () => {
for (const [lvl, p] of POWER) expect(powerForLevel(lvl), `L${lvl}`).toBe(p)
})
})
describe('grantXp — +1 XP to every listed member; event only on a titled-band crossing', () => {
const pilotRows = (): SkillRow[] => [
row('c1', 'NAVIGATION', 2, 14), row('c1', 'SCANNING', 0, 0),
row('c1', 'ENGINEERING', 0, 0), row('c1', 'RESEARCH', 0, 0),
]
it('L0 → L1: xp 4 +1 → 5 (L1), no event (no event for plain L1/L2)', () => {
const { rows, events } = grantXp([row('c1', 'NAVIGATION', 0, 4)], ['c1'], 'NAVIGATION', { c1: pilot })
expect(rows[0]).toEqual({ saveId: 's1', crewId: 'c1', skill: 'NAVIGATION', level: 1, xp: 5 })
expect(events).toEqual([])
})
it('L2 → L3: xp 14 +1 → 15, SENIOR event in the exact spec §6 text', () => {
const { rows, events } = grantXp(pilotRows(), ['c1'], 'NAVIGATION', { c1: pilot })
expect(rows.find((r) => r.skill === 'NAVIGATION')!.level).toBe(3)
expect(events).toEqual(['BEN OKONKWO is now a SENIOR PILOT — NAVIGATION L3'])
})
it('L4 → L5: xp 24 +1 → 25, MASTER event', () => {
const { events } = grantXp([row('c1', 'NAVIGATION', 4, 24), row('c1', 'SCANNING', 0, 0)], ['c1'], 'NAVIGATION', { c1: pilot })
expect(events).toEqual(['BEN OKONKWO is now a MASTER PILOT — NAVIGATION L5'])
})
})
describe('titleFor — from the member\'s HIGHEST skill level', () => {
it('SCIENTIST with SCANNING L3 + RESEARCH L5 → MASTER SCIENTIST (highest wins)', () => {
const scientist = { name: 'AVA VANCE', role: 'SCIENTIST' }
const rows = [row('c2', 'SCANNING', 3, 15), row('c2', 'RESEARCH', 5, 25), row('c2', 'NAVIGATION', 0, 0), row('c2', 'ENGINEERING', 0, 0)]
expect(titleFor(scientist, rows)).toBe('MASTER SCIENTIST')
})
it('GUNNER with all L0 → null', () => {
const gunner = { name: 'RIG HOLLOWAY', role: 'GUNNER' }
const rows = [row('c3', 'NAVIGATION', 0, 0), row('c3', 'SCANNING', 0, 0), row('c3', 'ENGINEERING', 0, 0), row('c3', 'RESEARCH', 0, 0)]
expect(titleFor(gunner, rows)).toBe(null)
})
})