How-To: Find and Fix Input Clashes
Goal: find every Input Action that a higher-priority Input Mapping Context silently hides, every key two contexts fight over, and every chord that can never fire - and repair them from one Review page - without pressing Play once. This page walks the whole tool, button by button. It is longer than most How-To pages on purpose: the analyzer models something the editor never shows you, so the first run is confusing without a guide.
bEnableInputClashAnalyzer is on by default in Project Settings -> Crimson -> Crimson Input Settings Clash Analyzer. Your Input Mapping Contexts and Input Actions are ordinary Enhanced Input assets; nothing in the analyzer needs a Crimson type.What the tool is checking, in one paragraph
When a player's control mappings are rebuilt, Enhanced Input sorts every applied context by priority (highest first) and walks their mappings. A mapping whose action has Consume Lower Priority Enhanced Input Mappings ticked claims its key: every mapping on that key in a lower-priority context is dropped, before the game runs a single frame. Two contexts at the same priority are ordered by whichever was added first, which no asset records. Chorded mappings are exempt from claiming, but only the engine's own Chorded Action trigger gets that exemption. The analyzer replays exactly that rebuild over a scenario - the contexts you say are applied together, at what priority, in which input mode - and reports every place the outcome is not what a designer would expect.
1. Open the analyzer
Tools -> Crimson Input -> Input Clash Analyzer (1), or the Input Clashes button in the Level Editor play toolbar (2). The tab is a normal dockable tab; it opens floating the first time and remembers where you dock it. The console command Crimson.InputClash.Open opens it too, and Crimson.InputClash.Open <ScenarioName> opens it with a saved scenario already analyzed.
bEnableInputClashAnalyzer is off; both need an editor restart.2. Discover what the project applies
Press Discover (1). The scan takes a few seconds on a large project and shows a progress bar. It finds every UInputMappingContext and UInputAction asset, plus every place a context is applied with a priority that can be read from assets: the engine's Default Mapping Contexts project setting, AddMappingContext nodes in Blueprint graphs (literal pins only), and any asset, class default, or component template that stores a context next to an integer property whose name contains Priority. Lyra-style InputMapping + Priority structs on a game feature action, a hero component's default mappings, CommonUI's InputMapping + InputMappingPriority on a screen widget, a data asset with its own MappingContextPriority - all of these count.
Subsystem->AddMappingContext(IMC, Priority)) leaves no trace in any asset. Discovery cannot see it, so you add it by hand in step 3 and type the priority your code passes. If a scenario's results look too good to be true, a hand-added context is usually what is missing.Input clash discovery: N contexts, M actions, K application sites in J assets. Open Add from discovery... and you see one submenu per asset that applies a context.3. Build a scenario
A scenario is the list in the Applied contexts panel. Fill it three ways, and mix them freely:
- Add from discovery... (1) lists every asset that applies a context, as a submenu. Open the asset you care about and pick Add all from this source (2) to add its contexts with the priorities it uses, or pick contexts one at a time. Add everything discovered adds every application site at once; Add every context in the project adds every context asset at priority 0 whether or not anything applies it.
- Add context... opens an asset picker filtered to Input Mapping Contexts. Use it for contexts your C++ applies. They arrive at priority 0; set the real value in the Priority column.
- Remove drops the selected rows; Clear empties the list; the On checkbox keeps a context in the scenario without analyzing it, handy for comparing with and without a screen open.
Then set each Priority (3) to what the game passes to AddMappingContext. Discovery fills this in; hand-added contexts start at 0. Double-click a row to open the context asset.
- means the asset failed to load; check the path. Press Discover before analyzing if you want the Fix column on Same priority ambiguity rows: the priority fixer writes to the asset that applies the context, so it needs the discovered sources.4. Tell it which input mode the player is in
The Input mode tags field (4) holds the gameplay tags the player is assumed to be in - typically InputMode.Game during play and InputMode.Menu (or both) while a screen is open. Enhanced Input only consults these when Enable Input Mode Filtering is on in Project Settings -> Engine -> Enhanced Input -> Input Modes; then every context whose Input Mode Query does not match is dropped from the rebuild, exactly as at runtime, and the analyzer shows it as Filtered by input mode in the Status column. With filtering off in the project, the field does nothing and you can leave it empty.
Two more knobs on the same row: Key profile analyzes a mappable key profile's override mappings instead of the defaults (the list is built from the profiles the selected contexts define). Apply context processor runs each context through FCrimsonInputContextProcessor (Input/CrimsonInputContextProcessor.h) first. When a plugin has registered a processor - typically one that injects chord triggers so rebindable chords are enforced - that duplicate is what the runtime actually applies, so leave it on. With no processor registered it is a no-op.
InputMode.Menu, press Analyze, and every gameplay context's Status flips from Active to Filtered by input mode; clear the tag and they flip back.5. Analyze and read the Clashes view
Press Analyze (5). With Auto analyze ticked the report also refreshes every time you change a priority, a checkbox, or a tag. The Clashes view lists one row per finding, most severe first. Filter with the three severity toggles (1), review the certain fixes with Fix all Auto (2), or type in the filter box (3) to match a key, an action, a context, or any word of the explanation. The Fix column (4) is step 7. Double-click a row to open the context it names; right-click for the action, the other context, or to copy the row as text. Copy Report puts the whole report on the clipboard.
| Severity | Category | What it means | What usually fixes it |
|---|---|---|---|
| Error | Hidden by higher priority | A higher-priority context consumes the key, so this mapping is dropped at rebuild. The action never sees the key. | Rebind the lower mapping, untick Consume on the higher action if both should fire, remove whichever is the duplicate, or accept it when the higher context is a screen meant to own the key while open. |
| Error | Action unreachable | Every mapping of an action in the scenario is hidden. The action can never fire here. | Fix the individual Hidden rows. |
| Error | Chord action unmapped | A Chorded Action trigger names an action nothing active maps; the chord can never fire. | Map the chord action in an active context. |
| Error | Invalid mapping | Null action, invalid key, or a null trigger or modifier entry. | Repair the entry in the context asset. |
| Warning | Same priority ambiguity | Two contexts at equal priority map a key and at least one consumes it. Add order decides, and nothing records it. | Give them different priorities: screens above always-on contexts, gameplay layers one step apart. |
| Warning | Chorded mapping consumes | A mapping with a chord gate has Consume ticked, so it claims the key for every lower context whether or not the chord is held. | Untick Consume on the chorded action (Auto fix). |
| Warning | Consumed by gated mapping | The same situation, seen from a mapping it already hid. | Same fix. |
| Warning | Gate blocks without sibling | An exact-match gate requiring no modifiers blocks whenever any modifier is held, and no chorded mapping on the key benefits. | Switch the trigger to yield-to-sibling semantics, or remove it. |
| Warning | Same context shared key / Cross context both fire / Gate overlap | Two different actions fire on one press. | Accept it if intended; otherwise rebind or remove one side. |
| Warning | Value type mismatch / Reserved action overlap / Duplicate mapping | The issues the engine's own QueryMapKeyInContextSet reports. | Add modifiers, move the other action, remove the duplicate. |
| Info | Chord family, Chord blocker caveat, Unevaluated gate, Context filtered, Context redirected, Rebindable gate default | Context for reading the rest; nothing is wrong by itself. | None. Unevaluated gate: see step 9. |
6. Read the Key Map view
Switch to Key Map (1) to see the same analysis from the key's point of view: one row per key, its receivers listed in the engine's evaluation order (2). An active receiver is white; a hidden one is dimmed with [HIDDEN by ...] after it; (consumes) marks the mapping that claims the key; gate: Shift+ shows a chord condition; gate: ? marks a custom trigger the analyzer could not evaluate (step 9). This is the view to use when someone reports key X does nothing - find the key and read down.
7. Fix what it found
Every finding the tool can repair shows a button in the Fix column. There are four kinds:
| Button | Tier | What it means |
|---|---|---|
| Fix | Auto | Always correct: a chorded mapping that consumes, a duplicate mapping, a screen context tied with a gameplay context. Safe to apply in bulk with Fix all Auto. |
| Options... | Propose | A concrete edit you should look at first, usually one of several: rebind the hidden mapping to a free key on the same device (the list only offers keys nothing active uses), stop the higher action consuming so both fire, remove the mapping, raise one of two tied contexts. |
| Accept... | Accept | No asset change. Two actions firing on one press, or a screen's action hiding a gameplay action while the screen is open, is a design choice; accepting records the pair on the scenario so it stops being reported. Save the scenario to keep it. |
| Why? | Manual | Explanation only: a chord action nobody maps, an invalid mapping, a reserved key. Needs a design decision. |
Every button opens the same Review window. Tick the fixes to apply (1), read the tier (2) and the exact edits listed under each fix - asset, property, old value, new value (3) - then press Apply (4). Everything applies in one Undo step; packages are marked dirty, not saved, so you can still look at the assets before committing. The scenario updates when a context priority changed, and the analysis re-runs so the fixed rows disappear.
A Propose fix shows a dropdown (1) with every option the tool could find; the first is its best guess. For a hidden mapping the list is Rebind to <free key> entries, then Untick Consume on <the hider> (2), then Remove the mapping, and finally Remove the higher mapping instead for the case where the higher one is the duplicate (a screen that rebinds a global navigation action on the same key), and Accept when the hiding is the design: a screen owns the key while it is open and the gameplay action underneath is meant to wait. Accepting records the pair on the scenario like any other accept and also retires the Action unreachable row it caused. When the hidden mapping is itself a chord, the untick option comes first: moving a chorded mapping defeats the chord.
8. Save the scenario
Type a name in the box next to the scenario dropdown and press Save (6). The scenario - contexts, priorities, input mode tags, key profile, processor toggle, and the accepted pairs - is written to Config/DefaultEditor.ini under UCrimsonInputClashSettings::InputClashScenarios, so it travels with the project and the team shares it. New starts an empty one; Delete removes the saved one. A * after the name in the dropdown means unsaved changes.
Crimson.InputClash.Report <Name> prints a saved scenario's full report to the log, Crimson.InputClash.Fixes <Name> prints the numbered fixes it would propose, Crimson.InputClash.Open <Name> opens the tab on it, and Crimson.InputClash.Apply <Name> [auto] [accept] [<fix>[=<option>] ...] [save] applies fixes by number (auto takes every Auto fix, accept every Accept fix, 3=1 takes option 1 of fix #3) through the same applier as the Review page, writes the scenario back to the config, and with save saves the touched packages. All four work from -ExecCmds in a commandlet-style editor run, which is how to gate a build on zero input errors.9. Teach it your custom chord trigger (optional)
The analyzer understands the engine's Chorded Action trigger by itself. Any other Implicit or Blocker trigger is opaque: its mapping shows gate: ? in the Key Map and an Unevaluated gate info row, and the analyzer treats it as unconditional. To make it precise, the trigger describes the condition it enforces: one or more alternatives (keys, modifier tags, or actions that must be held) and how they match - Exact (the held set must equal the condition; an empty condition means nothing may be held), AtLeast (extras are ignored, like the engine chord), or YieldToChordedSibling (fires unless another mapping on the same key claims the held set exactly).
Open your trigger Blueprint (parent class Input Trigger). In Class Settings -> Interfaces add Crimson Input Trigger Gate Descriptor, then implement Describe Input Gate: Make Crimson Input Trigger Gate -> Return Node, with Alternatives holding one Make Crimson Input Gate Condition whose Held Keys (or Held Modifier Tags) lists what your trigger requires, and Match set to Exact, At Least, or Yield To Chorded Sibling. Leave Statically Known ticked. The Mapping Key input tells you which mapping the trigger instance sits on, for triggers whose condition depends on the key.
Exact - an empty condition then means nothing may be held, which is what lets plain Y stand down while RT+Y fires. A trigger that only stands aside for a more specific sibling on the same key describes YieldToChordedSibling. AtLeast is how the engine's own Chorded Action behaves: extras held are ignored. If the player can rebind the chord, set bAuthoredDefaultOnly so reports note that an override may change the outcome.Use
HeldModifierTags rather than raw keys when your chord is authored as an Input.Modifier.* tag: tags resolve through ICrimsonInputModifierRegistry (Input/ICrimsonInputModifierRegistry.h) when a registry is present, so a tag-based chord and an engine Chorded Action on the same physical modifier read as one held identity. With no registry the tags stay opaque but still compare against each other.gate: ? in the Key Map becomes gate: <your description>, and the Unevaluated gate row for it is gone.10. Propose your own fixes (optional, C++)
The fix layer has the same hook as the analysis. Register an ICrimsonInputClashFixer and it runs before the built-in fixers, so it can claim a finding first and offer repairs that only make sense for your conventions - for a chord trigger of your own, set its Block When Unchorded flag to false as the alternative to unticking Consume. The edit builders in CrimsonInputClashEdits (consume flag, rebind, remove mapping, priority on a discovered source) cover the common asset writes; anything else is an FCrimsonInputClashEdit with your own Apply lambda. For restructuring a context, UCrimsonInputClashEditLibrary::MoveActionMappings(From, Action, To) moves every mapping of an action with its triggers, modifiers, and per-mapping settings; it is Blueprint-callable and reachable from editor Python as unreal.CrimsonInputClashEditLibrary.move_action_mappings.
// MyProjectEditorModule.cpp - propose repairs for your own conventions#include "InputClash/CrimsonInputClashFixTypes.h" // CrimsonInputEditorclass FMyChordFixer : public ICrimsonInputClashFixer{public:virtual FName GetName() const override { return TEXT("MyChord"); }virtual void Propose(const FCrimsonInputClashFixContext& Context, TArray<FCrimsonInputClashFix>& OutFixes) const override{const FCrimsonInputClashReport& Report = *Context.Report;for (int32 Index = 0; Index < Report.Findings.Num(); ++Index){const FCrimsonInputClashFinding& Finding = Report.Findings[Index];if (Finding.Category != ECrimsonInputClashCategory::GateBlocksWithoutSibling){continue;}FCrimsonInputClashFix Fix;Fix.FindingIndex = Index;Fix.Tier = ECrimsonInputClashFixTier::Propose;Fix.Title = FText::FromString(TEXT("Loosen the chord on this mapping"));Fix.Rationale = FText::FromString(TEXT("Switch the trigger to yield-to-sibling semantics."));FCrimsonInputClashFixOption& Option = Fix.Options.AddDefaulted_GetRef();Option.Label = FText::FromString(TEXT("Set Block When Unchorded = false"));// Build an FCrimsonInputClashEdit: Asset, Description, OldValue, NewValue, and an Apply lambda// that calls Modify(), writes the property, PostEditChange(), and MarkPackageDirty().OutFixes.Add(MoveTemp(Fix));}}};void FMyProjectEditorModule::StartupModule(){FCrimsonInputClashFixerRegistry::Get().Register(MakeShared<FMyChordFixer>());}
Troubleshooting
| Symptom | Cause | What to do |
|---|---|---|
| Discover finds contexts but no application sites | The project applies contexts from C++ only, or through a struct whose priority field is not named with Priority. | Add contexts by hand with Add context... and type the priorities. Rename the field if you own it. |
| A screen's actions are all Hidden by higher priority by an always-on context | The screen shares a priority with the always-on context, or sits below it. | Raise the screen's Input Mapping Priority; the Auto fix does exactly this. |
| Everything is filtered and nothing is analyzed | A Menu tag is set and the project's default input mode query is NONE(InputMode.Menu). | Expected for gameplay contexts. Add the screen's context to the scenario; it is Do Not Filter and stays active. |
| A Same priority ambiguity row has no Fix button | The tab has no discovery data, so the fixer does not know which asset applies the context. | Press Discover (or open with Crimson.InputClash.Open <Name> discover). Fixes are proposed again as soon as discovery finishes. |
| The Fix button is missing on a row | No fixer claims that category (Info rows, chord action unmapped), or the mapping's context could not be resolved. | Read the Details column; Info rows never have fixes. |
| Apply reports could not be applied | The asset changed since the analysis (someone edited it, or a previous fix moved the mapping). | Press Analyze and apply again from the fresh report. |
gate: ? on a mapping | A custom Implicit or Blocker trigger without a descriptor. | Step 9. |
| A context you just edited still analyzes with its old mappings | A context processor served a cached processed copy. Any processor that caches its processed form has to drop that copy when the source asset changes. | Re-run Analyze. If a project processor caches, invalidate on OnObjectPropertyChanged and OnObjectTransacted. |
See also
- API Reference