Rationale for how knowsAbout()/setKnowsAbout() works in adv3?

In adv3, sensing an object via any sense except sight automagically sets its “known” property (isKnown by default); sight sets the “seen” property (seen by default). And then Actor.knowsAbout() checks both the known and seen flags and returns true if either is true (or if the actor can currently see the object).

I assume there’s some corner case where canSee() and hasSeen() could end up in conflict, so forget about that. But what’s the philosophical rationale behind not just having sight set the “known” flag like every other sense?

I’m in the process of converting a bunch of sense, memory, and knowledge stuff into a module and I’m wondering if I’m missing something. The approach I’m currently using is:

  • Instead of using properties on objects, each actor gets their own table of memories
  • All of the senses are tracked separately (instead of just sight)
  • Thing.lookAroundWithinSense() is modified to note sense detection instead of just knowledge
  • Sensing an object via any sense marks the object as known

…and I’m just wondering if there’s something obvious I’m missing, because the default behavior doesn’t really make sense to me.

1 Like

I make liberal use of checking whether an obj or room is “seen”, where other objects may be marked isKnown for other reasons…

I’ve pondered this too, especially the trifecta between seen, known, and familiar.

I think what you’re running into is a combination of blurred implementation decisions made due to (a) the 99% case of IF games having a single player character, with multiple PC being added later, (b) the distinction between the player character versus the human player seeing/knowing something, and (c) historical inertia, as the seen/known/familiar flags are similar to those in TADS 2.

You didn’t mention familiar, but that’s important here. A PC may not have seen their bicycle when the game starts, but the bicycle can be marked familiar in the code so the PC knows about it before they encounter it. (At least, this is how adv3Lite deals with it: knowsAbout() is coded as “has seen or is familiar with.”) For example, this allows the PC to talk about their bicycle before actually seeing it in-game. It’s “in scope.”

There’s also familiarity with abstract topics and knowledge, which cannot be seen but may be familiar and may be known.

I suspect the module you’re working on is similar to code that I’ve toyed with writing. (I don’t have a WIP that would use it, so I’ve not made a stab at it.) Namely, proper memory tracking that’s tied to the Actor, and not a set of flags on the Things themselves. It sounds like you’re going further, tracking each Actor’s sense memory too.

So, the bicycle isn’t known, rather, one Actor may know about the bicycle and the other does not. NPCs could also have their own memory tracking, which is kind of cool.

If I did work on it, I’d be tempted to have a default Actor-like object that represents the human player, and therefore be able to track what the player knows, since it’s possible they know something the current PC does not (and vice-versa, I suppose, but that starts to get esoteric in terms of game design). I suppose this could be achieved by polling all Actors the PC can play and see if its been seen by any of them, but I still like the idea.

Also, adv3Lite has some other state that’s useful/interesting, such as lastSeenAt, which means all the PCs may have seen the object, but in different locations, and therefore have different “memories” of it.

This is the only quibble I have, since as I mentioned, “knowing” could reflect an abstraction or something not-yet-sensed by the player.

2 Likes

Not in adv3! familiar is an adv3lite thing.

In adv3 there’s a concept of “seen”, a concept of “known”, and a concept of “revealed”. The first two are (by default) handled by flags on objects and the last is the value for an arbitrary ID/key in a table (of conversation topics).

Yeah, the thing I’ve got (in the form of a mess of spaghetti code which I’m trying to refactor into a cleaner implementation) is:

  • A simple “sense memory” model that’s intended to more or less directly replicate adv3’s default seen/known/revealed behavior while extending it to include the other senses
  • Optional extensions to the sense memory model that includes additional information about the sense memory: the most recent location associated with the memory (i.e., the room the object was seen in); the write timestamp (the most recent turn the memory was updated, i.e. when was the object last seen); a write counter (how many times has the memory been updated, i.e. how many times has the object been seen); and a read timestamp and counter similar to the “write” ones
  • A “concept” model that’s intended to group memories together: the “white house” from North of House and the “white house” from Behind House are the same thing, the Kitchen through the window is part of the same object, and the “white house” should automatically be understood to be synonymous with the “entrance to the Great Underground Empire” when the player has made specific discoveries
  • An abstract “knowledge” model mostly intended for NPC decision-making. It uses weights for things not directly sensed.
    Maybe this should be called a “hypothesis” model to avoid confusion with the stock binary true/nil “known” logic

In other words, it’s mostly a (not strictly hierarchical) three-tiered model consisting of direct sense memories, things which are “known” in a binary true/false way (someone either knows about the secret passage behind the bookcase or they don’t), and more generic, non-true/false “knowledge” (Alice smelled suspicious things in the kitchen, in the basement, and in the attic, so if she decides to look for the le grand voleur de fromage she’d pick one of them but not necessarily deterministically).

Anyway, in adv3 the default behavior means that if you define something like:

startRoom: Room 'Void'
        "This is a featureless void.  There's another room to the north. "
        north = middleRoom
;
+pebble: Thing 'small round pebble' 'pebble' "A small, round pebble. ";
+flower: Thing 'smelly flower' 'flower'
        "A smelly flower. "
        smellPresence = true
;
+me: Actor;

…then on the first turn you’ll end up with…

pebble.seen = true
pebble.isKnown = nil

…but…

flower.seen = true
flower.isKnown = true

…because the flower has a smellPresence and the pebble does not. This seems…counterintuitive.

Oh, and the “sense memory” model is intended to be directly compatible with the stock adv3 one (with the possible exception of marking something as seen automagically making it known), but it also extends it by adding flags for the non-sight senses. This includes defining a Sense instance for taste, which is omitted in stock adv3.

lookAround() already calls lookAroundWithinSense() for sound and smell, this is just modified to set a sense-specific flag in the actor’s memory table instead of just marking the object as known.

