Does State.turns include a 'visit' to StoryInit?

Twine Version: 2.10.0

Question. Does State.turns include a “visit” to special passage StoryInit? And a related question: am I correct in thinking that everything in a Twine (with Sugarcube 2.37.3) story’s javascript section gets executed even before the contents of StoryInit are acted upon?

Does State.turns include a “visit” to special passage StoryInit ?

The State.turns getter variable returns the number of Passages that have being visited during the current playthrough. As the StoryInit special Passage isn’t normally visited, it isn’t normally included in the number of turns.

everything in … story’s javascript section gets executed … before the contents of StoryInit (is processed)

Yes. As that allows things defined in the Story JavaScript area to be potentially available (1) for use in StoryInit.

(1) I say “potentially available” because a thing’s availability depends on the Scope it was given when it was defined.
eg. if a thing is defined in a way that gives it “global like” scope, like defining it on the special setup variable, then it will be available to other parts of SugarCube’s runtime engine. However if the thing is defined to only be available to other things within the Story JavaScript area, then that Private Scoped thing won’t be available anywhere else in the project.

Thanks, GreyElf!

One other question: in a sample game that I’m coding in Sugarcube, play starts in the StartUp passage (I’m coding it in the desktop-version of Twine 2.10.0. I’ve reached that point in development where it’s no longer trivial to play through in real-time up to the passage I’m testing: passage Remembrance.

So, I’ve added a function at the end of my javascript section:

  setup.startAtRemembrance = function(){
  setup.StartupSituation = "Remembrance";
  setup.addVisits("Campfire")
  setup.addVisits("YourTent")
  setup.addVisits("YourBackpack")
  setup.addVisits("YourBackpack")
  setup.addVisits("YourBackpack")
  setup.addVisits("YourTent")
  setup.addVisits("Campfire")
  setup.addVisits("MakeCoffee")
  setup.addVisits("EnjoyCoffee")
  setup.addVisits("MakeCoffee")
  setup.addVisits("EnjoyCoffee")
  setup.addVisits("Campfire")  
};

The above function relies on the following function defined earlier in the javascript section of my Twine:

setup.addVisits = (passageName, number = 1) => {
  while (number--) {
    State.expired.push(passageName);
  }
};

Those multiple calls to the addVisits() function mirror the actual passages I have to playthrough in order to get to passage Remembrance — and yes, you can actually visit YourBackpack three times in succession due to it having a “take coffee” link nested within an if conditional.

I call function setup.startAtRemembrance() as the final line of code at the bottom of my javascript section. Then, in StoryInit I have the following:

/* We can modify $inventory contents here as needed, based on setup.StartupSituation */
<<set $inventory to ['a canteen', 'a pocket knife', 'a handkerchief']>>

<<switch setup.StartupSituation>>
<<case "NewGame">>
       /* we're in startup passage Campfire... */ 
	<<set $cupsOfCoffeeDrank to 0>>
    <<set $instantCoffeePackets to 2>>
<<case "Remembrance">>
	<<set $cupsOfCoffeeDrank to 2>>
    <<set $instantCoffeePackets to 0>>
	<<goto "Remembrance">>
<<default>>
	/* do nothing  */
<</switch>>

Now, given all the above, this solution is working great for the purpose of simulating actual play through the first N passages, and making my way as the player to passage Remembrance. However, I notice that — when testing in passage Remembrance with the followiing line of code — I’m told that passage Campfire has been visited 4 times, not the three times from my setup.addVisits() calls:

You have visited psg Campfire <<= visited("Campfire")>> times.

Is it showing 4 vs 3 visits because for a hot millisecond we’re in passage Campfire before the switch in StoryInit moves us to passage Remembrance?

Have you changed the default value of Config.history.maxStates in your project?

No. I’ve not altered maxStates for this sample game.

I would need to think about & test things before I could recommend a better method for simulating a “previous play-through” that could be used for debugging purposes, but some of the things I would likely consider are:

1: The Config.passages.start setting.

This setting can be used in StoryInit to control which Passage will be displayed when the runtime engine finishes its startup process.

<<set Config.passages.start to "Remembrance">>

There are a number of reasons why using that setting is better than calling <<goto>> in StoryInit, one of them being that the runtime engine still has things it needs to do after StoryInit has been processed before setup is finished, so the Engine.play() method the macro calls may be triggered too early. Using the setting allows the startup process to finish normally, just with a different Passage being shown.

2: Using the application’s “Test from Here” option and the Config.debug flag.

The “Test from Here” option can be used to temporary control which Passage is first shown. It basically affects what Passage Name is assigned to the Config.passages.start setting during the normal startup process, it also sets the Config.debug flag to true. That “default” Passage Name can be overwritten if needed using the technique from point 1.

3: Using specifically tagged Passages to control which “Test Case” is run.

Currently you are storing specific “previous playthrough” related code in both Story JavaScript and StoryInit. Which makes both those areas more messy, and potentially means you need to comment out or remove that specific content before creating a release.

I think a better option would be to store all the code needed to setup a specific “playthrough” in a normal Passage, and then assign that Passage a specific Passage Tag (like debug?) when you want to test that specific “playthrough”.

eg. Create a Passage named “Testing Remembrance” with content like…

/* Code for padding history with the required Moments */
...

/* Code for assigning alternative defaults to Story Variables */
<<set $cupsOfCoffeeDrank to 2>>
<<set $instantCoffeePackets to 0>>

/* Change which Passage is shown first, if not using "Test From Here" */
<<set Config.passages.start to "Remembrance">>

Code like the following can be added the end of your standard StoryInit to look for, and process the content of, any Passage with a specific Passage Tag…

/* Run Test Case if one is found */
<<script>>
	let testcase = Story.find( (p) => p.tags.includes("debug") );
	if (testcase) {
		jQuery.wikiPassage(testcase.name);
	}
<</script>>

And then anytime you want to test a specific test case Passage you just need to temporary add the debug Passage Tag to it.

note: I suggest you review the documentation of any of the above used methods you’re not aware of. The JavaScript could be placed a method definition like so…

setup.runTestCase = function () {
	let testcase = Story.find( (p) => p.tags.includes("debug") );
	if (testcase) {
		jQuery.wikiPassage(testcase.name);
	}
};

…and then replace the previous <<script>> macro call with the following <<run>> macro call..

/* Run Test Case if one is found */
<<run setup.runTestCase()>>

This way your testing code is isolated from your normal Story JavaScript and StoryInit code.

Far superior/safer to what I was doing. Thanks for the taking the time and educating me, GreyElf!

Okay, my debug-tagged TestingRemembrance passage now holds the following:

/* Code for padding history with the required Moments */
  setup.addVisits("Campfire")
  setup.addVisits("YourTent")
  setup.addVisits("YourBackpack")
  setup.addVisits("YourBackpack")
  setup.addVisits("YourBackpack")
  setup.addVisits("YourTent")
  setup.addVisits("Campfire")
  setup.addVisits("MakeCoffee")
  setup.addVisits("EnjoyCoffee")
  setup.addVisits("MakeCoffee")
  setup.addVisits("EnjoyCoffee")
  setup.addVisits("Campfire")  

/* Code for assigning alternative defaults to Story Variables */
<<set $cupsOfCoffeeDrank to 2>>
<<set $instantCoffeePackets to 0>>

/* Change which Passage is shown first, if not using "Test From Here" */
<<set Config.passages.start to "Remembrance">>

And my javascript contains this function:

setup.runTestCase = function () {
    let testcase = Story.find( (p) => p.tags.includes("debug") );
    if (testcase) {
        jQuery.wikiPassage(testcase.name);
    }
};
​

Works perfectly! I guess the only thing I have to remember is to only have a single “debug”-tagged passage at a time.

Previously, I could only run setup.addVisits( ) in the javascript section. Am I correct that the setup.runTestCase( ) function somehow confers needed scope so that I am now able to call setup.addVisits( ) from a passage?

The runtime engine makes the special setup variable available to the Private scoped Execution Context being used to process the content of a Passage (including the special ones), which is why the setup.runTestCase( ) method defined in Story JavaScript can be called from StoryInit. So because you defined your addVisits( ) method on setup it automatically becomes available too.

However, that is a JavaScript based method so it needs to be called via a macro like <<run>> or <<script>>, which is why I used those two macros to call my own setup.runTestCase( ) method.

eg. This will not work in a TwineScript based Passage…

/* Code for padding history with the required Moments */
  setup.addVisits("Campfire")
  setup.addVisits("YourTent")
  setup.addVisits("YourBackpack")
  setup.addVisits("YourBackpack")
  setup.addVisits("YourBackpack")
  setup.addVisits("YourTent")
  setup.addVisits("Campfire")
  setup.addVisits("MakeCoffee")
  setup.addVisits("EnjoyCoffee")
  setup.addVisits("MakeCoffee")
  setup.addVisits("EnjoyCoffee")
  setup.addVisits("Campfire") 

…but this will…

<<script>>
/* Code for padding history with the required Moments */
  setup.addVisits("Campfire")
  setup.addVisits("YourTent")
  setup.addVisits("YourBackpack")
  setup.addVisits("YourBackpack")
  setup.addVisits("YourBackpack")
  setup.addVisits("YourTent")
  setup.addVisits("Campfire")
  setup.addVisits("MakeCoffee")
  setup.addVisits("EnjoyCoffee")
  setup.addVisits("MakeCoffee")
  setup.addVisits("EnjoyCoffee")
  setup.addVisits("Campfire") 
<</script>>

UPDATE: You’re right: I found my erroroneous assumption. Thanks!

Disregard below…

The following code in passage TestingRemembrance does indeed appear to be working (I’ll link a zipped copy of the game) If you run the game and then click “CHKVARS” button in the sidebar, it shows us at 16 turns — warning, there’s some sloppy code in PsgCheckVars:

/* Thanks to GreyElf for suggesting a dedicated passage (in this case, with a tag named "debug") for simulating playthrough to a certain place in the story, for debugging purposes.  There is a function defined on the setup object in the javascript section of this twine named runTestCase( ). It looks for a passage tagged "debug". Finding one, it then executes whatever is in that passage via jQuery.wikiPassage(testcase.name) */

/* Code for padding history with the required Moments */
  setup.addVisits("Campfire")
  setup.addVisits("YourTent")
  setup.addVisits("YourBackpack")
  setup.addVisits("YourBackpack")
  setup.addVisits("YourBackpack")
  setup.addVisits("YourTent")
  setup.addVisits("Campfire")
  setup.addVisits("MakeCoffee")
  setup.addVisits("EnjoyCoffee")
  setup.addVisits("MakeCoffee")
  setup.addVisits("EnjoyCoffee")
  setup.addVisits("Campfire")  

/* Code for assigning alternative defaults to Story Variables */
<<set $cupsOfCoffeeDrank to 2>>
<<set $instantCoffeePackets to 0>>

/* Change which Passage is shown first, if not using "Test From Here" */
<<set Config.passages.start to "Remembrance">>

CampfireTale.zip (166.0 KB)