NixieFX
Documentation · Updated August 2026

Three.js runtime — guide & API reference

How to load an exported NixieFX bundle into a Three.js scene and everything the nixie-fx/three adapter exposes: ThreeVfxRenderer, effect instances, providers, runtime controls, stats, and the instanced rendering fast path.

TL;DR — the integration in one breath

npm install nixie-fx three, fetch your exported out/vfx bundle and verify it with loadVfxExportBundle, construct a ThreeVfxRenderer with your scene and camera (texture/mesh/material providers are all optional — effects without file assets need none), call createEffect(effect, { seed }), and advance vfx.update(deltaSeconds) exactly once per frame in seconds. The simulation is deterministic: same effect, same seed, same motion, on every machine and every backend.

Quickstart

Install nixie-fx together with Three.js. Three.js is an optional peer dependency of the package — importing nixie-fx/three is what actually requires it.

npm install nixie-fx three

The runtime consumes an exported out/vfx bundle — a directory containing manifest.json, compiled effect JSON, and byte-copies of every referenced asset. You produce that bundle either from the NixieFX editor (Export) or headlessly with npx nixie-fx export. Deploy the bundle with your game's static files (below it is served at /vfx).

import * as THREE from "three"; import { loadVfxExportBundle } from "nixie-fx/export"; import { ThreeVfxRenderer, ThreeVfxTextureStore } from "nixie-fx/three"; const BUNDLE_URL = "/vfx"; // your deployed out/vfx directory async function json(url) { const response = await fetch(url); if (!response.ok) throw new Error(`Failed to load ${url}`); return response.json(); } // 1. Fetch the manifest and every compiled effect, then let the official // loader verify hashes, validation status, and backend support. const manifest = await json(`${BUNDLE_URL}/manifest.json`); const effectsByPath = Object.fromEntries( await Promise.all( manifest.effects.map(async (entry) => [ entry.path, await json(`${BUNDLE_URL}/${entry.path}`), ]), ), ); const bundle = loadVfxExportBundle( { manifest, effectsByPath }, { requiredBackend: "three3d" }, ); // 2. Provide textures. Skip this entire step for effects that declare no // file assets — procedural billboards render with no providers at all. const textures = new ThreeVfxTextureStore({ resolveUrl: (path) => `${BUNDLE_URL}/${path}`, }); const effect = bundle.effectsById.get("impact-burst"); if (!effect) throw new Error("Bundle is missing impact-burst"); await textures.preload(effect.assets.filter((a) => a.type === "texture")); // 3. Mount the renderer and spawn an effect instance. const vfx = new ThreeVfxRenderer({ scene, camera, textureProvider: textures }); const burst = vfx.createEffect(effect, { position: [0, 1, 0], seed: 42 }); // 4. Advance exactly once per host frame, in SECONDS. const clock = new THREE.Clock(); renderer.setAnimationLoop(() => { vfx.update(clock.getDelta()); renderer.render(scene, camera); }); // Replay the one-shot wherever you need it: // burst.setTransform({ position: [x, y, z] }); // burst.restart(); // Teardown when the owning scene is released: // vfx.destroy(); // textures.destroy();

update() takes seconds, not milliseconds. If your loop hands you deltaMS, pass deltaMS / 1000. Passing milliseconds fast-forwards the whole effect in one frame — the most common integration bug.

Concepts

Export bundle anatomy

out/vfx/ manifest.json # effect + asset index, validation status, source hashes effects/*.json # compiled effects: emitters, timeline, assets, support <asset paths> # byte-copies of referenced files, preserving their # asset-root-relative paths (spark.png, # particles/spark.png, materials/fire.material, ...)

The manifest indexes every compiled effect (id, name, path, source hash, support report) and every deduplicated asset reference (texture, material, or mesh). Asset folders are not fixed — resolve manifest.assets[*].path and effect.assets[*].path relative to the bundle root and nothing else. Games load only this bundle; the authoring project (vfx-editor.prj and its effect JSON) is never a runtime input.

