claude-code-arcane 2.3.0 → 2.5.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 (141) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +131 -41
  3. package/agents/game/ue-blueprint-specialist.md +125 -0
  4. package/agents/game/ue-gas-specialist.md +133 -0
  5. package/agents/game/ue-umg-specialist.md +131 -0
  6. package/dist/cli.js +642 -478
  7. package/docs/SKILLS-CATALOG.md +42 -3
  8. package/docs/USER-GUIDE.md +28 -54
  9. package/hooks/detect-unreal.sh +90 -0
  10. package/package.json +2 -1
  11. package/profiles/agile.yaml +1 -1
  12. package/profiles/ai.yaml +1 -1
  13. package/profiles/android-native.yaml +1 -1
  14. package/profiles/audio.yaml +1 -1
  15. package/profiles/backend-dotnet.yaml +1 -1
  16. package/profiles/backend-go.yaml +1 -1
  17. package/profiles/backend-nestjs.yaml +1 -1
  18. package/profiles/backend-nextjs.yaml +1 -1
  19. package/profiles/backend-ts.yaml +1 -1
  20. package/profiles/business.yaml +1 -1
  21. package/profiles/clevel.yaml +1 -1
  22. package/profiles/clickup.yaml +1 -1
  23. package/profiles/core.yaml +1 -1
  24. package/profiles/database.yaml +1 -1
  25. package/profiles/design.yaml +1 -1
  26. package/profiles/docs.yaml +1 -1
  27. package/profiles/ecommerce.yaml +1 -1
  28. package/profiles/finance.yaml +1 -1
  29. package/profiles/flutter.yaml +1 -1
  30. package/profiles/freelance.yaml +1 -1
  31. package/profiles/frontend.yaml +1 -1
  32. package/profiles/infra.yaml +1 -1
  33. package/profiles/integrations.yaml +1 -1
  34. package/profiles/ios-native.yaml +1 -1
  35. package/profiles/jira.yaml +1 -1
  36. package/profiles/job-hunt.yaml +1 -1
  37. package/profiles/marketing.yaml +1 -1
  38. package/profiles/mobile.yaml +1 -1
  39. package/profiles/regulatory.yaml +1 -1
  40. package/profiles/second-brain.yaml +1 -1
  41. package/profiles/security.yaml +1 -1
  42. package/profiles/self-improving.yaml +1 -1
  43. package/profiles/statusline.yaml +1 -1
  44. package/profiles/testing.yaml +1 -1
  45. package/profiles/unity-design.yaml +1 -1
  46. package/profiles/unity-dev.yaml +1 -1
  47. package/profiles/unreal-dev.yaml +97 -0
  48. package/profiles/visual-novel.yaml +1 -1
  49. package/rules/gamedev/unreal-code.md +125 -0
  50. package/skills/arcane-list/SKILL.md +12 -7
  51. package/skills/install-mcp/SKILL.md +135 -46
  52. package/skills/install-mcp/references/unreal-operations.md +55 -0
  53. package/skills/ue-actor-component-architecture/SKILL.md +509 -0
  54. package/skills/ue-actor-component-architecture/references/actor-lifecycle.md +301 -0
  55. package/skills/ue-actor-component-architecture/references/component-types.md +592 -0
  56. package/skills/ue-ai-navigation/SKILL.md +526 -0
  57. package/skills/ue-ai-navigation/references/behavior-tree-patterns.md +392 -0
  58. package/skills/ue-ai-navigation/references/behavior-tree-workflow.md +107 -0
  59. package/skills/ue-ai-navigation/references/custom-bttask.md +104 -0
  60. package/skills/ue-ai-navigation/references/eqs-reference.md +423 -0
  61. package/skills/ue-animation-system/SKILL.md +521 -0
  62. package/skills/ue-animation-system/references/anim-notify-reference.md +486 -0
  63. package/skills/ue-animation-system/references/locomotion-setup.md +550 -0
  64. package/skills/ue-async-threading/SKILL.md +433 -0
  65. package/skills/ue-async-threading/references/thread-safety-guide.md +213 -0
  66. package/skills/ue-async-threading/references/threading-patterns.md +345 -0
  67. package/skills/ue-audio-system/SKILL.md +527 -0
  68. package/skills/ue-audio-system/references/audio-setup-patterns.md +700 -0
  69. package/skills/ue-blueprints/SKILL.md +134 -0
  70. package/skills/ue-blueprints/references/communication.md +82 -0
  71. package/skills/ue-character-movement/SKILL.md +475 -0
  72. package/skills/ue-character-movement/references/cmc-extension-patterns.md +335 -0
  73. package/skills/ue-character-movement/references/movement-pipeline.md +189 -0
  74. package/skills/ue-cpp-foundations/SKILL.md +533 -0
  75. package/skills/ue-cpp-foundations/references/components-and-gc.md +107 -0
  76. package/skills/ue-cpp-foundations/references/container-patterns.md +544 -0
  77. package/skills/ue-cpp-foundations/references/delegate-patterns.md +497 -0
  78. package/skills/ue-cpp-foundations/references/gameplay-classes-cpp.md +136 -0
  79. package/skills/ue-cpp-foundations/references/property-specifiers.md +306 -0
  80. package/skills/ue-data-assets-tables/SKILL.md +527 -0
  81. package/skills/ue-data-assets-tables/references/asset-loading-patterns.md +306 -0
  82. package/skills/ue-data-assets-tables/references/data-driven-design.md +378 -0
  83. package/skills/ue-editor-tools/SKILL.md +523 -0
  84. package/skills/ue-editor-tools/references/detail-customization-patterns.md +395 -0
  85. package/skills/ue-editor-tools/references/editor-module-setup.md +480 -0
  86. package/skills/ue-game-features/SKILL.md +477 -0
  87. package/skills/ue-game-features/references/experience-system.md +222 -0
  88. package/skills/ue-game-features/references/game-feature-patterns.md +308 -0
  89. package/skills/ue-gameplay-abilities/SKILL.md +518 -0
  90. package/skills/ue-gameplay-abilities/references/ability-task-reference.md +391 -0
  91. package/skills/ue-gameplay-abilities/references/gameplay-effect-reference.md +397 -0
  92. package/skills/ue-gameplay-abilities/references/gas-setup-patterns.md +369 -0
  93. package/skills/ue-gameplay-framework/SKILL.md +466 -0
  94. package/skills/ue-gameplay-framework/references/framework-class-map.md +243 -0
  95. package/skills/ue-input-system/SKILL.md +394 -0
  96. package/skills/ue-input-system/references/cpp-setup.md +131 -0
  97. package/skills/ue-input-system/references/enhanced-input-workflow.md +117 -0
  98. package/skills/ue-input-system/references/input-action-reference.md +731 -0
  99. package/skills/ue-mass-entity/SKILL.md +476 -0
  100. package/skills/ue-mass-entity/references/mass-entity-patterns.md +319 -0
  101. package/skills/ue-mass-entity/references/mass-fragment-reference.md +119 -0
  102. package/skills/ue-materials-rendering/SKILL.md +526 -0
  103. package/skills/ue-materials-rendering/references/material-parameter-reference.md +216 -0
  104. package/skills/ue-materials-rendering/references/post-process-settings.md +383 -0
  105. package/skills/ue-module-build-system/SKILL.md +487 -0
  106. package/skills/ue-module-build-system/references/build-cs-reference.md +418 -0
  107. package/skills/ue-module-build-system/references/common-build-errors.md +381 -0
  108. package/skills/ue-naming-conventions/SKILL.md +142 -0
  109. package/skills/ue-naming-conventions/references/asset-name-modifiers.md +233 -0
  110. package/skills/ue-naming-conventions/references/content-structure.md +83 -0
  111. package/skills/ue-networking-replication/SKILL.md +452 -0
  112. package/skills/ue-networking-replication/references/replication-patterns.md +526 -0
  113. package/skills/ue-networking-replication/references/rpc-decision-guide.md +393 -0
  114. package/skills/ue-niagara-effects/SKILL.md +531 -0
  115. package/skills/ue-niagara-effects/references/niagara-data-interfaces.md +491 -0
  116. package/skills/ue-niagara-effects/references/niagara-parameter-types.md +153 -0
  117. package/skills/ue-niagara-effects/references/niagara-workflow.md +115 -0
  118. package/skills/ue-packaging/SKILL.md +116 -0
  119. package/skills/ue-physics-collision/SKILL.md +423 -0
  120. package/skills/ue-physics-collision/references/collision-channel-setup.md +218 -0
  121. package/skills/ue-physics-collision/references/trace-patterns.md +499 -0
  122. package/skills/ue-procedural-generation/SKILL.md +505 -0
  123. package/skills/ue-procedural-generation/references/pcg-node-reference.md +381 -0
  124. package/skills/ue-procedural-generation/references/procedural-mesh-patterns.md +940 -0
  125. package/skills/ue-project-context/SKILL.md +305 -0
  126. package/skills/ue-sequencer-cinematics/SKILL.md +506 -0
  127. package/skills/ue-sequencer-cinematics/references/sequencer-patterns.md +629 -0
  128. package/skills/ue-serialization-savegames/SKILL.md +524 -0
  129. package/skills/ue-serialization-savegames/references/save-system-architecture.md +705 -0
  130. package/skills/ue-state-trees/SKILL.md +489 -0
  131. package/skills/ue-state-trees/references/state-tree-mass-integration.md +184 -0
  132. package/skills/ue-state-trees/references/state-tree-patterns.md +321 -0
  133. package/skills/ue-testing-debugging/SKILL.md +520 -0
  134. package/skills/ue-testing-debugging/references/automation-test-patterns.md +387 -0
  135. package/skills/ue-testing-debugging/references/profiling-commands.md +357 -0
  136. package/skills/ue-ui-umg-slate/SKILL.md +431 -0
  137. package/skills/ue-ui-umg-slate/references/common-ui-setup.md +484 -0
  138. package/skills/ue-ui-umg-slate/references/widget-types.md +487 -0
  139. package/skills/ue-world-level-streaming/SKILL.md +518 -0
  140. package/skills/ue-world-level-streaming/references/streaming-patterns.md +414 -0
  141. package/templates/gamedev/unreal-project-context.md +107 -0
