A3Lite: ugly seams between linked scenes

The linking of scenes in adv3Lite has a major problem:

from my analysis seems that the check for startsWhen happens BEFORE the running of endsWhen, creating an 1-turn hole between linked scenes (that is, scenes running back-to-back). And, specifically for where these three linking scenes happens, if the player moves in another location (poignantly, the only exit is south) the entire narrative goes also south, up to a major softlock.

For now, I’m cobbling together a kluge which block the going south (of the narrative…) during these two 1-turn seams, but someone has better ideas on how to ensure a seamless “passing the baton” between these scenes ??

(AFAICT, only Joey and Jim Aikin has used scenes with adv3Lite, but both of them hasn’t dealt with linked scenes…)

TIA and
Best regards from Italy,
dott. Piergiorgio.

2 Likes

Maybe the solution is to modify sceneManager.executeEvent() so that the block of code that checks for startsWhen is repeated after the block of code that checks for endsWhen. I haven’t tested it, but you might want to give it a go.

2 Likes

Turns out I may have blundered into hooks for this in Eric’s scenes.t module for adv3! As always, not sure how this might translate to adv3lite. Is it too much to assume same author = same implementation? :sweat_smile:

Basically, just call the activate() method directly! ie in your scene1 code (though it does beg the question why make it two scenes in the first place?)…

     scene2.activate();

When I implemented scenes, I was a bit concerned with performance of every-turn startWhen checking, so I made some hacks to provision a programmatic-only invocation of scene, ie ONLY the method above.

Here are my additions to the library version of scenes.t:

class Scene: DaemonControl

    //  UNCHANGED CODE NOT PASTED HERE, THESE SHOULD BE ADDED/MOD'D in orig

	/*	 (JJMcC added)
	 *   Is this implicitly activated via startsWhen/endsWhen properties?
	 *   Alternately, would need to be explicitly activated/deactivated
	 *   procedurally.  Default is to leave implicit
	 */
	isImplicit = true
    

/*  Build a list of scenes at Preinit */
// only add implicit scenes to every-turn checking
scenesPreInit : PreinitObject
    execute()
    {
		// JJMcC - replaced original routine (and named PreInitObj)
        // forEachInstance(Scene, {x: sceneController.sceneList.append(x) });    
		forEachInstance(Scene, function(sc) {
			if (sc.isImplicit) sceneController.sceneList.append(sc);
		});
    }
;

/* Watch for the opening and closing of scenes each turn */
// JJMcC: if ONLY non-implicit scenes don't bother starting daemon
sceneController: InitObject
    execute()  {   // new PromptDaemon(self, &sceneCheck);    } // JJMcC orig
		if (sceneList.length() > 0) new PromptDaemon(self, &sceneCheck);
	}
    
    /* The list of all scenes in the game */
    sceneList = static new Vector(10)
    
    sceneCheck()
    {
        local how = nil; 
        
        foreach(local cur in sceneList)
        {            
             
            /* Check if any active scenes are ready to be ended */   
            if(cur.isActive && ((how = cur.endsWhen) != nil))
                cur.deactivate(how);           
            
            /* Check if any non-active scenes are due for activation */
            if(!cur.isActive && cur.startsWhen 
               && (cur.isRecurring || cur.timesActive == 0))            
               cur.activate();               
               
        }
    }
;

Note I had to hack file directly as I needed to mod an unnamed preInit instance (which I also named above). Also note I erroneously commented that endsWhen-deactivate doesn’t work any more and needs to be manual. These changes don’t actually alter that, and in fact I still use endsWhen to terminate a scene. Bad comment.

EDIT: had to refresh my memory. The above hack DOES disable automated deactivate checking. Instead, I added the deactivate checking into the daemon() method which runs during every scene while active. endsWhenImpliesSolved is a custom property that controls the howEnded property. (The implication being I have other deactivate('solved') invocations for scenes that do not always imply solved when ending.)

    daemon() {
        inherited;
        if (endsWhen())
            deactivate(endsWhenImpliesSolved ? 'solved' : 'unsolved');
    } 

The adv3Lite version of Scene doesn’t have an activate() method; the equivalent method is called start(), but you could try calling that directly.

2 Likes