Target profiles

Every effect declares a targetProfile: pixi-ui-2d, three-world-3d, or portable (must satisfy both backends). The profile drives validation and the per-backend support reports; for a Three.js game you will normally author against three-world-3d or portable.

Deterministic simulation & seeds

The simulation core is shared by every backend and fully deterministic: an effect created with the same seed produces identical motion on every run, every machine, and both renderers. createEffect defaults the seed to a fixed constant, so even "unseeded" instances replay identically — pass a different seed per spawn if you want visual variety between instances. seek(timeSeconds) exploits the same property: it deterministically replays the simulation to any point in time.

Support reports

Every exported effect carries a support object: an overall status (supported / partial / blocked), the target profile, and per-backend reports under support.backends.pixi2d and support.backends.three3d, each with warnings and blockers (diagnostic objects with code, path, and message), plus informational notes when a backend renders an authored setting with different semantics. Check the three3d report before shipping: a blocked effect must not be treated as supported, and a partial effect contains deliberate approximations that the warnings enumerate. Passing requiredBackend: "three3d" to the loader enforces the blocked case for you.

API reference

Everything below is imported from nixie-fx/three unless stated otherwise. The bundle loader lives in nixie-fx/export (browser-safe; Node filesystem helpers are isolated at nixie-fx/export/node), and engine-level types re-export from the package root nixie-fx.

ThreeVfxRenderer

new ThreeVfxRenderer(options: ThreeVfxRendererOptions)

The Three.js backend adapter. It owns a single THREE.Group (renderer.root) that holds every effect instance, and implements the shared VfxRendererBackend contract (backendId: "three3d"). If parent or scene is passed, the root is mounted in the constructor; otherwise call mount(container) yourself.

Constructor option Type Default Description
camera THREE.Camera required Used for billboard orientation, distance sorting, and camera-facing modes. Swap later with setCamera().
scene THREE.Scene Container the root group is added to on construction.
parent THREE.Object3D Alternative mount container; takes precedence over scene when both are given.
textureProvider ThreeVfxTextureProvider Resolves texture asset refs to THREE.Texture. Optional — see Providers.
meshProvider ThreeVfxMeshProvider Resolves prepared mesh asset refs to THREE.BufferGeometry for mesh particles and mesh-surface emission. Optional.
materialProvider ThreeVfxMaterialProvider Lets the host replace the entire built-in particle material for chosen emitters. Optional.
materialGraphProvider (shaderId: string) => ShaderGraph | undefined Resolves material-instance shader IDs to parsed .material graphs. Optional; needed only when emitters use authored materials.
clockSpace "world" | "local" Declared in the options type but currently unused by the runtime (reserved).
previewBloomEnabled boolean false Encodes HDR particle color for a preview-grade bloom pass. Leave off when your game runs its own HDR/bloom pipeline.
previewBloomThreshold number 1 Emissive strength above which a particle counts as a bloom source (clamped ≥ 0). Also feeds stats.bloomSourceParticles.
previewExposureStops number 0 Preview exposure in stops, clamped to −2..2.
captureDebugTransforms boolean true Records a per-particle debug transform every frame for getParticleDebugTransforms(). Set false in production builds.
Member Description
backendId / capabilities "three3d" and the static capability record (true 3D quads, mesh particles, GPU depth, instanced material streams, runtime sampler bindings).
root THREE.Group containing every instance.
stats Aggregated ThreeVfxRendererStats across all instances, plus effectCount. See Stats.
mount(container) / unmount() Attach or detach the root group.
createEffect(effect, options?) Creates, mounts, and (by default) starts a ThreeVfxEffectInstance. Accepts an exported effect object (from bundle.effectsById) or an authoring-format definition; both are normalized. Throws if the renderer was destroyed.
removeEffect(instance, destroy = true) Detaches an instance from the renderer; destroys it unless destroy is false.
update(deltaSeconds) Advances every instance and refreshes stats. Call exactly once per host frame — do not also call instance.update() for instances owned by the renderer.
getSupport(effect) Computes the three3d VfxBackendSupportReport for a normalized effect definition at runtime.
setCamera(camera) Rebinds the camera on the renderer and all instances.
setPreviewBloomOptions({ enabled?, threshold?, exposureStops? }) Updates preview bloom settings on all instances.
destroy() Destroys every instance, unmounts the root, and makes further createEffect calls throw.

