How-To: Test Pops with the Dev Tools (Optional)

Goal: exercise the whole pop pipeline without real gameplay events - fire test broadcasts at any pawn or pop-target actor, live-edit the per-player filters, burst-test the pool cap, and inspect replication wiring - using the live Crimson.CombatText.DevTools window. Editor-only: the tool lives in the editor module and never ships.

Prerequisites
You completed Quick Start and are in PIE (the tool is PIE-gated - outside PIE it shows a start-PIE notice). Open it via Tools -> Crimson Combat Text -> Combat Text Dev Tools, or the console command Crimson.CombatText.DevTools.
Sections are dock tabs
Every titled section of a tab is a real dock tab: drag one to reorder it, drop it onto another to stack them as tabs, drag it to a window edge to split them side by side, or drag the divider between two to resize. Drag a section out to tear it off into its own window, or right-click it for Dock to Sidebar - sections that are reference-only start in the sidebar. The arrangement is saved per window and comes back after you switch tabs, close the window, or restart the editor. Reset layout (top of the window) restores the default arrangement.
The Crimson Combat Text Dev Tools window in PIE, showing the Net Context picker and the Spawn / Filters / Status tabs.
The Net Context picker
The picker at the top selects which PIE world (server / client N) the tool inspects. Spawn candidates always come from the server world (broadcasts are authoritative); the Filters and Status tabs read the selected context, so you can compare what each client sees.

What each tab shows

TabShowsUse it to
SpawnSearchable Target and Instigator combos listing every pawn in the server world (players, enemies, NPCs, summons) plus any actor carrying a `UCrimsonCombatTextTargetComponent` - player entries first, with a Refresh button (the list also re-scans automatically when a listed actor dies). Instigator offers None for the third-party path. Below: Value with an optional 50%-150% randomize, comma-separated context tags, Prefix/Custom text, and a Burst count (1-200). Two send buttons: Broadcast (server path) and Request direct (local path). Two more sections follow, Scope docked below and Apply gameplay effect stacked behind the test-event form: Scope (open and close an accumulation window on the target from the server - by hand, or by a replicated loose tag on its ASC) and Apply Gameplay Effect (apply any project UGameplayEffect to the target on the server).Fire the real combat pairings without gameplay code: enemy damaging a player (Instigator = enemy, Target = you), player damaging an enemy, or player vs player. Broadcast exercises replication, per-player filters, and server culling on every client; the direct request queues on the selected context's subsystem only and skips the player-settings filter. The two groups take it further: drive a stagger window by hand, or apply a real GameplayEffect so the pops come out of your own damage path.
FiltersThe selected context's local UCrimsonCombatTextPlayerSettings, live-editable: all six visibility toggles, widget scale, format override, max display distance - plus the configured heal/crit tags and which team hook is active (Blueprint override / C++ delegate / none). Offers an Add component now button when the component is missing.Flip a filter and immediately re-broadcast to see it take effect - no options menu required.
StatusA project-settings snapshot (registry/widget paths, ServerCullDistance, the distance-scale curve path or (none - constant size), pool sizes), whether the server GameState has the replication component, the selected context's subsystem readiness with loaded assets and active/pooled/queued counts, and whether the client cull component is present on the local PlayerController.Diagnose 'no pops' in seconds: unassigned assets, a missing GameState component, or a not-yet-loaded subsystem all show here.
Broadcast needs the GameState component
The Broadcast (server path) button is disabled when the server GameState has no UCrimsonCombatTextReplicationComponent (the tab says so). That is the same wiring Quick Start step 5 sets up - the tool cannot substitute for it.
Edits are session-local
Filter edits write directly to the runtime component and last for the PIE session. On the full stack, the CrimsonCore settings glue re-applies the saved user settings on top whenever a shared setting changes (How-To: Expose Pop Options as User Settings).
How the direct path finds the actor
Request direct must resolve the chosen server actor inside the selected context's world. Player pawns map reliably by PlayerState id; everything else falls back to actor-name matching, which is stable for level-placed actors - a runtime-spawned replicated actor may have no name match, and the tool then tells you to use Broadcast (the reliable path for those).

The Scope group

The Spawn tab's Scope section opens and closes an accumulation window on the server by hand, so you can watch a ScopeTag style climb one running total and pay it off without waiting for a real stagger. Walk a whole window end to end:

  1. Pick a Scope tag. The picker is scoped to the loaded registry, so it offers the ScopeTag and ScopeSummaryStyle values your styles actually name - Status.Stagger if you seeded the example styles. A scope is a single tag; select more than one and the group says which it is using.
  2. Press Add loose tag (server, replicated). This puts the tag on the server copy of the target's AbilitySystemComponent with TagAndCountToAll - the GAS path, exercised the way it ships, and the path the server watches for the tag being removed. Without an ASC on the target, press Begin scope (server) instead: it calls BeginCombatTextScope on the server GameState's UCrimsonCombatTextReplicationComponent, which is the manual path a project without GAS uses.
  3. Broadcast several hits at that target, tagged so they resolve to the scoped style (Damage for the example set). Watch the Running total readout climb.
  4. Pick a Scope instigator - The server's player pawn 0, Selected candidate (the actor in the Spawn tab's Instigator combo), or None (the unknown-attacker row) - to choose which ledger row the readout shows. It selects a readout only; it changes nothing about what the server accumulates. The server keeps one row per attacker, so the readout prints that one row.
  5. Press Remove loose tag (server) (or End scope (server)) to close the window.
