Starship Horizons
Log In
☰
Knowledge Base›Effect Banks

Effect Banks

An effect bank is a named visual effect — an explosion, an engine trail, a shield impact, a burning hull. Each is built from one or more renderer layers, and can fire a sound bank alongside them.

Banks live in World/effect-banks.xml and are edited in Hydra's Effect editor. They are id‑keyed and module‑layered like every other resource.

Layers make an effect

A hull detonation is not one thing. It is a fireball, a flash, debris and a light, fired together off one clock:

<effect-bank id="Explosion" category="death" instances="8">
  <description>Standard hull detonation: what a DESTROYED object throws</description>
  <alias>Object</alias>
  <sound bank="Explosion" />
  <layer renderer="fireball" />
  <layer renderer="flash" />
  <layer renderer="debris" />
  <layer renderer="light" curve="blast" intensity="6" />
</effect-bank>

Every layer of one effect derives its time from a single clock, so the layers of one explosion cannot drift apart from each other.

Compare that with a glancing hit, which is deliberately one layer and no fireball:

<effect-bank id="Impact Sparks" category="weapon" instances="12">
  <layer renderer="sparks" space="world" rate="240" duration="120"
         life="500" speed="26" spread="1" drag="6" color="#ffd08a" />
</effect-bank>

That contrast is most of effect authoring: what an effect leaves out is what makes it read as a different event.

The renderers

Each layer names a renderer. The base game provides fourteen:

RendererDraws
fireballAn expanding burning ball. The core of a detonation.
flashA brief bright pop.
debrisThrown fragments.
ringAn expanding shockwave ring.
shockwaveThe front of a rolling blast: a sphere that grows at the blast's speed until it reaches its full reach. World space only.
lightA real light that illuminates nearby geometry.
sparksShort‑lived hot particles.
smokeRising, drifting volume. bloom below 1 makes each puff swell fast, as exhaust does leaving a nozzle; texture and blend (normal or additive) change what a puff is.
spriteA single textured billboard that can grow, spin and fade.
trailA ribbon following a moving source.
beamA line between two points.
blast, blast-classicParticle bursts.
wormholeA lensed throat onto another sky.

Bank attributes

AttributeMeaning
idHow content references it.
categoryA filing label for the editor — the base game uses weapon, death, damage, defense, light, trail, environment.
instancesHow many can run at once; 4 when left out. Past the cap, the oldest is torn down. Same reasoning as sound banks: shield hits want 16, a wormhole wants 2.
persistentAn effect started on an object with an instance id is remembered on that object, so a console that reloads or joins late, and the Game Master, see it too. Engine trails and hull fires are persistent; explosions are not. Running until stopped is the renderer's part: a smoke or sparks layer with duration="0" emits until the effect is stopped.
tierThe lowest console video tier that draws it — low, medium or high, low when left out. Set on a bank or on one layer.
<alias>Alternate names. As with sound banks, this is how legacy references (Object, Missile, Shield) still resolve.
<sound bank="...">A sound bank to fire with the visual.

The wrapper can carry master-scale and clip-radius. The server reads and serves them, but nothing draws by them yet, so they do not change how big an effect is or how far away it is drawn.

Ids are unique by replacement, silently: a second bank with the same id in one file replaces the first, and a later module's bank replaces the whole of an earlier one. An alias spelled like a real bank id never resolves, because ids are looked up first.

The architectural point: the server does not understand a layer

This is the thing worth knowing, because it changes what you can do without touching the engine.

Of everything on a <layer>, the server reads only renderer, tier, space and offset, plus any <shader> and <bind> children. Every other attribute is carried as opaque key/value and forwarded to the client verbatim — as strings, unparsed.

So adding a new kind of effect is a client file plus data, with no engine rebuild. There is no server‑side schema to extend and no C# to write.

Writing your own renderer

A renderer is a JavaScript module at /js/effects/<name>.js. Module Html folders overlay one virtual web root by priority, so a module dropping Html/js/effects/plasma-arc.js is served and imported exactly like a built‑in one — and <layer renderer="plasma-arc" /> then works.

Name the file in lower case: the renderer name is lower‑cased before it is imported. The default export is a class extending Mythrical, from /js/three/effect-kit.js:

