Chimerror Productions

Quoll Work Thread, 2025-12-22 - 2025-12-22 04:04

Tags: blog gamedev inform7 interactive fiction programming quoll testing text adventures

It’s been about two months since I’ve made a blog post for Quoll work. I’ve been preferring to make updates on social media since I’ve been working on a few small bugs, as it’s a bit less overhead. But tonight, I made a big change to make it easier to handle having text adapt correctly over resets…

One of the main mechanics of this game is that our friendly technomage, Ada is working in a small “chunk” of time that only lasts so long, with each command given to her taking a minute. When the amount of time/turns in that chunk run out, she will be forced to the Time-Cursor. From there, she can go UP to reset not only the chunk of time, but her memory of what has happened in the chunk of time.

For Things and Rooms, this is handled by my code that I’ve written to track changes to their major properties like Things being familiar or unfamiliar and rooms being visited or unvisited. In many cases, I use the Subject Kind of Thing’s familiar property to keep track of this for conversational topics:

GNBSP-1-Missing-CDs-Subject is a privately-named unfamiliar subject.

To have Taffany-Being respond on the topic of the CDs:
  if GNBSP-1-Missing-CDs-Subject is unfamiliar:
    say "ADA: So what's up with these CDs?...";
    subject-familiarize GNBSP-1-Missing-CDs-Subject;
  otherwise:
    say "ADA: Can you remind me about those CDs?...";

In this case the magic happens when I use the subject-familiarize Phrase, which registers the Subject to be handled by my Chronomancy item management code, which doesn’t really need explaining for this blog post.

One of the issues with using Subjects is that Subjects are Things, and therefore technically objects that may or may not appear in the model world of the game. Additionally this means that unless I mark them as privately-named, they will be recognized by the parser as part of a valid command. But there are a lot of things that I need to track that doesn’t need to be exposed to the Playerr ever because I don’t intend them to be possible topics of conversation.

Usually this is a Truth State, that is, what Inform 7 calls a Boolean value that can be either true or false. This was generally fine when I only had one time chunk in the last release, but now that there are two time chunks, it now matters what happens to this value when Ada advances to the next time chunk.

When a time chunk advances, the value of a Truth State may or may not have changed in the previous time chunk. This means that I also need to check these values during time chunk initialization to determine if resetting the value will need to happen on resetting this time chunk. This led to this pattern of using two Truth State variables:

Ada-Told-About-Ada is a truth state that varies. Ada-Told-About-Ada is false.
Ada-Reset-Telling-About-Ada is a truth state that varies. Ada-Reset-Telling-About-Ada is false.

A Time Chunk Initialization Rule (this is the prepare to reset ada telling glitch about ada rule):
  if Ada-Told-About-Ada is false:
    now Ada-Reset-Telling-About-Ada is true;
  otherwise:
    now Ada-Reset-Telling-About-Ada is false.

A Time Chunk Reset Rule when Ada-Reset-Telling-About-Ada is true (this is the reset ada telling glitch about ada rule):
  now Ada-Told-About-Ada is false.

This is OK, but it means that the two variables and two Rules have to be re-written for every variable like this. I’ve generally been OK with this because I felt like trying to come up with a cleaner solution would be prohibitive and add the risk of breaking working code.

But now that I am going through and adding a bunch of new writing, I quickly realized that creating these again and again will pollute my variable namespace, and add the overhead of processing a bunch of individual rules. To fix rule-processing, I could try to collect all my individual rules into one big rule, perhaps one for each sublevel, but I favored the individual rules because I wanted to keep all the code for working with the variable close to it, but there is not an easy fix for the repeated definitions.

Maybe a cleaner solution is no longer that prohibitive, so let’s bundle these up into a Kind of Object:

A time-safe-bool is a kind of object.
It has a truth state called the reset-value. The reset-value of a time-safe-bool is usually false.
It has a truth state called the current-value. The current-value of a time-safe-bool is usually false.
A time-safe-bool can be volatile or static. A time-safe-bool is usually volatile.

