Decoupling Patterns
The Cardinal Rule - no Crimson plugin depends on another, except CrimsonCommon - is enforced by four patterns. Every cross-plugin link in the suite is one of these. The shared piece always lives in CrimsonCommon; the two plugins never include each other.
1. Interface bridge (primary)
Define an interface in CrimsonCommon. Plugin A holds a reference and casts to the interface; plugin B's type implements it. Neither knows the other's concrete types. The ability-set bridge is the canonical example - note the implementer owns its ASC and grants on it directly (there is no GetAbilitySystemComponent() on the interface).
// CrimsonCommon defines the contract:class ICrimsonAbilitySystemOwner{public:virtual void GrantAbilitySet(UCrimsonAbilitySet* Set, UObject* SourceObject,FCrimsonAbilitySet_GrantedHandles* OutHandles) = 0;virtual void RemoveGrantedAbilitySet(FCrimsonAbilitySet_GrantedHandles& Handles) = 0;};// CrimsonAbilitySystem: UCrimsonAbilitySystemComponent implements it.// CrimsonEquipment: uses it with zero CrimsonAbilitySystem includes -Cast<ICrimsonAbilitySystemOwner>(OwnerActor)->GrantAbilitySet(Set, Item, &Handles);
ICrimsonAbilitySystemOwner (grant abilities), ICrimsonInteractableTarget (be interactable), ICrimsonInteractionInstigator (choose an option), ICrimsonInputBindingProvider (read/write chord bindings), ICrimsonTeamAgentInterface (team membership), ICrimsonContextActionProvider (expose menu actions), ICrimsonContextActionContributor (inject actions into other objects' menus).2. Epic standard interfaces
When Unreal already defines the contract, use it. CrimsonInput discovers an ASC through Epic's IAbilitySystemInterface::GetAbilitySystemComponent() - so it binds ability input with only the GameplayAbilities engine module, and no dependency on CrimsonAbilitySystem.
3. GameplayMessageSubsystem
For fire-and-forget events, broadcast a struct on a tag channel via UCrimsonMessageSubsystem. Receivers subscribe independently; sender and receiver never reference each other. See How-To: Broadcast & Listen for Messages.
4. Independent save support
Persistence is data-in, data-out. A plugin exposes a plain save struct (getter/setter) and never depends on CrimsonSaveSystem or GameplayStatics. Each plugin that wants saving implements ICrimsonSaveableSystem (which lives in CrimsonSaveSystem, not here) on its own terms - no plugin is aware of another's save fragment.