Respawn: Flushing the UNDO savepoints

Is there a way to reset the UNDO gamestate database? I know restart does this, but I actually want to preserve CURRENT gamestate, but effectively restart the UNDO database from scratch. This would continue to allow UNDO but put a ‘floor’ beyond which UNDO is no longer allowed. Resetting the database would be the most elegant solution, but I cannot seem to find any way to do that.

I’ve done a cursory dive into the docs and this forum’s history and come up mostly empty.

Barring any info, the ‘best’ (read DEEPLY unsatisfying) workaround I’ve come up with would be to provision a respawnLevel property in the gamestate, autosave the current position, check the UNDONE gamestate against this property, and revert to autosave state (with No more undo information is available. message) state if not equivalent. Probably all while masking some game output. Yeah, feels kludgy.

Okay, a maybe-slightly-better idea. Provision a single-turn undoFence gamestate property which is only true on respawn, and use that property to disallow further UNDOs.

Still love to just reset that database tho.

I don’t know if I’ve tracked what the scenario is you’re wanting to create… (not saying I think I have a magical solution…)

IIRC, a year or two ago there was a debate on TADS3 exhausting the memory allocated to UNDO, perhaps you can find some hint for your problem in that debate ?

Best regards from Italy,
dott. Piergiorgio.

Thanks! That was a very interesting thread, though my main takeaway was that the UNDO database seems buried in the VM, and inaccessible to game writers.

Lol, fair enough, last two missives were sent from DEEEP in the problem-grappling zone. My scenario is this: at certain points in the game I want to offer the player the option to RESPAWN (in addition to or in lieu of standard UNDO/RESTART/RESTORE options). This option is a ‘new life’ situation, like every combat video game you’ve ever seen. Narratively, it is a new character instance, discreet from the one the player had been previously driving. I don’t actually create a new PC though. It is accomplished by teleporting to a spawn point with most of their belongings, but their gameplay ‘history’ is wiped out. It felt like the cleanest thing to do was:

  1. moveIntoForTravel them to the spawn point
  2. somehow erase the UNDO database
  3. (clean up any other respawn artifacts like not-actually-but-for-example restoring enemy health or whatever)

From the player’s perspective, this means you cannot UNDO past the respawn event, that becomes “Turn 0” in a sense.

Here is my current less-kludgey-than-I-thought solution, though it does NOT actually flush the UNDO database. As alluded to above, I provision a property in a gameState object that, when set, blocks the UNDO command. And ‘fakes’ the No more undo information is available. message.

/* ====================================================================
 *
 *   modify Undo to only allow when additionalGameMain does not fence it off
 *   (use to prevent undoing beyond RESPAWN events)
 *
 * ====================================================================
 */
modify UndoAction {
    doAction(issuingActor, targetActor, targetActorPhrase, countsAsIssuerTurn) {
        if (additionalGameMain.undoFence)
            gLibMessages.undoFailed();
        else
            inherited(issuingActor, targetActor, targetActorPhrase, countsAsIssuerTurn);
    }
}
additionalGameMain : object 
    undoFence = nil // prevents UNDO beyond current turn, when set
;

Then, my RESPAWN command mucks with that property appropriately:

respawnObj : object 
    respawn() {
        additionalGameMain.undoFence = true; // set for current turn only, prevents undo beyond this point
        gPlayerChar.moveIntoForTravel(baseCamp);
        gPlayerChar.lookAround(true);
        new Fuse(self, &clearFence, 0);
    }
    clearFence() { additionalGameMain.undoFence = nil; }
;

The respawn method is invoked with new OneTimePromptDaemon(respawnObj, &respawn); to allow any other current turn effects to resolve before moving player. Note this method spawns an ADDITIONAL fuse to disable the undoFence ensuring all subsequent moves allow UNDO. It doesn’t actually clear the UNDO database, just establishes boundaries the player cannot UNDO across.

Obviously, all this is kind of counter to traditional IF player experience. I am taking a leap of faith that I can justify this to the player in a satisfying way.

I haven’t got a 100% solution for this, but this basic approach (setting sentinel values on the game state, save and undo actions use them as fenceposts) is more or less what I settled on.

I don’t think adv3 itself does anything particularly fancy with the undo ring buffer, and I think the only thing that pings it by default is the normal turn execution loop. But I’ve got a bunch of fiddly stuff that uses savepoint() and undo() in try{}/catch(){} blocks, and so no naive approach (like mapping undo states to turn numbers/actions or anything like that) would work.

Main thing I think I want to use this with is inside short procgen segments, where the player can still save and quit, but that dumps them back to the start of the run (where a “run” is a couple minutes at most) and if they try to undo, they get a prompt saying that it will take them to the start of the run instead of the previous turn. And for that I’m also managing a lot of the save/undo state “manually”, but that’s dependent on the fact that the procgen segments are intentionally designed to mutate the global, persistent game state in a fairly limited number of ways. Don’t know if that’s applicable to what you’re trying to do, but it might be worth considering.