Numbered Disambiguation Choices (strikes) again (2025)

Fringey bugs in this 6M62 extension have generated exciting topics in the past seeking fixes, most of which I don’t completely understand.

Unfortunately I found a new problem with the extension. Well, new to me anyway, or at least a variation on an old one.

Here’s the 2011 discussion with a first degree version of the ‘I didn’t understand that number’ bug. Aaron reported this fixed and it did seem to be.

Here’s 2016 discussion of confusion in the extension re: selecting a choice numbered 1.

My new problem is like a second order version of ‘I didn’t understand that number’. It seems if you enter a command that requires disambiguation by the extension, and you choose thing 1, and this is followed up by a clarification question from inform, you will always get ‘I didn’t understand that number’ when you answer it, whether you answered with a number or by typing the name of something. Doesn’t happen if you choose 2 or higher for the initial disambiguation.

I’d really like to eliminate or circumvent this bug.

I’ve attached a 6M62 demo showing the bug with three coloured strings when you type ‘tie string’.

The demo includes my version of NDC pasted straight inside. My version was was spun off the Counterfeit Monkey version, already including general fixes from the 2016 discussion.

Summary

Volume - Demo environment

lab is a room.

a string is a kind of thing.

a blue string is a string. It is in lab.
a red string is a string. It is in lab.
a green string is a string. It is in lab.

Test me with "tie string/1/red/tie string/1/2/tie string/2/blue/tie string/2/1".

Volume - NDC Extension

Chapter - Setup

Does the player mean doing something when the player's command includes "[number]":
	let N be the corresponding disvalue of the number understood;
	if the noun is not nothing and the disambiguation id of the noun is N:
		it is very likely;
	otherwise if the second noun is not nothing and the disambiguation id of the second noun is N:
		it is very likely;

Section - Disambiguation ID

A disvalue is a kind of value. The disvalues are invalid-disvalue, default-disvalue, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14 and s15.
Understand "1" as s1.
Understand "2" as s2.
Understand "3" as s3.
Understand "4" as s4.
Understand "5" as s5.
Understand "6" as s6.
Understand "7" as s7.
Understand "8" as s8.
Understand "9" as s9.
Understand "10" as s10.
Understand "11" as s11.
Understand "12" as s12.
Understand "13" as s13.
Understand "14" as s14.
Understand "15" as s15.

Every thing has a disvalue called disambiguation id. The disambiguation id of something is usually default-disvalue. Every room has a disvalue called disambiguation id. The disambiguation id of a room is usually default-disvalue.

To decide which disvalue is the corresponding disvalue of (N - a number):
	(- MyDecideDisvalue ({N}) -).

Include
(-
[ MyDecideDisvalue n;
	if ((n < 1) || (n > 15))
		return (+ invalid-disvalue +);
	return n + 2;
];
-).

Understand the disambiguation id property as describing a thing. Understand the disambiguation id property as describing a room.

Section - List of disambiguables

The list of disambiguables is a list of objects that varies.

Section - disambiguation-busy

disambiguation-busy is a truth state that varies. disambiguation-busy is false. [In certain cases numbers could be printed twice. Thanks to Robert Jenkins for pointing this out.]

Chapter - Number Choices

Before printing the name of an object (called macguffin) while asking which do you mean (this is the Numbered Disambiguation Choices preface disambiguation objects with numbers rule):
	if disambiguation-busy is false:
		now disambiguation-busy is true;
		add macguffin to the list of disambiguables, if absent;
		let N be the number of entries in list of disambiguables;
		now the disambiguation id of macguffin is the corresponding disvalue of N;
		say "" (A);
		say "[N]";
		say ") " (B);

After printing the name of an object while asking which do you mean (this is the Numbered Disambiguation Choices cleanup disambiguation-busy flag rule):
	now disambiguation-busy is false.

Before asking which do you mean (this is the Numbered Disambiguation Choices reset disambiguables rule):
	repeat with item running through list of disambiguables:
		now disambiguation id of item is default-disvalue;
	truncate list of disambiguables to 0 entries.

-Wade

1 Like

You can try the following, which is a roughly backported fix from 10.1.2.

