'Does the player mean' disambiguation (finicky)

I’ve got a situation where I’m unable to force Inform to ask ‘Which do you mean?’ between a glass window and the glass part of a mirror when the player uses the noun ‘glass’. When I tweak the objects’ ‘does the player mean?’ levels, it always prefers one or the other, and picks it. And there comes a point in this project where I’d like it to ask ‘Which do you mean?’

In this test case below, pretend the player definitely wants to do things to the GLASS of the window at first. Then something happens in the game world and they’re potentially equally interested in doing things to the GLASS of the mirror (I call this ‘phase 2’ in the test) at which point I want ‘Which do you mean?’ asked if they type ‘glass’.

Note that I made the mirror’s glass a part of the panel object in this test to duplicate the structure of the objects in my WIP.

The default situation when the test is run is that Inform prefers the window, probably because it’s not part of something. Then we JUMP to bring in phase 1, where ‘does the player mean’ prefers the mirror’s glass via LIKELY. We JUMP again to bring in phase 2, where ‘does the player mean’ drops the mirror panel’s level back to ‘possible’, in which case the window is winning again:

Lab is a room. "JUMP to increment the test phase (can be done twice).".

a glass window is in lab.

a mirror is in lab.
a glass panel is part of mirror.

test phase is initially 1.

Instead of jumping:
	if test phase is 1:
		say "Switching to phase 2 (the mirror glass is now a 'likely' candidate).";
		now test phase is 2;
	otherwise if test phase is 2:
		say "Switching to phase 3 (the mirror glass goes back to being a 'possible' candidate).";
		now test phase is 3;

Does the player mean doing anything to panel when test phase is 2:
	it is likely;

Does the player mean doing anything to panel when test phase is 3:
	it is possible;

Test me with "touch glass/jump/touch glass/jump/touch glass".

Since there’s no level between possible and likely – and I’ve also tried increasing and decreasing the window’s level, or setting both objects to some other similar level – is there a symptomless way to force disambiguation here?

There’s no change in effect if I switch to a verb that doesn’t require something touchable (e.g. ‘examine’ instead of ‘touch’.)

The final caveat is I can’t use the Disambiguation Control extension in this project. It rewrites too much of the parser in ways incompatible with other code I’ve got going on.

Thanks much,

-Wade

1 Like

Hey Wade,
You’ll need to mess around with this a bit still, but try:

Lab is a room. "JUMP to cycle the test phase.".

a glass window is in lab.

a mirror is in lab. The description is "It's a mirror made with a single pane of glass."

a panel is part of mirror. The description is "It's the glass part of the mirror."

Understand "glass" as the mirror when the test phase is at least 2.
Understand "glass" as the panel when the test phase is at least 3.

Rule for printing the name of the panel when asking which do you mean:
	say "glass panel".

Does the player mean doing anything to the window when test phase is 4:
	it is unlikely;
	
test phase is initially 1.

Instead of jumping:
	if test phase is less than 4:
		increment test phase;
	otherwise:
		now test phase is 1;
	say "Switching to phase [test phase]. ";
	if test phase is:
		-- 1: say "(default).";
		-- 2: say "(adds 'Understand 'glass' as the mirror').";
		-- 3: say "(adds 'Understand 'glass' as the panel').";
		-- 4: say "(adds DTPM rule - 'window unlikely').";

Test me with "touch glass/jump/touch glass/mirror/jump/touch glass/panel/jump/touch glass".

which gives this:

>touch glass
You feel nothing unexpected.

>jump
Switching to phase 2. (adds "Understand "glass" as the mirror").

>touch glass
Which do you mean, the glass window or the mirror?

>mirror
You feel nothing unexpected.

>jump
Switching to phase 3. (adds "Understand "glass" as the panel").

>touch glass
Which do you mean, the glass window, the mirror or the glass panel?

>panel
You feel nothing unexpected.

>jump
Switching to phase 4. (adds DTPM rule - "window unlikely").

>touch glass
(the mirror)
You feel nothing unexpected.

-Mike

3 Likes

Interesting. So you put an inbetween object the game was happier to consider (the mirror itself which hosts the glass) into contention, then started tweaking the DTPMs some more.

I tried to make my example code readily comprehensible in this thread by making the inbetween object a mirror, which is obviously glass. In my WIP, the inbetween object is not obviously glass :slight_smile: However I’ve learned from your tricks and it’s got me moving. Thanks.

-Wade

The underlying problem is that Inform gives a match bonus to objects directly in the player’s location. Unfortunately, there’s no way to erase that with a DTPM rule. “Part of the mirror” doesn’t get that bonus.

This is why the above trick works – it sets up a contention between two objects that are directly in the room.

1 Like

