@@ -260,6 +260,24 @@ describe('aiCombat', () => {
260260 expect ( attacks [ 0 ] . targetId ) . toBe ( pilgrim . id ) ;
261261 } ) ;
262262
263+ it ( 'easy AI is more conservative about attacking' , ( ) => {
264+ const state = createGame ( SCENARIOS . biplanetary , map , 'TEST' , findBaseHex ) ;
265+ const aiShip = state . ships . find ( s => s . owner === 1 ) ! ;
266+ const enemyShip = state . ships . find ( s => s . owner === 0 ) ! ;
267+
268+ // Place them far apart — poor odds
269+ aiShip . position = { q : 0 , r : 0 } ;
270+ aiShip . velocity = { dq : 0 , dr : 0 } ;
271+ aiShip . landed = false ;
272+ enemyShip . position = { q : 8 , r : 0 } ;
273+ enemyShip . velocity = { dq : 3 , dr : 0 } ;
274+ enemyShip . landed = false ;
275+
276+ // Easy AI should skip combat with bad range + velocity mods
277+ const attacks = aiCombat ( state , 1 , 'easy' ) ;
278+ expect ( attacks ) . toHaveLength ( 0 ) ;
279+ } ) ;
280+
263281 it ( 'attack contains valid ship references' , ( ) => {
264282 const state = createGame ( SCENARIOS . biplanetary , map , 'TEST' , findBaseHex ) ;
265283 const aiShip = state . ships . find ( s => s . owner === 1 ) ! ;
@@ -282,3 +300,79 @@ describe('aiCombat', () => {
282300 }
283301 } ) ;
284302} ) ;
303+
304+ describe ( 'AI scenario handling' , ( ) => {
305+ it ( 'fleet action: AI generates orders for all 3 ships' , ( ) => {
306+ const state = createGame ( SCENARIOS . fleetAction , map , 'FA01' , findBaseHex ) ;
307+ const orders = aiAstrogation ( state , 1 , map ) ;
308+ const aiShips = state . ships . filter ( s => s . owner === 1 ) ;
309+ expect ( orders ) . toHaveLength ( aiShips . length ) ;
310+ expect ( aiShips . length ) . toBe ( 3 ) ;
311+ } ) ;
312+
313+ it ( 'fleet action: AI seeks combat when no target body' , ( ) => {
314+ const state = createGame ( SCENARIOS . fleetAction , map , 'FA02' , findBaseHex ) ;
315+ // Unland all ships and place opposing fleets nearby
316+ for ( const ship of state . ships ) {
317+ ship . landed = false ;
318+ ship . position = ship . owner === 0
319+ ? { q : 0 , r : 0 }
320+ : { q : 5 , r : 0 } ;
321+ ship . velocity = { dq : 0 , dr : 0 } ;
322+ }
323+ const orders = aiAstrogation ( state , 1 , map , 'hard' ) ;
324+ // At least one ship should burn toward enemy (not null)
325+ const hasBurn = orders . some ( o => o . burn !== null ) ;
326+ expect ( hasBurn ) . toBe ( true ) ;
327+ } ) ;
328+
329+ it ( 'blockade: AI interceptor seeks enemy runner' , ( ) => {
330+ const state = createGame ( SCENARIOS . blockade , map , 'BK01' , findBaseHex ) ;
331+ // The dreadnaught (player 1) starts in space
332+ const dreadnaught = state . ships . find ( s => s . owner === 1 ) ! ;
333+ expect ( dreadnaught . landed ) . toBe ( false ) ;
334+ const orders = aiAstrogation ( state , 1 , map ) ;
335+ expect ( orders ) . toHaveLength ( 1 ) ;
336+ } ) ;
337+
338+ it ( 'blockade: runner AI navigates toward Mars' , ( ) => {
339+ const state = createGame ( SCENARIOS . blockade , map , 'BK02' , findBaseHex ) ;
340+ const runner = state . ships . find ( s => s . owner === 0 ) ! ;
341+ // Unland runner and place it in open space
342+ runner . landed = false ;
343+ runner . position = { q : - 3 , r : - 5 } ;
344+ runner . velocity = { dq : 0 , dr : 0 } ;
345+
346+ const orders = aiAstrogation ( state , 0 , map , 'hard' ) ;
347+ expect ( orders ) . toHaveLength ( 1 ) ;
348+ // Hard AI should burn toward Mars (not drift)
349+ expect ( orders [ 0 ] . burn ) . not . toBeNull ( ) ;
350+ } ) ;
351+
352+ it ( 'AI handles all difficulty levels without errors' , ( ) => {
353+ const difficulties : Array < 'easy' | 'normal' | 'hard' > = [ 'easy' , 'normal' , 'hard' ] ;
354+ const scenarios = [ SCENARIOS . biplanetary , SCENARIOS . escape , SCENARIOS . blockade , SCENARIOS . fleetAction ] ;
355+
356+ for ( const scenario of scenarios ) {
357+ for ( const diff of difficulties ) {
358+ const state = createGame ( scenario , map , 'DF01' , findBaseHex ) ;
359+ // Unland ships for meaningful AI decisions
360+ state . ships . forEach ( s => {
361+ if ( ! s . destroyed ) {
362+ s . landed = false ;
363+ s . velocity = { dq : 0 , dr : 0 } ;
364+ }
365+ } ) ;
366+
367+ const orders = aiAstrogation ( state , 1 , map , diff ) ;
368+ expect ( orders . length ) . toBeGreaterThan ( 0 ) ;
369+
370+ const launches = aiOrdnance ( state , 1 , map , diff ) ;
371+ expect ( Array . isArray ( launches ) ) . toBe ( true ) ;
372+
373+ const attacks = aiCombat ( state , 1 , diff ) ;
374+ expect ( Array . isArray ( attacks ) ) . toBe ( true ) ;
375+ }
376+ }
377+ } ) ;
378+ } ) ;
0 commit comments