how reliable is the actor-following mechanism in adv3Lite?

I’m seeing some inconsistent behavior as Harry wanders around the scenery I’ve created with his son Max in tow.

When Harry first meets up with Max in person (not one of their cell phone conversations), my game code triggers the following behavior with harrysSonMax.startFollowing(). This appears to work. I see the Max followed Harry text everywhere they go. But sometimes, Max doesn’t actually end up where he’s supposed to. When I check in the debugger, I find he stops at certain locations and does not move forward, even though the game prompts say he is following.

That is, when Harry and Max are both at point A, then Harry moves on to Point B and then Point C, Max remains at Point A, even though the game prompt says he follows Harry at each stop along the way. I’ve also checked the status of fDaemon at each stop and find that it is not nil, which means the following code is still active even though Max isn’t.

I’ve also observed (or think I have, it’s sometimes hard to keep track) that under identical circumstances, when Harry goes from point A to point B, Max may follow in one run of the game but when I stop the game and restart and go through the same steps, he doesn’t.

I think I have found a work-around—whenever they get somewhere where I need to be certain about where Max is or is not, I do a hard moveInto() and a stopFollowing(), then another startFollowing() when I want him to start up again.

I guess my fundamental question is, who’s at fault here? Do I need to track down something in my code that is causing unstable behavior, or is the following mechanism just not 100% reliable?

Should I be concerned, or should I just go ahead with my workaround? It’s a bit cumbersome, having to manually move Max around. On the other hand, I need to know where Max is or isn’t.

Well, if I knew that the following mechanism was unreliable I’d have done something to fix it!

That doesn’t necessarily mean that there isn’t a bug, of course (it would be rash to guarantee that!), but just that without more information on the circumstances under which the following isn’t working as you expect, it’s impossible for me to say.

I can say that the mechanism is only designed to make an NPC follow the player character from room to room, so it wouldn’t make Max follow Harry into a Booth, for example. It’s also possible it might behave oddly if you moved Harry round the map by some weird unexpected means (though I can’t immediately think of what you’d have to do to get the effect you describe, not least because the library code already falls back on what you’ve been doing by hand, namely using moveInto to move the NPC if all else fails).

You could also break the mechanism by overriding certain methods (such as beforeTravel()) on the Max object, or by overriding beforeTravel() on Max’s current ActorState to prohibit travel through certain connectors (though I very much doubt you’d have done this).

But I’d really need to see a test case to be able to tell whether this problem is due to something in your code or something in the library.

Perhaps I phrased it badly, or at least too hastily; I am willing to acknowledge you would have fixed whatever needed fixing :slight_smile:

It’s a bit difficult to winnow the code down to a manageable chunk and still retain the anomalies I’m seeing. The code has gotten a bit complex.

But I have tried. And I do get some unexpected behavior in my pared-down test bed environment, although not exactly what I see in the full game code, which I tried to describe previously.

In the test-bed environment, Harry starts in his bedroom, goes out the living room to the street corner, where he meets up with Max. Max starts following at this point, north across the street, then west past a tattoo parlor and finally stopping in front of an adult toy store.

Harry, the dad, does not want Max, the teenager, following him in to the adult toy store where he needs to buy a set of handcuffs (he is a detective, after all, and the cuffs are tools of the trade). I’ve taken out a lot of extraneous stuff like actually buying the cuffs (and retrieving money from the ATM that should have been found on the street corner), since they don’t seem relevant to Max following Harry.

But the travel barrier that is supposed to block Max’s entry into the store is still present.

I have also added a locate command for debugging—it gives the actor’s current location, and tells the state of the actor’s fDaemon.

Here’s how it plays…

They’ve reached the toy store. Harry goes north to enter the store, and a break point in the noMax travelBarrier, debugger shows the following…

harrysSonMax.curState == maxFollowing
harrysSonMax.fDaemon == obj#ca2d (in other words, not nil)
harrysSonMax.location == sidewalk2