constructor(ctx)      // build; add everything to this.Group
Update(elapsed)       // ms since last frame, slow-mo aware;
                      // call this.Dispose() when the effect is over
Stop()                // optional: a persistent effect asked to end gracefully
Dispose()             // release GPU resources; detach this.Group

ctx carries the layer's authored attributes in params, live game state in bindings, the scene, what the effect is attached to (source, null for a world‑anchored effect), the trigger position and radius, and clock — the one timeline every layer of the effect shares, in milliseconds. life is only what the trigger asked for, and 0 when it asked nothing; the built‑in renderers use a life param if the layer sets one and clock otherwise. Parameters that arrive with a trigger override the layer's own.

Params arrive as strings, because the server never parses them. Fold them with the kit's FxNum and FxBool rather than parseFloat alone: those refuse to poison a uniform with NaN, and FxBool handles the "false" case, which is truthy as a bare string.

Use effect-kit.js rather than reimplementing its pieces. It exists because writing a good renderer previously meant copying engine shader source — and the worst thing to go without is the log‑depth handling, whose failure mode is not a warning: fragments depth‑test in the wrong space and the effect hides behind the world, which presents as "my effect does not draw". The viewer uses reversed depth where the browser supports it and log depth elsewhere, so a shader that skips the log‑depth chunks can draw on your machine and vanish on another.

Load textures through the kit's cached loader. Each renderer building its own loader leaked a GPU texture per instance, so every projectile impact cost one.

Declare a static Manifest = { name, description, params, targets, spaces } and the Effect editor can offer your renderer and edit its parameters. spaces lists the layer spaces it handles (local, world, emit) and is ["local"] when left out. A renderer without a Manifest still runs in game, but the editor reports it as an error and will not save a bank that uses it.

Effects compile while the mission loads

When a console loads, every effect its video tier can draw is built once out of sight and its shaders compiled, behind the Initializing spinner, so the first explosion of a fight does not stall the frame. Restarting the same mission does no work again.

A custom renderer takes part without doing anything special, provided its constructor builds its materials from params alone. During the warm‑up it is constructed with no source, no position and a radius of 1, and is never updated. Materials made later, in Update or after an asynchronous load, are not warmed, and a constructor that throws on that bare context just leaves the renderer cold: the first real play pays the cost, and nothing breaks.

Blasts

A blast is an object with a speed. A weapon with class="Blast", or ordnance with a <blast speed="…"> child above 0, sends a front rolling outward instead of hitting everything in reach at once. Its effect is the Shockwave bank unless effect names another. The front's effect is fired once, with the full reach as its radius, the travel time as its life, and a speed trigger param; the server does not update it afterwards. So a front bank's renderer must grow by its own clock, and a radius‑scaled layer such as flash does not belong in one, because it would draw the whole reach at once.

Ordnance can also name an impact-effect bank for what its round shows on a hit.

Two things that will catch you

The file is generated, and comments do not survive a save. effect-banks.xml round‑trips through the Effect editor, so any XML comment you add is lost the next time it is saved. Per‑bank notes go in <description>, which does round‑trip.

Offset is per‑layer and sits within the effect. A layer's offset positions it relative to the effect; positioning the effect as a whole is done by whatever placed it — a vessel class, a model, a placement, or a mission action. A mission's Effect action can give its own offset property, in the same model space, to put a one‑off effect at a point on a hull. Hull fires are the example: each layer is offset to a point on the hull, and the effect rides the ship because it is in the local frame.

Saving in the editor takes effect on the server at once, but open consoles keep the banks they loaded. Reload a console to see a change. A hand edit to the file is read when the server starts.

Practical advice

  • Build up from one layer. Add layers until it reads, then stop. The one‑layer spark shower is as deliberate as the five‑layer detonation.
  • Add a light to anything that should feel bright. A fireball with no light layer does not touch the ships around it, and that is most of why an explosion can look flat.
  • Set instances honestly. Shield hits happen in volleys.
  • Mark continuous effects persistent, or a console that reloads or joins late will not see them.
  • Use description. It survives, and it is the only note that does.
Last updated 19 October 2026