On good usage of verify() and check() in TADS 3 games

by Steve Breslin
compiled from discussions on rec.arts.int-fiction

In TADS 2, there was only one defined place to put checks for a command's validity: the "verify" routine, a/k/a the verDoXxx and verIoXxx methods. TADS 3 still has the verify methods, but adds a new point as well, known as the "check" routine. Although both the verify and check routines are places to check conditions on a command, the two have different purposes. The distinction can be a bit subtle, and can be confusing for authors coming from a TADS 2 background.

Strictly speaking

The verify() stage of action processing is designed to interrupt the action processing only when there's a logical problem with the command. Pouring a desk or eating a topic -- such actions would be illogical.

Sometimes, logical actions should be halted before the action processing stage, but after they have been recognized as logical. For example, the player tries to wear some jeans, but the jeans are too small. Maybe the player tries to melt something with the match, but the match isn't producing enough heat. In such cases, check() can interrupt the action, and explain the failure.

The sorts of failures that involve check() would have fit reasonably well in either the verification or the action phase in TADS 2.

So why two pre-action routines, verify() and check()?

The benefit of new check() stage is not in making the verification stage purely logic-oriented, nor in removing some of the conditional statements from the action stage. Such cosmetic adjustments would hardly justify the added complication of the check() stage.

In TADS 3, when we get past the check() stage we know that a viable action is under way. Any objects which want to interfere with that action can do so with beforeAction(). This means that check() can interrupt an action before other objects, say NPC's, have a chance to react to it. So a good use of check() can avoid sequences like this:

>ask stu about chair
As you are about to speak, Mary kicks you under the table. Glaring at her, you redirect your question to Dave.
The large chair isn't something you need to be asking about.

In the above example, Mary's response was a beforeAction(), and the line about the large chair being an unimportant topic was mistakenly placed in the action phase rather than the check() phase. By failing the command before the action phase, we could avoid printing Mary's beforeAction message. So if we want to tell the player that the chair is an unimportant topic before Mary becomes involved, we want to use the check() phase.

It is important to remember, therefore, that check() really is another verification phase, and not a preliminary action phase. Only if a command passes check() should a change in game state be made. In other words, some initial conditional statements should be part of the action stage. The check() stage should not perform preliminary checks which determine which action methods are called.

This begs the question: why not put all the pre-action conditions in the verify() phase, and do away with check() that way? The answer is that we want to break commands into two groups, ones which make sense and ones which do not. This is useful for making good interactive disambiguation sequences, which we can explain by example and make all this nicely clear.

Thinking about this from disambiguation: an extended example

Without recourse to a potentially confusing philosophical distinction between logical and illogical, we can understand the use of the check() stage as one which enables us to make extra adjustments to interactive disambiguation questions. (Interactive disambiguation questions are the questions which the game generates when the player makes an ambiguous command: "Which fan do you mean? The....")

An object which fails in the check() phase is favored over an object which fails in the verify() phase, as far as disambiguation is concerned. (In fact, during disambiguation, the parser doesn't even call the check() method. The parser calls the verify() method to narrow down the field, and then might have to ask the player to choose from the remaining objects, but the parser doesn't call check() at all until after the objects have been chosen.)

For example, we have three fans, one which is a fixed ceiling fan, which can be turned on and off, one which is a takeable oscillating fan, but which can only be taken when it is off, and one which is a hand-fan, a stylized pingpong paddle if you will. First we'll discuss verify() by itself, and then we'll discuss verify() in combination with check().

Good use of verify()

Say the player types:

>turn fan on

We definitely want to exclude the hand-fan, since it's obvious that the hand-fan can't be turned on or off, so this exclusion goes in the verify. So we might get the following disambiguation question:

>turn fan on
Which fan do you mean, the oscillating fan, or the ceiling fan?