So far, so good.

The travelBarrier explicitly says stopFollowing, (and just to be sure, in the main game code, explicitly moves Max to the sidewalk—harrysSonMax.moveInto(sidewalk2)—, though I took it out here because it wasn’t working).

addMax: TravelBarrier { canTravelerPass(traveler, connector) { local ret = true; if(harrysSonMax.curState == maxFollowing) harrysSonMax.startFollowing(); return ret; } } ;
In my pared-down test-bed environment, the work-around doesn’t work. When the game is resumed from the debugger…

Max should not have been able to enter the store. And, in the full code set, he cannot—the travel barrier works. Here, in the pared down code, it does not work. A new manifestation, I’m guessing, of the inconsistency I’m seeing elsewhere.

Here’s the full test-bed code…

[code]#charset “us-ascii”

#include <tads.h>
#include “advlite.h”

versionInfo: GameID
IFID = ‘243748b1-5310-4916-8436-890e9ccc16fd’
name = ‘test’
byline = ‘by Jerry Ford’
htmlByline = ‘by
Jerry Ford

version = ‘1’
authorEmail = ‘Jerry Ford jerry.o.ford@gmail.com
desc = ‘Testing adv3lite NPC following.’
htmlDesc = ‘Testing adv3lite NPC following.’
;

gameMain: GameMainDef
/* the initial player character is ‘harry’ */
initialPlayerChar = harry
paraBrksBtwnSubcontents = nil
usePastTense = true
;

// harry, main character
harry: Actor ‘Harry;;man self’ @harrysBedroom
“”
globalParamName = ‘harry’
isHim = true
isInitState = true
person = 3
proper = true
day = 2
;

// max character
harrysSonMax: Actor ‘Max;teenager teen age ager;son;him’ @streetCorner
“”

globalParamName = 'max'
person = 3   
bulkCapacity = 5000

;

  • maxFollowing: ActorState
    ;

// harry’s bedroom
harrysBedroom: Room
‘Bedroom’ ‘bedroom; room’
"The bedroom was small, the furniture adequate but utilitarian.
<>A plain brown dresser stood against the wall opposite
the foot of the bed; next to it was a wood, cane-back chair. A table
stood against the west wall. A small mirror hung on the wall above
it. Next to the table was a door, leading north. <> "

north = livingRoom

bedroomMentioned = nil

;
livingRoom: Room ‘Living Room’
“{the subj harry}'s living room was small and blah blah
blah…”

south = harrysBedroom
north = streetCorner

;

streetCorner: Room ‘Street Corner’ ‘street
corner in front of apt’
“Harry lived in an apartment above the corner.
<<if harrysSonMax.location ==
streetCorner>><<harrysSonMax.startFollowing>><>
<.p>”

south = livingRoom
north: TravelConnector
{
    destination = oppositeCorner
    travelDesc = "Harry stepped to the curb at the corner. The light was
        red, <i>Don't Walk</i> flashed on and off. Harry glanced down the
        street, saw an opening in traffic, and sauntered across to the other
        side.
        <.p>";
}

;
oppositeCorner: Room ‘Street Corner (NW)’ ‘opposite
street corner’
“On the north side of the intersection, just to the west of where
Harry stood at the corner curb, neon signage announced the
entry to a tattoo parlor. \b
Next to it, an adult toy shop beckoned. < 1 && harrysSonMax.fDaemon == nil &&
!handCuffs.isIn(harry)>>
Huh! Harry grunted. Adult toys…where every self-respecting
private detective shops for tools of the trade. Let me think…do I need
anything today?

<>
<<if harrysSonMax.fDaemon != nil>><<harrysSonMax.setState(maxFollowing)>>
<<harrysSonMax.startFollowing>><>
<.p>”

south: TravelConnector
{
    destination = streetCorner
    travelDesc = "Harry glanced at oncoming traffic at the same time he
        stepped off the curb. By luck, the light turned green just as he
        started across. ";
}
west = sidewalk

