claude-code-arcane 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +104 -55
  3. package/agents/career/career-strategist.md +88 -0
  4. package/agents/career/cv-reviewer.md +81 -0
  5. package/agents/career/hiring-manager.md +68 -0
  6. package/agents/career/mock-interviewer.md +76 -0
  7. package/agents/freelance/client-screener.md +84 -0
  8. package/agents/freelance/discovery-call.md +65 -0
  9. package/agents/freelance/pipeline-strategist.md +99 -0
  10. package/agents/freelance/proposal-reviewer.md +82 -0
  11. package/agents/game/ue-blueprint-specialist.md +125 -0
  12. package/agents/game/ue-gas-specialist.md +133 -0
  13. package/agents/game/ue-umg-specialist.md +131 -0
  14. package/dist/cli.js +57 -13
  15. package/docs/RELEASE-SETUP.md +1 -1
  16. package/docs/SKILLS-CATALOG.md +67 -4
  17. package/docs/USER-GUIDE.md +77 -72
  18. package/docs/agent-hierarchy.md +21 -1
  19. package/docs/presentations/build_arcane_deck.py +2 -2
  20. package/docs/presentations/build_arcane_deck_12.py +2 -2
  21. package/hooks/detect-unreal.sh +90 -0
  22. package/package.json +2 -2
  23. package/profiles/freelance.yaml +43 -0
  24. package/profiles/job-hunt.yaml +5 -2
  25. package/profiles/unreal-dev.yaml +97 -0
  26. package/rules/drafter-reviewer.md +16 -3
  27. package/rules/freelance-guardrails.md +53 -0
  28. package/rules/gamedev/unreal-code.md +125 -0
  29. package/skills/career-registry/SKILL.md +95 -0
  30. package/skills/career-registry/scripts/career_registry.py +488 -0
  31. package/skills/client-screen/SKILL.md +83 -0
  32. package/skills/client-screen/references/red-flags.md +95 -0
  33. package/skills/cover-letter/SKILL.md +4 -3
  34. package/skills/cv-tailor/SKILL.md +11 -2
  35. package/skills/freelance-deliver/SKILL.md +90 -0
  36. package/skills/freelance-hunt/SKILL.md +126 -0
  37. package/skills/freelance-hunt/references/templates/Connects.md +39 -0
  38. package/skills/freelance-hunt/references/templates/Contrato.md +60 -0
  39. package/skills/freelance-hunt/references/templates/Propuesta.md +66 -0
  40. package/skills/freelance-kickoff/SKILL.md +94 -0
  41. package/skills/freelance-pipeline/SKILL.md +109 -0
  42. package/skills/freelance-pipeline/scripts/import_history.py +425 -0
  43. package/skills/freelance-profile/SKILL.md +93 -0
  44. package/skills/freelance-proposal/SKILL.md +112 -0
  45. package/skills/freelance-scan/SKILL.md +196 -0
  46. package/skills/freelance-scan/references/dedup.md +35 -0
  47. package/skills/freelance-scan/references/platforms.md +167 -0
  48. package/skills/freelance-scan/scripts/freelance_search.py +1077 -0
  49. package/skills/install-mcp/SKILL.md +135 -46
  50. package/skills/install-mcp/references/unreal-operations.md +55 -0
  51. package/skills/interview-prep/SKILL.md +11 -3
  52. package/skills/job-aplicar/SKILL.md +5 -1
  53. package/skills/job-hunt/SKILL.md +17 -0
  54. package/skills/job-outcome/SKILL.md +15 -1
  55. package/skills/job-upskill/SKILL.md +10 -2
  56. package/skills/ue-actor-component-architecture/SKILL.md +509 -0
  57. package/skills/ue-actor-component-architecture/references/actor-lifecycle.md +301 -0
  58. package/skills/ue-actor-component-architecture/references/component-types.md +592 -0
  59. package/skills/ue-ai-navigation/SKILL.md +526 -0
  60. package/skills/ue-ai-navigation/references/behavior-tree-patterns.md +392 -0
  61. package/skills/ue-ai-navigation/references/behavior-tree-workflow.md +107 -0
  62. package/skills/ue-ai-navigation/references/custom-bttask.md +104 -0
  63. package/skills/ue-ai-navigation/references/eqs-reference.md +423 -0
  64. package/skills/ue-animation-system/SKILL.md +521 -0
  65. package/skills/ue-animation-system/references/anim-notify-reference.md +486 -0
  66. package/skills/ue-animation-system/references/locomotion-setup.md +550 -0
  67. package/skills/ue-async-threading/SKILL.md +433 -0
  68. package/skills/ue-async-threading/references/thread-safety-guide.md +213 -0
  69. package/skills/ue-async-threading/references/threading-patterns.md +345 -0
  70. package/skills/ue-audio-system/SKILL.md +527 -0
  71. package/skills/ue-audio-system/references/audio-setup-patterns.md +700 -0
  72. package/skills/ue-blueprints/SKILL.md +134 -0
  73. package/skills/ue-blueprints/references/communication.md +82 -0
  74. package/skills/ue-character-movement/SKILL.md +475 -0
  75. package/skills/ue-character-movement/references/cmc-extension-patterns.md +335 -0
  76. package/skills/ue-character-movement/references/movement-pipeline.md +189 -0
  77. package/skills/ue-cpp-foundations/SKILL.md +533 -0
  78. package/skills/ue-cpp-foundations/references/components-and-gc.md +107 -0
  79. package/skills/ue-cpp-foundations/references/container-patterns.md +544 -0
  80. package/skills/ue-cpp-foundations/references/delegate-patterns.md +497 -0
  81. package/skills/ue-cpp-foundations/references/gameplay-classes-cpp.md +136 -0
  82. package/skills/ue-cpp-foundations/references/property-specifiers.md +306 -0
  83. package/skills/ue-data-assets-tables/SKILL.md +527 -0
  84. package/skills/ue-data-assets-tables/references/asset-loading-patterns.md +306 -0
  85. package/skills/ue-data-assets-tables/references/data-driven-design.md +378 -0
  86. package/skills/ue-editor-tools/SKILL.md +523 -0
  87. package/skills/ue-editor-tools/references/detail-customization-patterns.md +395 -0
  88. package/skills/ue-editor-tools/references/editor-module-setup.md +480 -0
  89. package/skills/ue-game-features/SKILL.md +477 -0
  90. package/skills/ue-game-features/references/experience-system.md +222 -0
  91. package/skills/ue-game-features/references/game-feature-patterns.md +308 -0
  92. package/skills/ue-gameplay-abilities/SKILL.md +518 -0
  93. package/skills/ue-gameplay-abilities/references/ability-task-reference.md +391 -0
  94. package/skills/ue-gameplay-abilities/references/gameplay-effect-reference.md +397 -0
  95. package/skills/ue-gameplay-abilities/references/gas-setup-patterns.md +369 -0
  96. package/skills/ue-gameplay-framework/SKILL.md +466 -0
  97. package/skills/ue-gameplay-framework/references/framework-class-map.md +243 -0
  98. package/skills/ue-input-system/SKILL.md +394 -0
  99. package/skills/ue-input-system/references/cpp-setup.md +131 -0
  100. package/skills/ue-input-system/references/enhanced-input-workflow.md +117 -0
  101. package/skills/ue-input-system/references/input-action-reference.md +731 -0
  102. package/skills/ue-mass-entity/SKILL.md +476 -0
  103. package/skills/ue-mass-entity/references/mass-entity-patterns.md +319 -0
  104. package/skills/ue-mass-entity/references/mass-fragment-reference.md +119 -0
  105. package/skills/ue-materials-rendering/SKILL.md +526 -0
  106. package/skills/ue-materials-rendering/references/material-parameter-reference.md +216 -0
  107. package/skills/ue-materials-rendering/references/post-process-settings.md +383 -0
  108. package/skills/ue-module-build-system/SKILL.md +487 -0
  109. package/skills/ue-module-build-system/references/build-cs-reference.md +418 -0
  110. package/skills/ue-module-build-system/references/common-build-errors.md +381 -0
  111. package/skills/ue-naming-conventions/SKILL.md +142 -0
  112. package/skills/ue-naming-conventions/references/asset-name-modifiers.md +233 -0
  113. package/skills/ue-naming-conventions/references/content-structure.md +83 -0
  114. package/skills/ue-networking-replication/SKILL.md +452 -0
  115. package/skills/ue-networking-replication/references/replication-patterns.md +526 -0
  116. package/skills/ue-networking-replication/references/rpc-decision-guide.md +393 -0
  117. package/skills/ue-niagara-effects/SKILL.md +531 -0
  118. package/skills/ue-niagara-effects/references/niagara-data-interfaces.md +491 -0
  119. package/skills/ue-niagara-effects/references/niagara-parameter-types.md +153 -0
  120. package/skills/ue-niagara-effects/references/niagara-workflow.md +115 -0
  121. package/skills/ue-packaging/SKILL.md +116 -0
  122. package/skills/ue-physics-collision/SKILL.md +423 -0
  123. package/skills/ue-physics-collision/references/collision-channel-setup.md +218 -0
  124. package/skills/ue-physics-collision/references/trace-patterns.md +499 -0
  125. package/skills/ue-procedural-generation/SKILL.md +505 -0
  126. package/skills/ue-procedural-generation/references/pcg-node-reference.md +381 -0
  127. package/skills/ue-procedural-generation/references/procedural-mesh-patterns.md +940 -0
  128. package/skills/ue-project-context/SKILL.md +305 -0
  129. package/skills/ue-sequencer-cinematics/SKILL.md +506 -0
  130. package/skills/ue-sequencer-cinematics/references/sequencer-patterns.md +629 -0
  131. package/skills/ue-serialization-savegames/SKILL.md +524 -0
  132. package/skills/ue-serialization-savegames/references/save-system-architecture.md +705 -0
  133. package/skills/ue-state-trees/SKILL.md +489 -0
  134. package/skills/ue-state-trees/references/state-tree-mass-integration.md +184 -0
  135. package/skills/ue-state-trees/references/state-tree-patterns.md +321 -0
  136. package/skills/ue-testing-debugging/SKILL.md +520 -0
  137. package/skills/ue-testing-debugging/references/automation-test-patterns.md +387 -0
  138. package/skills/ue-testing-debugging/references/profiling-commands.md +357 -0
  139. package/skills/ue-ui-umg-slate/SKILL.md +431 -0
  140. package/skills/ue-ui-umg-slate/references/common-ui-setup.md +484 -0
  141. package/skills/ue-ui-umg-slate/references/widget-types.md +487 -0
  142. package/skills/ue-world-level-streaming/SKILL.md +518 -0
  143. package/skills/ue-world-level-streaming/references/streaming-patterns.md +414 -0
  144. package/templates/gamedev/unreal-project-context.md +107 -0
