Skip to content

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

  1. CharacterDef decode and resolve logic is embedded in ATankPlayerState.
  2. Multiple codepaths sync-load CharacterDef pieces independently:
  3. ATankPlayerState::SyncLoadCharacterDefSource
  4. UOnlineManagerSubsystem selection submit helper
  5. FOnlineSelectionTransportService broadcast helper
  6. Selection transport layer currently has content-loading responsibilities.
  7. GAS startup montage/ability resolution has duplicated runtime loading behavior.
  8. Ownership boundaries are blurry between transport/state classes and content/catalog classes.

Target Ownership Boundaries

UTankCatalogSubsystem (new/expanded authority)

Owns:

  • Catalog indices (ItemDef + CharacterDef)
  • EquippedJson parsing 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.h
  • Source/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> CharacterCatalogIndexToAssetId
  • TMap<FName, FPrimaryAssetId> CharacterIdToAssetId

Loaded asset caches

  • TMap<FPrimaryAssetId, TObjectPtr<UTankCharacterDef>> LoadedCharacterDefs
  • TMap<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

  1. Add subsystem class and initialize/deinitialize lifecycle.
  2. Implement catalog indexing for TankCharacterDef (parallel to existing item index logic).
  3. Implement decode + resolve APIs (EquippedJson -> catalog index -> CharacterDef).
  4. Implement CharacterDef load/cache API and class resolve API.
  5. Add verbose logs tagged with a new log category (LogTankCatalogSubsystem).

Exit criteria:

  • Subsystem can resolve EquippedJson to a loaded UTankCharacterDef in both client and server contexts.

Phase 2: Move PlayerState decode/resolve responsibility

  1. Replace internals of ATankPlayerState CharacterDef resolve helpers to call UTankCatalogSubsystem.
  2. Keep ATankPlayerState public function signatures stable for now to minimize callsite churn.
  3. Deprecate direct AssetManager scans in PlayerState implementation.

Exit criteria:

  • No direct GetPrimaryAssetIdList("TankCharacterDef") use remains inside ATankPlayerState.

Phase 3: Move Online selection content loading out of transport/manager

  1. In UOnlineManagerSubsystem, replace local “ensure fully loaded for submit” helper by catalog subsystem call.
  2. In FOnlineSelectionTransportService, replace local “ensure fully loaded for broadcast” helper by catalog subsystem call.
  3. Keep transport logic unchanged except dependency inversion for resolve/load operations.

Exit criteria:

  • No CharacterDef piecewise sync-loading helper remains in UOnlineManagerSubsystem or FOnlineSelectionTransportService.

Phase 4: GAS consolidation for CharacterDef runtime data

  1. Route startup ability metadata + montage hard-ref resolution through UTankCatalogSubsystem.
  2. Keep UTankCharacterDef as data-only (no new runtime policy additions there).
  3. 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

  1. Mark old helper methods as deprecated and remove after callsite cutover.
  2. Add guardrails for null world/subsystem/service availability.
  3. 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:

  • TryExtractCharacterCatalogIndexFromEquippedJson
  • TryResolveCharacterDefByCatalogIndex
  • TryResolveCharacterDefFromEquippedJson

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 UTankGameplayAbility startup montage resolution to use UTankCatalogSubsystem helper.

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

  1. Selection confirm path (client -> server -> Nakama persist) still resolves CharacterDef correctly.
  2. DS bootstrap path applies canonical state and resolves CharacterDef correctly.
  3. Repeated death/respawn cycles with camera switch do not regress due to content resolve failures.
  4. GAS ability startup montages resolve consistently after consolidation.

Negative tests

  1. Invalid EquippedJson (malformed JSON).
  2. Missing character selection entries.
  3. Unknown catalog index.
  4. Duplicate catalog index registrations.
  5. 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

  1. Risk: Behavior changes in selection flow during cutover.
  2. Mitigation: Keep public signatures stable first; internal delegation only in early phases.

  3. Risk: Hard ref growth/memory pressure.

  4. Mitigation: Scope handles to known-needed assets, add explicit release strategy if needed later.

  5. Risk: Hidden BP dependencies on old behavior.

  6. Mitigation: Preserve event timing and return semantics during migration.

Deliverables Checklist

  • [ ] UTankCatalogSubsystem class 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