import test from 'node:test'; import assert from 'node:assert/strict'; import { PIRATE_CLASSES, PIRATE_CLASS_COSTS, pirateDesiredHeading, pirateRosterForWave, pirateSpeedForRange, separationVector, waveDifficultyBudget, } from '../src/pirate-ai.js'; const rosterCost = (roster) => roster.reduce( (sum, pirateClass) => sum + PIRATE_CLASS_COSTS[pirateClass.key], 0, ); test('pirate classes expose distinct combat roles and projectile damage', () => { assert.ok(PIRATE_CLASSES.raider.speed > PIRATE_CLASSES.frigate.speed); assert.ok(PIRATE_CLASSES.raider.hull < PIRATE_CLASSES.frigate.hull); assert.ok(PIRATE_CLASSES.captain.hull > PIRATE_CLASSES.frigate.hull); assert.ok(PIRATE_CLASSES.captain.projectileDamage > PIRATE_CLASSES.frigate.projectileDamage); }); test('wave roster uses a monotonic difficulty budget with a six-ship cap', () => { assert.deepEqual( pirateRosterForWave(1).map(({ key }) => key), ['frigate'], ); assert.deepEqual( pirateRosterForWave(2).map(({ key }) => key), ['frigate', 'raider'], ); assert.deepEqual( pirateRosterForWave(4).map(({ key }) => key), ['captain', 'raider', 'raider', 'raider'], ); let previousCost = 0; for (let wave = 1; wave <= 12; wave += 1) { const roster = pirateRosterForWave(wave); const cost = rosterCost(roster); assert.ok(roster.length <= 6); assert.ok(cost <= waveDifficultyBudget(wave)); assert.ok(cost >= previousCost, `wave ${wave} should not get cheaper`); previousCost = cost; } }); test('pirates retreat away from the player when inside retreat range', () => { const heading = pirateDesiredHeading({ bearing: 20, currentHeading: 90, distance: 150, preferredRange: 335, retreatRange: 210, }); assert.equal(heading, 200); }); test('pirates choose the closest broadside heading inside preferred range', () => { const heading = pirateDesiredHeading({ bearing: 0, currentHeading: 80, distance: 300, preferredRange: 335, retreatRange: 210, }); assert.equal(heading, 90); }); test('retreat speed keeps pirates moving out of close range', () => { const retreat = pirateSpeedForRange({ distance: 150, preferredRange: 335, retreatRange: 210, baseSpeed: 118, }); const broadside = pirateSpeedForRange({ distance: 300, preferredRange: 335, retreatRange: 210, baseSpeed: 118, }); assert.ok(retreat > broadside); }); test('separation steering pushes nearby ships apart and ignores distant ships', () => { const ship = { x: 100, y: 100, active: true }; const close = { x: 150, y: 100, active: true }; const far = { x: 500, y: 100, active: true }; const vector = separationVector(ship, [ship, close, far], 125); assert.ok(vector.x < 0, 'ship should steer away from a close ship on its right'); assert.equal(vector.y, 0); assert.ok(Math.hypot(vector.x, vector.y) <= 1); });