reznok/gmcabilitysystem/gmas-task

Claude Code and agent skill by reznok/gmcabilitysystem

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Use when creating or modifying a GMAS ability task (UGMCAbilityTaskBase subclass) - target data, waits, heartbeat, tick placement, replay safety.

Install

npx vskill@latest install reznok/gmcabilitysystem/gmas-task

SKILL.md

Behavior below was verified against GMC 2.3.x and GMAS 1.4. Confirm signatures in this project's own headers (paths are relative to each plugin's root, e.g. Source/GMCCore/Public/Components/GMCReplicationComponent.h); GMC's source is not part of this plugin.

A GMAS task is how an ability waits for something, or carries a value the owning client knows into the move so the server sees the same value in the same move. Tasks live in Public/Ability/Tasks/ (paths below are relative to Source/GMCAbilitySystem/). How an ability is built and where its own hooks run: gmas:gmas-ability; the rules on bound state and replay that every task inherits: gmas:gmas-rules, gmas:gmc-prediction.

What a task is

UGMCAbilityTaskBase (Public/Ability/Tasks/GMCAbilityTaskBase.h) is a UGameplayTask whose owner (IGameplayTaskOwnerInterface) is the UGMCAbility and whose tasks component is the ability's UGMC_AbilitySystemComponent (the ASC derives from UGameplayTasksComponent). Create it with the class's static factory (NewAbilityTask<T>(OwningAbility) inside), bind the delegate pins, then call ReadyForActivation(); Blueprint async nodes do the last step for you. OnGameplayTaskInitialized on the ability fills Ability and AbilitySystemComponent before Activate runs.

MemberWhat it is
TaskID (int)per-ability counter from UGMCAbility::GetNextTaskID() (first task = 0), assigned in Activate → RegisterTask, stored in the ability's RunningTasks. The only link between the client twin and the server twin: both sides must create the same tasks in the same order
Ability, AbilitySystemComponent (TWeakObjectPtr)the owning ability and its ASC; AbilitySystemComponent->ActionTimer (double) is the move clock, ->GMCMovementComponent the pawn's movement component
Activate()registers the task and arms the heartbeat. Call Super first
Tick(float)called from the ability's Tick (TickTasks), prediction tick, before TickEvent; runs on live, combined and replayed moves
AncillaryTick(float)called from the ability's AncillaryTick (AncillaryTickTasks), before AncillaryTickEvent; never replayed. The base implementation is the heartbeat: call Super
ClientProgressTask()the owning side packs an FGMCAbilityTaskData (TaskType = Progress, AbilityID, TaskID) into an FInstancedStruct and calls QueueTaskData on the ASC
ProgressTask(FInstancedStruct&)both sides receive the payload here, inside the move that carried it. Empty in the base
Heartbeat()server side: stamps the last heartbeat time (real time, 1.4+)
EndTaskGMAS()what the ability calls on every registered task when it ends (FinishEndAbility); EndTask() by default
OnDestroy(bool)every end path funnels here once; the base removes the task from RunningTasks (1.4+). Do your cleanup, then Super
bTaskCompleted / IsTaskCompleted()the completion latch; the dispatcher skips Progress payloads for a task that set it (1.4+)
DrivesPawnLocally() (1.4.1+)true on the machine that produces the pawn's moves: any client (in practice the autonomous proxy), standalone, and a server for a pawn it controls locally (listen host, AI). False on a server for a remotely controlled pawn, which only waits for that client's payloads. Replaces IsClientOrRemoteListenServerPawn() (same answer, inverted name; deprecated, removed in 1.5)