@@ -0,0 +1,392 @@
1
+ # Behavior Tree Patterns
2
+
3
+ Common, reusable Behavior Tree structures for UE AI. Each pattern shows the tree topology, required Blackboard keys, and key C++ touchpoints from the AIModule source.
4
+
5
+ ---
6
+
7
+ ## Blackboard Keys (Common Set)
8
+
9
+ Define these in your `UBlackboardData` asset as a baseline. Extend per-character:
10
+
11
+ | Key Name | Type | Description |
12
+ |----------|------|-------------|
13
+ | `TargetActor` | Object (AActor) | Current enemy/target |
14
+ | `LastKnownLocation` | Vector | Last confirmed target location |
15
+ | `PatrolLocation` | Vector | Current patrol waypoint |
16
+ | `HomeLocation` | Vector | Spawn/home position |
17
+ | `InvestigateLocation` | Vector | Sound/sight disturbance location |
18
+ | `CoverLocation` | Vector | EQS-found cover position |
19
+ | `IsAlerted` | Bool | Has AI been alerted |
20
+ | `IsInCombat` | Bool | Actively fighting |
21
+ | `AttackCooldown` | Bool | Tag cooldown sentinel (used with BTDecorator_TagCooldown) |
22
+ | `StateFloat` | Float | Generic float (health %, threat score, etc.) |
23
+
24
+ ---
25
+
26
+ ## Pattern 1: Patrol / Chase / Attack
27
+
28
+ Classic three-phase NPC AI. The root Selector tries Combat first (highest priority), then Alerted search, then idle patrol.
29
+
30
+ ```
31
+ Root [Selector]
32
+ ├── [Sequence] "Combat" — BTDecorator_Blackboard(TargetActor, IsSet, AbortBoth)
33
+ │ ├── [Service] UpdateTarget (0.3s interval — re-scores nearest hostile)
34
+ │ ├── BTTask_MoveTo (TargetActor key, AcceptRadius=150)
35
+ │ └── BTTask_RunEQSQuery (AttackPositionQuery → CoverLocation)
36
+ │ └── [Sequence] "Attack Sequence"
37
+ │ ├── BTTask_RotateToFaceBBEntry (TargetActor)
38
+ │ └── MyBTTask_FireWeapon
39
+ │ └── BTDecorator_Cooldown (2.0s, AbortSelf)
40
+ │
41
+ ├── [Sequence] "Investigate" — BTDecorator_Blackboard(InvestigateLocation, IsSet, AbortBoth)
42
+ │ ├── BTTask_MoveTo (InvestigateLocation, AcceptRadius=100)
43
+ │ ├── BTTask_Wait (2.0s)
44
+ │ └── MyBTTask_ClearInvestigateLocation
45
+ │
46
+ └── [Sequence] "Patrol"
47
+ ├── [Service] PickNextPatrolPoint (5.0s interval, calls EQS or patrol spline)
48
+ ├── BTTask_MoveTo (PatrolLocation, AcceptRadius=50)
49
+ └── BTTask_Wait (WaitBlackboardTime key="PatrolWait")
50
+ ```
51
+
52
+ ### Perception Wiring
53
+
54
+ `OnTargetPerceptionUpdated` on the AIController:
55
+
56
+ ```cpp
57
+ void AEnemyAIController::OnTargetPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus)
58
+ {
59
+ UBlackboardComponent* BB = GetBlackboardComponent();
60
+
61
+ if (Stimulus.WasSuccessfullySensed())
62
+ {
63
+ if (Stimulus.Type == UAISense::GetSenseID<UAISense_Sight>())
64
+ {
65
+ BB->SetValueAsObject(TEXT("TargetActor"), Actor);
66
+ BB->SetValueAsVector(TEXT("LastKnownLocation"), Stimulus.StimulusLocation);
67
+ }
68
+ else if (Stimulus.Type == UAISense::GetSenseID<UAISense_Hearing>())
69
+ {
70
+ // Only set investigate location if we don't already have a visual target
71
+ if (!BB->GetValueAsObject(TEXT("TargetActor")))
72
+ {
73
+ BB->SetValueAsVector(TEXT("InvestigateLocation"), Stimulus.StimulusLocation);
74
+ }
75
+ }
76
+ }
77
+ else
78
+ {
79
+ // Lost sight — remember last known location
80
+ BB->SetValueAsVector(TEXT("LastKnownLocation"), Stimulus.StimulusLocation);
81
+ // Do NOT clear TargetActor — let the search phase handle it
82
+ }
83
+ }
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Pattern 2: Combat with Cover and Repositioning
89
+
90
+ AI actively seeks cover, attacks from cover, repositions when suppressed.
91
+
92
+ ```
93
+ Root [Selector]
94
+ ├── [Sequence] "Take Cover" — BTDecorator_Blackboard(IsInCombat, IsSet, AbortBoth)
95
+ │ ├── [Service] RunEQSCoverService (1.0s — updates CoverLocation)
96
+ │ ├── BTTask_MoveTo (CoverLocation, AcceptRadius=80)
97
+ │ └── [Selector] "Attack or Wait"
98
+ │ ├── [Sequence] "Peek and Shoot"
99
+ │ │ ├── BTDecorator_Blackboard(TargetActor, IsSet)
100
+ │ │ ├── BTTask_RotateToFaceBBEntry (TargetActor)
101
+ │ │ └── MyBTTask_FireWeapon
102
+ │ │ └── BTDecorator_Cooldown (1.5s)
103
+ │ └── BTTask_Wait (1.0s) ← suppress-wait when no LoS
104
+ │
105
+ └── [Sequence] "Find Enemy"
106
+ ├── BTTask_RunEQSQuery (LastKnownLocationSearch)
107
+ ├── BTTask_MoveTo (PatrolLocation, AcceptRadius=100)
108
+ └── BTTask_Wait (3.0s)
109
+ ```
110
+
111
+ ### Cover Query Service (C++)
112
+
113
+ ```cpp
114
+ // BTService_FindCover.cpp
115
+ void UBTService_FindCover::TickNode(
116
+ UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
117
+ {
118
+ AAIController* Controller = OwnerComp.GetAIOwner();
119
+ UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent();
120
+
121
+ if (!CoverQuery) { return; }
122
+
123
+ FEnvQueryRequest Request(CoverQuery, Controller);
124
+ Request.Execute(EEnvQueryRunMode::RandomBest25Pct,
125
+ FQueryFinishedSignature::CreateWeakLambda(Controller,
126
+ [BB](TSharedPtr<FEnvQueryResult> Result)
127
+ {
128
+ if (Result.IsValid() && Result->IsSuccessful())
129
+ {
130
+ BB->SetValueAsVector(TEXT("CoverLocation"), Result->GetItemAsLocation(0));
131
+ }
132
+ }));
133
+ }
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Pattern 3: Flee (Health Threshold Trigger)
139
+
140
+ AI retreats when health drops below a threshold, uses EQS to find a safe escape point.
141
+
142
+ ```
143
+ Root [Selector]
144
+ ├── [Sequence] "Flee" — MyBTDecorator_HealthBelow(30%, AbortBoth)
145
+ │ ├── BTTask_RunEQSQuery (FleePointQuery → PatrolLocation)
146
+ │ │ ← FleePointQuery: DonutGenerator(self, minR=500, maxR=2000)
147
+ │ │ + Trace test (not visible from TargetActor location)
148
+ │ │ + Pathfinding test (reachable)
149
+ │ ├── BTTask_MoveTo (PatrolLocation, AcceptRadius=100, AllowPartialPath=true)
150
+ │ └── BTTask_Wait (5.0s) ← catch breath before re-evaluating
151
+ │
152
+ └── [Sequence] "Normal Combat" (same as Pattern 1 combat branch)
153
+ ```
154
+
155
+ ### Health Threshold Decorator (C++)
156
+
157
+ ```cpp
158
+ // BTDecorator_HealthBelow.h
159
+ UCLASS()
160
+ class UBTDecorator_HealthBelow : public UBTDecorator
161
+ {
162
+ GENERATED_BODY()
163
+ public:
164
+ UBTDecorator_HealthBelow() { bAllowAbortLowerPri = true; bAllowAbortChildNodes = true; }
165
+
166
+ UPROPERTY(EditAnywhere, Category = "Condition", meta = (ClampMin = 0, ClampMax = 1))
167
+ float HealthThresholdNormalized = 0.3f;
168
+
169
+ protected:
170
+ virtual bool CalculateRawConditionValue(
171
+ UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override
172
+ {
173
+ APawn* Pawn = OwnerComp.GetAIOwner()->GetPawn();
174
+ IMyHealthInterface* HealthInterface = Cast<IMyHealthInterface>(Pawn);
175
+ if (!HealthInterface) { return false; }
176
+ return HealthInterface->GetNormalizedHealth() < HealthThresholdNormalized;
177
+ }
178
+ };
179
+ ```
180
+
181
+ ---
182
+
183
+ ## Pattern 4: Investigate Sound / Disturbance
184
+
185
+ AI pauses current activity, moves to sound source, looks around, then resumes.
186
+
187
+ ```
188
+ Root [Selector]
189
+ ├── [Sequence] "Investigate" — BTDecorator_Blackboard(InvestigateLocation, IsSet, AbortBoth)
190
+ │ ├── BTTask_MoveTo (InvestigateLocation, AcceptRadius=100)
191
+ │ ├── MyBTTask_LookAround (rotates pawn 360° over 3s)
192
+ │ │ └── BTDecorator_TimeLimit (5.0s) ← abort if takes too long
193
+ │ └── MyBTTask_ClearBBKey (InvestigateLocation)
194
+ │ ← clears BB so decorator deactivates and lower priority resumes
195
+ │
196
+ ├── [Sequence] "Combat" (as before)
197
+ │
198
+ └── [Sequence] "Patrol" (as before)
199
+ ```
200
+
201
+ ### LookAround Task (C++)
202
+
203
+ ```cpp
204
+ // BTTask_LookAround.cpp
205
+ EBTNodeResult::Type UBTTask_LookAround::ExecuteTask(
206
+ UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
207
+ {
208
+ auto* Mem = CastInstanceNodeMemory<FBTLookAroundMemory>(NodeMemory);
209
+ Mem->ElapsedTime = 0.f;
210
+ Mem->StartYaw = OwnerComp.GetAIOwner()->GetPawn()->GetActorRotation().Yaw;
211
+ return EBTNodeResult::InProgress;
212
+ }
213
+
214
+ void UBTTask_LookAround::TickTask(
215
+ UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
216
+ {
217
+ auto* Mem = CastInstanceNodeMemory<FBTLookAroundMemory>(NodeMemory);
218
+ Mem->ElapsedTime += DeltaSeconds;
219
+
220
+ float NewYaw = Mem->StartYaw + (Mem->ElapsedTime / LookDuration) * 360.f;
221
+ OwnerComp.GetAIOwner()->SetFocalPoint(
222
+ OwnerComp.GetAIOwner()->GetPawn()->GetActorLocation() +
223
+ FRotator(0.f, NewYaw, 0.f).Vector() * 500.f);
224
+
225
+ if (Mem->ElapsedTime >= LookDuration)
226
+ {
227
+ OwnerComp.GetAIOwner()->ClearFocus(EAIFocusPriority::Gameplay);
228
+ FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
229
+ }
230
+ }
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Pattern 5: Patrol Along Spline
236
+
237
+ Looping patrol along a spline, with stop duration at each point.
238
+
239
+ ```
240
+ Root [Sequence]
241
+ └── [Sequence + BTDecorator_Loop(NumLoops=0)] "Patrol Loop"
242
+ ├── MyBTTask_GetNextSplinePoint (writes PatrolLocation + PatrolWaitTime)
243
+ ├── BTTask_MoveTo (PatrolLocation, AcceptRadius=50)
244
+ └── BTTask_WaitBlackboardTime (BlackboardKey=PatrolWaitTime)
245
+ ```
246
+
247
+ ### GetNextSplinePoint Task (C++)
248
+
249
+ ```cpp
250
+ EBTNodeResult::Type UBTTask_GetNextSplinePoint::ExecuteTask(
251
+ UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
252
+ {
253
+ UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent();
254
+ AAIController* Controller = OwnerComp.GetAIOwner();
255
+
256
+ if (!IsValid(PatrolSplineActor)) { return EBTNodeResult::Failed; }
257
+
258
+ USplineComponent* Spline = PatrolSplineActor->FindComponentByClass<USplineComponent>();
259
+ if (!Spline) { return EBTNodeResult::Failed; }
260
+
261
+ int32 NumPoints = Spline->GetNumberOfSplinePoints();
262
+ int32 CurrentIdx = BB->GetValueAsInt(TEXT("PatrolIndex"));
263
+ int32 NextIdx = (CurrentIdx + 1) % NumPoints;
264
+
265
+ BB->SetValueAsInt(TEXT("PatrolIndex"), NextIdx);
266
+ BB->SetValueAsVector(TEXT("PatrolLocation"),
267
+ Spline->GetLocationAtSplinePoint(NextIdx, ESplineCoordinateSpace::World));
268
+ BB->SetValueAsFloat(TEXT("PatrolWaitTime"), WaitTimeAtPoint);
269
+
270
+ return EBTNodeResult::Succeeded;
271
+ }
272
+ ```
273
+
274
+ ---
275
+
276
+ ## Pattern 6: Squad AI with Shared Blackboard
277
+
278
+ Multiple AI agents share awareness via instance-synced blackboard keys.
279
+
280
+ **Setup**: Mark `TargetActor` and `AlertLevel` as "Instance Synced" in `UBlackboardData`.
281
+
282
+ When any squad member's AI controller updates these keys, `UAISystem` propagates the change to all blackboards sharing the same asset (see `UBlackboardComponent::SetValue` template for the sync loop).
283
+
284
+ ```cpp
285
+ // Squad leader sets target → all squad members receive it
286
+ void ASquadLeaderAIController::OnTargetPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus)
287
+ {
288
+ if (Stimulus.WasSuccessfullySensed())
289
+ {
290
+ // Setting a synced key broadcasts to all squad members sharing this BB asset
291
+ GetBlackboardComponent()->SetValueAsObject(TEXT("TargetActor"), Actor);
292
+ GetBlackboardComponent()->SetValueAsFloat(TEXT("AlertLevel"), 1.0f);
293
+ }
294
+ }
295
+ ```
296
+
297
+ The BT on each squad member reacts independently to the shared `TargetActor` key via `BTDecorator_Blackboard`.
298
+
299
+ ---
300
+
301
+ ## Pattern 7: Message-Driven Task (WaitForMessage)
302
+
303
+ Used when a task needs to wait for an external event (animation notify, ability end, timer) rather than polling.
304
+
305
+ ```cpp
306
+ EBTNodeResult::Type UBTTask_PlayMontage::ExecuteTask(
307
+ UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
308
+ {
309
+ ACharacter* Character = Cast<ACharacter>(OwnerComp.GetAIOwner()->GetPawn());
310
+ if (!Character || !MontageToPlay) { return EBTNodeResult::Failed; }
311
+
312
+ // Register to receive a specific message name
313
+ WaitForMessage(OwnerComp, TEXT("MontageCompleted"));
314
+ WaitForMessage(OwnerComp, TEXT("MontageFailed"));
315
+
316
+ // Start the montage
317
+ Character->GetMesh()->GetAnimInstance()->Montage_Play(MontageToPlay);
318
+
319
+ return EBTNodeResult::InProgress;
320
+ }
321
+
322
+ void UBTTask_PlayMontage::OnMessage(
323
+ UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory,
324
+ FName Message, int32 RequestID, bool bSuccess)
325
+ {
326
+ StopWaitingForMessages(OwnerComp);
327
+ FinishLatentTask(OwnerComp,
328
+ (Message == TEXT("MontageCompleted")) ? EBTNodeResult::Succeeded : EBTNodeResult::Failed);
329
+ }
330
+
331
+ // Sender side (e.g., AnimNotify or ability):
332
+ // UAIBlueprintHelperLibrary::SendAIMessage(Pawn, TEXT("MontageCompleted"), nullptr, true);
333
+ // Or from C++:
334
+ // UBehaviorTreeComponent* BTComp = Cast<UBehaviorTreeComponent>(Controller->GetBrainComponent());
335
+ // if (BTComp) { BTComp->HandleMessage(FAIMessage(TEXT("MontageCompleted"), nullptr, true)); }
336
+ ```
337
+
338
+ ---
339
+
340
+ ## Node Memory Pattern
341
+
342
+ For tasks that store per-instance runtime data (not shared across AI using the same tree asset):
343
+
344
+ ```cpp
345
+ struct FBTMyTaskMemory
346
+ {
347
+ FAIRequestID MoveRequestID;
348
+ float ElapsedTime = 0.f;
349
+ TWeakObjectPtr<UMyComponent> CachedComp;
350
+ };
351
+
352
+ UCLASS()
353
+ class UBTTask_MyTask : public UBTTaskNode
354
+ {
355
+ // ...
356
+ virtual uint16 GetInstanceMemorySize() const override
357
+ {
358
+ return sizeof(FBTMyTaskMemory);
359
+ }
360
+ };
361
+
362
+ // In ExecuteTask:
363
+ FBTMyTaskMemory* Mem = CastInstanceNodeMemory<FBTMyTaskMemory>(NodeMemory);
364
+ new(Mem) FBTMyTaskMemory(); // placement-new to initialize
365
+ Mem->ElapsedTime = 0.f;
366
+ ```
367
+
368
+ ---
369
+
370
+ ## Decorator Abort Modes
371
+
372
+ | FlowAbortMode | Behavior |
373
+ |---------------|----------|
374
+ | `None` | Decorator never aborts running tasks |
375
+ | `Self` | Aborts tasks within its own subtree if condition changes |
376
+ | `LowerPriority` | Aborts lower-priority branches when condition becomes true |
377
+ | `Both` | Combination of Self and LowerPriority |
378
+
379
+ Most reactive decorators (Blackboard, CanSeeTarget) should use `Both` so the tree re-evaluates when the condition changes in either direction.
380
+
381
+ Set via:
382
+ ```cpp
383
+ FlowAbortMode = EBTFlowAbortMode::Both;
384
+ bAllowAbortLowerPri = true;
385
+ bAllowAbortChildNodes = true;
386
+ ```
387
+
388
+ Then call `ConditionalFlowAbort` from external event handlers to trigger re-evaluation:
389
+ ```cpp
390
+ // In perception callback or timer:
391
+ ConditionalFlowAbort(OwnerComp, EBTDecoratorAbortRequest::ConditionResultChanged);
392
+ ```
@@ -0,0 +1,107 @@
1
+ # Behavior Trees — Editor-Side Workflow
2
+
3
+ > Adapted from the `unreal-behavior-trees` skill in [awesome-gamedev-agent-skills](https://github.com/gamedev-skills/awesome-gamedev-agent-skills) (Apache-2.0). Cross-skill references were remapped to Arcane slash commands.
4
+
5
+ Author NPC decision-making in UE5 with Behavior Trees driven by a Blackboard: structure the
6
+ tree with composites, gate branches with decorators, keep state current with services, and run
7
+ it from an AIController. Targets **UE 5.8**.
8
+
9
+ ## When to use
10
+
11
+ - Use when building enemy/NPC AI: creating a `BT_`/`BB_` asset pair, structuring
12
+ Selector/Sequence branches, adding decorators (conditions) and services (periodic updates),
13
+ writing custom `BTTask`/`BTService` nodes, or wiring an AIController to run the tree.
14
+ - Use when the project has Behavior Tree (`BT_`) and Blackboard (`BB_`) assets and an
15
+ `AAIController`.
16
+
17
+ **When *not* to use:** the *concept* of AI (FSM vs BT vs steering, cross-engine) →
18
+ `game-ai`. Pure navigation/pathing math is engine navmesh (BT's `MoveTo` uses it). Simple
19
+ one-off logic may be cheaper as a small state machine than a full tree.
20
+
21
+ ## Core workflow
22
+
23
+ 1. **Create the pair:** a Blackboard (`BB_`) holds typed keys (the AI's memory: `TargetActor`,
24
+ `LastKnownLocation`, `bIsInvestigating`); a Behavior Tree (`BT_`) references that Blackboard.
25
+ 2. **Possess and run.** An `AAIController` possesses the pawn and calls `RunBehaviorTree(BT)`,
26
+ which also initializes the referenced Blackboard.
27
+ 3. **Structure with composites.** **Selector** runs children left→right until one *succeeds*
28
+ (priority/fallback: "attack, else chase, else patrol"). **Sequence** runs children until one
29
+ *fails* (do-all: "move to cover → reload → peek"). **Simple Parallel** runs one main task
30
+ alongside a secondary.
31
+ 4. **Gate branches with Decorators** that read Blackboard keys (e.g. "Has Target?" guards the
32
+ combat branch). Set **Observer Aborts** so the tree re-evaluates when the key changes.
33
+ 5. **Keep the Blackboard current with Services** attached to a branch — they tick periodically
34
+ (e.g. update `TargetActor` via a sight check) only while that branch is active.
35
+ 6. **Do work in Tasks**, which return `Succeeded`, `Failed`, or `InProgress` (latent tasks like
36
+ `MoveTo` finish later).
37
+ 7. **Verify** with the Behavior Tree debugger during PIE — it highlights the running node and
38
+ shows live Blackboard values, so you see exactly which branch executes.
39
+
40
+ ## Patterns
41
+
42
+ ### 1. AIController that runs the tree (C++)
43
+
44
+ ```cpp
45
+ void AEnemyAIController::OnPossess(APawn* InPawn)
46
+ {
47
+ Super::OnPossess(InPawn);
48
+ if (BehaviorTree) // UPROPERTY(EditAnywhere) TObjectPtr<UBehaviorTree>
49
+ RunBehaviorTree(BehaviorTree); // initializes & uses the Blackboard the BT references
50
+ }
51
+ ```
52
+
53
+ ### 2. A priority tree (node structure)
54
+
55
+ ```text
56
+ ROOT
57
+ └── Selector (try combat, else investigate, else patrol)
58
+ ├── Sequence [Decorator: Blackboard 'TargetActor' Is Set, Observer Aborts: Both]
59
+ │ ├── Task: MoveTo (TargetActor) // latent: returns InProgress then Succeeded
60
+ │ └── Task: Attack
61
+ ├── Sequence [Decorator: 'LastKnownLocation' Is Set]
62
+ │ ├── Task: MoveTo (LastKnownLocation)
63
+ │ └── Task: Wait (3s) + clear key
64
+ └── Task: Patrol (BTTask_FindPatrolPoint -> MoveTo)
65
+ ```
66
+
67
+ `Observer Aborts: Both` makes the combat branch interrupt patrol the instant `TargetActor` is
68
+ set, and bail out when it's cleared — this is what makes the AI feel reactive.
69
+
70
+ ### 3. Updating the Blackboard from code (e.g. on seeing the player)
71
+
72
+ ```cpp
73
+ void AEnemyAIController::SetTarget(AActor* Target)
74
+ {
75
+ if (UBlackboardComponent* BB = GetBlackboardComponent())
76
+ BB->SetValueAsObject(TEXT("TargetActor"), Target); // key name must match the BB asset
77
+ }
78
+ // Clear with BB->ClearValue(TEXT("TargetActor")); to drop back to a lower-priority branch.
79
+ ```
80
+
81
+ ## Pitfalls
82
+
83
+ - **AI never starts** — the pawn isn't possessed (set the Pawn's *Auto Possess AI* to "Placed
84
+ in World or Spawned" and assign the AIController), or `RunBehaviorTree` was never called.
85
+ - **`MoveTo` instantly fails** — no NavMesh in the level (add a Nav Mesh Bounds Volume), or the
86
+ target is off the navmesh.
87
+ - **Branch doesn't react to changes** — the gating Decorator's **Observer Aborts** is set to
88
+ None; set it to Self/Lower Priority/Both so the tree re-evaluates when the key changes.
89
+ - **A task hangs the tree** — a custom task returned `InProgress` and never calls
90
+ `FinishLatentTask`. Always complete latent tasks.
91
+ - **Blackboard key typos** — `SetValueAsObject("Taget", ...)` silently does nothing; match the
92
+ key name and type exactly, or use a cached `FBlackboardKeySelector`.
93
+ - **Sequence vs Selector confusion** — Sequence = AND (stops on first failure); Selector = OR
94
+ (stops on first success). Swapping them inverts the behaviour.
95
+
96
+ ## References
97
+
98
+ - For a **custom C++ `UBTTaskNode`** (instant and latent `ExecuteTask` returning `EBTNodeResult`,
99
+ with a `FBlackboardKeySelector`), read `references/custom-bttask.md`.
100
+ - Primary docs: "Behavior Trees in Unreal Engine"
101
+ (`https://dev.epicgames.com/documentation/en-us/unreal-engine/behavior-trees-in-unreal-engine`).
102
+
103
+ ## Related skills
104
+
105
+ - `game-ai` — engine-agnostic AI design (FSM, BT, steering, pathfinding choices).
106
+ - `/ue-cpp-foundations` — the AIController and pawn classes in C++.
107
+ - `fps-shooter` / `tower-defense` — genres that compose enemy AI.
@@ -0,0 +1,104 @@
1
+ > Adapted from the `unreal-behavior-trees` skill in [awesome-gamedev-agent-skills](https://github.com/gamedev-skills/awesome-gamedev-agent-skills) (Apache-2.0). Cross-skill references were remapped to Arcane slash commands.
2
+
3
+ # Custom BTTask in C++ (UE 5.8)
4
+
5
+ Depth for `/ue-ai-navigation`: writing a custom Behavior Tree task node in C++. Verified
6
+ against the Behavior Trees documentation and the `UBTTaskNode` API. Requires the `AIModule`
7
+ dependency in `*.Build.cs`.
8
+
9
+ ## Instant task (returns immediately)
10
+
11
+ ```cpp
12
+ // BTTask_ClearTarget.h
13
+ #pragma once
14
+ #include "CoreMinimal.h"
15
+ #include "BehaviorTree/BTTaskNode.h"
16
+ #include "BTTask_ClearTarget.generated.h"
17
+
18
+ UCLASS()
19
+ class MYGAME_API UBTTask_ClearTarget : public UBTTaskNode
20
+ {
21
+ GENERATED_BODY()
22
+ public:
23
+ UBTTask_ClearTarget();
24
+ virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp,
25
+ uint8* NodeMemory) override;
26
+
27
+ // Exposes a Blackboard key picker in the BT editor.
28
+ UPROPERTY(EditAnywhere, Category = "Blackboard")
29
+ FBlackboardKeySelector TargetKey;
30
+ };
31
+ ```
32
+
33
+ ```cpp
34
+ // BTTask_ClearTarget.cpp
35
+ #include "BTTask_ClearTarget.h"
36
+ #include "BehaviorTree/BlackboardComponent.h"
37
+
38
+ UBTTask_ClearTarget::UBTTask_ClearTarget()
39
+ {
40
+ NodeName = "Clear Target";
41
+ }
42
+
43
+ EBTNodeResult::Type UBTTask_ClearTarget::ExecuteTask(UBehaviorTreeComponent& OwnerComp,
44
+ uint8* NodeMemory)
45
+ {
46
+ if (UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent())
47
+ {
48
+ BB->ClearValue(TargetKey.SelectedKeyName);
49
+ return EBTNodeResult::Succeeded; // done now
50
+ }
51
+ return EBTNodeResult::Failed;
52
+ }
53
+ ```
54
+
55
+ `EBTNodeResult::Type` values: `Succeeded`, `Failed`, `Aborted`, `InProgress`.
56
+
57
+ ## Latent task (finishes over time)
58
+
59
+ Return `InProgress`, then call `FinishLatentTask` when the work completes (e.g. a timer, an
60
+ async query, or a delegate callback).
61
+
62
+ ```cpp
63
+ EBTNodeResult::Type UBTTask_WaitRandom::ExecuteTask(UBehaviorTreeComponent& OwnerComp,
64
+ uint8* NodeMemory)
65
+ {
66
+ AAIController* AICon = OwnerComp.GetAIOwner();
67
+ if (!AICon) return EBTNodeResult::Failed;
68
+
69
+ const float Delay = FMath::FRandRange(MinTime, MaxTime);
70
+
71
+ // Schedule completion; capture the component so we can finish the right task instance.
72
+ FTimerHandle Handle;
73
+ AICon->GetWorldTimerManager().SetTimer(Handle, FTimerDelegate::CreateLambda(
74
+ [&OwnerComp, this]()
75
+ {
76
+ FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded); // resumes the tree
77
+ }), Delay, /*loop*/ false);
78
+
79
+ return EBTNodeResult::InProgress; // tree waits on this node until FinishLatentTask
80
+ }
81
+ ```
82
+
83
+ If a latent task can be aborted by the tree (e.g. a higher-priority branch takes over),
84
+ override `AbortTask` to cancel timers/queries and return `EBTNodeResult::Aborted`.
85
+
86
+ ## Reading and writing typed Blackboard values
87
+
88
+ ```cpp
89
+ UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent();
90
+ AActor* Target = Cast<AActor>(BB->GetValueAsObject(TargetKey.SelectedKeyName));
91
+ FVector Loc = BB->GetValueAsVector(TEXT("LastKnownLocation"));
92
+ BB->SetValueAsBool(TEXT("bIsInvestigating"), true);
93
+ ```
94
+
95
+ ## Notes
96
+
97
+ - Set `NodeName` in the constructor so the node reads clearly in the BT graph.
98
+ - `OwnerComp.GetAIOwner()` gives the `AAIController`; `GetAIOwner()->GetPawn()` the controlled
99
+ pawn — use these instead of caching world pointers.
100
+ - For nodes that need per-instance memory (counters, handles), override `GetInstanceMemorySize`
101
+ and use the `NodeMemory` block; for simple cases, member UPROPERTYs on a non-instanced node
102
+ are shared across all uses, so prefer instance memory or instanced nodes when state matters.
103
+ - A custom **Service** subclasses `UBTService` and overrides `TickNode`; a custom **Decorator**
104
+ subclasses `UBTDecorator` and overrides `CalculateRawConditionValue`.