Unlike my last two problems, in this case I can narrow the issue down to a few lines of code.
When I save and restore a window state, the window is restored as being entirely on the screen, even if it was partially off the screen before.
The issue is juce_ResizableWindow.cpp:551-557. If the new size of the window does not entirely fit on the screen, the screen is resized so it entirely fits in the window.
This conflicts with the code in juce_ComponentBoundsConstrainer.cpp, which allows (by default) most of the window to be off screen to the bottom, left or right, and it means that you can drag a window to a place that you can’t store and recall.
I fixed things for my purposes by merely commenting out those lines in juce_ResizableWindow.cpp but I suspect something more nuanced is needed as a general fix!