@@ -810,3 +810,81 @@ describe('Fleet Action scenario', () => {
810810 }
811811 } ) ;
812812} ) ;
813+
814+ describe ( 'Edge cases' , ( ) => {
815+ it ( 'no-burn orders for all ships produces valid movement' , ( ) => {
816+ const orders : AstrogationOrder [ ] = initialState . ships
817+ . filter ( s => s . owner === 0 )
818+ . map ( s => ( { shipId : s . id , burn : null } ) ) ;
819+ const result = processAstrogation ( initialState , 0 , orders , map ) ;
820+ expect ( 'error' in result ) . toBe ( false ) ;
821+ } ) ;
822+
823+ it ( 'skip ordnance when no ordnance exists' , ( ) => {
824+ initialState . phase = 'ordnance' ;
825+ const result = skipOrdnance ( initialState , 0 ) ;
826+ expect ( 'error' in result ) . toBe ( false ) ;
827+ } ) ;
828+
829+ it ( 'skip combat when no enemies are nearby' , ( ) => {
830+ initialState . phase = 'combat' ;
831+ const result = skipCombat ( initialState , 0 , map ) ;
832+ expect ( 'error' in result ) . toBe ( false ) ;
833+ } ) ;
834+
835+ it ( 'wrong player cannot submit orders' , ( ) => {
836+ // Player 0 is active, try submitting as player 1
837+ const orders : AstrogationOrder [ ] = initialState . ships
838+ . filter ( s => s . owner === 1 )
839+ . map ( s => ( { shipId : s . id , burn : null } ) ) ;
840+ const result = processAstrogation ( initialState , 1 , orders , map ) ;
841+ expect ( 'error' in result ) . toBe ( true ) ;
842+ } ) ;
843+
844+ it ( 'destroyed ships are skipped in movement' , ( ) => {
845+ const ship = initialState . ships [ 0 ] ;
846+ ship . destroyed = true ;
847+ const orders : AstrogationOrder [ ] = [ { shipId : ship . id , burn : null } ] ;
848+ const result = processAstrogation ( initialState , 0 , orders , map ) ;
849+ expect ( 'error' in result ) . toBe ( false ) ;
850+ } ) ;
851+
852+ it ( 'fleet action ends when one side is eliminated' , ( ) => {
853+ const fleetState = createGame ( SCENARIOS . fleetAction , map , 'FLT02' , findBaseHex ) ;
854+ // Destroy all of player 1's ships
855+ fleetState . ships . filter ( s => s . owner === 1 ) . forEach ( s => { s . destroyed = true ; } ) ;
856+
857+ // Run a no-op astrogation to trigger checkGameEnd
858+ const orders : AstrogationOrder [ ] = fleetState . ships
859+ . filter ( s => s . owner === 0 )
860+ . map ( s => ( { shipId : s . id , burn : null } ) ) ;
861+ const result = processAstrogation ( fleetState , 0 , orders , map ) ;
862+ if ( ! ( 'error' in result ) ) {
863+ expect ( result . state . phase ) . toBe ( 'gameOver' ) ;
864+ expect ( result . state . winner ) . toBe ( 0 ) ;
865+ }
866+ } ) ;
867+
868+ it ( 'blockade runner wins by landing on Mars' , ( ) => {
869+ const blockadeState = createGame ( SCENARIOS . blockade , map , 'BLK02' , findBaseHex ) ;
870+ const runner = blockadeState . ships . find ( s => s . owner === 0 ) ! ;
871+ // Position runner just outside Mars gravity, drifting toward Mars surface
872+ // Mars center is {q:10, r:8}, gravity ring at radius 1
873+ // Ship at {q:12, r:7} with velocity {dq:-1, dr:0} drifts to base hex {q:11, r:7}
874+ // Gravity then deflects it onto Mars surface — triggers landing on non-destructive body
875+ runner . position = { q : 12 , r : 7 } ;
876+ runner . velocity = { dq : - 1 , dr : 0 } ;
877+ runner . landed = false ;
878+
879+ // Process drift-only turn (no burn)
880+ const orders : AstrogationOrder [ ] = blockadeState . ships
881+ . filter ( s => s . owner === 0 )
882+ . map ( s => ( { shipId : s . id , burn : null } ) ) ;
883+ const result = processAstrogation ( blockadeState , 0 , orders , map ) ;
884+ if ( ! ( 'error' in result ) ) {
885+ expect ( result . state . phase ) . toBe ( 'gameOver' ) ;
886+ expect ( result . state . winner ) . toBe ( 0 ) ;
887+ expect ( result . state . winReason ) . toContain ( 'Mars' ) ;
888+ }
889+ } ) ;
890+ } ) ;
0 commit comments