;

sidewalk: Room ‘Sidewalk’ ‘sidewalk at tattoo parlor’
“Harry walked passed the tattoo shop. Neon signs
proclaimed “Cal-Ink Tattoo” and “Body Art” on the glass entry door, to the
north.
<.p>
The sidewalk continued east and west.
<.p>”

north = tattooParlor
west = sidewalk2
east = oppositeCorner

;

sidewalk2: Room ‘Sidewalk’ ‘sidewalk at toy store’
“A canvas awning reached over the sidwalk from the doorway to the north, on which
handpainted lettering announced “Adult Toys” and “all credit cards
accepted”.
<.p>
The sidewalk continued east and west.
<.p>”

north: TravelConnector
{
    destination = adultToyShop
    travelBarriers = [noMax]
    travelDesc = "<<if harrysSonMax.curState == maxFollowing>><q>You better 
        wait out here,</q> Harry said to Max. <q>And, Max, your mother 
        doesn't need to know about this, okay?</q>
        <.p><<end>>"
}
east = sidewalk

;

noMax: TravelBarrier
canTravelerPass(traveler, connector)
{
local ret = true;
harrysSonMax.stopFollowing();
harrysSonMax.moveInto(sidewalk2);
return ret;
}
;

tattooParlor: Room ‘Tattoo Parlor’ ‘tattoo parlor’
"<>Just inside the entry door of the <>Cal-Ink Tattoo
Parlor<>, a glass-topped
counter separated a small entry parlor from a larger working area that
<> resembled a small barbershop, or hair salon. Except that at each of the three
work stations, bottles of inks replaced ointments and lotions.
<.p>
<>The entry parlor offered a leather-covered sofa and
a similarly-upholstered chair surrounding a low coffee table.
<.p>
<>
The chairs, both in the waiting area and at the three work stations
behind the counter where the ink was applied, were empty. "

south: TravelConnector
{
    destination = sidewalk
    canTravelerPass(traveler)
    {
        local ret = true;
        return ret;
    }
}

;

adultToyShop: Room, Container ‘Adult Toy Shop’ ‘adult toy shop’
“Harry pulled open the door to the toy shop and stepped
inside. Floor stands and clothing racks featured…etc.
<.p>
<>“Harry, my man, good to see you again, and so soon,” bellowed the hefty,
bearded man behind the counter. Looking for something special today? or just browsing as usual?” Harv asked,
waiting expectantly.
<.p>"

south: TravelConnector
{
    destination  = sidewalk2
    travelBarriers = [addMax]
}

;

  • handCuffs: Thing ‘pair of handcuffs;hand;cuff’
    "The bracelets of the chrome-plated cuffs where lined with a bright pink
    faux fur. But otherwise, they looked genuine. They would in fact work if
    ever Harry had to confine someone. "
    ;

addMax: TravelBarrier
{
canTravelerPass(traveler, connector)
{
local ret = true;
if(harrysSonMax.curState == maxFollowing)
harrysSonMax.startFollowing();
return ret;
}
}
;

#ifdef __DEBUG

VerbRule(Locate)
‘locate’ multiDobj
: VerbProduction
action = Locate
verbPhrase = ‘locate/locating (who)’
missingQ = ‘who do you want to locate’
;
DefineTAction(Locate)
addExtraScopeItems(role) { makeScopeUniversal(); }
beforeAction(){}
afterAction(){}
turnSequence() {}
;

modify Actor
dobjFor(Locate)
{
action()
{
local num = gCommand.dobjs.length;
for(local x = num; x > 0; x–)
{
“Location of <<gCommand.dobjs[x].name>> is
<<if gCommand.dobjs[x].obj.location == nil>>nil<>
<<gCommand.dobjs[x].obj.location.name>> (fDaemon is <<if
gCommand.dobjs[x].obj.fDaemon != nil>>not<> nil)<> <.p>”;
}
abort;
}
}
;

#endif[/code]