NounDomain modification
Include (-

Global dont_inject_pronoun;

-) after "Definitions.i6t".

Include (-

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! Parser.i6t: Noun Domain
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

[ NounDomain domain1 domain2 context dont_ask
	first_word i j k l answer_words marker;
	#Ifdef DEBUG;
	if (parser_trace >= 4) {
	    print "   [NounDomain called at word ", wn, "^";
	    print "   ";
	    if (indef_mode) {
	        print "seeking indefinite object: ";
	        if (indef_type & OTHER_BIT)  print "other ";
	        if (indef_type & MY_BIT)     print "my ";
	        if (indef_type & THAT_BIT)   print "that ";
	        if (indef_type & PLURAL_BIT) print "plural ";
	        if (indef_type & LIT_BIT)    print "lit ";
	        if (indef_type & UNLIT_BIT)  print "unlit ";
	        if (indef_owner ~= 0) print "owner:", (name) indef_owner;
	        new_line;
	        print "   number wanted: ";
	        if (indef_wanted == INDEF_ALL_WANTED) print "all"; else print indef_wanted;
	        new_line;
	        print "   most likely GNAs of names: ", indef_cases, "^";
	    }
	    else print "seeking definite object^";
	}
	#Endif; ! DEBUG

	match_length = 0; number_matched = 0; match_from = wn;

	SearchScope(domain1, domain2, context);

	#Ifdef DEBUG;
	if (parser_trace >= 4) print "   [ND made ", number_matched, " matches]^";
	#Endif; ! DEBUG

	wn = match_from+match_length;

	! If nothing worked at all, leave with the word marker skipped past the
	! first unmatched word...

	if (number_matched == 0) { wn++; rfalse; }

	! Suppose that there really were some words being parsed (i.e., we did
	! not just infer).  If so, and if there was only one match, it must be
	! right and we return it...

	if (match_from <= num_words) {
	    if (number_matched == 1) {
	        i=match_list-->0;
	        return i;
	    }

	    ! ...now suppose that there was more typing to come, i.e. suppose that
	    ! the user entered something beyond this noun.  If nothing ought to follow,
	    ! then there must be a mistake, (unless what does follow is just a full
	    ! stop, and or comma)

	    if (wn <= num_words) {
	        i = NextWord(); wn--;
	        if (i ~=  AND1__WD or AND2__WD or AND3__WD or comma_word
	               or THEN1__WD or THEN2__WD or THEN3__WD
	               or BUT1__WD or BUT2__WD or BUT3__WD) {
	            if (lookahead == ENDIT_TOKEN) rfalse;
	        }
	    }
	}

	! Now look for a good choice, if there's more than one choice...

	number_of_classes = 0;

	if (number_matched == 1) {
		i = match_list-->0;
		if (indef_mode == 1 && indef_type & PLURAL_BIT ~= 0) {
			if (context == MULTI_TOKEN or MULTIHELD_TOKEN or
				MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN or
				NOUN_TOKEN or HELD_TOKEN or CREATURE_TOKEN) {
				BeginActivity(DECIDING_WHETHER_ALL_INC_ACT, i);
				if ((ForActivity(DECIDING_WHETHER_ALL_INC_ACT, i)) &&
					(RulebookFailed())) rfalse;
				EndActivity(DECIDING_WHETHER_ALL_INC_ACT, i);
			}
		}
	}
	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
	    dont_inject_pronoun = true; ! See bug I7-2115 for discussion of this
	}

	! 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 (dont_ask) return match_list-->0;

	! If we get here, there was no obvious choice of object to make.  If in
	! fact we've already gone past the end of the player's typing (which
	! means the match list must contain every object in scope, regardless
	! of its name), then it's foolish to give an enormous list to choose
	! from - instead we go and ask a more suitable question...

	if (match_from > num_words) jump Incomplete;

	! Now we print up the question, using the equivalence classes as worked
	! out by Adjudicate() so as not to repeat ourselves on plural objects...

	BeginActivity(ASKING_WHICH_DO_YOU_MEAN_ACT);
	if (ForActivity(ASKING_WHICH_DO_YOU_MEAN_ACT)) jump SkipWhichQuestion;
	j = 1; marker = 0;
	for (i=1 : i<=number_of_classes : i++) {
		while (((match_classes-->marker) ~= i) && ((match_classes-->marker) ~= -i))
			marker++;
		if (match_list-->marker hasnt animate) j = 0;
	}
	if (j) PARSER_CLARIF_INTERNAL_RM('A');
	else PARSER_CLARIF_INTERNAL_RM('B');

	j = number_of_classes; marker = 0;
	for (i=1 : i<=number_of_classes : i++) {
	    while (((match_classes-->marker) ~= i) && ((match_classes-->marker) ~= -i)) marker++;
	    k = match_list-->marker;

	    if (match_classes-->marker > 0) print (the) k; else print (a) k;

	    if (i < j-1)  print ", ";
	    if (i == j-1) {
			#Ifdef SERIAL_COMMA;
			if (j ~= 2) print ",";
	    	#Endif; ! SERIAL_COMMA
	    	PARSER_CLARIF_INTERNAL_RM('H');
	    }
	}
	print "?^";

	.SkipWhichQuestion; EndActivity(ASKING_WHICH_DO_YOU_MEAN_ACT);

	! ...and get an answer:

  .WhichOne;
	#Ifdef TARGET_ZCODE;
	for (i=2 : i<INPUT_BUFFER_LEN : i++) buffer2->i = ' ';
	#Endif; ! TARGET_ZCODE
	answer_words=Keyboard(buffer2, parse2);

	! Conveniently, parse2-->1 is the first word in both ZCODE and GLULX.
	first_word = (parse2-->1);

	! Take care of "all", because that does something too clever here to do
	! later on:

	if (first_word == ALL1__WD or ALL2__WD or ALL3__WD or ALL4__WD or ALL5__WD) {
	    if (context == MULTI_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN) {
	        l = multiple_object-->0;
	        for (i=0 : i<number_matched && l+i<MATCH_LIST_WORDS : i++) {
	            k = match_list-->i;
	            multiple_object-->(i+1+l) = k;
	        }
	        multiple_object-->0 = i+l;
	        rtrue;
	    }
	    PARSER_CLARIF_INTERNAL_RM('C');
	    jump WhichOne;
	}

	! Look for a comma, and interpret this as a fresh conversation command
	! if so:

	for (i=1 : i<=answer_words : i++)
		if (WordFrom(i, parse2) == comma_word) {
	        VM_CopyBuffer(buffer, buffer2);
	        jump RECONSTRUCT_INPUT;		
		}

	! If the first word of the reply can be interpreted as a verb, then
	! assume that the player has ignored the question and given a new
	! command altogether.
	! (This is one time when it's convenient that the directions are
	! not themselves verbs - thus, "north" as a reply to "Which, the north
	! or south door" is not treated as a fresh command but as an answer.)

	#Ifdef LanguageIsVerb;
	if (first_word == 0) {
	    j = wn; first_word = LanguageIsVerb(buffer2, parse2, 1); wn = j;
	}
	#Endif; ! LanguageIsVerb
	if (first_word ~= 0) {
	    j = first_word->#dict_par1;
	    if ((0 ~= j&1) && ~~LanguageVerbMayBeName(first_word)) {
	        VM_CopyBuffer(buffer, buffer2);
	        jump RECONSTRUCT_INPUT;
	    }
	}

	! Now we insert the answer into the original typed command, as
	! words additionally describing the same object
	! (eg, > take red button
	!      Which one, ...
	!      > music
	! becomes "take music red button".  The parser will thus have three
	! words to work from next time, not two.)

	#Ifdef TARGET_ZCODE;
	k = WordAddress(match_from) - buffer; l=buffer2->1+1;
	for (j=buffer + buffer->0 - 1 : j>=buffer+k+l : j-- ) j->0 = 0->(j-l);
	for (i=0 : i<l : i++) buffer->(k+i) = buffer2->(2+i);
	buffer->(k+l-1) = ' ';
	buffer->1 = buffer->1 + l;
	if (buffer->1 >= (buffer->0 - 1)) buffer->1 = buffer->0;
	#Ifnot; ! TARGET_GLULX
	k = WordAddress(match_from) - buffer;
	l = (buffer2-->0) + 1;
	for (j=buffer+INPUT_BUFFER_LEN-1 : j>=buffer+k+l : j-- ) j->0 = j->(-l);
	for (i=0 : i<l : i++) buffer->(k+i) = buffer2->(WORDSIZE+i);
	buffer->(k+l-1) = ' ';
	buffer-->0 = buffer-->0 + l;
	if (buffer-->0 > (INPUT_BUFFER_LEN-WORDSIZE)) buffer-->0 = (INPUT_BUFFER_LEN-WORDSIZE);
	#Endif; ! TARGET_

	! Having reconstructed the input, we warn the parser accordingly
	! and get out.

	.RECONSTRUCT_INPUT;

	dont_inject_pronoun = false;	! ADDED

	num_words = WordCount(); players_command = 100 + num_words;
	wn = 1;
	#Ifdef LanguageToInformese;
	LanguageToInformese();
	! Re-tokenise:
	VM_Tokenise(buffer,parse);
	#Endif; ! LanguageToInformese
	num_words = WordCount(); players_command = 100 + num_words;
	actors_location = ScopeCeiling(player);
	FollowRulebook(Activity_after_rulebooks-->READING_A_COMMAND_ACT);

	return REPARSE_CODE;

	! Now we come to the question asked when the input has run out
	! and can't easily be guessed (eg, the player typed "take" and there
	! were plenty of things which might have been meant).

  .Incomplete;

	if (context == CREATURE_TOKEN) PARSER_CLARIF_INTERNAL_RM('D', actor);
	else                           PARSER_CLARIF_INTERNAL_RM('E', actor);
	new_line;

	#Ifdef TARGET_ZCODE;
	for (i=2 : i<INPUT_BUFFER_LEN : i++) buffer2->i=' ';
	#Endif; ! TARGET_ZCODE
	answer_words = Keyboard(buffer2, parse2);

	! Look for a comma, and interpret this as a fresh conversation command
	! if so:

	for (i=1 : i<=answer_words : i++)
		if (WordFrom(i, parse2) == comma_word) {
			VM_CopyBuffer(buffer, buffer2);
			jump RECONSTRUCT_INPUT;
		}

	first_word=(parse2-->1);
	#Ifdef LanguageIsVerb;
	if (first_word==0) {
	    j = wn; first_word=LanguageIsVerb(buffer2, parse2, 1); wn = j;
	}
	#Endif; ! LanguageIsVerb

	! Once again, if the reply looks like a command, give it to the
	! parser to get on with and forget about the question...

	if (first_word ~= 0) {
	    j = first_word->#dict_par1;
	    if ((0 ~= j&1) && ~~LanguageVerbMayBeName(first_word)) {
	        VM_CopyBuffer(buffer, buffer2);
	        jump RECONSTRUCT_INPUT;
	    }
	}

	! ...but if we have a genuine answer, then:
	!
	! (1) we must glue in text suitable for anything that's been inferred.

	if (inferfrom ~= 0) {
	    for (j=inferfrom : j<pcount : j++) {
	        if (pattern-->j == PATTERN_NULL) continue;
	        #Ifdef TARGET_ZCODE;
	        i = 2+buffer->1; (buffer->1)++; buffer->(i++) = ' ';
	        #Ifnot; ! TARGET_GLULX
	        i = WORDSIZE + buffer-->0;
	        (buffer-->0)++; buffer->(i++) = ' ';
	        #Endif; ! TARGET_

	        #Ifdef DEBUG;
	        if (parser_trace >= 5)
	        	print "[Gluing in inference with pattern code ", pattern-->j, "]^";
	        #Endif; ! DEBUG

	        ! Conveniently, parse2-->1 is the first word in both ZCODE and GLULX.

	        parse2-->1 = 0;

	        ! 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 (~~dont_inject_pronoun)	{
			    PronounNotice(pattern-->j);
			    for (k=1 : k<=LanguagePronouns-->0 : k=k+3)
			        if (pattern-->j == LanguagePronouns-->(k+2)) {
			            parse2-->1 = LanguagePronouns-->k;
			            #Ifdef DEBUG;
			            if (parser_trace >= 5)
			            	print "[Using pronoun '", (address) parse2-->1, "']^";
			            #Endif; ! DEBUG
			            break;
			        }
		}
	        }
	        else {
	            ! An inferred preposition.
	            parse2-->1 = VM_NumberToDictionaryAddress(pattern-->j - REPARSE_CODE);
	            #Ifdef DEBUG;
	            if (parser_trace >= 5)
	            	print "[Using preposition '", (address) parse2-->1, "']^";
	            #Endif; ! DEBUG
	        }

	        ! parse2-->1 now holds the dictionary address of the word to glue in.

	        if (parse2-->1 ~= 0) {
	            k = buffer + i;
	            #Ifdef TARGET_ZCODE;
	            @output_stream 3 k;
	             print (address) parse2-->1;
	            @output_stream -3;
	            k = k-->0;
	            for (l=i : l<i+k : l++) buffer->l = buffer->(l+2);
	            i = i + k; buffer->1 = i-2;
	            #Ifnot; ! TARGET_GLULX
	            k = Glulx_PrintAnyToArray(buffer+i, INPUT_BUFFER_LEN-i, parse2-->1);
	            i = i + k; buffer-->0 = i - WORDSIZE;
	            #Endif; ! TARGET_
	        }
	    }
	}

	! (2) we must glue the newly-typed text onto the end.

	#Ifdef TARGET_ZCODE;
	i = 2+buffer->1; (buffer->1)++; buffer->(i++) = ' ';
	for (j=0 : j<buffer2->1 : i++,j++) {
	    buffer->i = buffer2->(j+2);
	    (buffer->1)++;
	    if (buffer->1 == INPUT_BUFFER_LEN) break;
	}
	#Ifnot; ! TARGET_GLULX
	i = WORDSIZE + buffer-->0;
	(buffer-->0)++; buffer->(i++) = ' ';
	for (j=0 : j<buffer2-->0 : i++,j++) {
	    buffer->i = buffer2->(j+WORDSIZE);
	    (buffer-->0)++;
	    if (buffer-->0 == INPUT_BUFFER_LEN) break;
	}
	#Endif; ! TARGET_

	! (3) we fill up the buffer with spaces, which is unnecessary, but may
	!     help incorrectly-written interpreters to cope.

	#Ifdef TARGET_ZCODE;
	for (: i<INPUT_BUFFER_LEN : i++) buffer->i = ' ';
	#Endif; ! TARGET_ZCODE

	jump RECONSTRUCT_INPUT;

]; ! end of NounDomain