The tag must be there before the first hit
A run learns it is scoped from a hit that lands while the scope is already open. Add the tag after the first hit and that hit lands in an ordinary time-windowed run, the scope never holds it, and a correctly authored style looks broken. Begin scope (server) has the same ordering rule.
Verify
Hits 2, 3 and 4 climb one running total instead of spawning unrelated numbers, and the readout shows that total with (scope is open). With the example set's default FadeWithMotion each hit re-pops the figure rather than pinning it. Removing the tag pays the run off with a pop styled by ScopeSummaryStyle (Damage.Summary for the example set), and the readout drops back to 0.

Send hits from a second instigator and they land as plain pops beside your number instead of joining it - a running total is personal. That attacker's payoff goes to their client, not yours.

The two server buttons call the same API a project without GAS uses. It lives on the GameState's replication component and is authority-only:

Get Game State -> Get Component by Class (CrimsonCombatTextReplicationComponent) -> Begin Combat Text Scope (Target = the actor, Scope Tag = your state tag), later -> End Combat Text Scope with the same pair. Both run on the server: call them from a GameMode, a server-side ability, or any graph gated on Has Authority. On a client they warn and do nothing. Get Combat Text Scope Total is a pure node returning what one attacker's row holds so far; its Instigator pin is required - pass the attacker, or leave it empty to read the unknown-attacker row.

The Apply Gameplay Effect group

A synthetic broadcast proves the registry resolves. It does not prove your damage code sends the tags you think it does. The Apply gameplay effect section (stacked behind the test-event form on the Spawn tab) applies any UGameplayEffect in the project to the target, on the server, so the pops come out of your real damage path instead of the tool's.

  1. Pick a Target (and optionally an Instigator) in the Spawn tab's combos. The instigator falls back to the target, so a self-inflicted test still carries a source and the filter chain sees a relationship rather than Unknown.
  2. Choose an Effect from the searchable list - it enumerates every UGameplayEffect class and Blueprint in the project - and set a Level. Refresh re-scans after you author a new one.
  3. If the effect reads its magnitude from a SetByCaller, type that tag into the SetByCaller box and set the magnitude beside it. Leave it empty for effects that carry their own magnitudes.
  4. Press Apply to target (server). The spec is built on the server's AbilitySystemComponent and applied to self; the result line names the effect, the level, the target and the magnitude it set.
Verify
The result line reads Applied [...] at level 1 to [...], and pops appear on every client the filter chain admits. A red Apply failed line names the reason instead: the class would not load, the target has no AbilitySystemComponent, the effect produced no spec, or the SetByCaller text is not a registered gameplay tag.
Seeing the aggregation window work
The cleanest demonstration is a periodic damage-over-time effect that ticks faster than 0.6 s and tags its damage Damage.Type.Poison - that row declares no window of its own, so the ticks join Damage's ledger and climb one number instead of stacking a column of 3s. Apply it here rather than broadcasting bursts, because the point is that your effect's period and the root style's window interact. On the full stack, CrimsonCore ships GE_CrimsonDamage_Poison_Periodic for exactly this and its damage effects read SetByCaller.Damage.Base (CrimsonCore - requires all Crimson plugins).

Testing recipes

RecipeStepsExpect
Verify a filter toggleRun 2+ players. On the Spawn tab set Instigator to another player and broadcast; then on the Filters tab (client context) turn Show ally damage dealt off and broadcast again.The first broadcast pops on that client; the second is filtered out. Set Instigator to None to exercise the third-party/null-instigator path instead.
Verify incoming vs dealt damageSet Instigator to an enemy pawn and Target to your player pawn, broadcast; then swap them (you hit the enemy) and broadcast again.The first pop is governed by Show incoming damage (always-see-what-hits-you), the second by Show my damage dealt - toggle each on the Filters tab to confirm.
Verify the MaxActiveTexts capSet Burst count to 100+ and broadcast (or request direct).Active pops never exceed MaxActiveTexts (the Status tab shows the live active/pooled counts) - the oldest pops are recycled early instead of the widget count growing.
Verify server cullingSet ServerCullDistance low in Project Settings, run 2 clients far apart, and broadcast at one of them.Only the nearby client shows the pop; the Status tab on each client reports whether the client cull component is present (culled path) or absent (multicast path).
Verify
With PIE running, a Spawn-tab broadcast at your own pawn pops a styled number on every client in range - styled by the registry rule matching the tags you typed.

See also

  • How-To: Filter Which Pops a Player Sees - what each Filters-tab knob does.
  • How-To: Cull Pops on the Server - the culled delivery path the recipes exercise.
  • How-To: Accumulate Damage Over a Stagger Window - what the Scope group is driving, and how to author it.
  • Concept: Style Resolution, Motion & Rendering - the pool guards the burst recipe stresses.
Dev Tools Resolver tab: tags typed in, showing the winning Styles row, applied Modifiers in order, and the resolved motion.