Just after I posted, I noticed the travel barrier said start following, not stop following, so I changed it to stop and tried it again, with the same results. Max still enters the toy store.

Sorry for the churn, but I was right the first time. The start was in the addMax barrier for leaving the shop.

The noMax barrier for entering the shop does stop the following, and it also does explicitly move Max to the sidewalk, but when the code moves out of the travel barrier, Max is in the shop (although the fDaemon did get stopped).

Your use of TravelBarriers looks ingenious, but I wouldn’t really expect them to work for this purpose, as it’s not really quite what they’re designed for.

I think what you need to do is to put Max into a particular ActorsState while following his Dad, and then use the beforeTravel() and afterTravel() methods of that ActorState to stop and start the following as required. Something along the lines of:

maxFollowing: ActorState
specialDesc = "Max was trailing along behind Harry. "
beforeTravel(traveler, connector)
{
if(traveler == harry && connector == sidewalk2.north)
{
harry.actorSay(‘You better
wait out here, Harry said to Max. And, Max, your mother
doesn’t need to know about this, okay?’);

        getActor.stopFollowing();
   }
}

afterTravel(traveler, connector)
{
if(traveler == harry && getActor.getOutermostRoom == sidewalk2)
getActor.startFollowing();
}
;

That’s not tested, and you may need to tweak it a bit, but it’s the sort of approach I’d take.

By the way, you could also be breaking the following mechanism if you’re doing a lot of this sort of thing:


belowDir: CompassDirection
    name = 'below'
    dirProp = &below
    sortingOrder = 1450
;

grammar directionName(below): 'below' : Production
    dir = belowDir
;

Doer 'go belowDir'
    direction = belowDir
    where = jeremiahOBrien
    exec(curCmd)
    {
        doInstead(Enter, belowDeckOBrien);
    }
;

At least, there could be a danger that using a Doer to intercept a travel command risks bypassing the library code that helps Max keep track of where Harry is going. I’m not quite sure why you’re making belowDeckOBrien a Booth rather than a Room here, since it seems to be a separate location, not a big chest sitting on fisherman’s wharf, which is how your code in the sample from which I took this effectively treats it. It would be safer to make belowDeckOBrien a Room in its own right, and then, since you’ve gone to the trouble of defining a below direction:

fishermansWharf: Room 'Fisherman\'s Wharf' 'fisherman\'s wharf'
    "Harry and Max are aboard the Jeremiah O'Brien at Fisherman's Wharf. \b
    Their only option is to go below.
    <.p>"
    
    below = belowDeckOBrien
;

I mention this because it you are regularly making use of Doers to translate travel commands into Entering Booths, it’s possible that this could be something else that’s giving the actor following mechanism a bit of a headache.

I’ve now tried out your code, and I’ve located the cause of the problem you’re reporting. The problem is that you’re calling startFollowing() on max twice, once here:

streetCorner: Room 'Street Corner' 'street
    corner in front of apt'
    "Harry lived in an apartment above the corner.
    <<if harrysSonMax.location ==
      streetCorner>><<harrysSonMax.startFollowing>><<end>>
    <.p>"
   
    south = livingRoom
    north: TravelConnector
    {
        destination = oppositeCorner
        travelDesc = "Harry stepped to the curb at the corner. The light was
            red, <i>Don't Walk</i> flashed on and off. Harry glanced down the
            street, saw an opening in traffic, and sauntered across to the other
            side.
            <.p>";
    }
;

And once here:

