I have scraped a bit more information on this since it was first noticed, and maybe some kind of conclusion worth looking into.
1) The VBL routine itself is short and linear - no backward branches (loops) or jumps. There are a few conditional skips but these don't contribute much to execution time and don't touch any hardware. The total duration in the VBL is very, very small (I can post it if required). The VBL simply cannot delay itself for >1 VBL so there must be an outside influence for this to happen.
2) There are other interrupts occurring, but none of them have a duration even close to 1 VBL. One special case might be disk accesses - but there are none initiated during the period of the crash (I use Hatari tracing to check that, and all printf activity is directed through natfeats now - and there are no other ROM calls left - none show via Hatari tracing). It happens in the title sequence of the game, where it just flips through 2 images, both of which are fully cached in memory at the time of the crash. No other resources are needed to run the attract sequence.
3) It is likely that the Videl register settings are reloaded on each attract mode 'page flip' - and this is suspiciously correlates with the bug triggering, although I can't prove that just now.. The Videl registers being loaded are exactly those which you can configure via ScreensPain (SCP).
4) There is no reading of Videl state at any time - no 'hardsync' style operations which wait for Videl or MFP state to change. I made sure of this while testing the bug.
5) If the VBL yields to itself (move.w #$2300,sr) even briefly before terminating, another VBL eventually occurs in that short time window, and leads to infinite regress. It doesn't appear to happen immediately - but reliably, and once it begins it seems to recur until the stack blows up. This does not occur on a real Falcon. It seems not to occur during gameplay or is rare enough to appear that way, but assured to occur after some seconds or minutes flipping pages in the frontend - the main difference being the frontend likely reloads Videl settings every few seconds where the main game loop does not.
6) Removing the 'yield' stops the regress (no crash), but other telltale
signs indicate there is still a problem - the double buffering is not
working properly in the game. Again, the problem does not show on a real Falcon.
6) The original (still crashing, but relatively infrequently) version of the code set the physbase registers ($ff820x) in the game loop, and not in the VBL. There were no problems with double-buffering using this code.
The code has since been modified to set the physbase register early in the
VBL itself. The probability of a crash seems to have got much worse
since then, and has resulted in broken double-buffering (visible tearing, some other effects which prove to me that the backbuffer is being shown early). This tearing is not visible on a real Falcon.
8) To rule out a software cause for the broken double-buffering, I tested with the buffers reversed. Having done this I can *still* see the backbuffer, but with more exaggerated tearing - i.e. neither of the buffers is now acting properly as a backbuffer. The buffers are not being switched at 55Hz intervals, but at apparently random (?) intervals.