Ah, that’s what I get for not checking adv3. I was in a bit of a rush when I wrote that.

adv3Lite has that too, but it’s not really tied to the world model, so I didn’t consider it in the same domain.

This is distinctly more ambitious than my idea! Good luck with it.

Personally, seen (and the cognate flag, examined) are useful in putting relationshp between items/rooms, and I guess IS the main tool of trade of mystery IF writer…

Agree that is counterintuitive the adv3 relationship between seen and isKnown, because logically the player known that in the location is the smell of a flower, or I have missed some point of the world model ?

Best regards from Italy,
dott. Piergiorgio.

Perhaps the thing to note is that the isKnown property is more like a bonus property for setting things to knownness apart from the five senses (conversations, prior knowledge, cut scenes etc.) The main approach to querying knownness is not to query the isKnown property, but to query the knowsAbout method. And of course the library provides the knownProp for games that want to separate other Actors’ knowledge from the PC’s…

I was just working on this for my own side project. I came to the conclusion that known should be a computed, read only property. familiar should be settable by the author and the library in 95% of the case should handle seen on its own.

The library doesn’t handle it that way, though. Adv3 explicitly sets the known flag for things in a room that are detected via sound or smell. That’s the only thing the library uses the known flag for.

If the intent was to just exclude sight from the concept of knowing about an object, that might make sense (although I don’t know why sight would be excluded when sound and smell aren’t)…but then knowsAbout() explicitly checks for sight detection anyway.

So it works out the same anyway: knowsAbout() answers the question “has the actor seen, heard, or smelled the object?”.

If the known flag was exclusively used to handle abstract knowledge (that is, knowledge of things not directly detected via the senses, like “the train arrives at 18:00” or “a pair of integers shares a greatest common divisor with their difference” or whatever) that would make sense too. In fact, implementing that kind of thing was one of the things that started me down this path. But that’s not the way adv3 actually handles it.

1 Like

Ah, I’d forgotten about described (which is the adv3 equivalent to examined).

But yeah, I’m not writing a mystery game (or at least not a solve-the-crime sort), but the underlying idea—uncovering information that leads to a revised understanding of previously-known events—is something that I’m very much working with, and specifically I want/need all the NPCs to react to changes in what they currently know.

1 Like

I was just going by the source comments:

/*
* Flag: this object is explicitly “known” to actors in the game,
* even if it’s never been seen. This allows the object to be
* resolved as a topic in ASK ABOUT commands and the like.
* Sometimes, actors know about an object even before it’s been seen
* - they might simply know about it from background knowledge, or
* they might hear about it from another character, for example.
*
* Like the ‘seen’ property, this is merely the DEFAULT ‘known’
* property that we use for actors. Each actor can individually use
* a separate property to track its own knowledge if it prefers; it
* can do this simply by overriding its isKnownProp property.
*/

Even though the library itself may only alter isKnown through non-sight senses, it sounds as though it was meant to be used as an optional flag for game authors…

It makes sense that you can flag known via things other than sense detection (to reflect things that the player learns about in other ways, like in conversation).

That doesn’t explain why it doesn’t flag things seen as “known”…and then turns around and checks for seen in knowsAbout().

I would understand if hearing and smell didn’t set the known flag and there was a generic canSense() and hasSensed() that checks for current/past detection via any sense and then separately knowsAbout() exclusively checked the known flag.

Or if sight also set the known flag and knowsAbout() just checked the known flag.

The puzzling thing is that the internals carefully track sight as a special case…but then the methods just add some logic so that it works out the same as if sight wasn’t a special case. Given:

startRoom: Room 'Void' "This is a featureless void. ";
+me: Person;
+pebble: Thing 'small round pebble' 'pebble' "A small, round pebble. ";
+flower: Thing 'smelly flower' 'flower'
        "A smelly flower. "
        smellPresence = true
;

…and then defining a system action >FOOZLE:

DefineSystemAction(Foozle)
        execSystemAction() {
                "\nme.knowsAbout(pebble)
                        = <<toString(me.knowsAbout(pebble))>>\n ";
                "\npebble.isKnown = <<toString(pebble.isKnown)>>\n ";
                "<.p> ";
                "\nme.knowsAbout(flower)
                        = <<toString(me.knowsAbout(flower))>>\n ";
                "\nflower.isKnown = <<toString(flower.isKnown)>>\n ";
        }
;
VerbRule(Foozle) 'foozle': FoozleAction verbPhrase = 'foozle/foozling';

…on the first turn you get…

Void
This is a featureless void.

You see a pebble and a flower here.

>foozle
me.knowsAbout(pebble) = true
pebble.isKnown = nil

me.knowsAbout(flower) = true
flower.isKnown = true

The player “knows about” both the flower and the petal, but only the flower “is known” to the player (because it is smelled and the pebble is not). This seems a counterintuitive way to handle things.

1 Like

I follow. The only thing that comes to mind is that sight/seen is orders more important/utilized than the other senses. On that score, authors (myself included) may frequently be concerned about whether an object or place has been seen and not just known, so having that separated is handy. And if marking it as seen counts as knowsAbout, then also marking it isKnown is slightly redundant. That’s the only thing I can think!

The idea of being able to tell if something is known about only indirectly (that is, only by means other than direct observation) absolutely makes sense…but the default implementation doesn’t get you that. Because knowsAbout(obj) && !hasSeen(obj) is true for an object that has (only) been smelled, for example.

And the only reason marking seen and isKnown is redundant for knowsAbout() is because that’s the way knowsAbout() is written. That’s the point. From a code cleanliness standpoint, there doesn’t seem to be any argument for why lookAround() doesn’t set the known flag for sight (as it already does for sound and smell) and then only check the known flag and not the sight flag in knowsAbout().