Identifying problems with RAP (Reactive Agent Planner)

This is a great little extension that turns NPCs into Terminator-like goal seeking machines. The nice thing about it is that it’s reactive, that is, when the environment changes the NPC adapts its goal path accordingly.

A year ago, I’d gotten so far as to get NPCs that would walk around a map along a goal-seeking route. I was pretty pleased with myself for that one, but also it was clear that the approach I’d used would not be expandable to non-spatial problems. And anyway it didn’t know how to deal with stairs or such.

NPCs need to deal with practical matters, so I’m looking at RAP. Which is really nice.

However, I notice two problems:

NPCs can easily get stuck in loops, because they don’t acknowledge failure. If you snatch an item away from an NPC just before he takes it, he just keeps trying to take it without realizing it’s no longer there:

The longer version, if you want to see some context:

[rant]RAP TESTBED
Reactive Agent Planner ver. 1.2 testbed
Copyright (C) 1998 by Nate Cull
Tads-3 port and revision/extension (t3RAP) Copyright (c) 2002-2004 by Steve Breslin

Living Room
Living room. South to bedroom. North to kitchen.
Bob and Rupert are standing here.

tell bob, put ball on table
You see no tell bob here.

bob, put ball on table
Bob leaves to the north.

n
Kitchen
Kitchen. South to living room. Southeast to Bathroom.

You see a kitchen table (on which is a big box) here.

Bob is standing here.
Bob leaves to the southeast.

se
Bathroom
A bathroom. Northwest to Kitchen.
There’s a small cupboard.

Bob is standing here.

Bob opens the cupboard, revealing a bronze key.

take key
Taken.

Bob cannot see that.

z
Time passes…

Bob cannot see that.

z
Time passes…

Bob cannot see that.

z
Time passes…

Bob cannot see that.

w
You can’t go that way. The only obvious exit leads northwest, back to the kitchen.

Bob cannot see that.

nw
Kitchen

Kitchen. South to living room. Southeast to Bathroom.

You see a kitchen table (on which is a big box) here.

z
Time passes…

s
Living Room
Living room. South to bedroom. North to kitchen.

Rupert is standing here.

give key to rupert
Rupert does not appear interested.

z
Time passes…

z
Time passes…

z
Time passes…

z
Time passes…

drop key
Dropped.

z
Time passes…

Bob enters from the north.

take key
Taken.

Bob cannot see that.

z
Time passes…

Bob cannot see that.

z
Time passes…

Bob cannot see that.[/rant]

Now meanwhile there are other tricky bits. For example:

Ok, so the adoption of a new goal kills entirely an old goal (fine).

But clearly, failure has to be dealt with, in that:

a) The NPC has to be able to acknowledge failure as an outcome and stop doing the same ineffective thing over & over, and

b) Error reporting has to be adjusted so that it makes some kind of sense from the point of view of someone outside the head of the actor who attempted the action.

Also there are a few things that are supposed to be working that I haven’t seen work. NPCs don’t give or take objects like they’re supposed to. It seems from the behavior that the problem here is that NPCs can’t see player inventory, which defeats their ability to take items from you (and also to give them to you? – perhaps because the “container” doesn’t exist to their eyes?)

Is anyone here conversant with RAP? Any ideas?

Conrad.

As I see it, there are two approaches –

  • Try to control the game environment as much as possible, such that the NPCs don’t get plans that will malfunction in this way; or,

  • Tweak the RAP so that it accounts for failure.

The first approach would mean repairing whatever change has happened in the T3 language (I assume) to make inventories opaque to NPCs.

The second would be a bit deeper. There are a couple of ways to go about it, that I can think of:

I could modify goal nodes so that they have a memory. If someone tried to do this exact same thing last turn, or within the last three turns (say), then the goal will make itself unavailable this turn. This would involve a variable that holds a copy of the turn counter and a check against that variable before returning the goal list.

Alternatively, I could look into making RAP aware of check() and verify() methods.

Conrad.

P.S. I’m leaning toward creating a variable, since that approach could possibly be extended to some kind of effort-tracking in the future.

Currently, a rapper, given a goal, will spend $100,000 to get a hot-dog this turn, rather than walk to the store to buy a pack for $2.50. To balance that kind of behavior would require very basic “strategy” -type thinking that might be trackable with some kind of “cost” variable.

RAP already has the hooks in it for dealing with failure, IIRC. You just have to write the appropriate “rule” to deal with it.

