Inconsistent behaviors from putatively identical objects

I was experimenting with one of the excellent examples (Witnessed 1 – §9.5) as one does, trying to add code to automatically open the battery compartment in each device – the recorder and flashlight – when inserting the battery into the device itself. This worked with one but not the other device, which seemed really odd.

Therefore I stripped the problem down to its basics, as one does, and still find really weird inconsistent behavior:

Test is a room.

A battery compartment is a kind of container.
A battery compartment is usually closed and openable.
One battery compartment is part of every device.

A light source is a kind of device.
The flashlight is a light source.

The cassette recorder is a device.

Understand "open [openable closed thing]" as opening.

The player carries the cassette recorder and the flashlight.

Test me with "open recorder / open flashlight".

produces this:

Test

>test me
(Testing.)

>[1] open recorder
It isn't something you can open.

>[2] open flashlight
You open the flashlight's battery compartment.

The flashlight and recorder appear to be identically created as devices, each of which has an openable battery compartment which the understand statement should open when opening the device itself. Yet only the flashlight works correctly. The Kinds Index shows both objects to be equivalently constructed. Each is a device with a battery compartment, but they behave differently.

What is going on?

I’m just as interested in tips as to how to figure out such issues as I am in a solution to this particular question.

Many thanks in advance!

Test

>test me
(Testing.)

>[1] open recorder
It isn't something you can open.

>[2] open flashlight
You open the flashlight's battery compartment.

>open cassette
You open the cassette recorder's battery compartment.

>

Err…what?

The command “open recorder” doesn’t work because the recorder isn’t openable. The openable object is the “recorder’s battery compartment”. So you could say “open recorder’s compartment”, or just “open recorder’s”.

The command “open flashlight” happens to word because Inform’s word recognition stops at nine characters! So “flashlight” and “flashlight’s” look like the same word to the parser.

