UTankCatalogSubsystem Implementation Plan¶
Goal¶
Introduce UTankCatalogSubsystem as the single runtime authority for:
- Character/item catalog indexing
- CharacterDef payload decode/resolve
- Runtime asset loading + hard-reference lifetime
- Reusable resolution APIs for gameplay systems
The objective is to remove duplicated CharacterDef loading/decoding logic currently spread across ATankPlayerState, UOnlineManagerSubsystem, and FOnlineSelectionTransportService, and to consolidate CharacterDef-related resolution used by GAS startup ability/montage flows.
Current Problems¶
- CharacterDef decode and resolve logic is embedded in
ATankPlayerState. - Multiple codepaths sync-load CharacterDef pieces independently:
ATankPlayerState::SyncLoadCharacterDefSourceUOnlineManagerSubsystemselection submit helperFOnlineSelectionTransportServicebroadcast helper- Selection transport layer currently has content-loading responsibilities.
- GAS startup montage/ability resolution has duplicated runtime loading behavior.
- Ownership boundaries are blurry between transport/state classes and content/catalog classes.
Target Ownership Boundaries¶
UTankCatalogSubsystem (new/expanded authority)¶
Owns:
- Catalog indices (ItemDef + CharacterDef)
EquippedJsonparsing for character selection extraction- CharacterDef lookup by catalog index / character id / asset id
- AssetManager load calls for CharacterDef bundles
- Runtime hard references/handles for loaded CharacterDefs and key dependent assets
- Shared helper APIs for gameplay systems (including GAS montage/ability metadata resolve)
Does not own:
- Selection lock policy
- Selection RPC transport lifecycle
- Replicated player state transitions
ATankPlayerState¶
Owns:
- Replicated selection state
- Replicated
CharacterDefSource - Per-player runtime overrides
Does not own:
- Asset registry scans
- CharacterDef JSON decode
- Direct catalog lookups
FOnlineSelectionTransportService¶
Owns:
- RPC workflow/state machine (bootstrap, confirm, verify, lock)
- Canonical selection progression and validation policy
Does not own:
- CharacterDef asset loading/caching
- CharacterDef parsing internals
GAS classes (e.g. UTankGameplayAbility)¶
Owns:
- Ability activation behavior
Does not own:
- Catalog indexing
- Broad CharacterDef load policy
Naming and Placement¶
- Class name:
UTankCatalogSubsystem - Base:
UGameInstanceSubsystem - Suggested files:
Source/Metal_terra/Public/Core/Subsystems/TankCatalogSubsystem.hSource/Metal_terra/Private/Core/Subsystems/TankCatalogSubsystem.cpp
API Plan (Phase 1 Surface)¶
Catalog build/query¶
void RebuildCatalogIndex();bool TryResolveCharacterDefAssetId(int32 CatalogIndex, FPrimaryAssetId& OutAssetId) const;bool TryResolveCharacterDefByCatalogIndex(int32 CatalogIndex, UTankCharacterDef*& OutDef, FString& OutError);bool TryResolveCharacterDefByCharacterId(FName CharacterId, UTankCharacterDef*& OutDef, FString& OutError);
Payload decode + semantic resolve¶
bool TryExtractCharacterCatalogIndexFromEquippedJson(const FString& EquippedJson, int32& OutCatalogIndex, FString& OutError) const;bool TryResolveCharacterDefFromEquippedJson(const FString& EquippedJson, UTankCharacterDef*& OutDef, FString& OutError);
Runtime load/cache¶
bool EnsureCharacterDefLoaded(UTankCharacterDef* InDef, UTankCharacterDef*& OutLoadedDef, FString& OutError, bool bLoadGameplayBundle = true, bool bLoadUiBundle = true);bool TryGetLoadedCharacterClass(UTankCharacterDef* InDef, TSubclassOf<class ATankCharacterBase>& OutClass, FString& OutError, bool bSyncLoadOnMiss = true);
GAS helper consolidation¶
bool TryResolveStartupAbilityRuntimeData(UTankCharacterDef* CharacterDef, const TSoftClassPtr<class UGameplayAbility>& AbilityClass, struct FTankCharacterStartupAbility& OutData, FString& OutError, bool bEnsureMontageLoaded = true);
Runtime Data Structures in UTankCatalogSubsystem¶
Character index caches¶
TMap<int32, FPrimaryAssetId> CharacterCatalogIndexToAssetIdTMap<FName, FPrimaryAssetId> CharacterIdToAssetId
Loaded asset caches¶
TMap<FPrimaryAssetId, TObjectPtr<UTankCharacterDef>> LoadedCharacterDefsTMap<FPrimaryAssetId, TSharedPtr<FStreamableHandle>> CharacterDefLoadHandles- Optional class cache:
TMap<TWeakObjectPtr<UTankCharacterDef>, TSubclassOf<ATankCharacterBase>> CharacterClassCache
Optional startup ability cache¶
- Key by
(CharacterDef, AbilitySoftClassPath)for resolved startup ability info.
Migration Plan¶
Phase 1: Introduce UTankCatalogSubsystem without cutover¶
- Add subsystem class and initialize/deinitialize lifecycle.
- Implement catalog indexing for
TankCharacterDef(parallel to existing item index logic). - Implement decode + resolve APIs (
EquippedJson-> catalog index -> CharacterDef). - Implement CharacterDef load/cache API and class resolve API.
- Add verbose logs tagged with a new log category (
LogTankCatalogSubsystem).
Exit criteria:
- Subsystem can resolve
EquippedJsonto a loadedUTankCharacterDefin both client and server contexts.
Phase 2: Move PlayerState decode/resolve responsibility¶
- Replace internals of
ATankPlayerStateCharacterDef resolve helpers to callUTankCatalogSubsystem. - Keep
ATankPlayerStatepublic function signatures stable for now to minimize callsite churn. - Deprecate direct AssetManager scans in PlayerState implementation.
Exit criteria:
- No direct
GetPrimaryAssetIdList("TankCharacterDef")use remains insideATankPlayerState.
Phase 3: Move Online selection content loading out of transport/manager¶
- In
UOnlineManagerSubsystem, replace local “ensure fully loaded for submit” helper by catalog subsystem call. - In
FOnlineSelectionTransportService, replace local “ensure fully loaded for broadcast” helper by catalog subsystem call. - Keep transport logic unchanged except dependency inversion for resolve/load operations.
Exit criteria:
- No CharacterDef piecewise sync-loading helper remains in
UOnlineManagerSubsystemorFOnlineSelectionTransportService.
Phase 4: GAS consolidation for CharacterDef runtime data¶
- Route startup ability metadata + montage hard-ref resolution through
UTankCatalogSubsystem. - Keep
UTankCharacterDefas data-only (no new runtime policy additions there). - Remove duplicated montage resolve branches where subsystem now provides canonical behavior.
Exit criteria:
- One consistent runtime path for startup montage/ability resolve/load behavior.
Phase 5: Cleanup and hardening¶
- Mark old helper methods as deprecated and remove after callsite cutover.
- Add guardrails for null world/subsystem/service availability.
- Add explicit diagnostics for resolve/load failure reasons.
Exit criteria:
- All CharacterDef decode/resolve/load responsibilities are centralized under
UTankCatalogSubsystem.
Integration Callsite Plan¶
ATankPlayerState¶
- Keep:
ApplyServerSelectionState- replicated state logic
-
SetCharacterDefSource -
Refactor internals of:
TryExtractCharacterCatalogIndexFromEquippedJsonTryResolveCharacterDefByCatalogIndexTryResolveCharacterDefFromEquippedJson
to delegate into UTankCatalogSubsystem.
UOnlineManagerSubsystem¶
- In selection submit paths, call
UTankCatalogSubsystem::EnsureCharacterDefLoaded(...).
FOnlineSelectionTransportService¶
- In bootstrap/apply/confirm/broadcast flows, resolve CharacterDef through
UTankCatalogSubsystem. - Remove internal content-loader helper.
GAS¶
- Update
UTankGameplayAbilitystartup montage resolution to useUTankCatalogSubsystemhelper.
Notes About Respawn Flow¶
Respawn class resolve is intentionally left under BP-driven orchestration per current direction. This plan does not force ATankGameMode to own respawn resolution policy in C++; it only ensures the catalog/resolve/load core is centralized and reusable by BP and C++ callers.
Testing Plan¶
Functional tests¶
- Selection confirm path (client -> server -> Nakama persist) still resolves CharacterDef correctly.
- DS bootstrap path applies canonical state and resolves CharacterDef correctly.
- Repeated death/respawn cycles with camera switch do not regress due to content resolve failures.
- GAS ability startup montages resolve consistently after consolidation.
Negative tests¶
- Invalid EquippedJson (malformed JSON).
- Missing character selection entries.
- Unknown catalog index.
- Duplicate catalog index registrations.
- AssetManager unavailable/late init.
Logging requirements¶
All failure paths should include:
- Context (
caller,net mode) - Identity (
player/controller/playerstate) - Input summary (
catalog index,equipped payload length) - Exact failure reason
Risks and Mitigations¶
- Risk: Behavior changes in selection flow during cutover.
-
Mitigation: Keep public signatures stable first; internal delegation only in early phases.
-
Risk: Hard ref growth/memory pressure.
-
Mitigation: Scope handles to known-needed assets, add explicit release strategy if needed later.
-
Risk: Hidden BP dependencies on old behavior.
- Mitigation: Preserve event timing and return semantics during migration.
Deliverables Checklist¶
- [ ]
UTankCatalogSubsystemclass added - [ ] CharacterDef index map(s) in subsystem
- [ ] EquippedJson parse/resolve APIs in subsystem
- [ ] CharacterDef load/cache APIs in subsystem
- [ ] PlayerState internals delegated to subsystem
- [ ] OnlineManager loading helper migrated
- [ ] SelectionTransport loading helper migrated
- [ ] GAS startup montage/ability resolve path consolidated
- [ ] Deprecated old helper paths removed
- [ ] Validation logs added and reviewed