[ PARSER_CLARIF_INTERNAL_R; ];

-) instead of "Noun Domain" in "Parser.i6t".
2 Likes

So is this is essentially 6M62 noun domain with the dont_inject fix added? (roughly four small changes?)

I global-searched my WIP for noun domain, hoping not to find anything, and encountered Unified Glulx Input’s replacement of noun domain. However, at the spots where the four changes are, UGI is otherwise identical to the vanilla noun domain. So I grafted in the changes and booted up. It didn’t crash, always a great way to start after an I6 change. And I went to the trouble room and tested the original problem, and it was solved!

So thanks much, I think you’ve fixed it.

Just let me know if there were any other changes for the fix other than at those four spots (which you can identify by searching the word ‘inject’)

-Wade

1 Like

Great! Yes, those are the only changes, so your grafting job should work.

I didn’t do extensive testing, so if you run into any issues, the first thing to do would be to change the location of the line:

dont_inject_pronoun = false;	! ADDED

Right now it’s immediately following the .RECONSTRUCT_INPUT label, but you could put it at the start of the routine, e.g. immediately following the line:

match_length = 0; number_matched = 0; match_from = wn;

I did notice that you sometimes get potentially undesirable clarification output if the noun is the same as the string that gets assigned disambiguation ID 1. This is because one (or the numeral) is interpreted as meaning the same as a/an, and the code’s not smart about resetting inferfrom on successful disambiguation via disambiguation ID. It’s probably possible to fix that if it’s a problem for you.