oppositeCorner: Room 'Street Corner (NW)' 'opposite
    street corner'
    "On the north side of the intersection, just to the west of where
    Harry stood at the corner curb, neon signage announced the
    entry to a tattoo parlor. \b
    Next to it, an adult toy shop beckoned. <<if harry.day > 1 && harrysSonMax.fDaemon == nil &&
    !handCuffs.isIn(harry)>>
    <i>Huh!</i> Harry grunted. <i>Adult toys...where every self-respecting
    private detective shops for tools of the trade. Let me think...do I need
    anything today?</i>
    <<end>>
    <<if harrysSonMax.fDaemon != nil>><<harrysSonMax.setState(maxFollowing)>>
    <<harrysSonMax.startFollowing>><<end>>
    <.p>"
   
    south: TravelConnector
    {
        destination = streetCorner
        travelDesc = "Harry glanced at oncoming traffic at the same time he
            stepped off the curb. By luck, the light turned green just as he
            started across. ";
    }
    west = sidewalk
;

So by the time Harry enters the adult toy shop, two Daemons are running to make Max follow him, and your call to stopFollowing() only stops one of them; the other continues to run with the results that you see.

In the next release I’ll put a check in the startFollowing() method to trap this possibility, so we won’t two following Daemons running at once.

That said, your coding style here is not what I would recommend (e.g. putting calls to startFollowing() in room descriptions and the like). I appreciate that this is just a test-bed, but it’s not really the way adv3Lite is meant to be used! :wink:

Instead I’d recommend something along the following lines:

#charset "us-ascii"

#include <tads.h>
#include "advlite.h"

versionInfo: GameID
    IFID = '243748b1-5310-4916-8436-890e9ccc16fd'
    name = 'test'
    byline = 'by Jerry Ford'
    htmlByline = 'by <a href="mailto:jerry.o.ford@gmail.com">
                  Jerry Ford</a>'
    version = '1'
    authorEmail = 'Jerry Ford <jerry.o.ford@gmail.com>'
    desc = 'Testing adv3lite NPC following.'
    htmlDesc = 'Testing adv3lite NPC following.'
;

gameMain: GameMainDef
    /* the initial player character is 'harry' */
    initialPlayerChar = harry
    paraBrksBtwnSubcontents = nil
    usePastTense = true
;

// harry, main character
harry: Actor 'Harry;;man self; him' @harrysBedroom
    ""
    globalParamName = 'harry'    
    person = 3       
    day = 2
;

// max character
harrysSonMax: Actor 'Max;teenager teen age ager;son;him'  @streetCorner
    "He was Max. "
       
    globalParamName = 'max'
    person = 3   
    bulkCapacity = 5000
   
   
;
+ maxFollowing: ActorState
    specialDesc = "Max was there. "
    
    beforeTravel(traveler, connector)
    {
        if(traveler == harry && connector == sidewalk2.north)
        {
            "<q>You better
            wait out here,</q> Harry said to Max. <q>And, Max, your mother
            doesn't need to know about this, okay?</q>";
            getActor.stopFollowing();
        }
    }
    
    afterTravel(traveler, connector)
    {
        if(traveler == harry)
            getActor.startFollowing();
    }
;

+ maxFollowAgenda: AgendaItem
    isReady = (harry.isIn(getActor.getOutermostRoom))
    
    invokeItem()
    {
        getActor.setState(maxFollowing);
        getActor.startFollowing();
       
        isDone = true;
    }
    
    initiallyActive = true
;

// harry's bedroom
harrysBedroom: Room
    'Bedroom' 'bedroom; room'
    "The bedroom was small, the furniture adequate but utilitarian.
    <<first time>>A plain brown dresser stood against the wall opposite
    the foot of the bed; next to it was a wood, cane-back chair. A table
    stood against the west wall. A small mirror hung on the wall above
    it. Next to the table was a door, leading north. <<only>> "
   
    north = livingRoom
   
    bedroomMentioned = nil
   
;
livingRoom: Room 'Living Room'
    "{The subj harry}'s living room was small and blah blah
    blah..."
   
    south = harrysBedroom
    north = streetCorner
;   

streetCorner: Room 'Street Corner' 'street
    corner in front of apt'
    "Harry lived in an apartment above the corner. "
   
    south = livingRoom
    north: TravelConnector
    {
        destination = oppositeCorner
        travelDesc = "Harry stepped to the curb at the corner. The light was
            red, <i>Don't Walk</i> flashed on and off. Harry glanced down the
            street, saw an opening in traffic, and sauntered across to the other
            side.
            <.p>";
    }
