Tactical Assignment Extension Points

GAIS_Cover
YuewuDev

Author

YuewuDev

Created: Oct 12, 2026Updated: Oct 12, 2026

Table of contents

Tactical Assignment Extension Points

Extend Tactical Assignment safely by changing one responsibility at its own stage: Sources provide inputs, Filters prune early, a Resolver chooses a concrete Command, Command definitions validate and score, an Allocator selects scored candidates, and the core finally commits and maintains reservations. Adding a rule at the wrong stage duplicates validation, creates misleading diagnostics, or bypasses World capacity.

Choose the right extension point first

Change needed

Extend here

Common wrong location

Where candidate Units or targets come from

UGAIS_TacticalAssignmentAssigneeSource / UGAIS_TacticalAssignmentTargetSource

Allocator or Command Definition.

A candidate plainly must not participate

Assignee/Target Filter

Source Gather result state.

Which Command one Intent means for different Units

IGAIS_CommandIntentResolver

Target Filter or Allocator.

Whether a Unit may execute, whether target is valid, base target preference

Public overrides on UGAIS_CommandDefinition

Duplicated command logic in Filters.

Which scored candidates are selected

UGAIS_TacticalAssignmentAllocator

Searching the World again or submitting Commands directly.

How many Units may occupy a physical target

UGAIS_ProjectPolicy

Private counters on a Batch or custom Allocator.

Independent-output contract for custom Sources

GatherActors and GatherTargets are BlueprintNativeEvents. Each Source writes only its candidates into the temporary array supplied for the current invocation. Sources run independently; the system then removes invalid items, deduplicates, and unions their outputs.

  • Use ValidateSourceConfiguration for configuration that cannot recover on the next frame: an EQS QueryTemplate, required RoleTag, or explicitly required SquadActor.
  • When Gather has no result this run, write an empty array. Do not implement or emulate a Source Deferred/Failed result.
  • Runtime failure, an empty EQS set, or invalid project-query output only makes that Source contribute nothing this run. Other Sources remain valid.
  • A Target Source may set SourceTag so UGAIS_TacticalAssignmentTargetFilter_SourceTag, Resolvers, and diagnostics can distinguish input origin.

If an old assignment's candidate disappears from this run's Source union, normal evaluation reconciles it as Stale and releases matching Commands and reservations. Do not turn an empty current output into Deferred merely to preserve stale state.

When to extend an Assignee Source

Extend UGAIS_TacticalAssignmentAssigneeSource when candidate Units live in an authoritative container without a built-in adapter: encounter participant registries, vehicle-seat systems, summon managers, or a project formation container. An override should:

  1. Reject only stable configuration errors in ValidateSourceConfiguration.
  2. Read only its own data source in GatherActors and write valid current Actors to output.
  3. Never read another Source's temporary output or cache last-run output as current candidates.
  4. Leave invalid-item cleanup, deduplication, and Source union to the core.

For “choose healers from all squad members,” the existing squad Source plus a Role/Tag Assignee Filter is sufficient and more reusable.

A project Source usually needs only ValidateSourceConfiguration for stable class/query/tag/registry references and GatherActors(ContextPawn, OutActors) for current members reachable through a component, Subsystem, or project container. Blueprint subclasses may implement both events; C++ subclasses suit hot queries, native containers, or stricter typing. Every Gather starts with an empty output array, and stable output order makes equal-score outcomes predictable.

When to extend a Target Source

Extend UGAIS_TacticalAssignmentTargetSource when targets come from a mission system, cover system, player pings, control-point registry, or project perception cache. It must emit real FGAIS_CommandTargetData:

  • Actor targets for enemies, allies, and facilities.
  • Location targets for guard, flank, and patrol points.
  • Actor plus Location when an Actor identity and a fixed approach/interaction location are both needed.
  • TargetTags describe the target itself; SourceTag describes the input channel.

Give a Source a stable default SourceTag, or write a more specific Tag on each TargetData. The core uses the Source default only when TargetData has no Tag. Do not duplicate one Actor under different Tags to evade capacity: all such candidates share its Actor reservation bucket.

The entry points mirror Assignee Sources: ValidateSourceConfiguration handles stable setup; GatherTargets(ContextPawn, OutTargets) emits this run's Actor, Location, or Actor-plus-Location data. The system drops data that cannot establish a physical-target identity. Actor targets use Actor identity and pure locations require finite coordinates. Outputs are unioned; one physical target with different SourceTags can remain separate logical candidates, but converges to one reservation bucket for capacity.

Filters prune candidates

Filters are appropriate for inexpensive, semantically clear eligibility tests: Actor tags, team attitude, target source, whether a target has an Actor, Squad Role, or distance to a Movement Intent desired location. Every configured Filter must pass before CanObeyCommand and IsValidTarget run.

Keep attack distance, resource cost, ability cooldown, and complex target relations in CommandDefinition so manual Commands and Tactical Assignment share one rule set. Put squad-specific rules in the Filter/Source adapters supplied by GAIS_SquadTacticalAssignmentAdapters; keep the core runtime context generic.

Custom Assignee Filters

Blueprint subclasses of UGAIS_TacticalAssignmentAssigneeFilter implement PassesAssigneeFilter and can read Context Actor plus candidate Actor, making them suitable for light project rules. C++ can override the context-aware entry point and use cached CommandSystem and Actor-owned tags.

