Distinguishing the (Mostly) Indistinguishable

Say you have two objects that in MOST cases you want to behave according to isEquivalent = true but in the rare cases where the parser decides to try and disambiguate you need to avoid the dreaded Which thing do you mean, the thing or the thing?

This problem is further complicated by the default distinguishers. In priority order they are basicDistinguisher, ownershipDistinguisher, and locationDistinguisher The first, basic, is designed to operate on items NOT isEquivalent’d. Falling through creates its own issues though, if all instances are not owned (ie ownedBy is null for at least one instance). There, the ownershipDistinguisher aliases unowned instances to kind of being owned by their location. This creates a different clumsy player experience. The disambig prompt would read like

Which thing do you mean, Paul's thing or the thing in the tray?

>tray
That was not one of the choices.  Which thing do you mean, Paul's thing or the
thing in the tray?

>in tray
You can’t stand in that.

Location prompting doesn’t really work smoothly for the player like adjective ones. The most elegant fix would be to make ownershipDistinguisher and locationDistinguisher smarter about location vocab. That’s not the way I went.

My impulse was to try and realign with vanilla basicDistinguisher. There’s a few issues with that. One of the biggest is that isEquivalent expects common objects to have SINGLE CLASS INHERITANCE. It cannot equiv objects with divergent multiple inheritance. I have no beef with that choice as a default, but divergent multiple inheritance is a way to solve this if you don’t want lots of cut’n’paste code. (Which ends up being mildly unavoidable anyway.) All this as prelude to a pretty kludgey workaround I would love to improve.

The first challenge is that basicDistinguisher uses disambigName as its default commonality test. Which means if it were to try and disambig equivalents, you are GUARANTEED that the disambig text would be identical. The second (or maybe first-and-a-half) challenge is that the easiest way to support MANY such pairs is with a mixin class that ALSO breaks equivalence testing. An example:

class Gimcrack1 : Thing 'gimcrack'
    isEquivalent = true
;

// mixins to modify disambigName
class PcOwned : object
    // vocabWords = '(my) (mine) (your) -'  // THIS DOESNT ACTUALLY WORK, SADLY
    disambigName = 'your <<name>>'
    theDisambigName = disambigName
    aDisambigName = disambigName
    owner = gPlayerChar
;
class Unowned : object
    // vocabWords = '(other) -'  // THIS EITHER
    disambigName = 'other <<name>>'
    pluralOrder = 110
;

myThing : PcOwned, Gimcrack1  '(my) (mine) (your) -';  // this does work, but kludgey
otherThing : Unowned, Gimcrack1 '(other) -';

Completely unworkable start, right? Will not be detected as equivalent both due to different disambigNames and multiple inheritance.

To fix the first, we create a base class which allows us create as many GimcrackX classes as we need. These will eqiv with their individual subclasses, not all Gimcracks:

class GimcrackBase : Thing
    equivalenceKey = (name)  // changes equivalence test to (common) name property
;
class Gimcrack1 : GimcrackBase 'gimcrack'
    isEquivalent = true
;

Then we modify basicDistinguisher to special case these

// Equivalent superclasses will not match because of mixins, but we can force
// the basicD can handle this, knowing the equivalence test will reference
// NAME instead of DISAMBIGNAME for GIMCRACKBASES
// this approach does require that we are disciplined about disambigName adjectives
// added to instances, which is a memory burden
//
modify basicDistinguisher
    canDistinguish(a, b) {
        if (a.ofKind(GimcrackBase) && b.ofKind(GimcrackBase)
            && a.equivalenceKey == b.equivalenceKey)
            return true;
        else return inherited(a,b);
    }
;

The last element is to ensure disambig vocabulary does in fact recognize distinguishing adjectives. Unfortunately, vocabWords are not inherited for mixins the way they are for single subclasses. This requires that EVERY INSTANCE include the appropriate boilerplate disambig vocab, hence
the kludgey per-instance vocab in myThing and otherThing above.

These changes allow for the following:

>taste gimcrack
Which gimcrack do you mean, your gimcrack, or the other gimcrack?

>other // or mine
It tastes much as you would expect.

>get gimcrack // parser selects the one you are not already carrying
Taken.

>i
You are carrying two gimcracks.

This is, let’s say, very lightly tested. Not sure if I will encounter other artifacts, but so far seems to be behaving as desired.

1 Like

Reading at a time when I don’t have time to digest everything going on, but I can give you this little tidbit of illumination:
Make your mix-ins VocabObject, not just object. Then your vocabWords will be used. This gotcha also happens to appear in JWZ’s TADbits :slight_smile:

2 Likes

…aaand now I am feeling doubly stupid. First because it was RIGHT THERE in the inheritance tree. Second, because I (now) routinely search intfiction for solutions before jumping in myself, and stalled on ‘Distinguisher’ when a simple ‘vocabWords’ search would have found it!

Really though, it’s your own fault for having SO MANY GREAT TIPS the mind can’t hold them all!

(If unclear from context, that worked like a champ. Changes defs to:

// mixins to modify disambigName
class PcOwned : VocabObject
    vocabWords = '(my) (mine) (your) -'  // THIS DOES ACTUALLY WORK, THX JZ
    disambigName = 'your <<name>>'
    theDisambigName = disambigName
    aDisambigName = disambigName
    owner = gPlayerChar
;
class Unowned : VocabObject
    vocabWords = '(other) -'  // THIS TOO
    disambigName = 'other <<name>>'
    pluralOrder = 110
;

myThing : PcOwned, Gimcrack1;  // kludge unnecessary
otherThing : Unowned, Gimcrack1;

)

1 Like

The TADbits are in sad need of organization, to be sure… glad you got out of the rut!