Now let's say that one of these fans is already on -- we would want to eliminate this option also, or else we're effectively asking: "Which fan do you mean, the fan that's already on, or the fan that is off?" The verify() stage is designed to eliminate the obviously silly interpretation of the command when there are other good options.

So if the ceiling fan is on, and the oscillating fan is off, we want something like this:

>turn fan on
(the oscillating fan)

Now let's think about the case in which all three fans fail the verify() stage: the hand-fan obviously cannot be turned on, the other two are already on, so they can't be turned on either. If the player types:

>turn fan on

-- we need to ask which fan is intended, so that we can print the appropriate failure message. So when all three fail the verify() stage, a disambiguation question like this is asked:

>turn fan on
Which fan do you mean, the hand-held fan, the oscillating fan, or the ceiling fan?

All of these will fail, of course, but they'll fail for different reasons, so we need to ask which the player meant before we explain why what the player is trying to do isn't going to work.

Good use of verify() in combination with check()

So far we have an example of how verify is used. Let's continue with this example to explore how check() is used in combination with verify() to make the intended disambiguation messages. We'll change the verb from "turn on" to "take".

The ceiling fan cannot be taken ever, since it's attached to the ceiling. The oscillating fan can be taken when it's off. The hand-held fan can be taken no problem. Let's say the oscillating fan is off:

>take fan

It's pretty clear what we want here:

>take fan
Which fan do you mean, the oscillating fan, or the hand-held fan?

Note that the ceiling fan isn't an option, since it can't be taken: it has failed the verify() stage. The two good options are offered in interactive disambiguation.

Now what if the oscillating fan is on:

>take fan

What do we want here? The answer to this question determines whether or not we want the oscillating fan's takeable-condition to be in check() or verify(). Is it obvious to the player that the oscillating fan cannot be taken when it is on? If not, we want this:

>take fan
Which fan do you mean, the oscillating fan, or the hand-held fan?

The above is produced when the fan.isOn condition goes in check(). If we instead put this condition in verify(), we will get this:

>take fan
(the hand-held fan)

If we want to eliminate the oscillating fan during object resolution, then we put the condition in verify(), so that the game will know that it's supposed to assume the player is referring to the hand-held fan when the oscillating fan is on. We should only do this if it should be obvious to the player that the oscillating fan cannot be taken when it's on. (Making too many assumptions about what the player means can spoil puzzles sometimes, and is somewhat intrusive generally speaking. In this sense, "logical" means "what's logical or obvious to the player." The verify() stage being a logic test should be understood in this spirit.)

We can make one other consideration: let's say that the hand-held fan is directly contained by the player character. The oscillating fan is on, and standing on the desk, and the ceiling fan is circling overhead. How do we want to handle this:

>take fan

Deciding this will also help us determine whether or not we want the fan.isOn condition to be in check() or verify(). If it's not obvious that the oscillating fan cannot be taken, we want the following:

>take fan
(the oscillating fan)
You try to take it, but it's too awkward to grab while it's oscillating. Maybe you should turn it off first.

But if we want the following:

>take fan
Which fan do you mean, the ceiling fan [which cannot be taken], the hand-held fan [which you already have], or the oscillating fan [which cannot be taken because it's on]?

-- then we should put the oscillating fan's isOn condition in verify(). Whether you want to assume that the player means the oscillating fan in this case is up to you; the "logic" will be different between games, based on the context, how obvious the situation is to the player, and based on the game author's sense of taste in deciding what player knowledge should or should not be assumed.

The point is that you should choose where the condition goes based on what you want to see in the disambiguation phase.

Of course, you don't need three fans to make up your mind about where the code should go, but you can imagine other objects in your game that could be competing with your object, to help you determine where the code should go from a stylistic point of view.

From a goal-oriented perspective, it all comes down to the practical implications of putting a condition in one place or another, and often there are no practical consequences, in fact, to the distinction between check() and verify(); but you can imagine disambiguation conflicts to insure you're using the right style in principle.