The Red Room is a room.
The White Room is a room.
The red door is a locked door. It is north of the White Room and south of the Red Room.
The player is in the White Room.
The player carries a wallet and a key. The key unlocks the red door.
The Green Room is a room.
The mysterious door is a locked door. It is west of the Green Room.
The Mysterious Hall is west of the mysterious door.
After deciding the scope of the player:
place the Green Room in scope;
After reading a command:
say "[the player's command]."
Test me with "unlock door / key".
Result:
White Room
You can see a red door here.
unlock door
unlock door.
What do you want to unlock the red door with?
key
unlock door it with key.
I didnât understand that sentence.
Does anyone know whatâs causing the parser to interpret my command as âunlock door it with keyâ? It only seems to happen under these specific circumstances:
The player is carrying at least two things.
The player has typed âunlock doorâ. Not âunlock red doorâ or âunlock door with keyâ.
Another door (or room, which a door leads to) has been placed in scope.
By the way, the scope is a bit of a red herring here. You can see the same bug with this code:
The Red Room is a room.
The White Room is a room.
The red door is a locked door. It is north of the White Room and south of the Red Room.
The player is in the White Room.
The player carries a wallet and a key. The key unlocks the red door.
The Green Room is a room.
The mysterious door is a locked door. It is west of the Green Room and east of the White Room.
The mysterious door is scenery.
The situation is that there are two doors in scope, but the parser selects one (the non-scenery one in this example) and moves on to the next stage of disambiguation.
Iâve got a stab at whatâs happening, I think. The parser notices that itâs missing a preposition and indirect object early, before it thinks about needing to disambiguate âdoorâ. So it sets the inferfrom global to 1 to indicate weâre going to need to add something after token 1. But then it notices that âdoorâ is ambiguous. Adjudicate decides the red door edges out the mysterious door and we wonât need disambiguation⌠but since inferfrom is already set when all of that is considered, itâs confused and thinks the direct object is what weâre inferring, so it adds the pronoun.
Iâm not sure what the motivation is for the whole pronoun thing. Iâm wondering if maybe itâs a vestige of an outdated strategy for how to deal with a missing noun. At any rate, thereâs no attempt to eliminate the existing tokens in the command that led to the choice of object â it just adds a word to the end, either a pronoun or a preposition. (In the latter case, itâs the right thing.)
In NounDomain in Parser.i6t we have:
if (number_matched > 1) {
i = true;
if (number_matched > 1)
for (j=0 : j<number_matched-1 : j++)
if (Identical(match_list-->j, match_list-->(j+1)) == false)
i = false;
if (i) dont_infer = true;
i = Adjudicate(context);
if (i == -1) rfalse;
if (i == 1) rtrue; ! Adjudicate has made a multiple
! object, and we pass it on
}
! If i is non-zero here, one of two things is happening: either
! (a) an inference has been successfully made that object i is
! the intended one from the user's specification, or
! (b) the user finished typing some time ago, but we've decided
! on i because it's the only possible choice.
! In either case we have to keep the pattern up to date,
! note that an inference has been made and return.
! (Except, we don't note which of a pile of identical objects.)
if (i ~= 0) {
if (dont_infer) return i;
if (inferfrom == 0) inferfrom=pcount;
pattern-->pcount = i;
return i;
}
If we add dont_infer = true; to the bottom of the if (number_matched > 1) conditional block, it fixes the behavior for this example.
Does it also horribly break something else because Iâm stumbling around in very long, complicated code? Maybe.
In case it helps, I tried including Locksmith by Emily Short (in Zarfâs modified version of your example) and it no longer gave me that error. Besides which, players detest being asked what they want to unlock a door with when theyâre carrying a key, and Locksmith gets you past that, too. However, I donât know if your test case was explicitly about what it seems to be about.
All this NounDomain stuff is way over my head. That said, I tried compiling and testing both the original code I posted and zarfâs amended version in 6G60: strangely enough, my version worked correctly but not zarfâs, whereas under 6M62 both versions produce âI didnât understand that sentence.â
Yes, you are right that this whole thing with unlocking can be avoided by including Locksmith. But I just realised that itâs not just the scope thing thatâs a red herring (as zarf pointed out), but unlocking and locking as well. The same thing can also happen with giving and showing (and maybe other actions applying to two things?)
For example:
The Recording Studio is a room.
John, Paul, George and Ringo are men in the Recording Studio.
The acoustic guitar and the electric guitar are things in the Recording Studio. The acoustic guitar is scenery.
After reading a command:
say "the player's command: [the player's command]."
Test me with "show guitar / john / give guitar / john".
The error produced this time is "You can only do that to something animate:
Recording Studio
You can see John, Paul, George, Ringo and an electric guitar here.
test me
the playerâs command: test me.
(Testing.)
[1] show guitar
the playerâs command: show guitar.
Whom do you want to show the electric guitar to?
[2] john
the playerâs command: show guitar it to john.
You can only do that to something animate.
[3] give guitar
the playerâs command: give guitar.
Whom do you want to give the electric guitar to?
[4] john
the playerâs command: give guitar it to john.
Right, but the problem in this example is the same as in Zarfâs example. When we just type âshow guitarâ, Inform happens to be picking the one thatâs scenery, and that leads to the bug. I mean, maybe you already understand that. If youâre asking what can we do about it â Iâm not sure off the top of my head. Things that happen before you can jimmy them with a âDoes the player meanâŚâ rule (like your example does) can be hard to fix.
The bug-avoiding solution would be to not allow scenery and something with a similar name to be in scope at the same time. Which often just means, not in the same location as each other, unless your game meddles with scope or has transparent windows you can look through or something like that.
Another way is to add in rules for supplying a missing noun (18.32 in the docs) but if the bug is widespread in its domain, that might be impractical, because youâd be adding it for every affected verb.
Yeah, I was asking what the best way to deal with this was. Sorry for being unclear.
It seems there are three options:
Go through the Actions index, check for potentially affected verbs, write a whole bunch of Understand âverb [something]â as verbing. Rule for supplying a missing second noun while verbing: ⌠rules. Or Understand âshow [something]â as showing vaguely ⌠This is what Locksmith does with unlocking, and, as you said, itâs a good idea to include it because constantly being asked âWhat do you want to unlock that with?â when thereâs one obvious candidate is annoying. But sometimes there isnât an obvious candidate and I want the game to ask the player to clarify.
Make sure that the game never has to ask âwhat do you want to verb that with âŚ?â in the first place by making sure things that share a part of its name with other things arenât in the same location. Unfortunately, I do have transparent windows/doors in my test game, as well as things the player can pick up and move around, so this isnât ideal.
Replacing the long (and scary-looking) âNoun Domainâ just to add a single line, as Zed suggested. Iâm a little nervous about doing this, as Iâm not sure if itâll have unintended consequences. That said, it seems to fix the problems I had with both lock/unlock and give/show, so unless something comes up, Iâll go with that solution for now.
Good luck. If that fix seems to not break everything, try to remember to report back in this thread! Actually, try to remember to report the opposite as well.
Iâd say being nervous about that course of action is wholly justified. Iâm still working on my test suite but have scarcely scratched the surface of the parser itself.
I remembered a couple of extensions rewrite NounDomain and indeed Disambiguation Control by Jon Ingold fixes this behavior. But it introduces its own known issues:
âDoing something withâ only checks for first noun:
â particularly annoying for give and show, which check their reversed forms first
â meaning the first attempt at parsing will ignore all rules about animate contexts!
â current workaround throws out non-animates for giving and showing to.
This isnât great (see comments in the appropriate section) - but the problem is the order in which the parser defines the grammar lines
and thatâs impossible to change!
The parser will âauto-guessâ if it matches a group of âindistinguishableâ items, but the parser doesnât always know when items are indistinguishable. In particular, if you use â[something related by containment]â to refer to a generic container / kind of container, the parser will treats these items as indistinguishable (same grammar) despite the fact the player can - easily - distinguish them.
â Need some way to step in and tell the parser âthese are not the sameâ
â was possible in I6 â##TheSameâ , not sure how to do it now
Responsive Disambiguation for 6M62 by Matt Weiner blew up with run-time errors for this case.
âLeave things as they areâ is always a possible solution. This bug affects many Inform games. If yours is one of them, itâs not the end of the world.
(If UNLOCK is the verb that players will run into most often in your game, then the Locksmith extension is an easy answer.)
I happened across this old problem and have been looking at it.
As far as I can tell, Zed is correct about the nature of the fault. The global inferfrom is used both to signal that an inference has been made and to record the token position in the grammar line at which the first inference was made. The intended function of the âpronoun gluingâ code is not clear; it seems best suited for cases in which a single suitable object is assumed while trying to infer the rest of an incomplete command, but it does not appear to be executed in such cases.
The handling of pronouns is such that they immediately halt parsing of a noun phrase, so, as Zed points out, adding one to a noun phrase is inherently non-functional unless any other words in the noun phrase are eliminated from the command buffer.
The only way to address this conclusively that occurs to me is to attempt to blank out any matching words in the command buffer before inserting the pronoun. The parser is working within a framework of grammar line tokens and does not keep track of which command words correspond to each token. It would be relatively straightforward to set up another array used by the parser (alongside the other line_* arrays) to store this information, perhaps using the same storage format as snippets. This information could then be used to blank out the noun phrase that matched the inferred object prior to insertion of the pronoun.
I built a proof-of-concept of this that seems to solve the OPâs problem. The guts of it are:
Array line_matchspans --> 32;
...
[ WipeSpan wd_start wd_count buf chr_start chr_count i;
chr_start = WordAddress(wd_start);
chr_count = WordAddress(wd_start + wd_count - 1) + WordLength (wd_start + wd_count - 1) - chr_start;
for (i = 0: i < chr_count: i++)
chr_start->i = ' ';
];
...
! [following are changes within NounDomain]
...
if (number_matched > 1) {
i = true;
if (number_matched > 1)
for (j=0 : j<number_matched-1 : j++)
if (Identical(match_list-->j, match_list-->(j+1)) == false)
i = false;
if (i) dont_infer = true;
i = Adjudicate(context);
line_matchspans-->pcount = (wn - match_length)*100+match_length; ! ADDED
if (i == -1) rfalse;
if (i == 1) rtrue; ! Adjudicate has made a multiple
! object, and we pass it on
}
...
! An inferred object. Best we can do is glue in a pronoun.
! (This is imperfect, but it's very seldom needed anyway.)
if (pattern-->j >= 2 && pattern-->j < REPARSE_CODE) {
if (line_matchspans-->j % 100 ~= 0) ! ADDED
WipeSpan(line_matchspans-->j / 100, line_matchspans-->j % 100, buffer); ! ADDED
PronounNotice(pattern-->j);
...
The parser inserts the pronoun after the end of the partial command, so I donât think that itâs necessary to check that the inserted pronoun will fit in the space originally occupied by the ambiguous noun phrase. However, this version is confused by partial commands that include a preposition such as >UNLOCK DOOR WITH (which is translated to >UNLOCK WITH IT WITH KEY). Since the code doesnât seem to care whether the player included a preposition or not, the call to WipeSpan() can be modified to include everything from the start of the noun phrase to the end of the partial command:
How interesting! The official solution is to introduce a new global dont_infer_pronoun, used as a flag, which is set when Adjudicate() has determined a single best match, and which when set suppresses the âpronoun gluingâ code.
@DavidG, you may be interested in adapting this to StdLib 6.12 if you have not already.