NWN Aurora Emitter → rollnw Particle System Map
This document is a companion to particle_system.md. It maps every Aurora Emitter field to its rollnw equivalent, states the expected visual outcome for common configurations, and documents known gaps.
How to use this document: Given an MDL emitter node, look up each field in the tables below to find the rollnw authoring IR field it maps to, then consult the visual outcomes sections to predict what should appear on screen.
Status legend: ✅ Implemented · ⚠️ Partial (parsed but not fully applied) · ❌ Not in renderer (simulation only or no-op) · 🚫 Deferred
Field Mapping
Emitter Style
| NWN Field | NWN Values | rollnw Field | rollnw Type | Status |
|---|---|---|---|---|
update |
Fountain / Single / Explosion / Lightning | emission.mode |
ParticleEmissionMode |
✅ |
render |
normal / linked / billboard_to_local_z / billboard_to_world_z / aligned_to_world_z / aligned_to_particle_dir / motion_blur | render.mode |
ParticleRenderMode |
✅ |
blend |
normal / punch-through / lighten | material blend | ParticleBlendMode |
✅ |
spawntype |
0 = per_second / 1 = per_distance (trail) | emission.metric |
ParticleSpawnMetric |
✅ |
Note: Non-targeted additive
Linkedemitters with broad rectangular spawn regions are lowered tobillboard_world_zwhen their authored data matches the soft-volume policy described below.
Emitter Parameters
| NWN Field | NWN Unit | rollnw Field | Notes | Status |
|---|---|---|---|---|
xsize, ysize |
cm | region.size (stored in cm) |
Converted ×0.01 to meters at spawn time; 0,0 = point emitter; for linked-chain P2P these also control the initial kink displacement (see cookbook) | ✅ |
renderorder |
short int | render.sort_order |
Higher = drawn on top | ✅ |
threshold |
meters | targeting.kill_radius |
P2P gravity: particle destroyed inside radius | ✅ |
combinetime |
seconds | targeting.transition_factor |
P2P bezier: path traversal time | ✅ |
deadspace |
degrees | — | aligned_to_particle_dir dead zone; parsed to EmitterNode, not lowered to authoring IR |
⚠️ |
inherit |
bool | simulation_space = emitter_attached |
Particles follow emitter translation and direction | ✅ |
inherit_local |
bool | simulation_space = spawn_attached |
Particles stay at world position of their spawn point | ✅ |
inheritvel |
bool | initial.velocity_inheritance |
Fraction of emitter velocity added to particle initial speed | ✅ |
inherit_part |
bool | — | No proven reproducible behavior; intentionally deferred | 🚫 |
Emitter Particles
| NWN Field | NWN Unit | rollnw Field | Notes | Status |
|---|---|---|---|---|
colorStart / colorEnd |
vec3 [0–1] | over_life.color |
3-stage: start / mid / end; mid driven by ColorMid + PercentMid controllers |
✅ |
alphaStart / alphaEnd |
float [0–1] | over_life.alpha |
Multiplied by texture alpha channel if present | ✅ |
sizeStart / sizeEnd |
float (meters) | over_life.size_x/y |
sizeStart_Y / sizeEnd_Y fall back to X value when zero |
✅ |
birthrate |
particles/sec | emission.rate |
Animatable via BirthRate controller keys |
✅ |
lifeExp |
seconds | initial.lifetime |
Animatable; particle removed at expiry | ✅ |
mass |
float | initial.mass |
Positive = gravity pulls down; negative = particle rises | ✅ |
spread |
degrees [0–360] | initial.spread_radians |
Converted to radians; 360 = spherical emission | ✅ |
particleRot |
rotations/sec | initial.rotation_rate |
Sprite rotation in the active billboard plane. aligned_to_world_z applies this as axial rotation around world Z so vertical shaft slices stay upright. |
✅ |
velocity |
m/s | initial.speed |
Base initial speed | ✅ |
randvel |
m/s | initial.speed (range) |
Random range from velocity to velocity + randvel |
✅ |
bounce_co |
float | collision.bounce_coefficient |
Only active when Bounce flag is set |
✅ |
blurlength |
float | render.blur_length |
motion_blur streak length; does not resize billboard_to_local_z footprints |
✅ |
loop |
bool | emission.looping |
Single update type: repeat emission |
✅ |
bounce |
bool | collision.bounce |
Simulated against walkmesh; no visual feedback in mudl | ❌ |
m_isTinted |
bool | render.tint_to_scene_ambient |
Tints particle colors to scene ambient light | ✅ |
splat |
bool | collision.splat |
Simulation only; no visual feedback | ❌ |
affectedByWind |
bool | affected_by_wind |
Wind forces applied in simulation; not visualized | ❌ |
Texture / Chunk
| NWN Field | NWN Type | rollnw Field | Notes | Status |
|---|---|---|---|---|
texture |
string (≤16 chars) | materials[].texture |
Set to NULL when using chunkName |
✅ |
twosidedtex |
bool | materials[].double_sided |
Parsed; GPU pipeline does not yet use it | ⚠️ |
xgrid / ygrid |
int | materials[].sheet.columns/rows |
Sprite atlas grid dimensions | ✅ |
fps |
float | materials[].sheet.frames_per_second |
✅ | |
frameStart / frameEnd |
int (0-based) | materials[].sheet.frame_begin/end |
Absolute atlas frame indices; a 4×4 texture has frames 0–15 | ✅ |
random |
bool | materials[].sheet.random_start |
Randomizes starting frame per particle | ✅ |
chunkName |
string (≤16 chars) | materials[].mesh |
Overrides texture; selects mesh_basic kernel |
✅ |
Emitter Advanced
| NWN Field | NWN Type | rollnw Field | Notes | Status |
|---|---|---|---|---|
lightningDelay |
float | beam.update_interval |
How often the beam shape is re-randomized | ✅ |
lightningRadius |
float | beam.jitter_radius |
Peak lateral displacement per subdivision | ✅ |
subdivision |
int | beam.subdivisions |
Number of jitter segments along the beam | ✅ |
lightningScale |
float | beam.noise_scale |
Maximum fluctuation amplitude per subdivision | ✅ |
blastRadius |
float | force_event.radius |
Wind burst: affected radius | ✅ |
blastLength |
float | force_event.length |
Wind burst: duration | ✅ |
opacity |
float [0–1] | render.opacity_scale |
Motion blur opacity multiplier | ✅ |
p2p |
bool | targeting.mode != none |
Requires a node reference child; will warn if absent |
✅ |
p2p_sel |
0=gravity / 1=bezier | targeting.mode |
point_gravity or point_bezier |
✅ |
p2p_bezier2 |
float | targeting.source_tangent |
SRC tangent: arc length for first half of path | ✅ |
p2p_bezier3 |
float | targeting.target_tangent |
TRG tangent: arc length for second half of path | ✅ |
grav |
float | targeting.gravity |
Acceleration (m/s²) toward reference node | ✅ |
drag |
float | targeting.drag |
Slingshot coefficient: particles overshoot then return | ✅ |
Expected Visual Outcomes
Update × Render Matrix
What you should see on screen for each combination:
| Update | Render | Expected Screen Result |
|---|---|---|
| Fountain | normal | Steady stream of camera-facing quads from the spawn region. Exception: stationary additive sheets with broad rectangular spawn regions and inherited/local simulation lower to billboard_to_local_z so the authored local plane stays fixed instead of turning into a camera-facing slab. World-space normal rect emitters stay camera-facing. |
| Fountain | linked | Continuous ribbon connecting living particles; quads stretch where particles are sparse. Exception: non-targeted additive emitters with broad rectangular spawn regions lower to billboard_world_z soft ground cards. |
| Fountain | billboard_to_local_z | Quads render in the emitter's local XY plane with local Z as the plane normal; attached particles keep their authored local rect/velocity motion while particleRot spins the sprite in that local plane |
| Fountain | billboard_to_world_z | Quads lie flat in the world XY plane with stable world axes; good for pools, auras |
| Fountain | aligned_to_world_z | Quads face sideways (perpendicular to ground) with world Z locked as the vertical axis; good for shafts, rings, halos |
| Fountain | aligned_to_particle_dir | Quads rotate to face their emit direction; deadspace would cull camera-facing ones (not yet applied) |
| Fountain | motion_blur | Quads stretched along velocity; blurlength scales the particle's longitudinal size rather than adding world-space length from speed |
| Single | normal | One particle emitted once; loops if loop = 1 |
| Explosion | normal | No particles until a detonate animation event fires; then birthrate particles burst simultaneously |
| Lightning | (beam internally) | Jittered polyline from emitter to reference node; re-randomized at each lightningDelay interval |
Blend Mode
NWN blend |
rollnw ParticleBlendMode |
GPU Pipeline | Visual Character |
|---|---|---|---|
normal |
alpha |
ParticleRenderer transparent pipeline |
Standard alpha compositing; suitable for textured particles with an alpha channel (smoke, chunks) |
punch-through |
cutout |
ParticleRenderer cutout pipeline |
Hard-edge cutout: pixels below 0.5 alpha are discarded; no semi-transparency |
lighten |
additive |
ParticleRenderer additive pipeline |
Additive blend; black pixels are transparent; creates glow/magic feel; white adds brightness |
Practical heuristic: If the texture has a black background and uses lightness to define shape (fire, sparks, magic), use lighten. If the texture has an explicit alpha channel (smoke puff, debris), use normal. For leaves or hard-edged decals, use punch-through.
P2P Reference Node Direction Convention
P2P reference node offsets are inverted relative to intuition. The engine pulls particles toward the reference position but defines "toward" backward:
- A reference at offset
(0 0 0.63)above the emitter pulls particles downward (toward –Z in world space). - A reference at offset
(0 0 -0.63)pulls particles upward.
When debugging P2P effects, always verify the observed direction of particle flow against the sign of the reference node's offset. This is a NWN engine convention, not a rollnw quirk.
Common Pattern Cookbook
For each of MerricksDad's canonical emitter patterns, the key fields and expected screen result:
Basic Puff (e.g., vim_magblue — magic missile impact)
update Explosion
render Normal
blend Lighten
texture fxpa_flare (single frame: xgrid 1, ygrid 1)
xsize 0 ysize 0 (point emitter)
colorStart 1 1 1 → colorEnd 0.36 0.76 1.0
alphaStart 1 → alphaEnd 0
sizeStart 0.5 → sizeEnd 0.07
birthrate 70 lifeExp 1
Expected: On detonate event — camera-facing colored flash that shrinks and fades over 1 second. Additive blending means the center appears brightest; edges fall off. Nothing visible between detonate events.
Animated Puff / Flame (e.g., vim_exp2flame)
update Fountain
render Normal
blend Lighten
texture fxpa_firebb (xgrid 4, ygrid 4, frameStart 0, frameEnd 15, fps 16)
xsize 60 ysize 50
inherit 1
birthrate 30 lifeExp 1
mass (negative or zero)
Expected: Continuous animated quads rising from a 60×50 cm base; each quad cycles through the 4×4 sprite sheet over its lifetime. inherit 1 makes the flames follow the emitter when it moves. Particles always face camera.
Chunks (e.g., vim_destruct — gore/debris)
update Fountain
render Normal
blend Normal
texture NULL
chunkName Red_M_Giblet
spread 6.28 (360°, all directions)
velocity 5 randvel 1
particleRot 1.5
birthrate (driven by animation keys, bursts near 100 then drops to 0)
lifeExp 4
Expected: 3D mesh instances launched in every direction from a point. Each chunk has a random-ish trajectory and tumbles (particleRot). Normal blend because they are opaque lit geometry.
Trail of Smoke (e.g., it_torch_000)
update Fountain
render Normal
blend Normal
texture fxpa_smoke01 (xgrid 4, ygrid 4, random 1, fps 30)
mass -0.2 (rises)
velocity 0 randVel 0.2
sizeStart 0 → sizeEnd 2
alphaStart X → alphaEnd 0
Expected: Expanding, fading puffs that rise from the emitter. random 1 means each puff shows a different noise frame. mass < 0 gives upward buoyancy; randVel adds subtle variance. Puffs diffuse visually as they scale up and fade out. twosidedtex is parsed but not yet GPU-applied — if the puff rotates edge-on it may vanish (currently not a practical issue since these are camera-facing).
Local-Z Ripple / Swirl (e.g., wmgst_t_051 spin02)
update Fountain
render billboard_to_local_z
blend Lighten
texture fxpa_ripple (xgrid 2, ygrid 2)
inherit 1
xsize 4 ysize 4 (small local XY spawn region)
birthrate 10
lifeExp 2
velocity 0 randvel 0.04
particleRot 5
blurlength 10
mass 0
Runtime mapping: The emitter's local XY axes define the quad plane and local Z is the plane normal. Attached/local particles keep the authored local rect spawn and velocity motion; particleRot rotates each sprite in that local plane. For wmgst_t_051 spin02, the visible spiral comes from absolute atlas frame 1 of fxpa_ripple on large 0.6m quads, not from rewriting particle centers into a synthetic spiral. blurlength is parsed but does not resize billboard_to_local_z effects.
Expected: A circular additive ripple disk that stays anchored to the item/socket orientation. The ripple texture spins in the authored local plane; particles should not grow into a wide cloud or orbit as independent center points.
Detonation Burst
update Explosion
event <time> detonate (in the animation sequence)
Expected: All Explosion-mode emitters in the model fire simultaneously when the detonate animation event fires. Using multiple detonate entries in one sequence triggers multiple bursts. Set birthrate = 0 on specific emitters to suppress them on a given detonate pass.
Lightning (P2P Beam)
update Lightning
lightningDelay (re-randomize interval)
lightningRadius (lateral jitter amplitude)
subdivision (number of segments)
lightningScale (per-segment max offset)
node reference <name> (child of the emitter node)
Expected: Animated jittered polyline from the emitter to the reference child node. Shape re-randomizes at each lightningDelay interval. More subdivisions = more jagged. Larger lightningRadius = more chaotic spread. End-fade applied via sine function.
P2P Gravity Vortex (e.g., vff_comvortex — Implosion)
update Fountain
render Normal
blend Normal
p2p 1 p2p_sel 0 (gravity type)
xsize 1000 ysize 1000 (wide spawn plane)
chunkName Fx_Rockchunksml
grav 2 (pull acceleration)
drag 1 (slingshot past then return)
threshold 1.5 (destroy within 1.5m of reference)
spread 0.175 (≈10°, mostly perpendicular to plane)
node reference <name> (target singularity)
Expected: Chunks spawn across the wide plane, drift toward the reference node with increasing speed, overshoot slightly (drag), curve back, and are destroyed when they enter the threshold radius. The reference node's position is inverted — if offset is (0 0 0.63), particles converge downward.
Linked Chain / Lightning Curtain (e.g., T_Door21 ground_lightning, vps_tentacle)
render Linked
p2p 1 p2p_sel 1 (Bezier)
combinetime controls kink traversal speed (see below)
lifeExp particle lifetime; kink travels (lifeExp/combinetime) fraction of path
xsize / ysize spawn region; lateral spread provides initial kink displacement
sizeStart / sizeEnd ribbon width over particle lifetime
Rendering model: The ribbon is drawn source (emitter position, fixed) → particle (animated kink) → target (reference node, fixed). Both endpoints are anchors. The kink is a per-particle waypoint that starts at the particle's spawn position and traverses toward the target at rate path_t = age / combinetime. Because xsize/ysize give each spawn a lateral offset within the emitter's local XY plane, the kink starts off the emitter→target axis and converges toward the target as the particle ages. This off-axis starting position is the source of the visible V-kink and zigzag character. When the particle dies, a new one spawns at a fresh random position, instantly resetting the kink shape.
xsize and ysize on linked-chain emitters define the spawn region that provides the initial kink displacement — they are not the ribbon width (that is sizeStart). Large values give extreme initial kinks; zero gives a point spawn at the emitter with no off-axis bend. The emitter's local orientation determines which world axis the spread falls on.
combinetime effect on kink convergence:
- Small combinetime relative to lifeExp (e.g., combinetime 0.11, lifeExp 0.1): kink reaches 91% of path before death → spends most of its life near the target end, nearly converged. Bolt starts wide/kinked and snaps toward straight quickly.
- Large combinetime relative to lifeExp (e.g., combinetime 1.0, lifeExp 0.1): kink only travels 10% of path before death → stays close to its spawn position the whole time.
- In both cases the bolt spans full height end-to-end (source → kink → target). combinetime does not affect bolt height, only how far the kink has converged when the particle dies.
Expected: Full-height ribbon spanning from emitter to reference node. The kink shape changes each spawn due to random lateral spawn offset. random 1 on the sprite sheet gives each new particle a different texture frame → flickering lightning character. Additive blend (Lighten) makes it glow. Bolt reshapes every lifeExp seconds.
Linked Broad Rect / Ground Mist (e.g., tnp_gmist)
update Fountain
render Linked
blend Lighten
p2p 0
xsize / ysize broad rectangular spawn region
lifeExp >= 1 second
velocity + randvel <= 1 m/s
Import model: This legacy NWN pattern uses Linked, but the authored intent is a volume of soft cards, not a ribbon. rollnw lowers it to billboard_world_z when the emitter is non-targeted, non-lightning, additive, broad rectangular, continuous, long-lived, and low-speed. Targeted Linked emitters (p2p 1) still use the linked-chain renderer.
Expected: Low, soft additive ground mist with overlapping horizontal cards. It should not draw connected ribbons between particles.
Wind Burst (e.g., vff_pulswind)
update Explosion
texture NULL
blastRadius 15
blastLength 1.2
Expected: Invisible emitter — no particles drawn. On detonate, pushes a force event outward in blastRadius. Affects particles with affectedByWind = 1 and animesh dangly objects. No visual representation in mudl; only observable through secondary effects on other particles.
Ground Aura / Flat Ring
render billboard_to_world_z (quads lie flat, face up)
or
render aligned_to_world_z (quads face sideways, ring-like)
spread 180–360 (wide horizontal emission)
mass 0 (no gravity drift)
velocity (low, or 0 for static)
Expected: billboard_to_world_z — quads flat on the ground plane with stable world-space axes; circular disc appearance when spread = 360. aligned_to_world_z — quads perpendicular to ground with world Z locked as the vertical axis; suitable for vertical shaft/ring/halo effects.
Known Gaps Summary
| Gap | Affected Fields | Impact |
|---|---|---|
twosidedtex not applied to GPU pipeline |
twosidedtex 1 |
Particles may disappear when camera views them edge-on; only matters for non-billboard modes |
deadspace not lowered to authoring IR |
deadspace on aligned_to_particle_dir |
Camera-facing particles in the dead zone are not culled; slightly more particles visible than NWN would show |
bounce, splat, affectedByWind simulation-only |
corresponding flags | Effects exist in simulation but mudl provides no visual confirmation |
inherit_part deferred |
inherit_part flag |
No implementation; a warning is emitted; NWN behavior not yet established |