Philip P. Ide

Author, programmer, science enthusiast, half-wit.
Life is sweet. Have you tasted it lately?

User Tools

Site Tools


blog:opensim:cortex_ai:about

About Cortex-Ai

Cortex-Ai.Core and satellites in a linkset object

The Cortex-Ai is a new NPC controller system I'm working on. The last controller system I made let the NPCs effectively control themselves, while the controller spawned and despawned them, allowed some to remain active even when there were no agents in the region, and also allowed the controller to act as a repository for the various script notecards which contained instructions for the NPCs.

The nice thing about that system was the scripts (NPC actions really, stored in notecards) could interrupt and load each other, and the controller and other objects could send data to the avatars.

Choosing a Controlling Mechanism

As with any project - in this case designing and building the Mars Complex - the needs of the project threw up new requirements vis NPCs. The Mars Complex is huge, sprawling across 289 grid coordinates in my own grid, and extending into a single 1×1 region in OSGrid. The Oxygen/Health system is a single in-world controller managing the oxygen and health levels of all agents (avatars with a hoomn™ in the driving seat) everywhere within the Complex.

When it comes to controlling NPCs, using a single controller to manage all the NPCs across this vast expanse of real estate is probably going to be a bit much for any single region to cope with, but using an external program to manage them is a realistic option. I already have a mechanism by which I can inject instructions to objects from an out-world resource. I could easily write this in Rust. I already have experience in writing Rust applications that work using frames - for example, setting the frame rate to 60 FPS would give a refresh rate fast enough to make hundreds of NPCs responsive to stimuli, and still leave the application in idle mode most of the time.

That could be fun to write, and technically challenging too (always a bonus in my mind), but I think I'll leave that to another day. In the meantime, I'm writing an in-world controller, which will control just the NPCs for its own region. This is a relatively straightforward process, and I have already figured out how simple it is to create an action language. Here's an example action notecard:

gender=female
wait=9
find=59ae2072-b694-47af-80e5-a6c7ae7ddf10
sit=59ae2072-b694-47af-80e5-a6c7ae7ddf10
animate=998e849f-4399-4e0e-bb15-10d236c5751c

The find and sit commands can take a UUID or an object name. The animate command can take an animation name but only if it is in the NPCs listener object. Using a UUID is more versatile. There are many other commands of course, which includes loops, if..elseif..else conditions, calling other scripts, goto and so forth. For the new system, I'm rewriting the action language to be even more powerful.

While the other system sent messages via llRegionSayTo, this one will send dataserver events.

Inventory Chaos

If we take the OSW NPC kit as an example - I'm not dissing it, but it does highlight the problem - there are scripts, animations, various configuration notecards, action notecards, NPC definitions, initialisation actions and quite possibly a kitchen sink. The more action notecards you create, and the more NPCs you add, the bigger the mess gets. The problem isn't the way the device has been designed, its the fact that prim inventories can't have folders.

To make this more manageable, the new NPC controller has the necessary script (just the one), a simple .config notecard, some animations and anything that an NPC might want to give to an agent. That's it. The Controller object though, is a link set, and the controller itself is just the root prim. Other prims in the link set respresent individual NPCs, and the prim name is set to the name of the NPC that will be created.

In the NPC prim, there is a .config notecard, a .profile notecard, a .profile_img texture (snapshot), and a small script which when requested will gather the NPC details and send them to the root prim. The details are pretty short:

# example .config notecard for an NPC
# single line comments must be in pairs
gender=m
greet-distance=1.0
walkm=VAAESWLK11P3
walkf=VAERIWK001
standm=VAAESST03B
standf=Stand-Female
sitm=Male-Sit
sitf=Female-Sit
lifetime=4
locality=dome|<166, 102,0.2>|26
startpos=<165,110,2>

Note that there are both male and female animations in the configuration, and the gender flag is used to determine which of these animations to select. This allows me to have a common script for both genders and just changing the gender and other NPC specific details allows me to quickly setup a new one. The following settings string is returned to the root prim:

