Files
ue-toolchain/plugins/ue-design-skills/skills/ue-gameplay-cues/SKILL.md
T
ue-toolchain dab3f35079 feat(skills): ship ue-design-skills bundle, licensing and delivery gate
Phase 0 of the handoff plan, as a marketplace rather than a flat skills/
directory. Content moved out of the LyraResearch archive and depersonalised:
addresses stay in the archive, recipes ship.

- plugins/ue-design-skills: 17 skills, 232 failure-mode entries, each with the
  six required fields; catalog.json as the harness-neutral source of truth and
  .claude-plugin/ as one adapter over it.
- _gate: 16 rules, one poisoned fixture per rule, plus surface coverage so a
  declared file cannot silently miss the line rules.
- ADR-0002 (harness-neutral bundle behind a marketplace) and ADR-0003 (split
  licensing: CC BY-ND 4.0 prose, Apache-2.0 code and metadata).
- LICENSE files at both levels, CONTRIBUTING.md, docs/licensing-options.md as
  the material the licence decision grew from.

Verified: gate.py 0 violations; test_gate.py 16/16 rules redden on their
fixtures with a clean baseline and 2 root files reaching the line rules.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 23:48:55 +07:00

15 KiB

name, description
name description
ue-gameplay-cues Design, implement or review the GameplayCue presentation layer in Unreal Engine GAS: what a cue is and when it is the right mechanism, Executed versus OnActive/WhileActive/OnRemove forms, the three addressing paths, Static versus Actor notifies, the dedicated-server rule, cue asset discovery and loading cost, registering cue paths from feature plugins, teardown and leaked looping cues, and the tag-to-asset validation gap. Use when adding VFX or audio feedback to abilities or effects, decoupling presentation from gameplay assets, debugging a cue that never fires or never stops, or auditing cue loading cost.

UE gameplay cues

The invariant:

A cue is a one-way, cosmetic, tag-addressed broadcast. Gameplay must produce exactly the same result if every cue in the project fails to fire.

Worked analysis of a real cue subsystem, including the parts that were built and never reached: patterns. Twenty detection recipes for silent cue failures: failure modes.

Read the failure modes before adopting or reviewing a cue-based design. Each entry names the mechanism, why it stays silent, why the obvious check misses it, and a command you can run against your own tree.

Related skills: ue-gas-architecture, ue-gameplay-tag-governance, ue-modular-gameplay, ue-asset-loading-and-memory, ue-gameplay-messaging.

Method, not architecture — how to check any claim in this bundle before repeating it: ue-evidence-discipline.


1. What a GameplayCue is, and the problem it solves

A GameplayCue is a presentation event addressed by a GameplayTag rather than by an asset reference.

Without it, the chain looks like this:

GE_Fire_Damage  --hard reference-->  NS_Fire_Impact (Niagara)
                                     SW_Fire_Impact (audio)

The gameplay asset now owns the VFX and the audio. Loading the effect loads the particles. A dedicated server, which needs the damage numbers and nothing else, loads them too. An artist changing the impact effect touches a gameplay asset.

With a cue, the chain is cut:

GE_Fire_Damage  --tag-->  "GameplayCue.Fire.Impact"  <--tag--  GCN_Fire_Impact

The gameplay asset references a string-like identifier. A separate registry maps that tag to a notify asset, discovered by scanning content directories. The gameplay asset has no idea the presentation exists.

What this buys:

  • gameplay assets carry no presentation payload, so they are small and server-safe;
  • presentation can be added, replaced or shipped in a separate plugin without touching gameplay;
  • the whole presentation layer can be absent on a dedicated server.

What it costs — and this is the part that gets skipped:

  • the join is no longer checked by anything. A tag with no notify is silence. A notify with no matching tag is a never-loaded asset. Neither is an error.
  • discovery is directory-scan based, so cue assets must be found, indexed and loaded by a manager that has its own lifecycle and its own failure modes;
  • the mechanism is one-way and unreliable by design, which makes it wrong for anything that must be correct.

Treat cues as an application of ue-gameplay-tag-governance with a dedicated loading subsystem attached. Every rule about tag joins applies here first.


2. The three forms

Form Fires when Use for
Executed a one-shot event impacts, hit reactions, one-off flashes
OnActive a duration effect starts, on clients present at that moment start of a looping effect
WhileActive an actor becomes relevant while the effect is already running joining/relevancy catch-up for the same loop
OnRemove the duration effect ends stopping the loop, cleanup

