I7: How best to track knowledge/belief for all characters?

I’m brainstorming ways to track PC+NPC knowledge and belief. An example of what I want:

Every object in the game (or at least every object of interest to the characters) has a “last known location” that is tracked independently for each character (PC or NPC). This will be updated each time an object is seen by a character, but it also serves to tell us whether the character is aware of the object’s existence: if the character knows the object exists but has no idea where it is, the last known location will be a special “unknown” value, whereas if the player doesn’t know of the object’s existence, the value will be null. The last known location can also track character’s beliefs about objects, whether or not they’ve ever seen them. For example, the last known location of the Holy Grail for Gawain is a barrow in Wales; he’s never seen the Grail, of course, and it isn’t in that barrow, but that’s where Gawain believes.

The problem, of course, is that this is a three-way relation–for each object, there is a person who thinks it is somewhere–and Inform doesn’t do three-way relations (at least not yet). I’ve thought of a couple of ways to handle this (using tables or lists), but neither of them really satisfy me. I started to explain the models I’ve come up with and why I don’t like them, but in the interests of brevity, maybe I’ll just ask how you would implement this. (I’d like for the solution to be flexible enough for an extension, which does impose some limitations.)

Thanks,
Erik

My first impulse would be to give every object a table (“every object has a table called the…” or something like that), and fill those tables with the names of all persons at the start of the game. But I might be missing a problem here, because you have already considered tables.

I recently had a situation where I wanted a three-way relation, and I ended up using a single three-column table.

The danger, of course, is running out of room in the table. For a three-way relation like this, the number of rows you need will always be X * Y, where X and Y are the numbers of instances of each kind in the domain. (number of people and number of objects in your case).

There is a request on uservoice for three-way relations, but I’ve started wondering if instead it would be better to put out a request for putting a (constant) numerical expression in the “with X blank rows” declaration for a table. There is already a sort-of hacky syntax for “with blank rows for every thing”, so there is a precedent. Since this seems most likely to be used with three-way relations, a syntax like this should cover most cases:

with blank rows for the combination of every person and thing

What do you think? Should I suggest that? Has someone else suggested something similar? Would you vote for it?

The alternative (if you don’t have the opportunity or inclination to calculate the table size by hand) is to do what Aaron Reed did in Remembering, and just give up keeping track of things once the table’s full.

If you were going to do it Victor’s way, it would be nice to have anonymous tables. Is that currently possible?

Oh, wait, my suggestion is not even possible, is it? You cannot automatically create a table for every thing.

You can give every thing of a certain kind a table; I have a bit of code now, which compiles, including the following:

A theme is a kind of thing. A theme has a table-name called transition-table.

Don’t know if this would work for “every thing” or not, and writing all those tables seems like it would certainly be a mondo headache. (But one tricky bit is that you need to say “table-name” instead of “table.”)

But creating table-names that vary (variables) is not the same thing as creating tables (“objects”), right? (In ATTACK I give every person a table-name that varies, but I fill those in with the names of pre-existing tables.) Wat you would want here is that as soon as you add an object, the compiler will add a new table (the Table of Knowledge about [Object]). Such a thing is possible with objects (by using parts), but I don’t think it’s possible with anything else.

Ah, sorry, I didn’t understand what you were getting at. Yeah, I’d be surprised if you could do this without, at the very least, some I6 wizardry.

I would also use a single, 3-column table. Using lists of lists (i.e. lists of pairs) just seems to beg for errors, plus if you have zillions of such relations, would be slower than tables. Each column in a table is an I6 array, so are amendable to quick lookup with Glulx opcodes like @linearsearch.

Thanks, all.

I like the single 3-col table approach better than any of my own ideas, despite the annoyances around getting the table sized. I think I’d want to use adjectives to limit the number of objects in the game that need to be tracked, though, which would probably make the compile-time syntax capmikee proposes for calculating the table size ess attractive, at least to I7’s developers (e.g., I might need “with blank rows for the combination of every person and knowable thing”).

My own frontrunner idea has been to give each object a list of rooms to represent the last known location, with the entry number of each list keyed to the character, e.g. yourself is indexed at entry 1, a certain NPC at entry 2, another at entry 3, etc.):

The last known location of the basketball is { [player] Locker, [coach] Court, [spectator] Bench }

You’d need a table–or just a list–to look up the index (e.g., player = 1, coach = 2, etc.), and you’d also want to avoid having authors declare or alter the lists directly, since the structure would change when you add a new character to the game. Instead, you’d provide an initialization phrase plus a few phrases for setting particular relations manually, e.g., “(P - a person) believes the last known location of (T - a thing) is (R - a room)”. This would be fast enough; for Glimmr, I implemented bitmap fonts with a similar structure (table lookup for glyph index & metadata + list search for drawing individual bits), and the results are pretty zippy. But I don’t like the need for the intermediary lookup table in this case, since the author would have to remember to update it manually with the list of people.

I’d also implement the 3-column table with an assumption that the author wouldn’t access it directly, but it has the advantage of not requiring the author to make a separate list of characters keyed to numerical index.

–Erik

Well, at the same time you’re making the lists of beliefs, you could make a list of people, and have their entry number be the key code. So instead of a table, you’d have {yourself, coach, spectator}, and since yourself is entry 1, any entry 1 of an object’s list would correspond to the player.

