Okay, I finished refactoring my transaction objects and I've got all the tests back running again (moving to using a state object actually made things a little stricter; it turns out some of the tests were playing fast and loose so I had to tweak one or two of them slightly (and fix a few bad assumptions in the state code of course)).
You really notice how useful tests are when you attempt something like this though. Every time I moved a method over to the state, I ran the tests and made sure everything was passing (modulo a couple of test scripts that I knew I'd have to leave failing until I'd completed the move).
Any failing tests pointed me back to areas where I'd misunderstood what was going on, and the whole thing gave me confidence that the cleaned up code was still doing the same thing as the old ugly code. I've inherited rats' nests like this before, and without tests it's a nightmare, you end up spending ages trying to capture the old behaviour with tests before you can go on to actually cleaning up the code.
Next step, unifying this state object with the 'dispatch state' that a transaction hangs onto as a sort of 'bookmark' into its processing pipeline.