2 Likes

I see what you mean. I might not have noticed, probably because the whole process is already about clarifying, so it doesn’t look out of place.

If the fix comes easily, I’d be happy to learn it, but you’ve already solved overnight what I thought might turn out to be a major problem. I suspect with your mad scientist status, it may be hard to stop you from pursuing a fix anyway :grinning_face_with_smiling_eyes:

-Wade

1 Like

I have an extension for disambiguation that might be of interest to you. I made it over a decade ago to make the disambiguation bits extremely customisable. I’ve also tested it against this issue plus all the others and it works fine. I’ve also tested it against other issues that could come up in disambiguation as well to the point that it’s very robust and battle hardened now.

Custom Disambiguation.i7x (26.2 KB)

The Inform 10 version requires Parser Replacement. I also have a version for 6M62 as well.

Custom Disambiguation.i7x (25.6 KB)

The documentation is still WIP, so feel free to ask any questions regarding usage, but it should be quite simple to use. Hope this helps!

3 Likes

Well, this sounds very good! I mean, we or I have been wrestling with numbered disambiguation troubles for years.

I will test it with my WIP. On the one hand, I feel nervous system-swapping in a project this big and old. On the other hand, where else are the most gains to be made? The thing is, this particular system is very much on its own. You don’t do much hacking to your game for NDC - you just add it. Therefore you can probably just remove it, too, then try a replacement.