;
oppositeCorner: Room 'Street Corner (NW)' 'opposite
    street corner'
    "On the north side of the intersection, just to the west of where
    Harry stood at the corner curb, neon signage announced the
    entry to a tattoo parlor. \b
    Next to it, an adult toy shop beckoned. <<if harry.day > 1 && harrysSonMax.fDaemon == nil &&
    !handCuffs.isIn(harry)>>
    <i>Huh!</i> Harry grunted. <i>Adult toys...where every self-respecting
    private detective shops for tools of the trade. Let me think...do I need
    anything today?</i>
    <<end>> "
   
    south: TravelConnector
    {
        destination = streetCorner
        travelDesc = "Harry glanced at oncoming traffic at the same time he
            stepped off the curb. By luck, the light turned green just as he
            started across. ";
    }
    west = sidewalk
;

sidewalk: Room 'Sidewalk' 'sidewalk at tattoo parlor'
    "Harry walked passed the tattoo shop. Neon signs
    proclaimed \"Cal-Ink Tattoo\" and \"Body Art\" on the glass entry door, to the
    north.
    <.p>
    The sidewalk continued east and west.
    <.p>"

    north = tattooParlor
    west = sidewalk2
    east = oppositeCorner
;

sidewalk2: Room 'Sidewalk' 'sidewalk at toy store'
    "A canvas awning reached over the sidwalk from the doorway to the north, on which
    handpainted lettering announced \"Adult Toys\" and \"all credit cards
    accepted\".
    <.p>
    The sidewalk continued east and west. 
    <.p>"

    north = adultToyShop
    
    east = sidewalk
;



tattooParlor: Room 'Tattoo Parlor' 'tattoo parlor'
    "<<first time>>Just inside the entry door of the <<only>>Cal-Ink Tattoo
    Parlor<<first time>>, a glass-topped
    counter separated a small entry parlor from a larger working area that
    <<only>> resembled a small barbershop, or hair salon. Except that at each of the three
    work stations, bottles of inks replaced ointments and lotions.
    <.p>
    <<first time>>The entry parlor offered a leather-covered sofa and
    a similarly-upholstered chair surrounding a low coffee table.
    <.p>
    <<only>>
    The chairs, both in the waiting area and at the three work stations
    behind the counter where the ink was applied, were empty. "
   
    south = sidewalk
    
;

adultToyShop: Room, Container 'Adult Toy Shop' 'adult toy shop'
    "Harry pulled open the door to the toy shop and stepped
    inside. Floor stands and clothing racks featured...etc.
    <.p>
    <<first time>>\"Harry, my man, good to see you again, and so soon,\" bellowed the hefty,
    bearded man behind the counter. Looking for something special today? or just browsing as usual?\" Harv asked,
    waiting expectantly.
    <.p>"
   
    south = sidewalk2
    
;
+ handCuffs: Thing 'pair of handcuffs;hand;cuff'
    "The bracelets of the chrome-plated cuffs where lined with a bright pink
    faux fur. But otherwise, they looked genuine. They would in fact work if
    ever Harry had to confine someone. "
;

That said, testing the above code did throw up another issue that may be what’s behind the other problems you described in your original post, and that’s to do with the timing of Daemons. In the file events.t, in the definition of the Daemon class, in its construct() method at around line 395, there’s a statement that reads:

        /* 
         *   set my initial execution time, in game clock time 
         */
        nextRunTime = libGlobal.totalTurns + interval - 1;

For the code I’ve given above to work, you’ll need to change it to:

        /* 
         *   set my initial execution time, in game clock time 
         */
        nextRunTime = libGlobal.totalTurns + interval;

I’d be interested to hear if this also affects the other problems you were experiencing.