The RAP for TADS3 was translated into Inform 7 under the name Planner, so you might look at the Inform version to see if it’s operation is any more clear to you. I compare the Inform version of RAP to, well, a hinting extension here: sparkynet.com/spag/backissue … tml#wisdom

Reading that article (besides being easier on the brain than reading code) may give you insight into how to progress in your WIP.

Is the I7 RAP close enough to the T3 rap that the documentation crosses over?

Conrad.

Yeah, that was interesting. I never looked at hinting as AI, before reading some of your words.

First a question. I do not understand this at all:

Ron @ SPAG:

There may be a terminological disconnect here, between I7 and T3, but I do not fathom what you’re saying here.

I wouldn’t expect it’s true that one couldn’t put rSteps from RAP’s planbase into global variables, although I can’t especially envision why you’d want to. If the goal is to have same-family NPCs have identical ways of doing certain things – multiple dog NPCs, for example, inherit a common way of fetching the newspaper or taking dictation – then I imagine that’d be done through some kind of inheritance, with the modify keyword, or by updating some parts of the planbase in certain NPCs.

There’s no documentation on that, but I imagine that’s how you’d do something like that. Why would you want to make a planbase unit (an rStep, I guess) global?

(More generally – there are some data types I7 disallows being made global??)

The T3 RAP does not consult with check rules. Says so in Steve Breslin’s manual.

In my WIP, the second type of failure would be easily dealt with: the NPC puts it on the backburner and every so often re-evaluates the game world to see if it has an actionable plan. Or possibly does random stuff in a goal-disoriented fashion.

Conrad.

The concepts cross over, but TADS3 is a much more powerful language than the Inform of 2008, so I doubt TADS has any need for stand-in objects.

Quite possibly. Since everything’s already an object in TADS, everything else always works with objects. There isn’t any such thing as a relation in TADS, except perhaps an object that has that name + two properties detailing what the “relation” connects.

Yes. Quite a few, though I think that list has grown shorter since the new type system was introduced about a year or two ago.

I’m inclined to believe it should… or at least the verify rules which are, in Inform, the same thing as check rules.

I showed you the article and alternate implementation mainly so you have a high-level view of what and how AI works in IF, which comes in handy when you want to read or modify the source to the AI for your own ends. I imagine the manuals for TADS’ RAP are much more useful when it comes down to the nitty-gritty.

Verify rules in TADS are closer to does-the-player-mean rules in Inform; they’re primarily for parser manipulation and disambiguation control. Check rules serve a similar purpose between the languages, and I agree that it makes sense to modify or rewrite RAP to follow them.

These waters get deep real quick.

The whirligig has to know, for example, not only when an action failed, but when it doesn’t matter that it failed.

For example, if you’re trying to open a door, and OPEN DOOR fails because the door is already open, the machine has to go ahead with the plan anyway.

I don’t know how to begin to address this kind of stuff. – I’m not saying I won’t try it, but it’s way beyond me.

–Does check() only apply to actions by the PC, too, or does it also apply to NPC actions?

Where do I find the docs for the {you/he} syntax?

Conrad.

It applies to all actions, though you’ll need to deal with the inevitable output issues. For the player character, it’s enough to print a message describing the failure. For an NPC, you may need to set a flag somewhere so they can adapt to the failure, and you may not want to print anything at all.

Here you go: Message Parameter Substitutions.

OK–

With dread I ask: Where do I go to start rewriting, or how do I overwrite, the default check() messages?

I expect I have to go through verb by verb, and I’ll need to consult the old ones.

Conrad.

For the most part, adv3 uses verify instead of check, which helps somewhat - the verify rules help to clarify input, and you won’t have any input issues if you’re initiating complete actions in code. Put another way, if a verify rule blocks the action, it’s likely to be a logical error in your code rather than something you’d want to paper over with a special case.

Most actions are defined on Thing first, and thing.t contains essentially no check rules. Thing subclasses - nearly everything else - do have some check rules, which you can find by searching the source code for “failCheck”.

I would recommend not bothering with this; just implement the game so it works from the player’s POV when interacting with objects, then extend that implementation so it supports the same action from an NPC.

Once you’ve done this once or twice, you should have a very straightforward framework that you can apply to new actions. Covering additional objects will be basically free if you implement the check rules at the class level and make each object an instance of one of your custom classes.