Not at a computer currently, but here's off the top of my head how I handle game states. My NMI is currently the same for each state, so I don't have to worry about that.
I have 2 variables, gamestate and gamestatold. Each frame, I compare these two variables, and if they're different, I use the new gamestate variable as an index to jump to the initialization logic, and gamestateold is set to gamestate. Otherwise, it goes on to the main logic for the state. The reason I don't just immediately go to a new state in the middle of logic is that I could be knee-deep in subroutines, and this makes it extremely easy for bugs to happen, and very hard to find them
Here's an example
Code:
gamestate .dsb 1
gamestate_old .dsb 1
jump_ptr. .dsb 2
GameStateInits:
.dw TitleStateInit, PlayStateInit, GameOverInit
GameStates:
.dw TitleState, PlayState, GameOverState
MainLoop:
;increment frame counter
lda gamestate
cmp gamestate_old
beq +
;initialize new game state
sta gamestate_old
asl ;*2, use as index to access word table
tax
lda GameStateInits,x
sta jump_ptr+0
lda GameStateInits+1,x
sta jump_ptr+1
jsr IndirectJump
lda gamestate
+ ;go to main loop of gamestate
asl
tax
lda GameStates,x
sta jump_ptr+0
lda GameStates+1,x
sta jump_ptr+1
jsr IndirectJump
;wait for vblank
jmp MainLoop
IndirectJump:
jmp (jump_ptr)
TitleStateInit:
;load title screen
;play title music
rts
TitleState:
;check if start button is pressed, if so go to play state
lda buttons_pressed
and #%00010000
beq +
lda #STATE_PLAY
sta gamestate
+ rts
PlayStateInit:
;load level
;load objects
;play level music
rts
PlayState:
;update objects
;update player
;respond to controller input
rts
GameOverInit:
;clear variables
;show game over screen
rts
GameOver:
;wait for start button to be pressed
;if so, either reset game or go back to title state
rts