Even though it more or less works for the flashlight, it’s not an ideal situation. You have two objects with very similar names (“flashlight” and “flashlight('s) battery compartment”). Any given command like “open”, “turn on”, “put battery in…” has to be applied to the right one, and that’s hard to arrange in a way which is transparent to the player. It’s a parser fail waiting to happen.

It would be better to model each device as a single object. This is also kind of a nuisance, since Inform doesn’t like the idea of something which is both a container and a device. But you can hack it together, and it will be more reliable in play.

Many thanks, Zarf! So it was primarily a naming issue and the nine character word recognition limitation. Didn’t know about that. That explains why the understand statement worked for the flashlight but not the recorder.

And thanks for the pointer about how to model the objects!

But why did “open cassette” work to open the cassette recorder’s battery compartment?

Would you be kind enough to expound on “hack it together” into a thing that is both container and device? Or point us in the right direction? Cheers :slight_smile:

In assemblies like this that have been created by a line like “One foo is part of every bar,” when you create a bar named baz, Inform usually names the automatically created thing “baz’s foo.” So here Inform generates the name of the object as “cassette recorder’s battery compartment.” Then–as usual with names–each individual word can be understood as the name of the thing. So “cassette,” “battery,” and “compartment” all work, but the other word is “recorder’s” rather than “recorder.”

Thanks matt w, interesting answer, hopefully will stop me falling in similar traps.

(It totally won’t, but we can hope :slight_smile: )

[edit] So, you’re saying Inform automatically disambiguated “cassette” (a non openable thing, but 8 characters) into “cassette recorder’s battery compartment” but wouldn’t do the same for “recorder”?

Nope, I’m still confused :frowning:

[quote]
So, you’re saying Inform automatically disambiguated “cassette” (a non openable thing, but 8 characters) into “cassette recorder’s battery compartment” but wouldn’t do the same for “recorder”?

[quote]
Correct.

It’s pretty simple underneath – it looks at a list of words and sees what matches. The list of words for this object is “cassette”, “recorder’s”, “battery” “compartment”. If the player types CASSETTE, that’s in the list. RECORDER is not in the list, but RECORDER’S is. COMPARTMEZZLE counts as in the list because the parser is only looking at the first nine characters.

So Inform thinks:

“you want to open something, I have two things with the same name(the same word in both lists), lets pick the openable one and see if that works?”

I think there’s another subtlety worth mentioning here. But I’m no expert on I7 internals, so Zarf, please correct me if this is wrong:

On the Z-machine, words are truncated to nine characters, but non-letters occupy more than one character slot. The word “recorder’” (nine characters including the final apostrophe) would require ten character slots, because the apostrophe isn’t a letter of the alphabet. Thus, what really happens is that “recorder’s” gets truncated to “recorder”, which does match the input. So in this case, I think we would get different behaviour depending on whether we’re compiling for the Z-machine or Glulx, and I draw the conclusion that the original example was compiled for Glulx.

Normally the parser would ask a disambiguation question. However, the original code contains this line:

Understand "open [openable closed thing]" as opening.

This matches on openable non-open things. Since there is one, the parser latches onto it and never considers any other objects.

(This is not my favorite way to bypass disambiguation, but it does work.)

[lft and zarf answered more succinctly while I was typing, but here’s a verbose answer]

There are two different issues here! One question is why “recorder” doesn’t work for the cassette recorder’s battery compartment, and the other question is why “cassette” manages to pick the cassette recorder’s battery compartment rather than the cassette recorder itself. These turn out to be totally different issues.

For the first one: The thing here is that the part of the Inform compiler that assigns the name “cassette recorder’s battery compartment” to the compartment in question doesn’t talk to the parser at all. When you write the code that creates the compartment, it’s exactly as if you had written something along the line of “The cassette recorder’s battery compartment is a closed openable container. It is part of the cassette recorder.” This is the work of the compiler, which runs when you press “play” and turns the Inform 7 code first into Inform 6 and then into z-machine/glulx stuff.

The parser knows that each of the individual words, including “recorder’s,” can be used to refer to the compartment–but it doesn’t know that the word “recorder” is supposed to have anything to do with “recorder’s.” As far as the parser is concerned those are two completely different words. (This is the work of the parser, which is running as the game goes on, as part of the stuff your original code compiled to.)

So there’s no question of automatic disambiguation here–the Inform parser doesn’t see “recorder” as referring to the “cassette recorder’s battery compartment,” so it sees no need for disambiguation.

The question of why “cassette” picks the compartment instead of the recorder is a little more complicated and has to do with how the parser works. Specifically, it’s this line you wrote:

Understand "open [openable closed thing]" as opening.

This becomes one of the grammar lines the Inform parser will check when it sees a command of the form “OPEN blah blah blah.” In this case, it looks to see if the words typed after OPEN will match any openable closed thing, as per the token you put in there. In this case, the compartment is the only thing that can be matched by the word “cassette,” since the recorder isn’t an openable closed thing. So when the parser finds that match, it decides that that’s how it should be interpreting the command, and there’s no need for disambiguation–since “CASSETTE” produced only one match for [openable closed thing].

Now, if there weren’t any matches at all, the parser would go on to check other grammar lines for commands beginning with “OPEN”–in particular, the grammar line for “open [something]”. You can see this if you try “open cassette” twice in a row. The second time, the compartment isn’t an openable closed thing (since it’s not closed), so the parser moves on to try to match “CASSETTE” against “[something]”. Now there are two possible things that “CASSETTE” could refer to, the compartment and the recorder. Here the Inform parser adverts to a rather opaque algorithm that, among other things, prefers things the player holds to other things for most actions, and decides to automatically disambiguate to the recorder, printing a clarifying message. So you get:

If you delete your Understand line, you will see that the parser goes straight to the second response.

tl;dr: The reason the parser treats “cassette” as referring to the compartment the first time has to do with gnarly parser internals and the fact that the compartment is the only thing that fits the Understand line you wrote. The reason the parser doesn’t understand “recorder” as the compartment is because “recorder” isn’t part of the compartment’s name; only “recorder’s” is.

This is fascinating tricksy stuff, thank you to everyone who took the time to write about it!

I came up with an instead rule that seems to resolve this particular issue:

Instead of opening an unopenable device (called the powerable) when a battery compartment (called the powerhold) is part of the powerable:
	try opening the powerhold.

(Of course, you’d need to write similar rules for every other action that needs to be “redirected” to the battery compartment.)
I guess zarf’s warning about similarly-named objects still applies, but it seems to work at least with a small example like this…

Nice thought! You can use a kind of action to avoid writing different rules for every other action, by getting the action first thing and changing the noun (or second noun):

[code]Test is a room.

A battery compartment is a kind of container.
A battery compartment is usually closed and openable.
One battery compartment is part of every device.

A light source is a kind of device.
The flashlight is a light source.

The cassette recorder is a device.

Understand “open [openable closed thing]” as opening.

The player carries the cassette recorder and the flashlight and an AA battery.

Opening is action preferring compartment as noun. Closing is action preferring compartment as noun.

Inserting something into something is action preferring compartment as second noun.

First before action preferring compartment as noun when the noun is an unopenable device that incorporates a battery compartment (called the powerhold):
now the noun is the powerhold.

First before action preferring compartment as second noun when the second noun is an unopenable device that incorporates a battery compartment (called the powerhold):
now the second noun is the powerhold.[/code]

Now, if you think of another action that needs to be done to the compartment instead of the recorder, you can just write “Searching is action preferring compartment as noun” and it’ll be redirected.

(Note that the action name kinds like “action preferring compartment as noun” aren’t logically complex–they don’t refer to actions, compartments, or nouns. As far as Inform is concerned I might just as well have written apcan or something like that. I just wrote them out like that to try to make clear what kind of action needed to be incorporated there.)