Client and server halves. Each side runs the ability's code and so creates its own instance of every task. Nothing about the task object is replicated; what crosses is the payload. QueueTaskData(const FInstancedStruct&) appends to QueuedTaskData; the ASC's PreLocalMoveExecution moves the oldest one (FIFO, 1.4+; LIFO before) into the bound TaskData slot (ClientAuth_Input, CombineIfUnchanged), so it rides the next move's input and reaches the server with it. On both machines SendTaskDataToActiveAbility hands the slot to UGMCAbility::HandleTaskData → ProgressTask on the task with that TaskID: in the prediction tick for abilities with bActivateOnMovementTick = true (replayed with the move), in the ancillary tick for the others; the slot is cleared at the end of the ancillary tick. One payload per move: N queued payloads take N moves. A payload for a TaskID this side never issued is dropped with a [TaskDiag] ... never issued on this side warning (1.4+): the sign that the two sides created different tasks. 1.4.1+: a payload whose struct is not an FGMCAbilityTaskData is dropped with one Error at dispatch, before any task sees it.

Heartbeat watchdog. From AncillaryTick, every live task on a client sends RPCTaskHeartbeat(AbilityID, TaskID) (Server, Reliable) once per HeartbeatInterval = 1 s of real time (FPlatformTime::Seconds(), 1.4+; 0.2 s of ActionTimer before). On a dedicated or listen server, for a remotely controlled pawn, the twin checks the age of its last stamp: more than HeartbeatMaxInterval = 3 s and it logs [TaskHeartbeat] Timeout: cancelling ability ... (Error, LogGMCAbilitySystem, 1.4.1+), broadcasts the ASC's OnTaskTimeout(AbilityTag), calls Ability->CancelAbility() (no end event, no chain window; CancelAbilityEvent runs) and ends itself; RPCClientEndAbility then cancels the client instance. On 1.4.0 it logged Server Task Heartbeat Timeout on LogGMCReplication plus the dump on LogTemp and called EndAbility(), the natural end. Activation seeds the stamp one interval ahead, so the first kill comes no sooner than about 6 s after the task started. Finished tasks neither send nor check (1.4+); locally controlled server pawns do neither. A task that exists on the server only is killed this way after 6 s; one that exists on the client only beats a TaskID the server never issued ([TaskDiag] Heartbeat for TaskID=... never issued, 1.4+). The watchdog covers only abilities with a live task: an ability whose tasks all ended but that never calls EndAbility runs unwatched, and the server logs [TaskDiag] Ability task-less for N s and still active at Verbose after 6 s (1.4+).

Ending. EndTask() (engine) moves the task to Finished and calls OnDestroy(false); TickTasks skips finished tasks (1.4+; before, every entry stayed registered and ticked). When the ability ends it calls EndTaskGMAS() on everything still in RunningTasks, after logging [AbilityCut] Ability ending with N unfinished task(s) (Warning, 1.4+) if any task had not completed: an ability that ends normally has no running tasks left.

Built-in tasks as models

Every factory takes UGMCAbility* OwningAbility first (omitted below); Blueprint shows the display names ("Wait For Input Key Press"), C++ uses the factory names in the second column.