Awkward as all get-out, but it wouldn’t rely on the author to remember to do anything.

(You could also auto-set via giving each person a number property and then just updating them sequentially.)

Edit: Reading comprehension is not top notch today.

Sorry. I missed the “list” bit.

That’s an idea; I could do that. It’s all still a bit fussier than the single-table approach, but it doesn’t suffer from the need to maintain the table size. It’s also nicely object-centric–look at any object, and you know very quickly what all the characters know or believe about it. On the other hand, to compile a list of all the objects thought to be in the Lounge regardless of who’s doing the thinking, you’d have to iterate through all objects and then through all of the lists. The latter is probably at least as time-consuming as the table-based model.

Hm, I wonder if glulx acceleration functions really do much for table lookup–tables were already pretty fast, weren’t they? Anyway, I’m sure they don’t hurt either!

More importantly, are searches of simple lists comparably fast?

–Erik

I think that’s what the relations code does. I’m not sure I totally understood it, but it looks like an object has a property corresponding to each kind that it belongs to, which lists its number within that kind. (e.g. if there are five people, the person number would range from one to five). I don’t know if the property is accessible within I7.

That’s interesting, I didn’t realize that. If it’s a number that’s stored in an I6 property, it should be quite easy to expose at the I7 level–especially if you don’t need to change the number at the I7 level (as you would not for this application). I’ll have to look into this when time permits…

–Erik

OK, I think I’ve figured out how this works. The common built-in kinds like “thing” (in I6, described by the constant K2), “direction” (K3), “door” (K4), “person” (K8), “container” (K6), etc. each have their own instance counter property, which counts instances of that kind. The name of the property is derived from the constant number, so the instance counter of “thing” is IK2_Count. The properties are independent of one another: for example, since doors are also things, a given door will likely have different instance counts for its “thing” index and for its “door” index.

Here’s how to set I7-accessible versions of these properties:

[code]A person has a number called the person index.
The person index property translates into I6 as “IK8_Count”.

A thing has a number called the object index.
The object index property translates into I6 as “IK2_Count”.[/code]

Note that these indices seem to begin at 0 rather than 1: the “yourself” object, at least, has index 0 for both IK8_Count (person index) and IK2_Count (thing index). On the other hand, the null value is also 0, so possibly yourself is not actually a member of either kind…?

–Erik

You said there’s an index property for things, but is there none for objects? I guess you don’t need one for objects because you can use the object number. I’m not sure how that works for the relations code, but I would guess that 0 only means nothing when it’s an object number - the null object is not a member of any other kind.

The relations code also has a special behavior for Kinds of Value - these seem to always start at 1, so it subtracts 1 from the value before using it as an index into the relation array.

Well, I’m calling it a null value here because, in addition to being the first index number for a given kind, a zero in the count property also indicates nonmembership in the kind. So, if you define two persons and a thing and then look at IK8_Count (the instance of the object within the person kind) for all things in the game, you’ll get:

yourself: 0
Bob: 1
Leroy: 2
axe: 0

“Yourself” is a person and is indexed as the 0th instance of the person kind. “Axe”, which isn’t a person at all, has the same index value for the IK8_count variable (it’s effectively a null or empty value in this case).

This isn’t likely to be a problem in practice, since you’ll almost always be properly limiting the set before you check the instance counter; that is, you only iterate through people when checking the IK8_Count, and only through things when checking the IK2_Count. But it’s probably important to be aware of it, and to realize that you won’t be able to reliably use the IKx_Count of an object to (for example) deduce whether it’s a member of a given kind.

–Erik

Good advice!

So does every object have storage for every possible property, whether or not it’s allowed to have it?

Yes, every object has the predefined instance counter properties (see the Definitions template for the list). I’m not sure how user-defined kinds are handled, though, or whether it would be as easy to make their counters accessible at the I7 level.

–Erik

The list property method for implementing this is, using the instance count index, quite simple. Here’s the basic code required:

[code]A person has a number called the person index.
The person index property translates into I6 as “IK8_Count”.

A thing has a list of rooms called the last known location.

Unkenned is a room with printed name “unknown”. [“unkenned” to avoid conflict with Epistemology’s “unknown”.]

To initialize known locations:
let L be a list of rooms;
repeat with N running from 1 to the number of persons:
add unkenned to L;
repeat with item running through things:
now the last known location of the item is L.

To (P - a person) thinks/believes/knows (T - a thing) is in/at/-- (R - a room):
now entry (1 + person index of P) of the last known location of T is R.

To (P - a person) knows the location of (T - a thing):[***]
let O be an object;
let O be the holder of T;
while O is not a room:
let O be the holder of O;
now entry (1 + person index of P) of the last known location of T is O.

To decide what room is the location of (T - a thing) according to (P - a person):
decide on entry (1 + person index of P) of the last known location of T.[/code]

I’m not sure if the code block marked with the triple asterisk really needs to do the recursive analysis to find the room–I feel like I’m missing some syntax that would do it in a single line. (I tried “let O be a random room that encloses T”, but that returned “nothing”.)

Anyway, to set the initial knowledge state for a game, you would write things like this:

When play begins: initialize known locations; Mortimer knows the location of the knife; Virginia believes the axe is in the kitchen.

I still don’t know whether this has any advantages (speed or otherwise) over doing the same thing with a 3-column table, but the code is probably a little bit shorter.

–Erik