createEffect options (VfxEffectOptions)

Option Type Default Description
position [x, y, z] [0, 0, 0] World-space effect origin.
rotation [x, y, z] Euler rotation of the instance root, in radians.
scale [x, y, z] Scale of the instance root.
seed number 0x7F4A7C15 Deterministic simulation seed. Identical seeds replay identical motion.
timeSeconds number 0 Initial simulation clock value (clamped ≥ 0).
autoStart boolean true When false, the instance is created idle; call play(), restart(), or emitBurst() to run it.
runtimeParameters { emissionRateMultiplier?, initialVelocityMultiplier? } identity Effect-level multipliers applied inside the deterministic engine runner.

ThreeVfxEffectInstance

The handle returned by createEffect. It owns a root group (one per instance, inside the renderer's root), a per-instance stats object, and the full lifecycle API:

Member Description
isActive true while the underlying simulation is running (looping, or a one-shot that has not completed).
play() Starts a fresh run if the effect completed, otherwise resumes emission; also unpauses.
pause() Freezes the instance; update() becomes a no-op until play().
stop() Halts the simulation and immediately clears all rendered particles.
allowCompletion() Stops further emission but lets live particles finish their lifetimes — the graceful way to end a looping effect.
update(deltaSeconds) Advances this instance only. Use for standalone instances; renderer-owned instances are advanced by renderer.update().
seek(timeSeconds) Deterministically replays the simulation from zero to the target time in fixed 1/60 s steps. Cost is proportional to the target time — fine for scrubbing and replays, not for per-frame use.
restart() seek(0) + play — one call to run a one-shot again.
setTransform({ position?, rotation?, scale? }) Moves the effect. position also feeds the simulation (emission follows the new origin; world-space particles already emitted stay put).
setVisible(visible) Toggles rendering; the simulation keeps running while hidden.
setRenderOrder(n) Base Three.js render order for the instance; emitter layer ranks offset from it.
setCamera(camera) Rebinds the camera for this instance and redraws.
setRuntimeParameters(patch) Effect-level emissionRateMultiplier / initialVelocityMultiplier.
setPreviewBloomOptions({ enabled?, threshold?, exposureStops? }) Per-instance preview bloom settings.
updateDefinition(effect, { preserveViews? }) Hot-swaps the effect definition on a live instance (editor-style live editing). Returns the normalized definition; rebuilds emitter views unless preserveViews is true.
getParticleDebugTransforms(out?) Appends the last frame's per-particle debug transforms (mode, position, normal, size, matrix, bounds) — the data behind the editor's wireframe overlay. Empty when captureDebugTransforms is false.
stats / root Per-instance ThreeVfxEffectStats and the instance's THREE.Group.
destroy() Stops the effect, disposes owned GPU resources, and removes the root from its parent. Host-provided materials and provider-owned textures are never disposed.

Runtime controls & host injection

Beyond the shared lifecycle, the Three instance exposes host-injection APIs that drive an authored effect from game state without touching the effect definition. Identity values (1, [1,1,1,1], null) always restore authored behavior.

instance.setEmitterRuntimeParameters(emitterId, patch)

Merges an emitter-scoped parameter patch. The first two fields run inside the shared deterministic engine; the rest apply at draw time on the Three backend only:

Patch field Type Identity Description
emissionRateMultiplier number 1 Scales continuous emission for this emitter (engine-side, deterministic, composes with the effect-level multiplier).
initialVelocityMultiplier number 1 Scales spawn velocity (engine-side).
sizeMultiplier number 1 Uniform draw-time size scale (clamped ≥ 0).
sizeMultiplierValue ParticleScalarValue | null null Scalar-or-curve size multiplier sampled per particle at its normalized age; compiled to a lookup table on set, so per-frame sampling never allocates. Partial values are accepted (e.g. { mode: "curve", curve: [...] }).
colorTint [r, g, b, a] [1, 1, 1, 1] RGBA multiplier over the sampled particle color; alpha scales opacity.
colorOverLifetimeGradient ParticleColorGradientSettings | null null Replaces the authored color-over-lifetime gradient at draw time — even when the authored color module is off. Normalized on set; partial values accepted.

instance.emitBurst(emitterId, { count?, position? }) → number

Emits an immediate burst from one emitter and returns the emitted count (capacity-clamped; count defaults to 1, position temporarily overrides the emitter origin for just this burst). If the effect already completed, it restarts first so the burst always lands — the way hosts trigger moments like "the score changed" without authoring burst schedules around wall-clock time.

instance.setEmissionGeometry(emitterId, geometry | null)

Injects a live THREE.BufferGeometry as the emission source for a mesh-shaped emitter, overriding the authored spawn.meshAsset (or supplying one where none is authored). The geometry is copied on bind; to pick up later mutations, call it again. null returns the emitter to provider/asset-driven emission.

instance.setRenderGeometry(emitterId, geometry | null)

Injects live geometry for a mesh-rendered (meshAsset) emitter — each particle renders as a copy of it — overriding the authored mesh asset, or supplying one when no asset is authored. Rebuilds that emitter's view; null restores the provider/asset.

// Drive an authored ember effect from live game state // (this exact pattern renders the nixiefx.com landing digits): embers.setEmissionGeometry("embers", digitMesh.geometry); // spawn on the digit embers.setTransform({ position: [0, 0, digitZ] }); embers.emitBurst("embers", { count: 24 }); // the digit changed embers.setEmitterRuntimeParameters("embers", { emissionRateMultiplier: 2.5, colorOverLifetimeGradient: hotGradient, // null restores the authored one sizeMultiplierValue: { mode: "curve", curve: sparkSizeCurve }, });

loadVfxExportBundle

loadVfxExportBundle(input, options?) → VfxExportBundle
— from nixie-fx/export

Verifies and indexes an exported bundle without doing any I/O — you fetch the JSON however your platform does (HTTP, filesystem, bundler import) and hand over the parsed values. It checks envelope kinds and format version, effect/asset uniqueness, effect↔manifest identity (id, source ids, hashes, timestamps), that every effect-referenced asset appears in the manifest, recomputes the manifest source hash, and rejects bundles whose validation was blocked.

Input / option Type Default Description
input.manifest unknown required Parsed manifest.json value.
input.effectsByPath Record<string, unknown> required Parsed effect JSON keyed by each manifest entry's path (e.g. "effects/impact-burst.json").
input.assetPaths Iterable<string> Bundle-relative paths of deployed asset files, when the host can enumerate them.
options.requiredBackend "pixi2d" | "three3d" Throws if any effect is blocked on this backend. Pass "three3d" for a Three.js game.
options.requiredEffectIds string[] Throws unless every named effect id is present.
options.requireEveryAsset boolean false With assetPaths, throws if any manifest asset file is missing from the deployment.

The returned VfxExportBundle has three fields: manifest (the typed manifest), effectsById, and effectsByPath — both ReadonlyMaps of verified VfxExportedEffect objects, which is exactly what createEffect consumes. The lower-level validators parseVfxExportManifest(value) and parseVfxExportedEffect(value) are exported too.

Providers — all optional

Every provider is optional. An effect that references no file assets — for example the CLI's default effect, whose billboards use the built-in procedural shape — renders with new ThreeVfxRenderer({ scene, camera }) and nothing else. Add a provider only when the effect's assets list needs it.

ThreeVfxTextureProvider

Resolves texture references ({ type: "texture", id, path }) to THREE.Texture. path is the raw authored asset-root-relative path — treat it as a lookup key; hosts that pack atlases map it to their own frames inside the provider. getTexture must be synchronous, so preload before spawning.

Method Required Description
getTexture(ref) yes Returns the loaded THREE.Texture for a ref, or undefined if not (yet) available.
preload(refs) no Loads a batch of refs ahead of first use.
resolveUrl(ref) no Maps a ref to a fetchable URL.
release(scope?) no Frees provider-owned textures on scene teardown.

new ThreeVfxTextureStore({ resolveUrl, loadingManager?, configureTexture? })

A ready-made provider that owns loading, configuration, and cleanup. You supply resolveUrl(path) (map the bundle-relative path to a URL); it loads through THREE.TextureLoader, sets SRGBColorSpace, deduplicates in-flight requests, and lets you tweak each texture via configureTexture(texture, ref). Methods: preload(refs), preloadEffect(effect), getTexture(ref), resolveUrl(ref), release() (dispose all, keep usable), destroy().

ThreeVfxMeshProvider

One synchronous method: getMeshGeometry(ref) → BufferGeometry | null. Used for both mesh-rendered particles (mesh.asset) and mesh-surface emission (spawn.meshAsset). Return prepared geometry — raw FBX or other authoring formats are not runtime inputs. The renderer re-queries every frame with cheap identity checks, so returning null now and the loaded geometry later works as an async load trigger; unresolved mesh-rendered emitters simply stay hidden and are reported in stats.missingMeshRefs.

ThreeVfxMaterialProvider

Optional escape hatch: getParticleMaterial?(effect, emitterId) → Material | null. Returning a material hands that emitter's entire surface pipeline to the host — no texture frames, no material graph, no instanced fast path; the runtime only drives transforms and disposal stays with the host.

ThreeVfxMaterialGraphProvider

A function, (shaderId) => ShaderGraph | undefined. When emitters use authored node materials, parse each exported .material JSON (a graph, from the bundle's material assets) with normalizeShaderGraph from nixie-fx/materials and return graphs by their id. Unresolvable references are reported in stats.missingMaterialRefs and the emitter renders with a fallback texture.

Stats

instance.stats is a ThreeVfxEffectStats; renderer.stats aggregates all instances and adds effectCount. Values refresh on every update().

Field Meaning
activeParticles Live particles in the simulation.
visibleParticles Particles actually drawn this frame.
capacity Summed maxParticles across emitters.
emittedLastFrame Particles spawned during the last update.
bloomSourceParticles Particles whose emissive strength exceeded previewBloomThreshold — a cheap proxy for "how much will bloom light up".
drawCalls Estimated draw calls this frame: 1 per instanced emitter, 1 per visible particle on the legacy path, plus 1 per visible trail mesh.
instancedDrawCalls Emitters rendered through the instanced fast path.
legacyParticleDrawCalls Per-particle mesh draws — the number to watch when budgeting. See the performance guide.
missingMeshRefs Mesh asset refs the provider could not resolve (render or emission).
missingMaterialRefs Material shader ids with no graph resolved.
unsupportedFeatures Human-readable strings — backend support blockers (path: message) plus material features the Three compiler could not honor. Non-empty means something authored is not rendering as designed.
effectCount (renderer only) Number of live effect instances.

Texture frames, flipbooks & HDR color

Flipbooks. Emitters with the texture-sheet animation module slice the authored texture into a uniform grid at view build time and select each particle's frame deterministically from its age, seed, and the authored frame curve. Flipbook emitters render on the per-particle path (they are excluded from the instanced fast path).

Procedural billboards. Billboard emitters with no texture and no material render the built-in procedural shape — circle or square with an alpha falloff controlled by billboard.softness — generated in pure math (no canvas), cached per shape + softness, and shared across views. This is why assetless effects need no texture provider.

HDR color. Authored emissive intensity multiplies particle color linearly, so colors can exceed 1.0. By default the runtime tone-maps over-range colors (ACES-style) so they stay plausible on an LDR canvas while sub-1.0 colors pass through untouched. With previewBloomEnabled, over-threshold energy is instead encoded into the output color to feed a threshold-driven bloom pass — a preview-grade approximation. Games with a real HDR pipeline should keep it disabled and drive their own bloom; stats.bloomSourceParticles and the authored intensities survive export precisely for that.

Utility functions

Export Description
normalizeThreeVfxEffect(value) Normalizes an exported effect or authoring JSON into a ParticleEffectDefinition — what createEffect uses internally.
threeGeometryToEmissionInput(geometry) Adapts a BufferGeometry into the engine's plain-array emission input (positions, indices, normals); returns null without usable position data. Reads through attribute accessors, so interleaved and normalized attributes work.
reverseGeometryWinding(geometry) Flips triangle winding in place — the runtime uses it for the flipWinding mesh option.
repairMirroredGeometryWinding(geometry) Reverses winding only when the geometry's transform mirrors it (negative determinant).
updateInstancedParticleOrder(state, sortMode, out) Low-level helper computing the instance write order for a sort mode. Exposed for tests and custom tooling; games normally never call it.

Performance guide

The instanced fast path

The single biggest lever. Eligible emitters render as one InstancedMesh draw call regardless of particle count; everything else falls back to the legacy path of one THREE.Mesh (and one draw call) per visible particle. An emitter is instanced when all of these hold:

Condition Falls back to per-particle meshes when…
Billboard mode The emitter renders mesh particles.
Unlit shading shading: "lit" (needs real lighting materials).
A texture — authored or the built-in procedural shape The texture provider cannot resolve the authored path (fix the provider key).
No node material assigned The emitter uses a material graph (compiles to a bespoke shader per particle).
No trails, no flipbook Trails or texture-sheet animation are enabled on the emitter.
Opacity from texture alpha, not inverted opacitySource is a channel/luminance/constant variant, or opacityInvert is on.
Alpha or additive blend premultiplied blend — deliberately excluded, because the instanced shader discards near-zero-alpha texels, which would kill exactly the low-alpha glow texels premultiplied blending exists for.
Any sort mode Never — age sorts pre-order the instance write order, and distance sorts re-sort written instances against the camera each frame, so sorting no longer costs you the fast path.
No host material override A materialProvider supplied a custom material for this emitter.

Version note: in the published nixie-fx 0.1.1, the fast path additionally required an authored texture file (the procedural shape did not qualify) and a sort mode of none or oldestFirst. Current builds lift both restrictions; with 0.1.1, keep hot billboard emitters textured and on those two sort modes to stay instanced.

Draw-call budgeting

Watch stats.instancedDrawCalls versus stats.legacyParticleDrawCalls. A hundred instanced emitters are cheap; a hundred legacy particles are a hundred draw calls. Practical rules:

  • Keep high-count emitters (sparks, embers, rain) inside the fast path conditions above. Reserve trails, flipbooks, lit shading, and node materials for low-count hero emitters.
  • Per-emitter maxParticles is the hard cap (clamped to 1..4096 at validation); the runtime pre-allocates instanced buffers at that capacity, so set it near real peak usage.
  • Set captureDebugTransforms: false in production — it records a per-particle transform object every frame, which is allocation pressure you only want in tooling.
  • seek() replays the simulation from zero at 1/60 s steps — never call it per frame; use update().
  • One renderer per scene is the intended shape; effect instances are cheap, renderers are not free.

Bloom and HDR

Preview bloom (previewBloomEnabled) encodes bloom energy into vertex colors — good for editor-like previews, wrong for a game that already runs UnrealBloomPass/postprocessing HDR. In a real pipeline, leave it off: authored HDR intensities arrive linearly in particle color, over-range values tone-map gracefully, and your own bloom threshold does the rest.

Textures

The instanced shader samples the emitter texture across the full 0..1 UV range, so one texture per emitter is the contract — atlas sub-frames via texture.offset / repeat are not applied on the fast path. Since every instanced emitter is a single draw call anyway, atlasing buys you nothing on Three (it matters on the PixiJS backend, where batching is texture-driven). Prefer small power-of-two textures with meaningful alpha; flipbook sheets should be uniform grids exactly as authored.

The PixiJS runtime

The same exported bundle also runs on PixiJS — PixiVfxRenderer from nixie-fx/pixi follows the same lifecycle (construct with a parent container and optional textureProvider, createEffect, update(seconds) once per frame, destroy()), with 2D screen-space semantics and batched particle containers instead of world quads. Load the bundle with requiredBackend: "pixi2d" and read the support.backends.pixi2d report — some features (prepared 3D mesh particles, mesh-surface emission, lit shading, GPU depth) are Three.js-only. The full capability matrix lives in the feature reference.

Beyond Pixi and Three — porting

The export format is engine-neutral JSON: emitters, curves, gradients, and material graphs carry no PixiJS or Three.js types anywhere. The two shipped adapters are thin renderers over the same deterministic simulation core, and the whole runtime is MIT licensed with compact, heavily commented source — the Three adapter is a workable reference implementation for porting to another engine (Godot, Unity, bevy, a custom renderer), whether the port is written by a person or by an AI assistant reading the source. If you build one, the support-report machinery is designed to be extended with new backend ids.

Troubleshooting

Symptom Cause and fix
Effect plays but every particle is a flat white square The effect references a texture the provider did not resolve — no textureProvider was passed, the texture was not preloaded before spawning (getTexture is synchronous), or the provider keys by a different path than the authored ref.path. Assetless procedural billboards are not this symptom — they render their circle or square shape with no provider.
Nothing renders at all Check in order: is update() being called with seconds; is the renderer mounted (pass scene/parent or call mount()); was the instance created with autoStart: false and never played; is a mesh-rendered emitter waiting on a meshProvider (see stats.missingMeshRefs — unresolved mesh emitters hide instead of falling back).
Particles vanish behind opaque scenery, or punch holes in it Depth flags. depthTest is authored per emitter; depth writes are automatically disabled for additive/premultiplied blends and for translucent particles (alpha < 1), and an instanced batch disables them when any instance is translucent. Opaque/masked material blends force depth writes on. If particles z-fight your geometry, author the intended flags rather than fighting the materials at runtime.
Additive glow looks dim or clipped Over-range HDR color is tone-mapped by default. If you run your own bloom, raise authored intensity and let your pipeline pick it up; do not enable preview bloom on top of a real bloom pass.
loadVfxExportBundle throws The message names the check that failed: blocked validation ("blocked by validation errors" — re-export after fixing the project), a missing effect file ("missing effects/…" — deploy the whole bundle, then re-fetch), a stale manifest hash (mixed files from two exports — redeploy atomically), or an effect "blocked on required backend three3d" (author against a Three-compatible profile).
Bundle requests 404 in production out/vfx must be deployed as static files and every path resolved against the bundle root. Asset paths can contain folders (particles/spark.png) — copy the directory tree, not just manifest.json and effects/.
npm install fails on peer versions nixie-fx 0.1.x declares three >=0.184.0 <0.186.0 and pixi.js >=8.19.0 <9.0.0 as optional peers, and requires Node ≥ 20. You only need the renderer you import.
Something authored is not showing up Read stats.unsupportedFeatures: each entry is a path: message string pointing at the authored setting the backend cannot honor (plus any material-graph features the compiler skipped). Cross-check the effect's exported support.backends.three3d report — a partial status lists every approximation in its warnings.

Resources

This reference is maintained by the team behind NixieFX, the browser-based particle editor for PixiJS and Three.js. A machine-readable version is available at /threejs-runtime.md, and a site index for LLMs at /llms.txt.