Thanks for your detailed investigation here. I'm not sure, but I think editing "hidden from AI" / etc. is actually just triggering a re-render, which "shows" the issue, rather than actually causes it.
One possibility is that you're using some custom code in that character that (likely via oc.messageRenderingPipeline) purposefully/inadvertantly edits the prompt inside the <image>...</image> tag, which would cause a mismatch.
I was thinking I could match it via a heuristic like just checking (imgN:0) if the initial __savedImages exact-prompt-match fails, but I think that would mean editing the message to change an image prompt wouldn't work - i.e. it'd just load the stale one rather than loading the new one. I could maybe make imgN increment for any prompt that's manually edited, but I haven't thought that through fully and my suspicion is that that gets a bit iffy. I think we do need to keep exact-match, and if so, then any custom code that 'renders' a message (oc.messageRenderingPipeline) shouldn't touch anything inside <image> tags. Same goes for custom code that edits the actual content of a message of course.
sometimes generates images with a completely empty prompt
Thanks! Fixed.