I have trying to understand why in graphics mode 2 the last character cell was showing bogus information and finally understood the problem. It was on the hardware side, the character address was incremented too early, and the VHDL code was using the new value although it should still have used the old value. This has to do how character cells are treated in graphics mode 2: in this mode the screen supports 768 unique character values, but they are specified in a funny way. The screen is divided into 3 vertical bands of 256 characters, in each band the 8-bit character values are re-used.
My problem was that the increment for the very last character cell happened too early, causing that particular character to roll over to a non-existing "fourth set" of 256 characters. The same problem probably also caused problems for the last characters of the bands 0 and 1 too. Anyway, now it is fixed and Yin/Yang displays in all its glory without bugs - this bug actually was visible in the Parsec too - but not anymore. Updated code in GitHub.