StateTree improperly transitioning from completion states in 5.6 due to new StateTree.GlobalTasksCompleteOwningFrame cvar

I’m hoping I’m just having some sort of misunderstanding about how this should work. It’s a bit complex to illustrate but I’ll try to pare it down.

I have a subtree state in a state tree asset, that looks like this, for a basic patrol behavior.

This behavior can operate in one of 2 modes.

  • If the AI has a ‘behavior context’ actor, it will move to one of those actors
  • Otherwise, the behavior can fall back to an “unguided” patrol, which ultimately just uses GetRandomReachablePointInRadius

[Image Removed]

Note that the expectation here is that if GuidedPatrol fails, it should go to UnguidedPatrol(via the failed transition). GuidedPatrol doesn’t have a condition tied to it, so I can’t rely on the Select Child failed, try next. It runs a task to query and select a patrol destination based on some data fetched through a behavior actor(and does some other stuff related to cooldowns). If no results are found, the task returns failed, which I expect to trigger the transition to unguided, but this is not occurring, and I’m not sure why.

Instead what happens, according to the vislog, is that the state tree oscillates rapidly. I have attached a vislog dump from the oscillating frames.

According to the log, the failure of GuidedPatrol is causing failure transitions to fire from higher up the tree. I can see in the debugger that the FStateTreeExecutionContext::TickTriggerTransitionsInternal is hitting the MaxIterations count of 5, but there’s no indication of that part by looking at the logs that I can see.

According to the log, on one of the frames, it shows Completed subtree ‘Patrol’ from global: Failed.

I’m not sure what constitutes a global: Failed, but the task that is actual failing belongs to a state in a subtree that should be able to process its own failed state, and transition to its sibling Unguided state. The log doesn’t mention UnguidedPatrol at all, despite a very explicit transition to it being present on the GuidedPatrol state, which is what is actually failing, so it’s not even trying to transition to it. Instead, failing from higher up the tree.

[Image Removed]

Here is the snippet that executes the patrol stuff

[Image Removed]

  • [Image Removed]

So questions

  • Why is Patrol:failed the transition getting taken, and not the GuidedPatrol:failed
  • When a state fails, does the entire stack fail? Surely states closer to the failure task, like the owning state itself, should get first opportunity at transitions?
  • I’ve disabled the tree failed state on Patrol to no avail.
  • What do Tree Failed/Tree Succeeded with respect to linked subtrees?
    • Do they just complete that subtree?
    • Or do they complete the entire hierarchy, regardless of nested subtree levels.
    • If it’s the entire hierarchy, how do you set up a ‘return from this subtree’?
    • Are there plans to support debugging Link Assets? I’d like to piece out my state tree, as it’s getting complicated, but losing the debug into those linked states is pretty undesirable.

The only other thing special about Patrol is that it is a linked subtree, and it’s running in an Idle Behavior state, which is also a subtree, so maybe there is some execution quirk with nested linked states that I am not aware of.

According to the StateTree.GlobalTasksCompleteOwningFrame cvar, it looks like in 5.6, completions are only suppose to complete the linked state.

But as a user, I would expect transitions to only bubble up if they are not handled at the leaf state they occur in. Since GuidedPatrol is failing, it should be taking the failed transition in GuidedPatrol. Am I wrong about this?

Thanks in advance.

J

I see that in this block, it’s selecting the parent frame as FrameIndexToStart, which causes completion transitions to be considered from the parent down. This doesn’t make sense to me.

[Image Removed]

The comment doesn’t even seem to agree with what is going on. The last completed state isn’t the parent state(Patrol). The last completed state should be the one that failed, which is (GuidedPatrol). This seems to be why GuidedPatrol failed transitions aren’t being considered.

Am I wrong about something here?

[Image Removed]

This entire new ExecutionContext::Private::bGlobalTasksCompleteOwningFrame just skips the frame level that actually errored.

Why does it loop to find the FrameIndex that failed, but not give anything on that level the opportunity to handle it?

The fallback code, below this, loops through and sets FrameIndexToStart and StateIndexToStart to the failed state/frame

Judging by the comment associated with the bGlobalTasksCompleteOwningFrame, this new chunk of code is meant to be new 5.6 behavior, but why would it be proper or desirable that a frame has no ability to handle its own success/failure states? That seems entirely counter intuitive.

[Image Removed]The reference to Global taskcan only mean that it’s meant to handle the completion state of a globally running task on the state tree, but the task that is failing here is just on a regular state, in a regular linked node, and this ‘global task’ code is causing it to fail from the parent frame upwards, which makes total sense for a global task, but not for this situation. How do you make a distinction between global tasks and any other task on any other state?

Sure enough, turning off StateTree.GlobalTasksCompleteOwningFrame fixes the issue and gets my behavior working again.

The takeaway here seems to be that the code for this cvar, at least in TriggerTransitions(), is being applied too broadly. If it’s truly meant to only be applied to global tasks, not just any task, it’s bugged. If it’s meant to be applied to any task in any frame, then a) that doesn’t really make much sense, and 2) everything about how it is documented in the cvar suggests otherwise.

That is incredibly strange that a non-existent global task appears to be causing the transitions here. I know the CVar was set in place to allow upgrading to the new flow for global task completion in 5.6 where global tasks would complete the tree in which they are located rather than completing the root tree. Are you using this StateTree as part of a RunParallelStateTree node or Linked Asset? If you remove/disable the Tree Failed transition on MoveToGuidedPatrolPoint does it still get stuck in trying to bail out of the subtree that is running? I believe transitions are now checked from leaf-to-root rather than starting at the state the task belongs to. It may be that failing in that state is causing the Tree Failed transition to be taken. If that begins working as expected/was working, you could have GuidedPatrol use a StateTreeDelegate transition to go to UnguidedPatrol.

-James