Thanks for your input KuWizard. I do have the feeling you have nailed what the problem is - though by no means did I ignore the warning - I just didn't know what it meant.
According to my AI "assistant", when specifically asking about this:
"Yes, the standard BGA game template (proforma) and many (if not most) BGA games will indeed have that exact definition for STATE_END_GAME in their states.inc.php, including "action" => "stGameEnd".
The key point remains the interpretation of the comment "// Please do not modify":
It means do not change the fundamental properties of this state definition: keep name as "gameEnd", keep type as "manager", because the BGA framework relies on these for state 99.
It means do not try to fundamentally alter how BGA handles state 99, like trying to make it transition somewhere else using nextState from within stGameEnd, or trying to use PHP tricks to assign multiple action functions.
It does NOT mean you cannot write code inside the stGameEnd function in your .game.php file. In fact, you must put your final score calculation and database saving logic there, because that's the specific function the action key tells the framework to execute before it takes over.
So, to be perfectly clear:
Your states.inc.php definition for STATE_END_GAME is correct and standard.
Having a corresponding public function stGameEnd() in your .game.php file is correct and standard.
Putting your final score calculation and saving logic inside that stGameEnd() function is correct and standard.
This confirms that the problem is NOT with your state definition or the naming/existence of your stGameEnd function. These parts are set up exactly as BGA expects."
So, given your comment, I am still not sure if I have overloaded stGameEnd.
I have tried to put one or even two game states which handled the custom scoring but still couldn't get the end of the game working properly - even without having an stGameEnd function in Game.php. I feel it is to do with asynchronous functions not completing at the right time though I have to admit I am very hazy on my understanding of how these things work in javascript.
BTW - the game will end properly with scores and stats if I do not include stGameEnd() (and do not try to have the custom scoring)