I’ll need to look into this a bit more before I’m 100% convinced this is the right change to make in the library. It is a timing issue, and it may be a tricky one in that fixing this particular instance of the following problem like this might break something else. The complication arises from the fact that the ‘right’ value of nextRunTime (with or without the -1 at the end) may depend on where in turn cycle the Daemon constructor is called from (with the -1 in place, calling it from an AgendaItem is too late), so I’ll need to give some more thought about how to tackle this in the long term.

EDIT: I’ve now looked at this and decided that in the long term the -1 needs to be restored (i.e. the original statement with nextTime = libGlobal.totalTurns + interval - 1 is actually correct); this is what adv3 does, and the adv3Lite implementation of Events such as Daemons is closely based on adv3’s.

The problem is that an author may want to set up a Daemon from an AgendaItem (which is what my code above basically does, by calling getActor.startFollowing from an AgendaItem), but that AgendaItems are being (indirectly) executed from a Daemon, which gives rise to tricky timing issues. I think the long-term solution is to provide a slightly different mechanism for executing AgendaItems and the like. I’ve altered my copy of the library accordingly, but I’ll need to run more tests to make sure it doesn’t break anything else.

Thanks. I will implement your suggestions and run it for a while to see if things start working better. Actually, I already implemented some of what you said, taking the follow controls out of the travel barrier and using beforeTravel() and afterTravel() in the ActorState, and it does seem to be working better, at least for the limited trials I’ve run so far.

Well, that may be A problem, but it is not The problem. I put the second following statement in because Max was not following Harry from one corner to the other without it. Actually those are something like 2nd and 3rds, if not 4ths and 5ths. I encountered this problem several moves back.

Which brings up…

Well, yes and no. What I’m posting on this forum is test-bed code that isolates the problem. But the test bed is secondary to the actual game code, which, for better or worse, is in fact an actual game. I came to TADs as a newbie IF wannabe, started learning how to do it, found your Lite library which seemed to solve what I perceived to be a couple of major hurdles on the TADs learning curve (customizing the text flow, having to learn a gazillion classes), and have been learning both IF and Lite simultaneously by writing an actual, full-fledged game, start to finish. I have a story, I have a map, and—with your generous and invaluable help—I’m working my way through the code.

When I encounter problems along the way, it is often useful to me to isolate the problem by recreating a test bed .t that zeros in on the problem code and cuts out all the extraneous material. That’s what you’ve been seeing.

As for the timing issue…

…yes. I did observe Max and Harry in different locations sometimes, then Max catching up an instant later.

Before I added the locate debug command, I was putting the locater code into embedded expressions along the lines of…

…Harry is at <<harry.location>> \p
Max is at <<max.location>>

(That’s not actual code, just paraphrased from memory). What I observed was, just after a move from A to B, the game transcript would show…,

Harry is at B
Max is at A

But the debugger showed them both at B. I concluded that there was some kind of timing issue such that the two were in different locations at the moment the description text was rendered, but then the follow code kicked in and moved Max immediately after.

That’s why I started hard-coding the Max move with moveInto(), among much other experimentation in trying to work around the problem.

I also did not anticipate a new fDaemon at each instance of startFollowing(). My bad, I should have known better, but it does seem to explain some (though not all) of the problems I observed.

Sorry, I should have made myself clearer. I appreciate that you’re working on an actual game; what I meant was that the code you posted here was test-bed code extracted from the game and so might not be representative of the way you were coding the game itself.

That may well be, but it’s a bit different from the timing issue I had in mind, which results from trying to set up one Daemon from another. The issue is when you want a Daemon to execute on every turn (as the following Daemon must, to check whether it needs to move the actor who’s doing the following). Should the Daemon then execute on the turn on which it’s set up? The adv3 Daemon which I’ve effectively copied answers this question with a yes, but then the new Daemon has to add itself to the list of Daemons that are currently being executed at the point at which it’s created, and this doesn’t really work; the timing issue is that the new Daemon just misses the boat, as it were. But then, having just missed the boat, its next execution time is never updated, so it never gets executed. That’s what I was trying to cure by removing the -1 (so that a new Daemon first gets executed on the next turn, not the current one, and thus doesn’t risk missing the boat), but it’s not really the right solution, and should only be regarded as a temporary fix at best. The next version of adv3Lite will address the problem in a different way.

