Concept: Tag-Driven Input

CrimsonInput's core idea is one level of indirection: game code never holds a raw UInputAction*. It holds a FGameplayTag, and a UCrimsonInputConfig resolves that tag to the concrete action at runtime. Rebind the key, swap the asset, or ship a different config per character - the tag-facing code never changes.

Three assets, three jobs

AssetMapsHolds
UInputActionone logical actionvalue type + triggers/modifiers
UInputMappingContextkeys -> actionsthe physical bindings (WASD, mouse, buttons)
UCrimsonInputConfigactions -> gameplay tagsthree tagged lists (Native / Ability / UI)

The config holds no keys and the IMC holds no tags - you always have both. The config's three Find...InputActionForTag functions are the read side; the component's binders are the write side. Splitting the lists by consumer (C++ callback, GAS, CommonUI) keeps each binding path reading only the actions it owns.

Why a tag and not a pointer

ConcernRaw UInputAction*FGameplayTag via config
Rebinding / asset swapEvery referencing site breaksOnly the config entry changes
Per-character inputDuplicate binding codeSwap the UCrimsonInputConfig
Cross-system referencesHard asset couplingSystems share a tag, not an asset

Decoupled from the ability system

Ability input never names a CrimsonAbilitySystem type. The binders hand you an FGameplayTag; you forward it to whatever ability system your pawn exposes through the engine's IAbilitySystemInterface::GetAbilitySystemComponent(). That is why CrimsonInput depends on the GameplayAbilities module but not on any Crimson ability plugin - and why it drops into non-GAS projects by simply not using the ability list.

The plugin ships no input tags
You define the InputTag.* hierarchy in your project. The plugin only provides the machinery that resolves and binds them, keeping your tag taxonomy yours.