I will let you know how it goes when I get the opportunity.

-Wade

1 Like

@climbingstars Looks terrific so far. I’m using 6M62. First I checked the code for any major overlaps with Unified Glulx Input, which I’m using, and which also modifies parser.i6t a lot. And then weren’t any direct ones. There could be some edge bits, but I’ll have to look at some code side by side in detail to check those. In the meantime, the extension’s compiling and working in a huge game. I haven’t stress-tested it yet, but this is a good start.

I also see your extension eliminates NDC’s buggish attitude to numbered items which move out of scope, so that’s much appreciated.

I’m probably going to make it so that numbered items are forgotten as soon as the player types a line without a number in it. I had NDC doing this, and it looks like it should be easy to adjust it to work with CD.

-Wade

@climbingstars , I have a question.

Sometimes NDC would poll situations with this code:

if the number of entries in list of disambiguables > 0:

Is the equivalent in CD this?

if the number list is not empty:

-Wade

@climbingstars Okay, the first bug report :slight_smile:

I have an approaching action (e.g. GO TO ROOM, makes you go there). In my WIP, if the GO TO command produces a disambiguation list for several rooms, only choice 1 of N can then be chosen. Entering a number above 1 produces ‘You can’t use multiple objects with that verb.’

Below is a basic demo that shows this in a vanilla project.

