trouble with "immediately undo rule"

I can’t seem to get the “immediately undo rule” to work for me. Here’s my sample:

Room1 is a room. "This is room 1.".
Room2 is west of Room1. "This is room 2.".

player is in Room1.

newundoing is an action out of world.
understand "newundo" as newundoing.

carry out newundoing:
	follow the immediately undo rule.

“undo” behaves normally. “newundo” only pretends that it did something:

Room1
This is room 1.

>w

Room2
This is room 2.

>undo
Room1
[Previous turn undone.]

>w

Room2
This is room 2.

>newundo
Room2
[Previous turn undone.]

>newundo
Room2
[Previous turn undone.]

>newundo
Room2
[Previous turn undone.]

Am I using the rule incorrectly?

I believe the problem is that it creates the undo state right before parsing and executing the action, so when you jump back to that undo state, you’ve only undone the action of newundoing. This is why UNDO isn’t an action normally (like AGAIN and OOPS, it happens outside of the action machinery).

Is there a way to make it behave the way one would expect?

I don’t think you can get any additional control over UNDO without extensions that tap it at the I6 level.

If you’re using Inform 10.1 the one to use would be Nathanael Nerode’s Undo Output Control:

-Wade

Yeah, you’re going to need to tap into the parser code, which practically speaking means I6.

This seems to work. It isn’t perfect – for example, if the UNDO fails it prints an unrecognized command message – but it allows for undo-related code in I7. (It also removes OOPS, if anyone cares.)

Include
(- 
Global just_undid_flag = 0;

[ Keyboard  a_buffer a_table  nw i w w2 x1 x2;
	sline1 = score; sline2 = turns;
	while (true) {
		! Save the start of the buffer, in case "oops" needs to restore it
		for (i=0 : i<64 : i++) oops_workspace->i = a_buffer->i;
	
		! In case of an array entry corruption that shouldn't happen, but would be
		! disastrous if it did:
		#Ifdef TARGET_ZCODE;
		a_buffer->0 = INPUT_BUFFER_LEN;
		a_table->0 = 15;  ! Allow to split input into this many words
		#Endif; ! TARGET_
	
		! Print the prompt, and read in the words and dictionary addresses
		PrintPrompt();
		DrawStatusLine();
		KeyboardPrimitive(a_buffer, a_table);
	
		! Set nw to the number of words
		#Ifdef TARGET_ZCODE; nw = a_table->1; #Ifnot; nw = a_table-->0; #Endif;
		
		! Conveniently, a_table-->1 is the first word on both the Z-machine and Glulx
		w = a_table-->1;

		return nw;
	}
]; 

[ SpecialSaveUndo i;
		just_undid_flag = 0;
		i = VM_Save_Undo();
		#ifdef PREVENT_UNDO; undo_flag = 0; #endif;
		#ifndef PREVENT_UNDO; undo_flag = 2; #endif;
		if (i == -1) undo_flag = 0;
		if (i == 0) undo_flag = 1;
		! this code gets run immediately after a Restore_Undo
		if (i == 2) {
			just_undid_flag = 1;
			VM_RestoreWindowColours();
			VM_Style(SUBHEADER_VMSTY);
			SL_Location(); print "^";
			! print (name) location, "^";
			VM_Style(NORMAL_VMSTY);
			IMMEDIATELY_UNDO_RM('E'); new_line;
		}
];

-) instead of "Reading the Command" in "Parser.i6t".

to i7_save_undo: (- SpecialSaveUndo(); -).

to decide what number is just_undid: (- just_undid_flag -).
	
after reading a command:
	if the player's command matches "undo":
		follow the immediately undo rule;
	[if you want to undo for some other reason, it has to be done now, before the save_undo occurs.]
	i7_save_undo;
	if just_undid is 1: [resuming immediately after an undo]
		say "Put your post-UNDO code here.";
		[halt further command processing]
		rule succeeds;

Notably, I believe both DRINK POISON THEN UNDO and UNDO THEN SOUTH will fail with this.

Does UNDO THEN SOUTH ever work? If so, how?

Huh, it seems it doesn’t in default I7! I thought the library protected those variables across the UNDO, but it does not. My bad.