Rules

  1. OnActive and WhileActive must produce the same visual state. A client who was present and a client who arrived late must see the same thing. Putting the "start" logic only in OnActive means late joiners see nothing.
  2. Everything OnActive/WhileActive spawns, OnRemove destroys. Looping cues are the main source of leaked VFX in GAS projects.
  3. Never use Executed for anything with a duration, and never use the duration forms for a one-shot — the recovery paths differ.
  4. Assume OnRemove may run without OnActive having run on that client, and the reverse. Write both defensively.

3. Static versus Actor notifies

Notify type Instanced State Use for
Static no — a CDO handles the call none one-shot effects, sounds, decals
Actor yes — an actor is spawned per instance yes looping effects, anything requiring cleanup

Rules

  1. Default to Static. It allocates nothing and cannot leak.
  2. A Static notify must not store per-instance state. It runs on the class default object; a member written during one invocation is shared by every subsequent one, including other players' invocations.
  3. Use an Actor notify only when there is something to tear down — a looping particle system, an attached component, a timeline.
  4. Actor notifies are actors: they have relevancy, they cost replication consideration if not marked otherwise, and they can outlive their owner if removal is missed.

4. The three ways a cue gets addressed

1. GameplayEffect         GE lists cue tags; ASC fires them with the effect
2. Direct ASC call        ExecuteGameplayCue / AddGameplayCue / RemoveGameplayCue
3. IGameplayCueInterface  the actor handles cue tags itself

Rules

  1. Prefer route 1 for anything tied to an effect's lifetime. The effect and its presentation then start and stop together by construction.
  2. Route 2 is for presentation with no effect behind it — a UI-driven flash, a client-local confirmation. It is manual, so its removal is manual too.
  3. Route 3 is for actor-specific handling that no generic notify can express. It couples the actor to the cue vocabulary; use sparingly.
  4. Do not mix routes for one cue tag. Two owners of the same presentation produces double effects that appear only when both paths trigger.

5. The network rule

Cues never run on a dedicated server. The presentation layer does not exist there.

Consequences that must be designed for, not discovered:

  1. A cue may not carry gameplay side effects. Damage, score, state changes, spawning — none of it may live in a notify, because on a dedicated server that code never executes. This is the single most important rule in this document.
  2. Cue parameters are a lossy channel. They are packed for replication; whatever your conversion helper drops is dropped silently on every client. Verify the round trip for every field you rely on, especially tag containers.
  3. Cue delivery is unreliable. Packet loss, relevancy changes and joining mid-effect all produce missed cues. Design for a missed cue being invisible, not desynchronising.
  4. Cue assets belong in the client asset bundle only. Putting them in the server bundle loads presentation content on a machine that will never use it.

Test that the rule holds

Run the feature on a dedicated server with the presentation content deliberately absent. Gameplay results must be byte-identical.


6. Registering cue paths from feature plugins

A feature plugin that ships cue assets must register its content directory with the cue manager, or its cues are invisible.

The important structural point: cue path registration must happen earlier than feature activation. The manager builds its tag-to-asset index during its object library scan; a path added after that scan is not indexed until the next rescan. This is earlier than any of the standard activation hooks.

The pattern that solves it:

The feature action is a pure data declaration; a lifecycle observer is the executor.

The action object declares only the directory list and validates it in the editor. A separate observer, registered with the feature policy, listens for the earliest lifecycle phase and performs the registration for every action of that type it finds.

Rules

  1. Register on the earliest available phase, not on activation.
  2. Batch the rescan. Add all paths, then rebuild the index once — one rescan per feature, not one per directory.
  3. Refresh the primary-asset registration when the path set changes, on the way in and on the way out. Skipping it on removal leaves the asset manager describing directories that no longer exist.
  4. Implement removal and assert it. Count the paths removed and compare against the paths added; a mismatch means the registry is drifting across activation cycles.
  5. Use the same manager instance on both sides of the lifecycle. Registering through your subclassed manager and unregistering through the engine base class is a silent asymmetry — it compiles, runs, and leaves entries behind.

7. Loading and cost

Cue assets are discovered by scanning directories and are, by default, loaded on demand — which means a hitch the first time each cue fires.

The mitigation is preloading, and it is where most complexity in a cue manager lives: async library loading, a preload set, always-loaded tags, reference tracking, and hooks for GC and map transitions.

Rules

  1. Decide the load policy explicitly and verify it is reachable. A load-mode switch that short-circuits every branch renders the entire preload subsystem dead code — the machinery exists, is maintained, is never executed, and any diagnostic command reports zero because there is genuinely nothing there.
  2. A constant is not a knob. A local const bool inside a namespace whose name ends in Cvars is not a console variable and cannot be changed at runtime. If a policy must be tunable, register it as an actual console variable; otherwise do not imply that it is one.
  3. Preload the small set that must never hitch — the cues on the critical path, hit feedback above all — and let the rest load on demand.
  4. Put cue paths in the client bundle only (see §5).
  5. Measure the cue set size before optimising it. Discovery cost scales with the number of scanned directories; load cost scales with the assets actually referenced.