2|1.000000|VAAESST03B|VAAESWLK11P3|Male-Sit|0.914919|4|dome#<166, 102,0.2>#26|<165,110,2>
 
These fields are:
2               # version of the satellite script (so I can tell if I missed updating one)
1.000000        # greeting distance - when walking to an avatar, how far short should we stop so we don't bump them
VAAESST03B      # stand animation
VAAESWLK11P3    # walk animation
Male-Sit        # sit animation
0.914919        # avatar height / 2
4               # avatar lifetime code
dome^<166, 102,0.2>^26  # define an area for reacting to agents
<165,110,2>     # spawn location

The root prim then analyses this and may perform some more processing on it, before storing the data in the LinksetDataStore. When the root script is reset, it sends a message to all linked prims to return this information. When it has everything back from them all, it is ready to begin controlling NPCs.

The NPC prim is called again with a spawn request - if the NPC is already spawned, it will despawn it. When spawning, it will set the NPC profile image and 'about' text, then return the NPC UUID to the root prim.

All this keeps the inventories simple, and if you want to change anything about an NPC, you simply edit the prim with its image on.

NPCs Have Lives

Yup, the reason for all this rewriting is so my NPCs can have lives. They get up in the morning, maybe go to a food bar to grab a coffee and breakfast and catch up with friends, go to work, go to lunch, take breaks occasionally, stop off at a food joint on the way home and go back to their apartment (once the door is closed, they can be despawned until morning). Some won't despawn after work - they might get dressed in mufti and go to a bar for a few hours, or they might put on their glad rags and go on a date, starting at a restaurant, then cutting a rug at a nightclub, and ending up going to their date's home for the night. Followed by the walk of shame in the morning to put their work clothes back on. Let's concentrate on their work patterns for now.

Some of them do shift work, so their hours change on a daily basis. Some of them work on multi-day cycles. For example, the constables will rotate their duties:

  1. guard duty at airlock #1 day
  2. guard duty at airlock #2 day
  3. patrol route #1 day
  4. patrol route #2 day
  5. patrol route #3 day
  6. day off

This makes a six day cycle, and since there are six in-world days (frames) per 24hrs in the real world, repeating this cycle would create the Groundhog Day effect but stretch it over six frames. If an agent returned to the same spot every day at the same time, the NPCs would all be in exactly the same positions. We could extend this so there were an additional six days, this time working the night shift, but they'd be in exactly the same positions at the same time each other day.

If there are six frames in each day, a very simple solution is to have an odd number of shifts. Dropping 'route #3' from the cycle neatly achieves this (and requires less actors). Now we can add in a night shift, granting ten shifts to the cycle. If an agent enters the region at 10AM GMT every day, it will be ten real days between seeing a constable at the same post twice.

While it is possible to keep them alive at all times so they just cycle through their dreary lives, it would be better to only have them spawned when there are agents in the vicinity.

For this reason, there is the locality option in the configuration. Instead of spawning them when an agent enters the region, they only spawn if the agent enters the area defined by the locality. The locality would be defined either as a dome with a vector point at the center of the floor and a float describing the radius/height, or a box with two vectors describing top-left and bottom-right of the cubic area (any two vectors will do, they get normalised to tl and br). This is important because the regions are large and the populated areas are much smaller. There's no point spawning everything when the agent is nearly a kilometer away.

If they're only spawned when agents are present, it will be necessary to keep track not only of what cycle each NPC is on but also what part of the cycle. That'll be the task of another script, and is complex enough it'll almost certainly be easier to perform the calculations external to the simulator and made available on a per-NPC basis on demand. Once an NPC is spawned and set the correct place in their cycle, they can keep track of their cycle themselves, so the external program is only required as they are spawning.

This website uses cookies. By using the website, you agree with storing cookies on your computer. Also you acknowledge that you have read and understand our Privacy Policy. If you do not agree leave the website.More information about cookies

Discussion

Enter your comment:
B E D​ W I
 
blog/opensim/cortex_ai/about.txt · Last modified: by Phil Ide

Except where otherwise noted, content on this wiki is licensed under the following license: Copyright © Phil Ide
Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki