Overall, I didn't implement new functionality in the Ink port today, because I was noticing simplifications I could make to the code now that I'm not trying to make the state machine a thing, and then I was working on the design for the variable observer feature. I just now wrote a test for it according to my initial plan.
My initial plan is something I'm not totally sure about, but I can defend it to myself. Basically, the context is that most of the rest of the interface I put together is about having the consumer manage immutable state. So, I could, in theory, emit the state changes explicitly and have the caller dispatch them. But that seems kind of awkward, even by the standards of the designs I've inflicted on this port in the past. When I get to the end of the equivalent of the "continue" function, I know that the consumer "should" continue on, and therefore wants the observer functions to have triggered by then.
So, for my initial implementation, my plan is to have some kind of accumulator queue of variable changes that gets populated from the VariablesState, and cleared and processed at the end of the "continue" function. I'll probably want to have some kind of aggressive exception handling on the observer functions, because this code doesn't care what they do, and implementing any kind of control flow via exceptions in consumer code seems perverse. (Also, observer code should probably be putting stuff on message queues or something like that anyway, I think.)
(While I'm thinking in this area, I think it'd be safe to remove the string part of the output from the function, which would then be more-or-less fluent. That kind of big rewrite would be helped by another idea I have in mind, which is to reduce boilerplate in the test code. I have some ideas in mind that would reduce many tests to one-liners.)
Anyway, if I try to work on that now, I'll probably mess it up in some ludicrous way, so I'll wait until later for that.