import test from 'node:test'; import assert from 'node:assert/strict'; import { DEFAULT_PIRATE_SHOT_DAMAGE, PLAYER_MAX_HULL, normalizePlayerDamage, reducePlayerProjectileHits, resolveTaggedProjectile, } from '../src/game-logic.js'; test('first pirate projectile removes exactly the default damage', () => { const result = reducePlayerProjectileHits({ hull: PLAYER_MAX_HULL, hits: [{ damage: DEFAULT_PIRATE_SHOT_DAMAGE }], }); assert.equal(result.hull, 94); assert.equal(result.appliedHits, 1); assert.equal(result.totalDamage, 6); assert.equal(result.gameOver, false); }); test('damage normalization handles invalid and extreme values safely', () => { assert.equal(normalizePlayerDamage(undefined), 6); assert.equal(normalizePlayerDamage('not-a-number'), 6); assert.equal(normalizePlayerDamage(999), 25); assert.equal(normalizePlayerDamage(-12), 1); assert.equal(normalizePlayerDamage('8'), 8); }); test('invalid hull state recovers from max hull before applying projectile damage', () => { const result = reducePlayerProjectileHits({ hull: Number.NaN, hits: [{ damage: 6 }], }); assert.equal(result.hull, 94); assert.equal(result.gameOver, false); }); test('lethal projectile damage clamps hull at zero and reports game over', () => { const result = reducePlayerProjectileHits({ hull: 5, hits: [{ damage: 6 }], }); assert.equal(result.hull, 0); assert.equal(result.totalDamage, 6); assert.equal(result.gameOver, true); }); test('empty projectile queue is a no-op', () => { const result = reducePlayerProjectileHits({ hull: 72, hits: [], }); assert.equal(result.hull, 72); assert.equal(result.totalDamage, 0); assert.equal(result.appliedHits, 0); assert.equal(result.gameOver, false); }); test('every distinct pirate projectile can damage once with no cross-shot suppression', () => { const first = reducePlayerProjectileHits({ hull: 100, hits: [{ damage: 6 }], }); const second = reducePlayerProjectileHits({ hull: first.hull, hits: [{ damage: 6 }], }); assert.equal(first.hull, 94); assert.equal(second.hull, 88); }); test('multiple distinct pirate projectiles in the same frame all apply damage', () => { const result = reducePlayerProjectileHits({ hull: 100, hits: [ { damage: 6 }, { damage: 6 }, { damage: 6 }, ], }); assert.equal(result.hull, 82); assert.equal(result.totalDamage, 18); assert.equal(result.appliedHits, 3); }); test('projectile resolver is independent of Phaser overlap argument order', () => { const player = { getData() { return undefined; }, }; const pirateShot = { getData(key) { return key === 'projectileOwner' ? 'pirate' : undefined; }, }; assert.equal(resolveTaggedProjectile(pirateShot, player, 'pirate'), pirateShot); assert.equal(resolveTaggedProjectile(player, pirateShot, 'pirate'), pirateShot); }); test('projectile resolver never mistakes the player for an untagged shot', () => { const player = { getData() { return undefined; }, }; const untagged = { getData() { return undefined; }, }; assert.equal(resolveTaggedProjectile(player, untagged, 'pirate'), null); });