8. Teardown

Cues attach to an avatar. When the avatar changes — death, possession change, respawn — cues that are still active belong to nothing.

The correct teardown sequence when unbinding an ability system from an avatar:

1. verify the ASC's avatar is actually this actor
2. cancel abilities
3. clear ability input bindings
4. remove all gameplay cues
5. clear the avatar

Rules

  1. Check avatar identity before tearing down. A component that unbinds unconditionally can rip the ASC away from an avatar it no longer owns.
  2. Remove all cues explicitly. Do not rely on the removal of the effects that added them; direct-call cues (route 2) have no effect behind them.
  3. Test the cycle twice. Leaks and duplicates appear on the second respawn, not the first.

9. The validation gap, and what to do about it

Nothing in the engine checks that:

  • a cue tag referenced by an effect has a notify asset;
  • a notify asset's tag is referenced by anything;
  • a cue tag is spelled the way the notify expects.

All three failures present identically: no visual, no log, no error.

Minimum guardrails

  1. Constrain cue tag properties with meta=(Categories="GameplayCue") so authoring uses a dropdown.
  2. Write a content validator that resolves every cue tag in every effect against the manager's index, and reports both directions of orphan.
  3. Log once, in development builds, when a cue fires with no handler. This converts the silent case into a searchable one.
  4. Keep cue tags in one namespace with one owner. A cue tag placed in the wrong namespace because "the notify happens to live there" is a rename waiting to happen — and renames need redirects (see ue-gameplay-tag-governance).

10. Review checklist

  • No gameplay logic inside any notify.
  • Feature works identically with all cue assets absent.
  • OnActive and WhileActive converge on the same visual state.
  • Everything spawned in the duration forms is destroyed in OnRemove.
  • Static notifies hold no per-instance state.
  • Actor notifies exist only where there is something to tear down.
  • One addressing route per cue tag.
  • Cue assets are in the client bundle only.
  • Cue parameter round-trip verified for every field used.
  • Feature-plugin cue paths registered at the earliest lifecycle phase.
  • Path removal implemented, asserted, and using the same manager instance.
  • Load policy is reachable, and any "knob" is a real console variable.
  • Teardown checks avatar identity and removes all cues.
  • A validator reports orphan tags and orphan notifies.

11. Tests

Behaviour

  • one-shot cue fires once per execution, not per client-side prediction retry;
  • looping cue starts on OnActive, is matched by WhileActive for a late joiner, and stops on OnRemove;
  • OnRemove without a preceding OnActive does not crash or leak;
  • cue with no registered notify produces no error and no side effect.

Network

  • dedicated server + remote client: gameplay results identical with and without cue content;
  • client joining mid-effect sees the same state as a client present at the start;
  • relevancy lost and regained during a looping cue converges to the correct state.

Lifecycle

  • death and respawn twice: no duplicated or orphaned cues;
  • feature plugin activate, deactivate, activate: cue path count returns to its original value;
  • possession change mid-cue.

Cost

  • first-fire hitch measured for a non-preloaded cue;
  • preloaded set size and residency reported by a diagnostic command that has been verified to read a live code path.

12. When not to use a cue

Use something else when:

  • the effect must be guaranteed — it is gameplay, not presentation;
  • the receiver needs a return value or acknowledgement — direct call;
  • the event drives UI state that must survive a missed message — replicated state plus a local message (ue-gameplay-messaging);
  • exactly one known actor reacts, in the same module — direct call or delegate.

A cue earns its cost when presentation is genuinely optional, genuinely many-to-many, and genuinely absent on the server. Used for anything else, it is a tag join with no validation and unreliable delivery — which is a description of a bug that has not happened yet.


Provenance

The failure modes and the worked analysis in references/ come from a line-by-line audit of Epic's Lyra Starter Game on Unreal Engine 5.6, read as source rather than run. The cue subsystem there is unusually instructive because much of its preload machinery is present, maintained, and unreachable in the shipped configuration — a shape that reading the class list will never reveal.

Source addresses stay in the research archive that produced this skill; what ships is the detection recipe. Each entry carries a stable identifier (GC-01 and up) that resolves back to the audited location, so any specific claim can be produced on request.

Evidence boundary

The measured material refers to one project on one engine version. It is evidence of failure modes, not a guarantee about other versions. Re-run the detection recipes against your own tree before acting on any specific claim.