@@ -0,0 +1,518 @@
1
+ ---
2
+ name: ue-world-level-streaming
3
+ description: "World Partition, level streaming, level travel, data layers, world subsystems"
4
+ category: "gamedev"
5
+ argument-hint: "[level o streaming strategy]"
6
+ user-invocable: true
7
+ allowed-tools: Read, Glob, Grep, Bash, Write, Edit
8
+ ---
9
+
10
+ # UE World & Level Streaming
11
+
12
+ You are an expert in Unreal Engine's world management and level streaming systems.
13
+
14
+ Ask before writing: this skill edits world settings, data layers and streaming code. Propose the change and get approval before using Write or Edit.
15
+
16
+ ---
17
+
18
+ ## Context
19
+
20
+ Read `docs/unreal/project-context.md` before advising. Pay attention to:
21
+ - **Engine version** — World Partition is UE5 only; sub-level streaming works in both UE4 and UE5.
22
+ - **Build targets** — Dedicated server has no rendering-driven streaming; streaming must be server-safe.
23
+ - **World size** — Determines whether World Partition or manual sub-level streaming is appropriate.
24
+ - **Multiplayer** — Seamless travel requirements and per-player streaming radius.
25
+
26
+ ---
27
+
28
+ ## Information to Gather
29
+
30
+ Before recommending a streaming approach, confirm:
31
+
32
+ 1. **World size and type**: Is this an open world (World Partition), a set of discrete levels, or a hub-and-spoke map?
33
+ 2. **Multiplayer**: Are you running a dedicated server? Are per-player streaming radii needed?
34
+ 3. **Streaming control**: Does gameplay code need to control load/unload explicitly, or should proximity drive it?
35
+ 4. **Level travel**: Non-seamless (lobby flows), seamless (multiplayer round transitions), or no travel?
36
+ 5. **Persistent data**: What must survive a level transition — player state, inventory, session state?
37
+
38
+ ---
39
+
40
+ ## World Partition (UE5)
41
+
42
+ ### Enabling World Partition
43
+
44
+ Enable via the Level menu: **World -> World Partition -> Convert Level**. Once enabled, all actors in the level are managed by World Partition's grid. The level can no longer have traditional sub-levels. Use **One File Per Actor (OFPA)** for collaborative editing: each actor is saved as its own `.uasset` under `__ExternalActors__`.
45
+
46
+ ### Runtime Data Layers
47
+
48
+ Data layers replace the old sub-level toggle pattern. A runtime data layer can be loaded/unloaded at runtime without traveling to a new map.
49
+
50
+ ```cpp
51
+ // MyGameMode.cpp
52
+ #include "WorldPartition/DataLayer/DataLayerManager.h"
53
+
54
+ void AMyGameMode::ActivateDungeonDataLayer()
55
+ {
56
+ UDataLayerManager* DLMgr = UDataLayerManager::GetDataLayerManager(GetWorld());
57
+ if (!DLMgr) return;
58
+
59
+ // Get by asset reference (set up in editor as a UDataLayerAsset)
60
+ UDataLayerAsset* DungeonLayer = DungeonDataLayerAsset.LoadSynchronous();
61
+ DLMgr->SetDataLayerRuntimeState(DungeonLayer, EDataLayerRuntimeState::Activated);
62
+ }
63
+
64
+ void AMyGameMode::DeactivateDungeonDataLayer()
65
+ {
66
+ UDataLayerManager* DLMgr = UDataLayerManager::GetDataLayerManager(GetWorld());
67
+ if (!DLMgr) return;
68
+
69
+ UDataLayerAsset* DungeonLayer = DungeonDataLayerAsset.LoadSynchronous();
70
+ DLMgr->SetDataLayerRuntimeState(DungeonLayer, EDataLayerRuntimeState::Unloaded);
71
+ }
72
+ ```
73
+
74
+ **Data layer states:**
75
+ - `Unloaded` — not loaded, not visible.
76
+ - `Loaded` — loaded into memory, not visible (pre-warming).
77
+ - `Activated` — loaded and visible (fully active).
78
+
79
+ ### Streaming Sources
80
+
81
+ Each player controller is a streaming source by default. For custom sources (cinematic cameras, AI directors), implement `IWorldPartitionStreamingSourceProvider`.
82
+
83
+ ### HLOD
84
+
85
+ HLOD provides distant merged-mesh representations of World Partition cells. Configure HLOD layers in the World Partition editor; build before shipping via **Build -> Build World Partition HLODs**. Without HLOD, content beyond the streaming radius is simply absent.
86
+
87
+ ### Converting Sub-Levels to World Partition
88
+
89
+ Use **Tools -> World Partition -> Convert Level**. Actors migrate into the persistent level under WP management. Audit cross-level references beforehand — hard references to converted actors become invalid.
90
+
91
+ ### World Partition and Multiplayer
92
+
93
+ In a multiplayer session, each player controller acts as a streaming source with a configurable radius. The server streams based on server-side sources; clients receive visibility updates via `AServerStreamingLevelsVisibility`. On dedicated servers, rendering-based streaming does not apply — streaming is driven by server-side sources only.
94
+
95
+ Streaming radius is configured per-partition in the World Partition editor UI (`LoadingRange` on `URuntimePartition`), not via ini.
96
+
97
+ ---
98
+
99
+ ## Level Streaming (Manual Sub-Levels)
100
+
101
+ ### ULevelStreaming State Machine
102
+
103
+ From `LevelStreaming.h`, the full state sequence is:
104
+
105
+ ```
106
+ Removed -> Unloaded -> Loading -> LoadedNotVisible -> MakingVisible -> LoadedVisible -> MakingInvisible -> LoadedNotVisible
107
+ |
108
+ FailedToLoad (check logs; level asset missing or corrupt)
109
+ ```
110
+
111
+ Query state with:
112
+
113
+ ```cpp
114
+ ULevelStreaming* StreamingLevel = /* ... */;
115
+ ELevelStreamingState State = StreamingLevel->GetLevelStreamingState();
116
+
117
+ switch (State)
118
+ {
119
+ case ELevelStreamingState::Unloaded: /* not in memory */ break;
120
+ case ELevelStreamingState::Loading: /* async load in progress */ break;
121
+ case ELevelStreamingState::LoadedNotVisible: /* in memory, not rendered */ break;
122
+ case ELevelStreamingState::MakingVisible: /* adding to world */ break;
123
+ case ELevelStreamingState::LoadedVisible: /* fully active */ break;
124
+ case ELevelStreamingState::MakingInvisible: /* removing from rendering */ break;
125
+ case ELevelStreamingState::FailedToLoad: /* check logs */ break;
126
+ }
127
+ ```
128
+
129
+ ### UGameplayStatics: LoadStreamLevel / UnloadStreamLevel
130
+
131
+ For Blueprint-friendly async streaming with latent actions (from `GameplayStatics.h`):
132
+
133
+ ```cpp
134
+ // MyActor.cpp — async load using FLatentActionInfo
135
+ #include "Kismet/GameplayStatics.h"
136
+
137
+ void AMyActor::StreamInRoom(FName LevelName)
138
+ {
139
+ FLatentActionInfo LatentInfo;
140
+ LatentInfo.CallbackTarget = this;
141
+ LatentInfo.ExecutionFunction = FName("OnRoomLoaded");
142
+ LatentInfo.Linkage = 0;
143
+ LatentInfo.UUID = GetUniqueID();
144
+
145
+ UGameplayStatics::LoadStreamLevel(
146
+ this, // WorldContextObject
147
+ LevelName, // e.g., FName("Room_01")
148
+ true, // bMakeVisibleAfterLoad
149
+ false, // bShouldBlockOnLoad — keep false for async
150
+ LatentInfo
151
+ );
152
+ }
153
+
154
+ UFUNCTION()
155
+ void AMyActor::OnRoomLoaded()
156
+ {
157
+ // Room is now loaded and visible
158
+ }
159
+
160
+ void AMyActor::StreamOutRoom(FName LevelName)
161
+ {
162
+ FLatentActionInfo LatentInfo;
163
+ LatentInfo.CallbackTarget = this;
164
+ LatentInfo.ExecutionFunction = FName("OnRoomUnloaded");
165
+ LatentInfo.Linkage = 0;
166
+ LatentInfo.UUID = GetUniqueID() + 1;
167
+
168
+ UGameplayStatics::UnloadStreamLevel(
169
+ this,
170
+ LevelName,
171
+ LatentInfo,
172
+ false // bShouldBlockOnUnload
173
+ );
174
+ }
175
+ ```
176
+
177
+ For soft object pointers (preferred for packaging safety), use `LoadStreamLevelBySoftObjectPtr` with the same arguments.
178
+
179
+ ### ULevelStreamingDynamic: Runtime Level Instances
180
+
181
+ Use `ULevelStreamingDynamic::LoadLevelInstance` to load the same level package multiple times at different transforms — for procedural dungeons, modular buildings, or instanced rooms (from `LevelStreamingDynamic.h`):
182
+
183
+ ```cpp
184
+ #include "Engine/LevelStreamingDynamic.h"
185
+
186
+ void AMyDungeonGenerator::SpawnRoom(FVector Location, FRotator Rotation)
187
+ {
188
+ bool bSuccess = false;
189
+ ULevelStreamingDynamic* StreamingLevel = ULevelStreamingDynamic::LoadLevelInstance(
190
+ this, // WorldContextObject
191
+ TEXT("/Game/Levels/Room_Corridor"), // LongPackageName — full path
192
+ Location,
193
+ Rotation,
194
+ bSuccess
195
+ );
196
+
197
+ if (bSuccess && StreamingLevel)
198
+ {
199
+ // Bind to delegate to know when visible
200
+ StreamingLevel->OnLevelShown.AddDynamic(this, &AMyDungeonGenerator::OnRoomShown);
201
+ StreamingLevel->OnLevelHidden.AddDynamic(this, &AMyDungeonGenerator::OnRoomHidden);
202
+
203
+ LoadedRooms.Add(StreamingLevel);
204
+ }
205
+ }
206
+
207
+ void AMyDungeonGenerator::UnloadRoom(ULevelStreamingDynamic* StreamingLevel)
208
+ {
209
+ if (StreamingLevel)
210
+ {
211
+ StreamingLevel->SetShouldBeLoaded(false);
212
+ StreamingLevel->SetShouldBeVisible(false);
213
+ StreamingLevel->SetIsRequestingUnloadAndRemoval(true);
214
+ }
215
+ }
216
+ ```
217
+
218
+ For networking: use `OptionalLevelNameOverride` to give all clients and server the same package name for a given instance. Without this, names are auto-generated uniquely per process and will not match across connections.
219
+
220
+ ```cpp
221
+ ULevelStreamingDynamic::FLoadLevelInstanceParams Params(
222
+ GetWorld(),
223
+ TEXT("/Game/Levels/Room_Corridor"),
224
+ FTransform(Rotation, Location)
225
+ );
226
+ Params.OptionalLevelNameOverride = &InstanceName; // FString, same on server and clients
227
+ Params.bInitiallyVisible = true;
228
+
229
+ bool bSuccess = false;
230
+ ULevelStreamingDynamic* Level = ULevelStreamingDynamic::LoadLevelInstance(Params, bSuccess);
231
+ ```
232
+
233
+ ### OnLevelShown / OnLevelHidden Delegates
234
+
235
+ From `LevelStreaming.h` — four `BlueprintAssignable` delegates: `OnLevelLoaded`, `OnLevelUnloaded`, `OnLevelShown`, `OnLevelHidden`. Bind with `AddDynamic`:
236
+
237
+ ```cpp
238
+ StreamingLevel->OnLevelShown.AddDynamic(this, &UMyManager::HandleLevelShown);
239
+ StreamingLevel->OnLevelLoaded.AddDynamic(this, &UMyManager::HandleLevelLoaded);
240
+ ```
241
+
242
+ ### Streaming Volumes
243
+
244
+ `ALevelStreamingVolume` automatically controls sub-level loading when the player camera is inside or outside the volume. From `LevelStreamingVolume.h`:
245
+
246
+ ```cpp
247
+ // EStreamingVolumeUsage — set on the volume in editor
248
+ SVB_Loading // load but do not make visible
249
+ SVB_LoadingAndVisibility // load and make visible (most common)
250
+ SVB_VisibilityBlockingOnLoad // force blocking load when entering
251
+ SVB_BlockingOnLoad // block load of associated levels
252
+ SVB_LoadingNotVisible // load, keep invisible (pre-warm)
253
+ ```
254
+
255
+ Volumes are assigned to a sub-level via its `EditorStreamingVolumes` array. Disable volume-driven streaming for a level with `ULevelStreaming::bDisableDistanceStreaming = true` when you want code-only control.
256
+
257
+ ### Manual Visibility Control
258
+
259
+ ```cpp
260
+ // Get streaming level reference from world
261
+ const TArray<ULevelStreaming*>& Levels = GetWorld()->GetStreamingLevels();
262
+ for (ULevelStreaming* Level : Levels)
263
+ {
264
+ if (Level->GetWorldAssetPackageFName() == FName("/Game/Levels/MySubLevel"))
265
+ {
266
+ Level->SetShouldBeLoaded(true);
267
+ Level->SetShouldBeVisible(true);
268
+ break;
269
+ }
270
+ }
271
+ ```
272
+
273
+ Force flush all streaming (blocks until complete — use sparingly):
274
+ ```cpp
275
+ UGameplayStatics::FlushLevelStreaming(this);
276
+ ```
277
+
278
+ ---
279
+
280
+ ## Level Instances
281
+
282
+ `ALevelInstance` places a level as a reusable chunk in the editor. Actors inside are editable as a unit. For runtime instancing, see `ULevelStreamingDynamic` above.
283
+
284
+ **Packed Level Actors** merge instance meshes into a single static mesh for performance. Enable via right-click on Level Instance → **Pack Level Actor**.
285
+
286
+ **Per-instance property overrides (UE5.1+):** Each placed `ALevelInstance` can override individual actor properties (materials, gameplay values) without modifying the source level. Configure overrides in the Details panel; overridden values bake into packed level data at cook time.
287
+
288
+ ---
289
+
290
+ ## Level Travel
291
+
292
+ ### Non-Seamless: UGameplayStatics::OpenLevel
293
+
294
+ Destroys the current world and loads a new one; all clients disconnect. From `GameplayStatics.h`:
295
+
296
+ ```cpp
297
+ UGameplayStatics::OpenLevel(this, FName("/Game/Maps/MainMenu"), true);
298
+ UGameplayStatics::OpenLevel(this, FName("/Game/Maps/GameLevel"), true, TEXT("?Difficulty=Hard"));
299
+ UGameplayStatics::OpenLevelBySoftObjectPtr(this, GameLevelAsset, true); // packaging-safe
300
+ ```
301
+
302
+ ### Server Travel (Multiplayer, Non-Seamless)
303
+
304
+ Initiated on the server; all connected clients follow (`World.h`):
305
+
306
+ ```cpp
307
+ GetWorld()->ServerTravel(TEXT("/Game/Maps/Level02?listen"), /*bAbsolute=*/false);
308
+ ```
309
+
310
+ ### Seamless Travel
311
+
312
+ Seamless travel loads the destination map in the background via a transition (midpoint) map. Clients stay connected. From `World.h`:
313
+
314
+ ```cpp
315
+ void UWorld::SeamlessTravel(const FString& InURL, bool bAbsolute);
316
+ bool UWorld::IsInSeamlessTravel() const;
317
+ void UWorld::SetSeamlessTravelMidpointPause(bool bNowPaused);
318
+ ```
319
+
320
+ **Setup requirements:**
321
+
322
+ 1. Set `bUseSeamlessTravel = true` on `AGameModeBase`:
323
+
324
+ ```cpp
325
+ // bUseSeamlessTravel is already declared in AGameModeBase — do NOT redeclare it.
326
+ // Just set it in the constructor:
327
+
328
+ // MyGameMode.cpp constructor
329
+ bUseSeamlessTravel = true;
330
+ ```
331
+
332
+ 2. Set a transition map in `DefaultEngine.ini`:
333
+
334
+ ```ini
335
+ [/Script/Engine.GameMapsSettings]
336
+ TransitionMap=/Game/Maps/Transition
337
+ ```
338
+
339
+ 3. Override `GetSeamlessTravelActorList` to control which actors persist:
340
+
341
+ ```cpp
342
+ // GameMode — called on server side during transition
343
+ void AMyGameMode::GetSeamlessTravelActorList(bool bToTransition, TArray<AActor*>& ActorList)
344
+ {
345
+ Super::GetSeamlessTravelActorList(bToTransition, ActorList);
346
+
347
+ if (!bToTransition)
348
+ {
349
+ // bToTransition=false means we're moving TO the destination
350
+ // Add actors that should survive (e.g., GameState, custom managers)
351
+ ActorList.Add(MyPersistentManager);
352
+ }
353
+ }
354
+
355
+ // GameMode — called after destination map is loaded
356
+ void AMyGameMode::PostSeamlessTravel()
357
+ {
358
+ Super::PostSeamlessTravel();
359
+ // Re-initialize any post-travel systems
360
+ }
361
+
362
+ // GameMode — handle re-possessing players after travel
363
+ void AMyGameMode::HandleSeamlessTravelPlayer(AController*& C)
364
+ {
365
+ Super::HandleSeamlessTravelPlayer(C);
366
+ // Restore player-specific state here
367
+ }
368
+ ```
369
+
370
+ 4. Trigger on server:
371
+
372
+ ```cpp
373
+ // From GameMode, server-only
374
+ GetWorld()->ServerTravel(TEXT("/Game/Maps/Level02?listen"));
375
+ // Seamless travel is automatic because bUseSeamlessTravel is true
376
+ ```
377
+
378
+ **Travel sequence:** current world -> transition map -> destination world. Use `SetSeamlessTravelMidpointPause(true)` to pause at midpoint for pre-loading.
379
+
380
+ ### Client Travel
381
+
382
+ For client-initiated travel (join server, change options), call `APlayerController::ClientTravel(URL, ETravelType::TRAVEL_Absolute)` from the player controller.
383
+
384
+ ---
385
+
386
+ ## World Subsystems
387
+
388
+ `UWorldSubsystem` (from `Subsystems/WorldSubsystem.h`) is auto-instantiated once per `UWorld`. It is destroyed when the world is destroyed — including on level travel. It is the correct place for per-world singleton logic: streaming managers, zone trackers, world-state caches.
389
+
390
+ ```cpp
391
+ // MyStreamingManager.h
392
+ UCLASS()
393
+ class MYGAME_API UMyStreamingManager : public UWorldSubsystem
394
+ {
395
+ GENERATED_BODY()
396
+ public:
397
+ virtual void PostInitialize() override; // after all subsystems init
398
+ virtual void OnWorldBeginPlay(UWorld& InWorld) override; // after all BeginPlay
399
+ virtual void PreDeinitialize() override; // cleanup hook
400
+ virtual bool ShouldCreateSubsystem(UObject* Outer) const override; // filter world type
401
+
402
+ void RequestLoadZone(FName ZoneName);
403
+ void RequestUnloadZone(FName ZoneName);
404
+ private:
405
+ TMap<FName, TWeakObjectPtr<ULevelStreaming>> ActiveZones;
406
+ };
407
+ ```
408
+
409
+ Access from anywhere with a world context:
410
+
411
+ ```cpp
412
+ UMyStreamingManager* Manager = GetWorld()->GetSubsystem<UMyStreamingManager>();
413
+ if (Manager)
414
+ {
415
+ Manager->RequestLoadZone(FName("Zone_A"));
416
+ }
417
+ ```
418
+
419
+ ### UTickableWorldSubsystem
420
+
421
+ For per-frame updates (distance checks, zone detection). Inherit from `UTickableWorldSubsystem`. Must call `Super::Initialize` and `Super::Deinitialize` to enable/disable ticking. Implement `GetStatId` returning a `RETURN_QUICK_DECLARE_CYCLE_STAT`.
422
+
423
+ ---
424
+
425
+ ## Persistent Data Across Level Transitions
426
+
427
+ | Mechanism | Lifetime | Use Case |
428
+ |---|---|---|
429
+ | `UGameInstance` | Entire application session | Cross-level player state, session config |
430
+ | `UGameInstanceSubsystem` | Entire application session | Services that outlive any world |
431
+ | Seamless travel actor list | Transition only | Actors that physically cross (GameState, managers) |
432
+ | `USaveGame` + `SaveGameToSlot` | Disk-persistent | Long-term saves, progression |
433
+ | `UWorldSubsystem` | Per world | World-scoped cache; push data to `UGameInstance` in `Deinitialize()` before travel clears it |
434
+
435
+ ### GameInstance Pattern
436
+
437
+ Store cross-level data in `UGameInstance` properties (survives all level travel). Access from anywhere with a world context:
438
+
439
+ ```cpp
440
+ UMyGameInstance* GI = GetGameInstance<UMyGameInstance>();
441
+ if (GI) GI->PlayerScore += 100;
442
+ ```
443
+
444
+ ---
445
+
446
+ ## Common Mistakes and Anti-Patterns
447
+
448
+ **Loading everything at once.** Setting `bShouldBlockOnLoad = true` on many sub-levels causes hitches. Use async loading and the latent action pattern. Only block on load when the game is behind a loading screen.
449
+
450
+ **Streaming volume gaps.** Overlapping volumes cause spurious unload/reload cycles. Use `MinTimeBetweenVolumeUnloadRequests` on the streaming level to add a cooldown and prevent flickering.
451
+
452
+ **Broken seamless travel in multiplayer.** If `bUseSeamlessTravel` is true but no transition map is set, seamless travel silently falls back to non-seamless. Always set `TransitionMap` in `DefaultEngine.ini`.
453
+
454
+ **Cross-level hard references.** Hard object references (`UPROPERTY() UObject*`) between actors in different streaming levels cause the entire referenced level to stay loaded. Always use `TSoftObjectPtr` or `TSoftClassPtr` across level boundaries.
455
+
456
+ **Dynamic streaming level names not matching server and client.** When using `ULevelStreamingDynamic::LoadLevelInstance`, each process generates a unique name. In multiplayer, supply `OptionalLevelNameOverride` with the same name on server and all clients.
457
+
458
+ **World Partition on dedicated server.** The server does not use rendering-driven streaming. Streaming sources must be explicitly added server-side (e.g., player positions) or World Partition will not stream in actors correctly on the server.
459
+
460
+ **Modifying `StreamingLevels` directly.** Do not add to `UWorld::StreamingLevels` directly. Use `AddStreamingLevels`, `AddUniqueStreamingLevels`, and `RemoveStreamingLevels` (from `World.h`) which handle internal bookkeeping and `StreamingLevelsToConsider`.
461
+
462
+ **Forgetting to call `Super::Initialize` / `Super::Deinitialize` in `UTickableWorldSubsystem`.** These calls enable and disable ticking respectively. Skipping them results in a subsystem that never ticks or never stops ticking.
463
+
464
+ ---
465
+
466
+ ## Quick Reference: Key APIs
467
+
468
+ | API | Header | Notes |
469
+ |---|---|---|
470
+ | `UGameplayStatics::LoadStreamLevel` | `Kismet/GameplayStatics.h` | Async latent load of named sub-level |
471
+ | `UGameplayStatics::UnloadStreamLevel` | `Kismet/GameplayStatics.h` | Async latent unload |
472
+ | `UGameplayStatics::FlushLevelStreaming` | `Kismet/GameplayStatics.h` | Blocking flush — use behind loading screens |
473
+ | `UGameplayStatics::OpenLevel` | `Kismet/GameplayStatics.h` | Non-seamless level travel |
474
+ | `ULevelStreamingDynamic::LoadLevelInstance` | `Engine/LevelStreamingDynamic.h` | Runtime level instancing |
475
+ | `ULevelStreaming::GetLevelStreamingState` | `Engine/LevelStreaming.h` | Query current stream state |
476
+ | `ULevelStreaming::SetShouldBeLoaded` | `Engine/LevelStreaming.h` | Drive load state |
477
+ | `ULevelStreaming::SetShouldBeVisible` | `Engine/LevelStreaming.h` | Drive visibility |
478
+ | `ULevelStreaming::SetIsRequestingUnloadAndRemoval` | `Engine/LevelStreaming.h` | Remove level from world |
479
+ | `UWorld::ServerTravel` | `Engine/World.h` | Multiplayer level transition |
480
+ | `UWorld::SeamlessTravel` | `Engine/World.h` | Background seamless transition |
481
+ | `UWorld::GetStreamingLevels` | `Engine/World.h` | Iterate all streaming levels |
482
+ | `UDataLayerManager::SetDataLayerRuntimeState` | `WorldPartition/DataLayer/DataLayerManager.h` | World Partition data layer control (use `UDataLayerManager::GetDataLayerManager(World)`) |
483
+ | `UWorldSubsystem::OnWorldBeginPlay` | `Subsystems/WorldSubsystem.h` | Post-BeginPlay init hook |
484
+ | `AGameModeBase::GetSeamlessTravelActorList` | `GameFramework/GameModeBase.h` | Control actor persistence |
485
+
486
+ ---
487
+
488
+ ## Related Skills
489
+
490
+ - `/ue-gameplay-framework` — GameMode travel callbacks, `PostSeamlessTravel`, actor persistence rules.
491
+ - `/ue-data-assets-tables` — async asset loading patterns that complement level streaming.
492
+ - `/ue-networking-replication` — net visibility transactions, server streaming authority.
493
+ - `/ue-cpp-foundations` — subsystem patterns, `UGameInstance` lifetime.
494
+ - `/ue-naming-conventions` — asset prefixes and Content folder layout for these assets
495
+
496
+ ## Reference Files
497
+
498
+ - `references/streaming-patterns.md` — level streaming configurations: distance, data layers, streaming sources
499
+
500
+ ---
501
+
502
+ ## Checklist
503
+
504
+ Validate before considering the work done:
505
+
506
+ - [ ] Streaming sources and distances are tuned; nothing loads the whole world
507
+ - [ ] Data layers are used for state variation instead of duplicated levels
508
+ - [ ] Level transitions handle actors that must survive travel
509
+ - [ ] Conventions in `docs/unreal/project-context.md` were read and followed
510
+ - [ ] The Common Mistakes section of this skill was reviewed against the change
511
+
512
+ **PASS** — every item holds. **CONCERNS** — the work stands but at least one item is unresolved and worth flagging to the user. **FAIL** — an item is violated in a way that breaks correctness, replication or the frame budget; fix it before handing the work over.
513
+
514
+ ## Next Steps
515
+
516
+ - `/perf-profile` — measure hitches at streaming boundaries
517
+ - `/code-review` — architectural and quality review of the change
518
+ - `/commit` — conventional commit once the change is verified