Object is the base Kind of Thing, so it doesn’t carry all the baggage for being placed in the model world, which is nice. I had already used Objects in my code for handling the Propositional Reckoning mechanics of formulas and premises. With the time-safe-bool Kind, I can define a List of them to keep track of them with Time chunk managed bools is a list of time-safe-bools that varies., and then add some Phrases to work with the List:

To update TSB (B - a time-safe-bool) to (S - a truth state):
  unless B is volatile:
    say "ERROR: Trying to update a time-safe-bool that has been made static!";
  otherwise:
    unless the current-value of B is S, now the current-value of B is S;
    unless the reset-value of B is S, add B to time chunk managed bools, if absent.

To clear out managed TSB:
  repeat with B running through time chunk managed bools:
    if the current-value of B is not the reset-value of B, now the reset-value of B is the current-value of B;
    now B is static;
  truncate time chunk managed bools to 0 entries.

And extend my default time chunk Rules to manage the time-safe-bools in the List:

A Time Chunk Initialization Rule (this is the default time chunk initialization rule):
  clear out managed TSB.

A Time Chunk Reset Rule (this is the default time chunk reset rule):
  [...]
  repeat with B running through time chunk managed bools:
    if B is static:
      say "ERROR: Static time-safe-bool found in managed bools list!";
    otherwise if the current-value of B is not the reset-value of B:
      now the current-value of B is the reset-value of B;
  [...].

A Last Time Chunk Advance Rule (this is the default time chunk advance rule):
  [...]
  clear out managed TSB.

With all this in place, I can now write a lot less code with only one variable added to the namespace:

There is a time-safe-bool called Ada-Has-Rejected-Giving-To-Plushies.

Response for a plush animal toy when given-or-shown something:
  if the current-value of Ada-Has-Rejected-Giving-To-Plushies is false:
    say "...";
    update TSB Ada-Has-Rejected-Giving-To-Plushies to true;
  otherwise:
    say "...".

Very nice, right? There were some headaches implementing it though. Usually when I define a Thing, I write something like Ricketts is a grue. So you might think I could write Ada-Has-Rejected-Giving-To-Plushies is a time-safe-bool, but this does not work. The issue is that the Object Kind does not support the Foo is bar. pattern, just Things. But I have to declare something to exist, Inform 7 does not provide a method to create Objects on the fly at run-time. For earlier Objects, this led to code like this:

A formula is a kind of object. A formula can be eigened. A formula is usually not eigened. There are 50 formulas.

To decide which formula is represented by formula-text (T - a text):
  let F be a random not eigened formula;
  if F is nothing:
    say "Error: No formulas left";
    decide on F;
  otherwise:
    [...]
    now F is eigened;
    decide on F.

Notice that I have to declare that There are 50 formulas, and then I use the decide which Phrase to find a formula that I haven’t used yet and then use it (managed by the eigened Property). This makes sense in the case of formulas since I needed to be able to translate text to them, but luckily, in this case, I was able to just change the wording to There is a time-safe-bool called Ada-Has-Rejected-Giving-To-Plushies, but this type of wording issue comes up a lot. Inform 7 may look a lot like English, but it is not English.

Another issue was that as Inform 7 loads included Extensions in the order that I include them in the source code, I needed to move the inclusion of this before my first use in my Plushies Extension. My first attempt, however, caused a regression because my Chronomancy Extension overwrites a Phrase defined by the Optimized Epistemology by Andrew Plotkin Extension. By moving it up, Optimized Epistemology ended up being loaded after Chronomancy, reverting to its implementation of the Phrase.

But neither of those were too bad and now I’m done with this new feature that will make things so much smoother. I will still need to go and replace all the existing definitions with time-safe-bools, but I will probably save that for a minor release after this major release.

Not a bad day’s work!

Jaycie “chimerror” Mitchell