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
| Asset | Maps | Holds |
|---|---|---|
UInputAction | one logical action | value type + triggers/modifiers |
UInputMappingContext | keys -> actions | the physical bindings (WASD, mouse, buttons) |
UCrimsonInputConfig | actions -> gameplay tags | three 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
| Concern | Raw UInputAction* | FGameplayTag via config |
|---|---|---|
| Rebinding / asset swap | Every referencing site breaks | Only the config entry changes |
| Per-character input | Duplicate binding code | Swap the UCrimsonInputConfig |
| Cross-system references | Hard asset coupling | Systems 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.
InputTag.* hierarchy in your project. The plugin only provides the machinery that resolves and binds them, keeping your tag taxonomy yours.