I’m not an expert on the internals or anything like that, but the part where Inform decides for itself which thing you want seems like the most obdurate part of the parser, bar none. I think it’s happening in the routine in Parser.i6t called “ScoreMatchL”:

click here for the ScoreMatchL code from Parser.i6t
[ ScoreMatchL context its_owner its_score obj i j threshold met a_s l_s;
!   if (indef_type & OTHER_BIT ~= 0) threshold++;
    if (indef_type & MY_BIT ~= 0)    threshold++;
    if (indef_type & THAT_BIT ~= 0)  threshold++;
    if (indef_type & LIT_BIT ~= 0)   threshold++;
    if (indef_type & UNLIT_BIT ~= 0) threshold++;
    if (indef_owner ~= nothing)      threshold++;

    #Ifdef DEBUG;
    if (parser_trace >= 4) print "   Scoring match list: indef mode ", indef_mode, " type ",
      indef_type, ", satisfying ", threshold, " requirements:^";
    #Endif; ! DEBUG

    #ifdef PREFER_HELD;
    a_s = SCORE__BESTLOC; l_s = SCORE__NEXTBESTLOC;
    if (action_to_be == ##Take or ##Remove) {
        a_s = SCORE__NEXTBESTLOC; l_s = SCORE__BESTLOC;
    }
    context = context;  ! silence warning
    #ifnot;
    a_s = SCORE__NEXTBESTLOC; l_s = SCORE__BESTLOC;
    if (context == HELD_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN) {
        a_s = SCORE__BESTLOC; l_s = SCORE__NEXTBESTLOC;
    }
    #endif; ! PREFER_HELD

    for (i=0 : i<number_matched : i++) {
        obj = match_list-->i; its_owner = parent(obj); its_score=0; met=0;

        !      if (indef_type & OTHER_BIT ~= 0
        !          &&  obj ~= itobj or himobj or herobj) met++;
        if (indef_type & MY_BIT ~= 0 && its_owner == actor) met++;
        if (indef_type & THAT_BIT ~= 0 && its_owner == actors_location) met++;
        if (indef_type & LIT_BIT ~= 0 && obj has light) met++;
        if (indef_type & UNLIT_BIT ~= 0 && obj hasnt light) met++;
        if (indef_owner ~= 0 && its_owner == indef_owner) met++;

        if (met < threshold) {
            #Ifdef DEBUG;
            if (parser_trace >= 4)
            	print "   ", (The) match_list-->i, " (", match_list-->i, ") in ",
            	    (the) its_owner, " is rejected (doesn't match descriptors)^";
            #Endif; ! DEBUG
            match_list-->i = -1;
        }
        else {
            its_score = 0;
            if (obj hasnt concealed) its_score = SCORE__UNCONCEALED;

            if (its_owner == actor) its_score = its_score + a_s;
            else
                if (its_owner == actors_location) its_score = its_score + l_s;
                else
                    if (its_owner ~= compass) its_score = its_score + SCORE__NOTCOMPASS;

            its_score = its_score + SCORE__CHOOSEOBJ * ChooseObjects(obj, 2);

            if (obj hasnt scenery) its_score = its_score + SCORE__NOTSCENERY;
            if (obj ~= actor) its_score = its_score + SCORE__NOTACTOR;

            !   A small bonus for having the correct GNA,
            !   for sorting out ambiguous articles and the like.

            if (indef_cases & (PowersOfTwo_TB-->(GetGNAOfObject(obj))))
                its_score = its_score + SCORE__GNA;

            match_scores-->i = match_scores-->i + its_score;
            #Ifdef DEBUG;
            if (parser_trace >= 4) print "     ", (The) match_list-->i, " (", match_list-->i,
              ") in ", (the) its_owner, " : ", match_scores-->i, " points^";
            #Endif; ! DEBUG
        }
     }

    for (i=0 : i<number_matched : i++) {
        while (match_list-->i == -1) {
            if (i == number_matched-1) { number_matched--; break; }
            for (j=i : j<number_matched-1 : j++) {
                match_list-->j = match_list-->(j+1);
                match_scores-->j = match_scores-->(j+1);
            }
            number_matched--;
        }
    }
];

The most recent version of Appendix B I have says " The scoring system used to evaluate the possibilities is discussed in detail in the DM4," which is here I guess.

Perhaps one could go into the the I6 and clonk the bit that gives a match bonus to objects directly in the location, but that would take a braver man than I am at the moment. It seems to have to do with l_s and a_s, so maybe strategically clobbering those lines at some point would help? One would probably want to do it in a way that preserved the rules for “preferably held” tokens. I’m also not sure exactly what would get broken by doing that (and it might depend on the project).

2 Likes