NIR
A Niagara system is a binary node graph — emitters, module stacks, renderers, parameter stores, simulation stages and per-node script wiring — that you can only really read by opening the Niagara editor and clicking through emitter after emitter. NIR decompiles that same asset into compact, line-based text: the system and its emitters, every module row in stack order with its resolved inputs, the renderers, and the underlying script graphs written out node by node. It reads the graph out to text for review and AI context; compiling text back into the asset is a planned follow-up, not something it does today.
Why it matters
Diffable in version control
An emitter tweak becomes a handful of readable lines you can diff between revisions instead of an opaque .uasset no reviewer can open in a pull request. The same text lands in the asset-dump cache as nir.txt, so git log -p and grep both work on it.
Reviewable like code
Module stacks read top to bottom in execution order, each input resolved to a literal, a linked parameter like $User.Speed, or a nested dynamic input — so you audit what an emitter actually does without tracing pins across the canvas by eye.
A format an AI can actually read
Your assistant gets the whole system as text in one call — emitters, stacks, renderers, and the node-by-node script bodies — so it can answer questions and reason about the effect instead of poking at the graph blind.
Faithful and deterministic
The RPC and the nir.txt sidecar both call the same decompiler, so on-disk text and live output are byte-identical. Any node class outside the known set is surfaced as # unknown-node with a matching warning rather than silently dropped.
A taste
A system block names exposed param values, then nests an emitter per
handle. Inside each emitter, module rows carry their stack order and enabled state, and each
input resolves to a literal, a linked $-parameter, or a nested dynamic
input:
system "NS_ThrusterExhaust" {
param User.SpawnRate : Float = 12.0 @scope user
emitter "Exhaust" {
simTarget CPUSim
stack ParticleSpawn {
module InitializeParticle @0 enabled
input Lifetime = 2.0
input Color = $User.Color
module AddVelocity @1 enabled
input Velocity = dynamic NoiseClamp { input Range = 0.5 }
}
renderer Sprite
}
}
Standalone scripts, simulation stages and event handlers additionally expand into a
graph <Usage> { ... } body of node identity lines and explicit
link From.Pin -> To.Pin wiring, so the per-node data flow is inspectable, not just the stack.
How your assistant uses it
It reads the current system out as text with niagara.decompile_nir — a single call that
returns the whole system, emitters and script bodies for review or context. This IR is decompile-only, so
the flow is read and reason, never write:
You: What does NS_ThrusterExhaust spawn, and at what rate?
call("niagara.decompile_nir", {assetPath:"/Game/FX/NS_ThrusterExhaust"})
→ {ir:"system \"NS_ThrusterExhaust\" {\n param User.SpawnRate : Float = 12.0 ... }"}
One CPU sprite emitter at 12/sec, 2s lifetime, with noise-clamped velocity.
@(x, y) for traceability, but equivalence is logical, not visual, and referenced
module scripts are emitted as path references rather than expanded inline.