Examples include “only Units with ammunition,” “only Units not player-locked,” and “only Units in the current encounter.” A rule that depends on a resolved Command or one Unit-target pairing belongs in a Resolver or CommandDefinition, not this stage.

Custom Target Filters

Blueprint subclasses of UGAIS_TacticalAssignmentTargetFilter implement PassesTargetFilter and receive Context Actor plus full TargetData. The C++ context entry point also provides TargetActor, SourceTag, and target owned tags.

Use them for “only control points enabled in this mission phase,” “exclude player-reserved cover,” or “let a Batch consume only one perception channel.” Target Filters run before a specific Unit-target pair exists, so a rule depending on final attack cost for that Unit and target belongs in CommandDefinition target validation and scoring.

Resolver: give semantics to the Unit that can execute them

IGAIS_CommandIntentResolver::ResolveCommandIntent receives Context Actor, candidate Actor, IntentTag, preprocessed target data, and SourceTag, then returns the FGAIS_CommandReference that continues to validation.

The default UGAIS_UnitComponent reads CommandIntentMappings, which suits fixed mappings. When the Command depends on role, weapon, Buff, target source, or runtime state, implement a Resolver on the Unit, Actor, or project component. A Resolver only chooses a Command Reference; it neither submits Commands nor makes the final target choice.

Provider lookup begins at the candidate Actor, then its ActorComponents, and uses the first valid result. Multiple Resolvers on a Unit can divide responsibility explicitly: a weapon component handles attack Intents while a medical component handles support. Fixed mappings, dynamic weapon selection, and project-special rules can therefore coexist.

The resolved Command still passes CanObeyCommand, Cancelable policy, target type, IsValidTarget, and scoring. Resolver selects which Command; CommandDefinition owns that Command's execution contract.

Allocator selection and capacity boundary

A custom UGAIS_TacticalAssignmentAllocator::Allocate receives candidates that have already passed Sources, Filters, Intent resolution, Command validation, and scoring, plus the TargetCapacities view. It should only write original candidates it wants to keep to SelectedAssignments.

The World Subsystem and core commit phase still verify that selections correspond to canonical candidates, Actors are not repeated, and target capacity remains available. Do not hold persistent cross-Session counts, fabricate candidates, Gather Sources again, or call CommandSystem directly in an Allocator; each conflicts with the reservation ledger.

UGAIS_TacticalAssignmentAllocator_GreedyWithCapacity already chooses the best available candidate per Actor while respecting capacity. Write a custom Allocator only for real needs such as role quotas, global composition, or special ordering:

  • Ensure every enemy receives one attacker before adding high-score Units.
  • Maintain a melee/ranged ratio.
  • Spread guards across positions.
  • Choose combinations for squad-level value rather than Unit-by-Unit greed.

Allocate receives CandidateAssignments and TargetCapacities, and writes only SelectedAssignments. Every selected item must come from input, an Actor may appear once, and new consumption must respect the capacity view. The core performs a final check regardless.

Custom target occupancy

Extend UGAIS_ProjectPolicy, override ResolveTacticalAssignmentTargetCapacity, then select that class in Generic AI System Settings ProjectPolicyClass.

The capacity policy can inspect CanonicalTargetData, for example:

  • a Boss Actor returns Limited(4);
  • a normal enemy returns Limited(1);
  • a location tagged as a public rally point returns Unlimited;
  • a target locked by gameplay returns Blocked.

Actor identity and a location grid form reservation identity. SourceTag and TargetTags can help policy interpret a target, but do not make new buckets. Capacity should be consistent for one Actor; a resolved value remains while its bucket is occupied. Policy runs as a CDO; UGAIS_TacticalAssignmentSubsystem owns runtime claims, cross-Session counts, and release.

TacticalAssignmentLocationTargetGridSize controls when nearby positions share a bucket. A smaller grid distinguishes dense positions; a larger grid treats an area as one shared target. It affects every location target in the project and should match real point spacing.

When an assignment leaves a Session or the Session stops, the Subsystem releases its claim. Command execution continuing afterward does not extend claim lifetime, so a new tactical plan can immediately use released capacity.

Deferred versus Stale

Deferred comes from a World-update guard conflict or synchronous reentry. It makes no change to tracked assignments or reservations, and a later Task reevaluation retries it. Empty Source output or query failure is simply that Source's empty contribution to normal evaluation.

After normal evaluation completes, the current Source union is authoritative. A disappeared candidate, changed Filter, or Command that is no longer valid turns old assignments Stale or failed through the normal diff and cleans matching resources. A custom Source should emit current truth every run and leave expired candidates to Session reconciliation.

Validation order after extension

  1. Use a fixed Source to validate Resolver and CommandDefinition.
  2. Validate the new Source independently; empty output must not block other Sources.
  3. Check pre/post-Filter counts and expected SourceTag, team attitude, or Role.
  4. Confirm every Allocator selection came from CandidateAssignments and accept core rejection when capacity is insufficient.
  5. Use GameplayDebugger to inspect pool, Candidates, Selected, Stale, and Commit Outcome.

For field choices, see Tactical Assignment Reference; for end-to-end setup, see Configure a Tactical Assignment Plan; for global behavior, see Tactical Assignment System.