Summary
"Test"

Include Custom Disambiguation by Brent Worthington 6M62.

lab is a room.
red bedroom is east of lab.
green bedroom is east of red bedroom.
blue bedroom is east of green bedroom.

approaching is an action applying to one object.
Understand "go [any room]" as approaching.

Carry out approaching:
	say "You've expressed interest in approaching [the noun].";

Test me with "go red/go green/go blue/go bedroom/1/go bedroom/3/go 1".

Something the demo can’t show is that after this error occurs, if I immediately follow the error message with a ‘GO 1’, a noun might be selected for 1 but it’s a seemingly random noun that has never come up in disambiguation yet.

In this demo, all we get is ‘That noun did not make sense in this context’, which nevertheless shows that something has gone wrong.

-Wade

Thanks! I actually created custom disambiguation because numbered disambiguation choices didn’t offer the flexibility to be able to make list style choices. I know of a lot of the issues that numbered disambiguation choices and essentially built custom disambiguation further to solve all these issues plus others I came across. It’s pretty much a case of add and go, but it can be customised as well.

I’m sort of familiar with the code of unified glulx input and it shouldn’t caused any interference with custom disambiguation, as unified glulx input modifies parser letter A while custom disambiguation makes only a small addition to parser letter D. You can have numbered items be forgotten as soon as the player types a line without a number in it by doing something like this.

After reading a command when "[the player's command]" exactly matches the regular expression "\D+" (this is the reset disambiguation on a non numerical input rule): follow the reset disambiguation values rule.

That is correct. Incidentally, “the number list” is what it used to be called in earlier versions of numbered disambiguation choices, custom disambiguation just kept the name. The extension also has this baked into the reset rule so that it only does the full reset when it needs to, especially as the reset rule can be called many times repetitively.