Class (UGMCAbilityTask_*)FactoryWaits for or sendsTicks inCompletes
WaitDelayWaitDelay(Time)Time seconds of move timepredictionTimeStarted + Time <= ActionTimer in Tick; Completed. No payload: both sides compute the same answer
WaitForInputKeyPress, WaitForInputKeyReleaseWaitForKeyPress(bCheckForPressDuringActivation = true, MaxDuration = 0), WaitForKeyRelease(bCheckForReleaseDuringActivation = true, MaxDuration = 0)the Enhanced Input Started / Completed event of the ability's AbilityInputAction on the owning machineancillary (OnTick(Duration) each tick)the event calls ClientProgressTask on the driving machine (DrivesPawnLocally(), 1.4.1+; a server for a remote pawn binds and queues nothing); both sides fire Completed(Duration) or, past MaxDuration, TimedOut(Duration) when the payload's move is processed. With bCheck...DuringActivation, Press completes when the key is already pressed (1.4.1+; 1.4.0's poll was inverted) and Release when it is already released; neither polls during a replay (1.4.1+). Without an input action or an Enhanced Input component they complete through the payload at once; Press also when bAllowMultipleInstances is set (Warning)
WaitForInputKeyPressParameterizedWaitForKeyPress(InputAction, bCheckForPressDuringActivation = true, MaxDuration = 0)same, for any UInputActionancillarysame pins; binds, polls and gates on its own InputAction (1.4.1+; 1.4.0 read the ability's AbilityInputAction)
SetTargetData<Byte, Int, Float, Vector3, Transform, Hit, GameplayTag, Object, InstancedStruct>SetTargetData<Type>(Value), e.g. SetTargetDataVector3(Vector3), SetTargetDataHit(InHit)sends one value from the owning machine–Activate calls ClientProgressTask where DrivesPawnLocally(); ProgressTask fires Completed(Value) on both sides in the move carrying it, then EndTask. Every variant compares the payload's struct with its own exactly and drops a mismatch with one Error (1.4.1+; only Vector3 checked on 1.4.0)
WaitForGameplayTagChangeWaitForGameplayTagChange(WatchedTags, ChangeType = Changed)a matching tag added (Set), removed (Unset) or either–AddFilteredTagChangeDelegate callback from CheckActiveTagsChanged, which runs in the ancillary tick and in GenSimulationTick (proxies: no task lives there), each machine on its own; Completed(MatchedTags) once, after EndTask; the handle is removed in OnDestroy (1.4.1+; 1.4.0 leaked the binding and could fire twice). A prediction the server corrects leaves the two sides disagreeing
WaitForGMCMontageChangeWaitForGMCMontageChange()GetActiveMontage(MontageTracker) on the organic movement component to differ from the one at activationpredictionCompleted(CurrentMontage); ends at once with an Error log without an organic component or a running montage
RotateYawTowardsDirectionWaitForRotateYawTowardsDirection(TargetDirection, RotationSpeed)the pawn's yaw to reach the directionpredictiondrives RotateYawTowardsDirection(Direction, Rate, DeltaTime) (Source/GMCCore/Public/Components/GMCMovementUtilityComponent.h) each tick; Completed(Duration) within 0.01°. Duration is move time (ActionTimer, 1.4.1+; world time on 1.4.0, cosmetic only)

Patterns the built-ins show:

  • Local wait (WaitDelay, montage, yaw): read bound state and ActionTimer in Tick, complete on both sides independently. No payload, no network cost, replay-exact.
  • Owner-known value (SetTargetData*, input tasks): only the owning machine has the fact; ClientProgressTask sends it, ProgressTask completes both twins in the same move. The input tasks are the model for a wait that ends on such a fact; SetTargetData* for a value computed at activation.
  • Deterministic timeout (input tasks, 1.4+): MaxDuration is compared against ActionTimer - StartTime; the owner queues the payload when it crosses the limit, and OnTaskCompleted re-derives TimedOut from the payload move's ActionTimer so both sides pick the same pin even when the crossing and the payload land in one server frame.
  • Replay-safe input poll: the input tasks read the live key state only where DrivesPawnLocally() and not while IsReplayingForGMASLogic(); the legitimate completion is already in the move history as a payload. 1.4.1+ for all three; on 1.4.0 only WaitForInputKeyRelease had the replay guard.

Writing a wait task

UMyTask_WaitUntilGrounded completes when the pawn's movement component reports ground contact, or times out. It completes locally: both sides see the same bound state in the same move.

cpp
// MyTask_WaitUntilGrounded.h
#pragma once
#include "Ability/Tasks/GMCAbilityTaskBase.h"
#include "MyTask_WaitUntilGrounded.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMyWaitUntilGroundedPin, float, Duration);

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/reznok/gmcabilitysystem/gmas-task/badge)](https://verified-skill.com/skills/reznok/gmcabilitysystem/gmas-task)

Something wrong with this skill or its page? Tell us.