So now I've excised all the fake PC stuff, and I feel much better about the situation.
I've been looking through the GCC internal documentation looking for additional features I may have missed which would help. Unfortunately, I didn't notice anything I haven't tried before. There were a few macros that caught my eye, but sadly are of no use for this project.
These macros defines how byte values are converted to word values when stored in registers, which sound like it would be perfect for the crazy byte representation the TMS9900 uses. Unfortunately, this isn't useful. These macros can only select between signed and unsigned extension. Regardless of the selection made, GCC assumes that the byte value can be retrieved from the least significant 8 bits of the register. This is no good.
These macros deal with which registers are used to store values, and how values can be converted between data types. Again, this sounds great for dealing with byte values. Again, they are not useful. GCC only uses these for values larger than a word. In our case, values larger than 16 bits. GCC reasonably assumes that any register which can be used to store word values can be used to store values smaller than a word. It also assumes that byte values are always stored in the least significant bits of any register used.
So, having gone through all the documentation again, the only way to have a proper GCC port is to follow the path we ultimately took: make changes to the internals of the compiler and force mode to changes be preserved until instruction generation. This is fine, but there are a lot of places where these mode changes can happen. We'll likely be dealing with faulty byte-to-word conversions for a long time as user code explores the edge cases. Ick.
Nothing left to do now but track down the reported bugs.