Well, I guess my bit about it being very robust and battle hardened goes out the window now! :grinning_face_with_smiling_eyes: I’ve now fixed this issue. Turns out I was able to replicate the issue using the first example code! The issue is that the extension only activates the disambiguation bits in limited circumstances, ie. when the current token is a noun. However, I forgot to allow disambiguation for routines and this one is a scope routine. So the disambiguation didn’t happen and it acted like it would’ve done without the extension. Here’re the updated versions of the extensions.

Custom Disambiguation.i7x (26.3 KB)

Custom Disambiguation.i7x (25.7 KB)

The top one is for Inform 10 and the bottom one is for Inform 6M62. Hope this helps! :smiley:

2 Likes

Cool, I’m about to go try it out.

I may have to A/B the changes with my eye, as in an effort to make the source more readable to myself, I changed my local copy to use the indentation style. I’m afraid I can’t follow begins and ends.

I’ll put a question in the pipe in the meantime - I asked about ‘if the number list is not empty’. Is that test different than ‘if the disambiguation flag is true’ ?

-Wade

The revised versison eliminates the ‘can’t use multiple objects’ bug. However, I would have expected the final ‘GO 1’ command in the demo project to still try going to the red bedroom. Is it that the automatic scope-culling routine has thrown it away as a possibility between turns?

-Wade

BIG EDIT: Sorry, what I said in this edited post was not true.

THE POST NOW: There is no problem I can find beyond the one I mentioned in the preceding post:

-Wade

Annoyingly, Inform’s parser interprets X 2 as “examine two unspecified things” rather than “examine the thing with 2 in its name”. You’re just expected to use ordinals instead of cardinals when you’re distinguishing objects by number.

Personally I think that “feature” should be removed from the parser because it causes far more problems than it solves. When was the last time you wanted X 2 to mean “examine two arbitrary objects in this location”?

I don’t know how closely related this is, but I have resorted to this kind of trickery when trying to play a poorly-coded game in the past. In particular ‘>GET 2’ might be nice if there’s no other way to refer to the thing you want to get? More often, the trick I resort to is when I say ‘>GET THING’ and the game replies, “What do you mean, the thing or the thing?” and I say “>ANY”. But this feels similar.

Hehe.

With NDC in place and disambiguation options in play, X 2 will go to ‘I can’t see any such thing’ if it can’t. In CD I instead get ‘The noun does not make sense in this context,’ after an action change (e.g. GO 3, X 2) which to me is less desirable.

-Wade

Ooh, unfortunately that I can’t help with. That’s NOTINCONTEXT_PE, which is a special form of CANTSEE_PE (“you can’t see any such thing”) that’s supposed to be applied when you used a pronoun that doesn’t fit a routine filter token. I have no idea how Custom Disambiguation is tripping that.

(If you look at the source code I linked, ignore the documentation. As far as I can tell it’s not so much “misleading” as “straight-up wrong”; it’s describing a behavior from I6 (the name property on a room) that’s not used in I7 and has been removed from the I7 parser.)

That is correct! I use the latter more as I believe it’s a faster check.

Not quite. There’s a rule that fires every turn that resets the disambiguation when at least one item is “invisible”. It works fine if you unlist this rule.

The reset values when out of scope rule is not listed in the every turn rulebook.

I think for your use case, it would be better to manually reset disambiguation when it is required and not use that rule, since it only allow visible items.

follow the reset disambiguation values rule;

You can add this line wherever you want to reset the disambiguation.

It’s not strictly speaking Custom Disambiguation that is triggering this as disambiguation is turned off for that part. It’s happens when you use the “any” token. I’ve traced it to a scope routine that’s created by the I6 compiler and baked into the Inform Grammar Tables. So you won’t see the code for this at I6 level. This routine changes the next best error message to NOTINCONTEXT_PE where the best error message is ASKSCOPE_PE. The routine then returns -1, which causes the best error message to be set to the next best error message, giving the NOTINCONTEXT_PE error message as a result.

1 Like

Fascinating! I’ve often lamented how obscure and hard-to-understand the Inform parser is. I didn’t realize there are chunks of it that aren’t even in CommandParserKit at all…