Sometimes ad-hoc coding of this sort is unavoidable in IF, but when it starts getting used as a kind of systemic work-around for something that ought to work reasonably straightforwardly, it’s likely to be a symptom that something has gone amiss (either with your own code or with the library).

Your bad, perhaps, but the sort of bad from which adv3Lite should really protect you, since it’s not that hard for it to do so. For the next update I’ve added a check in startFollowing() to ensure that the method only does anything if fDaemon is nil. Without such a check the startFollowing() method is simply an accident waiting to happen.

It would be good if we could track down what’s causing the other problems you observed. I think the Daemon timing issue is only really relevant to calling startFollowing() from an AgendaItem, so if you haven’t done that, it’s unlikely to be the cause. In your original post you mentioned seeing messages to the effect that Max had followed Harry although on occasion Max wasn’t moved to Harry’s location. That might be easily fixable with a sledgehammer approach (just move Max into Harry’s location regardless), but I’m a bit reluctant to go down that route for a couple of reasons:

(1) Using moveInto() to move Actors around is equivalent to teleporting them round the map; this risks bypassing the physics of the game simulation (instead of Max walking along after Harry he’s effectively beamed from point A to point B by the transporter room of the Enterprise, ignoring inconvenient barriers such as walls and doors together with any side-effects of travel). It’s the sort of thing you may seem to get away with for a while, but can end up causing subtle bugs (unless you really need to use moveInto() in situations where it’s unavoidable).

(2) Taking the sledgehammer approach avoids finding out what’s actually going wrong. The behaviour you described in your original post looks very odd. It might come about if Max followed Harry normally on one move and then Harry was moved in an odd way on the next (e.g. by moveInto) under particularly odd circumstances, so that Max lost track of where Harry went.

What should happen is that Max ‘remembers’ the last connector Harry traveled via and then travels via the same connector. If Harry is moved other than by traveling via a connector (which could just be a Room) on any turn, then this might confuse the following mechanism even though it has a fall-back mechanism.

Okay, thanks.

Yes, agreed, but it might take a bit of time. You’ve set me straight on some things that will significantly affect the way things work moving forward. I’ve removed all start/stop following commands implemented as embedded expressions, and am using the before/afterTravel() methods in the ActorState. I’ll need to watch the results for a while to see what’s still amiss and what got fixed by changing the way fDaemon gets started and stopped. I may try fiddling with the daemon in an AgendaItem, as well, but I don’t want to muddy up the results too much until I have a better understanding of what’s happening without that complication.

I will put the changed code through some paces and let you know what happens.

I’m sure that’s as well. By the way, it’s not really a good idea to use the room description property of a Room for doing anything other than displaying a room description (for example). Putting embedded expressions to cause side-effects is likely to cause problems; consider what happens if a player issues multiple LOOK commands, for example. (For the same reason you really shouldn’t have the adult toyshop shopkeeper’s greeting in the room description - but perhaps that was just a quick and dirty way of doing it in the testbed).

You might want to patch Actor.startFollow() (in actor.t) with the following pending the next release:

startFollowing()
    {
        /* 
         *   Create a new Daemon to carry out the following and make a note of
         *   it
         */
        if(fDaemon == nil)
            fDaemon = new Daemon(self, &followDaemon, 1);
    }

The test if(fDaemon == nil) should prevent the spawning of extra Daemons should your code still accidentally end up calling startFollowing() once too often; this may help clear up some problems.

Or you may want to wait until the next release that fixes the AgendaItem/Daemon timing problem (the patch would be a bit complicated and I’m not satisfied it’s been adequately tested yet).