JJ, ISTR to have pointed that the adv3 extension of scenes.t is the ur-text (that is, the starting point) of adv3lite’s scenes module. But, as Eric has pointed, the evolution has carried many changes and improvements…
For your question, indeed I have assessed the idea of a single, big, scene, but the three parts are too different in nature (one railroaded thru some locations, then an introspective THINKing scene, then meet and talk with Azuj, ending the first act and set the stage of the 2nd act
(my thanks to Eric for the well-explained, since the days of the adv3 extension, howEnded, where will be set Azuj’s mood after the talk with her spouse-in-trouble…)

Eric, your suggestion sounds good, if not excellent. Tomorrow (Saturday) I’ll work around your suggested modify.

Thanks to both, and

Best regards from Italy,
dott. Piergiorgio.

1 Like

Great, let me know how you get on and in due course I’ll add something similar to the adv3Lite library. I may include a check at the scene-ending phase to see if any scenes have ended, and only repeat the scene-starting phase if at least one has. I’ll probably also include a user-settable flag to allow users to revert to the old behaviour if the new behaviour breaks something in their existing code.

tested. For some mysterious reasons, the modify you suggested don’t work:

modify sceneManager
    /* The executeEvent() method is run each turn to drive the Scenes 
mechanism */
    executeEvent()
    {
        /* Go through each Scene defined in the game in turn. */
        for(local scene = firstObj(Scene); scene != nil ; scene = 
nextObj(scene,
            Scene))
            
        {
            /* 
             *   If the scene's startsWhen condition is true and the scene is
             *   not already happening, then provided it's a recurring scene 
or
             *   it's never been started before, start the scene.
             */
            if(scene.startsWhen && !scene.isHappening 
               && (scene.recurring || scene.startedAt == nil))
                scene.start();
            
            /*  
             *   If the scene is happening and its endsWhen property is 
non-nil,
             *   then record the value of its endsWhen property in its 
howEnded
             *   property and end the scene.
             */
            if(scene.isHappening && (scene.howEnded = scene.endsWhen) != nil)
                scene.end();

            if(scene.startsWhen && !scene.isHappening 
               && (scene.recurring || scene.startedAt == nil))
                scene.start();

            /* If the scene is happening, call its eachTurn() method */
            if(scene.isHappening)
                scene.eachTurn();
          
            if(scene.startsWhen && !scene.isHappening 
               && (scene.recurring || scene.startedAt == nil))
                scene.start();
        }        
    }  
;

As you see, I tested either, then both, before and after the eachTurn, no banana. the seams remains, with this funny result:

>think

You could think about (1) Azuj, (2) Miyai, (3) their names, (4) your situation or (5) the weather.


Being in an environment conductive to introspection, you start to ponder about your situation: some specific thoughts came into your mind:
You could think about (1) alpha, (2) beta, (3) panorama around or (4) random things.
[introspection scene running...]

as you can see, the seam between the 1st and 2nd scene gives TWO set of thinking choices: the first is the “standard” ones, the second is the one available during the introspection scene. of course, the active one is the second…

oh, well. For now, I’ll put the kluge locking the southern door with a flag active from the first to the last of those scenes, avoiding that narrative literally goes south, then I’ll ponder how to handle these seams…

Perplexed regards from Italy,
dott. Piergiorgio.

Update: the best solution for now is, as noted by both of you, brute-forcing the start of the next scene in the WhenEnding of the prior scene:

whenEnding() { 
  introspection.start();
  "You sit on the parapet, looking up at the night sky of 
Railei; You feel 
certain that sooner both your spouses will came, and Azuj will leave no 
stone unturned until she understand what happened, and Miyai do her utmost 
in diagnosing and healing yourself... but it's still to be coded !";

but this involves the opposite problem, that is, overlapping the last turn of the earlier scene with the first of the next scene, with a minor but fun issue:

Being in an environment conductive to introspection, you start to ponder about your situation: some specific thoughts came into your mind:
You could think about (1) alpha, (2) beta, (3) panorama around or (4) random things.


(Enumeration and/or hyperlinking of topic suggestions can be toggled on and off using the commands ENUM SUGGS and/or HYPER SUGGS respectively. )


You sit on the parapet, looking up at the night sky of Railei; You feel certain that sooner both your spouses will came, and Azuj will leave no stone unturned until she understand what happened, and Miyai do her utmost in diagnosing and healing yourself... but it’s still to be coded !

aside the first-time appareance of the help on enumeration, which gives here a convenient separator between the parts of the issue, the funny thing is that the WhenStarting of the next scene is fired prior of the WhenEnding of the prior scene. Not a real issue, one of the two calls can be empty, leaving the scene :wink: to the other call.
as things are now, an overlap is far better than a seam, esp. in this specific case where the entire narration can literally go south.

(of course, the next scene should have a never-reached startsWhen, e.g. a dummy flag always nil, or a gRevealed never revealed, lest got the well-known nil object reference error…)

suboptimal solution, but a solution: I’ll tick that box or not ?

Best regards from Italy,
dott. Piergiorgio.

in the light of the “solution” I have put together, thanks to both of you, perhaps the best library-side change is a settable flag reverting the running order of WhenEnding and WhenStarting, like beforeRunsBeforeCheck ?

(I’m not sure of having explained well…)

Best regards from Italy,
dott. Piergiorgio.