Preconditions and opportunities in goal system
Dave Mark - President and Lead Designer of Intrinsic Algorithm LLC
Professional consultant on game AI, mathematical modeling, simulation modeling
Co-founder and 10 year advisor of the GDC AI Summit
Author of the book, Behavioral Mathematics for Game AI
Blogs I write:
IA News - What's happening at IA | IA on AI - AI news and notes | Post-Play'em - Observations on AI of games I play
"Reducing the world to mathematical equations!"
You might want to design a system where the 'preconditions' are objects and get added to a stack and get removed by 'tasks' when the precondition is no longer relevant. Doing that will make it easier to make tasks hierarchical.
In my design the objects (tokens) have included in them a contingency such as jumping back to the FSM state which achieved the condition (or aborts the whole task for reevaluation and restart)
step 0
set a timelimit/effort limit for the whole task (contingency = abort)
step 1
Subtask : move to location A (will test preconditions)
on arrival set a precondition token which will fail if proximity to LocA isnt met (contingency = go back to step1)
goto next step
Step 2
loop
doing whatever was to be done at LocA
retest periodicly that preconditions are met
when this action succeed then cleanup preconditions (remove LocA from stack)
step3
do next action
The opportununistic situations would force you to have mutliple task solutions existing at once and probably only one active at a time -- unless you make a clever resource manager where manipulative resources like each hand and feet, eyes etc.. can be doing different tasks at the same time from seperate solutions (within the same task stream simultaneous use could be choreographed in the script and locally interlocked/scheduled).
The opportunity situation MAY activate a different course of action (and stall the running task) but that would depend on you evaluationa and prioritization system to decide which,
Preconditions are still checked on the topmost goal, but if the check fails, instead of marking it as completed or failed right away, which would affect subgoals also, the entire subgoal list with nested composite goals are searched for any opportunity goals. If any are found, they are then extracted. The outer goal which failed its precondition is marked as completed, and will be removed on next update.
However, the extracted opportunity goal is then transplanted at root level and left to continue its execution.
Dave Mark - President and Lead Designer of Intrinsic Algorithm LLC
Professional consultant on game AI, mathematical modeling, simulation modeling
Co-founder and 10 year advisor of the GDC AI Summit
Author of the book, Behavioral Mathematics for Game AI
Blogs I write:
IA News - What's happening at IA | IA on AI - AI news and notes | Post-Play'em - Observations on AI of games I play
"Reducing the world to mathematical equations!"
The problem concerns the goal-based system as described in Mat Buckland's book "Programming AI by example". This is what I am trying to extend. So it has nothing at all to do with goal based planners (GOAP). Rather, it is like a nested structure of states to be performed in sequence.
The system has nested objects called "atomic goals" and "composite goals", where composite goals can have subgoals. The subgoal container in my case is a command queue, in the book he uses a stack.
In this system, a goal to control path following is called GoalFollowPath and it has (atomic) subgoals called GoalTraversePathEdge.
What I called opportunity goals above is my own term for any unrelated goals that are inserted for processing in the middle of another goal sequence.
This is where the problem with preconditions comes in, they may not be related to the opportunity goals that have later been added further down.
Also, the preconditions are my own idea (though they are common in general AI I believe??). They are not a part of the system in the book. But, seeing the shortcomings of my present system, I want to design them somehow.
The system allows tasks (FSM) to run subtasks, including execution of a solver for a subtask's goal to choose between competing solutions. Im still working on the functionality where preevaluation can force specific sub-solutions to be locked into the plan or to leave it as unreolved to be evaluated later when the situation is more local. The uncertainty of future situations makes the delayed resolution of the plan important.
Your opportunity goals should be treated like any other goal -- it will (or wont run) depending on how its priority is evaluated. An interupted task would then resume, probably after reevaluation of its preconditions which might fail because the objects situation has changed in the interim.
Depending on how difficult (costly) it is to revaluate certain preconditions it can be useful to seperate them into sets with different frequencies. Preconditions directly required for an action currently being performed would be checked constantly (ie- if you are digging with a shovel and the shovel broke or flew out of your hands the 'digging' action would have to stop -- the logic would check the state of the shovel each animation cycle and then abort the action if the shovel was no longer intact or in hand). But other preconditions which for example caused the task to be done need be checked less frequently (ie- preassigned resource limits for a task like time or expendible items or energy).
Quite a while ago after looking at the mechanics of this kind of behavior control, I decided that games will be needing ALOT more CPU resources to handle the constant reevalution required for reactive/adaptive behavior.
But it would be difficult to design, wouldn't it?
Where in the code would you place the evaluation of preconditions? If it is placed in the top goal, which I had in mind because it is logical, it would need to know somehow what was happening further down and disable itself. Or there could be a flag that is set on it when special subgoals are executing like my suggestion of explicitly defining certain goals as "opportunity goals".
When you are talking about preconditions being costly to compute - I think they should always be quick checks such as your example with the shovel.
Doing costlier re-evaluations of goals is in my mind a different part of making the agent adaptive/reactive. This means running the evaluator functions/planning algorithms at a low frequency while the agent is busy in order to to see if better goals have emerged and should replace the current goal.
In my case, replacing the curent goal leads to the same problem that I'm looking at now with what to do with the opportunity goals.
For example, if task X is good to go, you can insert it. If you then insert the higher priority task A into the list, it is placed in front of X. When A is done, you then can check to see if X is still fair game. If so, you proceed. If not, you can solve to see what you need to accomplish first... just as normal.
You don't need to be constantly checking the validity of actions while executing a different one... only when you are ready to move on. That saves on processing clock cycles like crazy.
Dave Mark - President and Lead Designer of Intrinsic Algorithm LLC
Professional consultant on game AI, mathematical modeling, simulation modeling
Co-founder and 10 year advisor of the GDC AI Summit
Author of the book, Behavioral Mathematics for Game AI
Blogs I write:
IA News - What's happening at IA | IA on AI - AI news and notes | Post-Play'em - Observations on AI of games I play
"Reducing the world to mathematical equations!"
I saw this long ago and added a 'quantum' level for goals which classified their priority so that only the current highest quantum needs to be evaluated.
Of course that requires a filtering system to interpret the situational data which can itself get byzantine. A tracking mechanism to remember objects which where previously seen so that they need not be fully reevaluated was then added
(previous objects can trigger when they changed state -- like proximity or different behaviors that indicate a new threat/opportunity).
Note - problem with AI is that you still have to process all the info clutter even if you discard most of it as being insignificant.
----------
"You don't need to be constantly checking the validity of actions while executing a different one"
I agree, but 'actions' are hierarchical (at least in my system as tasks) and the lower level tasks inherit preconditions from their parents (or a mutation of them). Example a 'Timelimit' precondition which would be fairly common for tasks that have Retries and 'Get as much as you can' built into them have to limit some allocated timelimit to attempt achieving that tasks goal (which might also result in backing off and trying another solution). The parent has its own time limit and allocated some chunk of that time to do each step needed for its task. (solution would be to build a new precondition stact for the subtask selecting/mutating only the relevant preconditions from the parent and letting the parent retest later after the (probably) short subtask completes)
Actions at the primitive level (like swinging a shovel) are often tied to a hard time cycle based on the animation and only run once so are easy to estimate and are self-limiting (so dont need an extra precondition checks).
More complex action sequences have different animations depending on if they succeed (a sword swing that strikes or bounces off a shield) or can be externally interupted (shovel torn out of your hands by an enemy). Most game dont have these complex animation actions and use the same animation set for all results, but a more realistic system (maybe like a fighting game) would and then you have a sequence of primitives controlled by logic which you would have check external situational factors to diverge to different results (and thus the precondition tests).
One advantage of using FSM for the task script is that the insertion and removal of the precondition 'tokens' is contraollable and the 'contingency' for failure likewise (it can be logic that makes an evaluated decision). It can be a jump back to an earlier state which achieved the subgoal that a precondition represents/guarantees (which should be only local to the task script) or an abort out to the parent where the parents preconditions will be tested and it will be handled at that level.
I have been working on a wizard system to build and validate task scripts (since there can be alot of cross indexing and consistency checks for jump labels and precondition cleanups if you abort out of sequence). Obviously it also saves on dealing with alot of repetitious wrapper code for the FSM (and standard/default handling of results).
The basic structure for each 'state' of a 'script' FSM is :
(Note -- each state actually has several 'phases' within it)
-----------
Initialization:
setup for the 'action' - get needed info, allocate a 'result' token
etc - stuff that is done once
optional set retry max count
Start: (note- seperate from init becaus may be executed repeatedly by retries)
test preconditions (local high frequency, external lower optional)
reset local data needed for retry cycle
determine parameters (if any) for the 'action' call below
Run:
do the 'action' (either a primitive or call another subtask or call a solver)
(if calling down a hierarchical level preconditions set here for the subtask)
wait for result
Result:
get result code ie- SUCCESS FAILED INPROGRESS ABORTED ... other specific codes
(tied to animation subsystem and sit waiting for the animation to run through)
do handlers for result code:
case 1: SUCCESS:
create a 'token' for partial local achievment (like getting a tool)
optional if task is qnantity based decide if want to rerun to get 'more'
else return from task with success result OR jump to the NEXT state down
case 2: FAIL:
decide if Retry is to be done (jump back to Start phase)
else return from task with fail result OR jump to a special contingency state
abort back to parent task will force all loacl 'tokens' to be discarded
case3: INPROGRESS:
test subset of precondition to allow interrupt while waiting for long 'actions'
(im not sure if ^^^ is needed or better handled by decomposing into a FSM)
etc... (specific RC like TIMELIMIT make this logic simpler...)
some varying results can cause decisions and jumps to different states
------------------
Script
State1
State2
State3
Done
-------------------
Most of the FSM script is built of sequences of the above structure with some variations possible for parallel sub tasks (possibly a local planner call).
Notice that a script can call another task script (starting a subtask) which itself will be an FSM, so its alot like the way procedure calls are made with parameteres and a 'return' value.
The 'solver' is a search for solutions to be done (late) 'at that time' vs precalculated/preplanned, which can use the immediate situation factors to
select the best (adaptive) solution. Unfortunately doing that search may be costly (good place to use the 'thinking' animation).
Note - the 'result' token is not just a RC but include pointers to result items acquired by the actions (and quantity if its a 'pile') so the parent can then turn that into a precondition for subsequent tasks to manipulate (ie- task1 gets the shovel, task2 uses the shovel, task3 discards the shovel and taskes results home ---- the second task is invalid if that shovel isnt in possession...).
My larger system has a 'ownership' system for item resources where a task is allocated items as 'tools' which blocks other tasks from grabbing them or at least discarding it when its still needed. Actually Im working on a system of 'resource management' where items once freed by a task get retained based on their utility (ability to be used for potential tasks -- which lowers the estimated cost for solutions that use that tool). That system will be required if proper parallel simultaneous task execution is to be done. ( Of course now we need a goal which is maintaining 'tool' possessions within carrying limits (or caching them off object) and readjusting the load when a new 'tool' candidate is available (now need an evaluation of 'utility' ...) and probably add the functionality to LOOK for tools to add to the inventory to maximize versatility -- but not in conflict with higher immediate (or future??) planned goal tasks........
The real MESS of this ever growing AI system is that of preevaluation of solution candidates when deciding which one to do . When more than one solution is present, calculating its probability of success (in an uncertain world system and with partial information) isnt always simple. Often a non-optimal solution can be better because it is more versatile and offers more adaption to achieving its subtasks which are required for it to succed (yes, the evaluation is processed hierarchically also).
Processing for Uncertainty leads to having to maintain a complex world representation of significant factors/info which are no longer visible (memory) and tracking what information is NOT there. Information is (massively) filtered and skewed depending on the objects usage of the info.
Remembering which solutions worked best in the past is a whole nuther area Ive barely scratched programwise -- as its complex to try to factor the situation to determine a pattern match without over/under generalization happening.
----------------
Usually after hearing about all this extra stuff most people start screaming and running for the hills or at least back to simple choreographed scripting for their game. One thing I immediately started looking into was clustering and interprocess (multicore) programming to see how ugly it will be (because of the 2 magnitudes MORE cpu power this AI will require to do even half this stuff)
Anyway you can still incrementally add AI and planners will be one of the next advances to try to make behaviors look smarter than they do currently.