On this page
  1. tecs.gfx.particles
  2. Emission and motion
  3. Playback
  4. Pool and snapshots
  5. Blending
  6. Module contents
    1. Constructors
    2. Types
    3. Functions
    4. Values
  7. Constructors
    1. newCurve
    2. newEffect
    3. newGradient
  8. Types
    1. Color
    2. Curve
    3. Draining
    4. Effect
    5. EffectOptions
    6. EmitterOptions
    7. EmitterState
    8. Gradient
    9. Holding
    10. InitialOptions
    11. ParticleEmitter
    12. Pool
    13. PoolOptions
    14. Range
    15. RenderOptions
    16. ScheduleOptions
    17. SpawnOptions
    18. UpdateOptions
    19. Value
  9. Functions
    1. find
    2. names
    3. plugin
    4. poolOf
    5. reset
  10. Values
    1. CURVE_SAMPLES
    2. EFFECT
    3. EFFECT_FLOATS
    4. EMITTER
    5. EMITTER_FLOATS
    6. STATE_FLOATS

tecs.gfx.particles

GPU particle effects, emitter playback, pool sizing, and rendering limits.

An entity represents an emitter. The GPU owns individual particles, so game code controls an effect and its emitter rather than inspecting particles one at a time.

local sparks <const> = tecs.gfx.particles.newEffect({
    name = "game.sparks",
    capacity = 256,
    schedule = {rate = 120, duration = 0.3},
    spawn = {shape = "disc", width = 4, outward = true},
    initial = {
        lifetime = {min = 0.2, max = 0.5},
        speed = {min = 40, max = 120},
        size = 3,
    },
    update = {
        drag = 2.0,
        size = tecs.gfx.particles.newCurve({
            {0.0, 1.0},
            {1.0, 0.0},
        }),
    },
})

world:addPlugin(tecs.gfx.particles.plugin({
    renderer = app.renderer,
    capacity = 4096,
}))

world:spawn(
    tecs.Transform2D(120, 80, 0, 1, 0, 1, 1),
    tecs.gfx.particles.ParticleEmitter({effect = sparks})
)

An immutable Effect describes schedule, spawn, initial state, updates, and rendering. A ParticleEmitter names the effect and carries playback state. Effect names form a snapshot compatibility surface.

Emission and motion

Schedules combine rate, delay, duration, looping, and timed bursts. duration = 0 keeps a cycle open. An emission that finds no free effect slot either drops the new particle or replaces the oldest, according to overflow.

Spawn shapes include point, line, rectangle, rectangle edge, disc, ring, and cone. World-space particles detach from later emitter movement. Local-space particles follow the emitter for their full life. inheritVelocity applies only in world space.

Curves and gradients sample normalized age from zero to one. Numeric properties accept a constant or a {min, max} range.

Playback

play starts or resumes emission. stop resets the schedule while live particles drain. pause holds the schedule and live field. clear kills the field without changing emission. restart resets schedule and randomness. burst queues a count for the next step.

finished derives its answer from the schedule and the longest lifetime. estimatedCount integrates the schedule over the mean lifetime, and may differ after overflow or a truncated burst. Game code cannot inspect, move, kill, or count individual particles.

Pool and snapshots

The plugin fixes world capacity and maximum emitters at installation. Each emitter reserves its effect capacity. An emitter that cannot fit draws nothing and logs the refusal.

Snapshots store effect name, seed, configuration, and playback state. They do not store individual particles, so a restored emitter starts empty and refills.

Blending

render.blend selects "alpha", "additive", or "opaque", and defaults to "alpha". The first two draw in the forward pass over the composited image, depth tested and not written, so alpha means what it says: a gradient that ends transparent fades out, and "additive" adds light instead of covering, which is what fire, glow, and sparks want. "opaque" keeps the effect in the G-buffer.

A blended effect is not in the G-buffer, so it casts no shadow and no light's occluder mask sees it, and it is not shadowed by one either. It also draws after compositing, so all of one effect's particles composite as one group in pool-slot order rather than being sorted against each other: an effect names one layer and its particles take one depth within it. Sorting within a pool is a separate feature.

One blended emitter puts the frame's forward lane to work, which is five compute passes and a draw a world with nothing blended in it does not pay. A world of "opaque" effects pays none of it, which is the other reason that name exists.

Module contents

Constructors

Constructor Description
newCurve Compiles keyframes into a curve over normalized age.
newEffect Registers an immutable effect under options.name and shares it among every emitter using that name.
newGradient Compiles keyframes into a color gradient over normalized age.

Types

Type Kind Description
Color type Represents a color accepted by particle authoring fields.
Curve record Stores a compiled scalar curve.
Draining record A slot range whose emitter is gone, held until its last particle can no longer be alive.
Effect record The handle newEffect returns and a ParticleEmitter names.
EffectOptions record Configures newEffect.
EmitterOptions record Configures a ParticleEmitter.
EmitterState enum The three values an emitter's state takes.
Gradient record Stores a compiled color gradient.
Holding record One emitter's place in the pool, as the pool remembers it.
InitialOptions record Configures a particle's initial properties.
ParticleEmitter record Controls an effect's playback state and per-instance scales.
Pool record Represents a world's installed particle pool.
PoolOptions record Configures plugin.
Range record The two-bound form of a Value.
RenderOptions record Configures how particles draw.
ScheduleOptions record Configures when particles emit.
SpawnOptions record Configures where particles spawn.
UpdateOptions record Configures how a particle changes over its normalized lifetime.
Value type Accepted wherever a property may vary per particle.

Functions

Function Kind Description
find Static Returns the effect registered under name, or nil when none exists.
names Static Returns every registered effect name in registration order.
plugin Static Installs the pool on a world.
poolOf Static Returns the pool installed on a world, or nil.
reset Static Forgets every registered effect, so a spec can register a set again.

Values

Value Type Description
CURVE_SAMPLES integer Read-only. Reports how many samples one compiled curve or gradient holds.
EFFECT {string : integer} Read-only. Reports each field's offset within an effect record.
EFFECT_FLOATS integer Read-only. Reports how many floats each effect record contains.
EMITTER {string : integer} Read-only. Reports each field's float offset within an emitter record, counting from zero.
EMITTER_FLOATS integer Read-only. Reports how many floats each emitter record contains.
STATE_FLOATS integer Read-only. Reports how many GPU state floats one particle carries.

Constructors

tecs.gfx.particles.newCurve Static

Compiles keyframes into a curve over normalized age.

function tecs.gfx.particles.newCurve(keys: {CurveKey}): Curve

Arguments

Name Type Description
keys {CurveKey} Supplies {age, value} pairs with age from zero through one, in any order. Empty or nil raises, and one key defines a constant.

Returns

Type Description
Curve Returns the piecewise-linear curve resampled at CURVE_SAMPLES points. Ages outside zero through one read as the nearest endpoint.

tecs.gfx.particles.newEffect Static

Registers an immutable effect under options.name and shares it among every emitter using that name. The function requires a unique name.

function tecs.gfx.particles.newEffect(options: EffectOptions): Effect

Arguments

Name Type Description
options EffectOptions Every section but name is optional and defaults to something drawable.

Returns

Type Description
Effect Returns a handle that stays valid until reset. The process-wide registry makes it reachable from every world.

tecs.gfx.particles.newGradient Static

Compiles keyframes into a color gradient over normalized age.

function tecs.gfx.particles.newGradient(keys: {CurveKey}): Gradient

Arguments

Name Type Description
keys {CurveKey} Supplies {age, color} pairs under the same rules as newCurve. Alpha fades a blended effect and is ignored by an opaque one.

Returns

Type Description
Gradient A gradient resampled like a curve, interpolating each channel separately, so two colors blend through whatever lies between them componentwise rather than around a color wheel.

Types

tecs.gfx.particles.Color type

Represents a color accepted by particle authoring fields.

type tecs.gfx.particles.Color = Color

tecs.gfx.particles.Curve record

Stores a compiled scalar curve.

record tecs.gfx.particles.Curve
    samples: {number}
end

tecs.gfx.particles.Curve.samples field

Read-only. Provides evenly spaced samples over normalized age from zero to one.

tecs.gfx.particles.Curve.samples: {number}

tecs.gfx.particles.Draining record

A slot range whose emitter is gone, held until its last particle can no longer be alive.

global record tecs.gfx.particles.Draining
    base: integer
    count: integer
    releaseAt: number
    blended: boolean
end

tecs.gfx.particles.Draining.base field

Engine-owned. Stores the first particle slot awaiting release.

tecs.gfx.particles.Draining.base: integer

tecs.gfx.particles.Draining.count field

Engine-owned. Stores the number of particle slots awaiting release.

tecs.gfx.particles.Draining.count: integer

tecs.gfx.particles.Draining.releaseAt field

Engine-owned. Stores the time when the slots become reusable.

tecs.gfx.particles.Draining.releaseAt: number

tecs.gfx.particles.Draining.blended field

Engine-owned. Reports whether the effect that held this range blends. The pool carries this value because the emitter and its effect are gone while the range's particles are still being drawn.

tecs.gfx.particles.Draining.blended: boolean

tecs.gfx.particles.Effect record

The handle newEffect returns and a ParticleEmitter names.

record tecs.gfx.particles.Effect
    name: string
    index: integer
    capacity: integer
    blend: string
    maxLifetime: number
    meanLifetime: number
    emitFor: number
    rate: number
    delay: number
    duration: number
    looping: boolean
    bursts: {number}
end

tecs.gfx.particles.Effect.name field

Read-only. Reports the effect name and the only property that outlives the process. Everything a save, a tool or a person identifies an effect by is this.

tecs.gfx.particles.Effect.name: string

tecs.gfx.particles.Effect.index field

Read-only. Reports where its record sits in the assembled effect table, counting from one. A position in registration order, so it means nothing outside the process that assigned it and nothing stores one.

tecs.gfx.particles.Effect.index: integer

tecs.gfx.particles.Effect.capacity field

Read-only. Reports how many live particles one emitter can hold.

tecs.gfx.particles.Effect.capacity: integer

tecs.gfx.particles.Effect.blend field

Read-only. Reports the blend mode the effect resolved to, which is one of the three render.blend names and never nil. The pool reads it to say how many of its slots may reach the forward pass.

tecs.gfx.particles.Effect.blend: string

tecs.gfx.particles.Effect.maxLifetime field

Read-only. Reports the longest particle lifetime in seconds, which decides when an emitter that has stopped emitting has finished.

tecs.gfx.particles.Effect.maxLifetime: number

tecs.gfx.particles.Effect.meanLifetime field

Read-only. Reports the mean particle lifetime in seconds, halfway between the bounds initial.lifetime was authored with. This is the window estimatedCount integrates the schedule over, and it is the steady-state live count divided by the emission rate.

tecs.gfx.particles.Effect.meanLifetime: number

tecs.gfx.particles.Effect.emitFor field

Read-only. Reports seconds from play to the last emission, or -1 for an effect that never stops emitting.

tecs.gfx.particles.Effect.emitFor: number

tecs.gfx.particles.Effect.rate field

Read-only. Reports the continuous emission rate used by finished and estimatedCount.

tecs.gfx.particles.Effect.rate: number

tecs.gfx.particles.Effect.delay field

Read-only. Reports the emission delay in seconds.

tecs.gfx.particles.Effect.delay: number

tecs.gfx.particles.Effect.duration field

Read-only. Reports the emission duration in seconds.

tecs.gfx.particles.Effect.duration: number

tecs.gfx.particles.Effect.looping field

Read-only. Reports whether the emission cycle repeats.

tecs.gfx.particles.Effect.looping: boolean

tecs.gfx.particles.Effect.bursts field

Read-only. Provides the compiled burst schedule.

tecs.gfx.particles.Effect.bursts: {number}

tecs.gfx.particles.EffectOptions record

Configures newEffect.

record tecs.gfx.particles.EffectOptions
    name: string
    capacity: integer
    overflow: string
    schedule: ScheduleOptions
    spawn: SpawnOptions
    initial: InitialOptions
    update: UpdateOptions
    render: RenderOptions
end

tecs.gfx.particles.EffectOptions.name field

Caller-writable. Sets the required process-wide unique effect name. Snapshots use this name to identify the effect.

tecs.gfx.particles.EffectOptions.name: string

tecs.gfx.particles.EffectOptions.capacity field

Caller-writable. Sets how many live particles one emitter can hold. Defaults to 256, and under one raises. A steady-state emitter needs at least rate * maxLifetime; a smaller capacity registers, drops the emissions that find no slot, and logs a warning saying so, because a spray thinner than the one authored is otherwise found by eye.

tecs.gfx.particles.EffectOptions.capacity: integer

tecs.gfx.particles.EffectOptions.overflow field

Caller-writable. Selects "drop" or "replace" when an emission arrives with no free slot: give way, or take the oldest live particle's. Defaults to "replace", and anything else raises.

tecs.gfx.particles.EffectOptions.overflow: string

tecs.gfx.particles.EffectOptions.schedule field

Caller-writable. Configures when the effect emits particles. An absent section takes every default below it.

tecs.gfx.particles.EffectOptions.spawn field

Caller-writable. Configures where the effect spawns particles. An absent section takes every default below it.

tecs.gfx.particles.EffectOptions.spawn: SpawnOptions

tecs.gfx.particles.EffectOptions.initial field

Caller-writable. Configures each particle's initial properties. An absent section takes every default below it.

tecs.gfx.particles.EffectOptions.update field

Caller-writable. Configures how particles change over their lifetime. An absent section leaves a particle on its launch velocity.

tecs.gfx.particles.EffectOptions.update: UpdateOptions

tecs.gfx.particles.EffectOptions.render field

Caller-writable. Configures how particles draw. An absent section draws white quads on layer one.

tecs.gfx.particles.EffectOptions.render: RenderOptions

tecs.gfx.particles.EmitterOptions record

Configures a ParticleEmitter.

record tecs.gfx.particles.EmitterOptions
    effect: Effect
    state: EmitterState
    seed: number
    rateScale: number
    sizeScale: number
    timeScale: number
    tint: Color
end

tecs.gfx.particles.EmitterOptions.effect field

Caller-writable. Selects the required effect. An absent one raises.

tecs.gfx.particles.EmitterOptions.effect: Effect

tecs.gfx.particles.EmitterOptions.state field

Caller-writable. Sets the initial playback state. Defaults to "playing", and anything outside the three raises.

tecs.gfx.particles.EmitterOptions.state: EmitterState

tecs.gfx.particles.EmitterOptions.seed field

Caller-writable. Sets the random seed. Defaults to one.

tecs.gfx.particles.EmitterOptions.seed: number

tecs.gfx.particles.EmitterOptions.rateScale field

Caller-writable. Multiplies the effect's emission rate. Defaults to one.

tecs.gfx.particles.EmitterOptions.rateScale: number

tecs.gfx.particles.EmitterOptions.sizeScale field

Caller-writable. Multiplies every particle's size. Defaults to one.

tecs.gfx.particles.EmitterOptions.sizeScale: number

tecs.gfx.particles.EmitterOptions.timeScale field

Caller-writable. Multiplies the speed of the effect clock. Defaults to one.

tecs.gfx.particles.EmitterOptions.timeScale: number

tecs.gfx.particles.EmitterOptions.tint field

Caller-writable. Multiplies every particle's color. Defaults to none, which multiplies by opaque white.

tecs.gfx.particles.EmitterOptions.tint: Color

tecs.gfx.particles.EmitterState enum

The three values an emitter's state takes.

global enum tecs.gfx.particles.EmitterState
    "paused"
    "playing"
    "stopped"
end

tecs.gfx.particles.Gradient record

Stores a compiled color gradient.

record tecs.gfx.particles.Gradient
    samples: {number}
end

tecs.gfx.particles.Gradient.samples field

Read-only. Provides evenly spaced RGBA samples over normalized age, with four floats per sample.

tecs.gfx.particles.Gradient.samples: {number}

tecs.gfx.particles.Holding record

One emitter's place in the pool, as the pool remembers it.

global record tecs.gfx.particles.Holding
    item: ParticleEmitter
    index: integer
    base: integer
    count: integer
    tint: Color
    tintR: number
    tintG: number
    tintB: number
    tintA: number
end

tecs.gfx.particles.Holding.item field

Engine-owned. Stores the emitter assigned to this allocation.

tecs.gfx.particles.Holding.item: ParticleEmitter

tecs.gfx.particles.Holding.index field

Engine-owned. Stores the reusable emitter index.

tecs.gfx.particles.Holding.index: integer

tecs.gfx.particles.Holding.base field

Engine-owned. Stores the first particle slot.

tecs.gfx.particles.Holding.base: integer

tecs.gfx.particles.Holding.count field

Engine-owned. Stores the number of particle slots.

tecs.gfx.particles.Holding.count: integer

tecs.gfx.particles.Holding.tint field

Engine-owned. Stores the tint as it was last read and what it parsed to. Parsing a color string per emitter per frame would be the one allocation in a loop that otherwise has none, and a tint changes rarely.

tecs.gfx.particles.Holding.tint: Color

tecs.gfx.particles.Holding.tintR field

Engine-owned. Stores the parsed red tint channel.

tecs.gfx.particles.Holding.tintR: number

tecs.gfx.particles.Holding.tintG field

Engine-owned. Stores the parsed green tint channel.

tecs.gfx.particles.Holding.tintG: number

tecs.gfx.particles.Holding.tintB field

Engine-owned. Stores the parsed blue tint channel.

tecs.gfx.particles.Holding.tintB: number

tecs.gfx.particles.Holding.tintA field

Engine-owned. Stores the parsed alpha tint channel.

tecs.gfx.particles.Holding.tintA: number

tecs.gfx.particles.InitialOptions record

Configures a particle's initial properties.

global record tecs.gfx.particles.InitialOptions
    lifetime: Value
    speed: Value
    size: Value
    rotation: Value
    angularVelocity: Value
    accelerationX: Value
    accelerationY: Value
    color: Color
end

tecs.gfx.particles.InitialOptions.lifetime field

Caller-writable. Sets particle lifetime in seconds. Defaults to one, and its upper bound is the effect's maxLifetime.

tecs.gfx.particles.InitialOptions.lifetime: Value

tecs.gfx.particles.InitialOptions.speed field

Caller-writable. Sets initial speed in world units per second. Defaults to zero.

tecs.gfx.particles.InitialOptions.speed: Value

tecs.gfx.particles.InitialOptions.size field

Caller-writable. Sets initial size in world units. Defaults to one.

tecs.gfx.particles.InitialOptions.size: Value

tecs.gfx.particles.InitialOptions.rotation field

Caller-writable. Sets initial rotation in radians. Defaults to zero.

tecs.gfx.particles.InitialOptions.rotation: Value

tecs.gfx.particles.InitialOptions.angularVelocity field

Caller-writable. Sets initial angular velocity in radians per second. Defaults to zero.

tecs.gfx.particles.InitialOptions.accelerationX field

Caller-writable. Sets constant horizontal acceleration. Independent axes make a plume drift apart instead of translating as a block. Defaults to zero.

tecs.gfx.particles.InitialOptions.accelerationX: Value

tecs.gfx.particles.InitialOptions.accelerationY field

Caller-writable. Sets constant vertical acceleration. Defaults to zero.

tecs.gfx.particles.InitialOptions.accelerationY: Value

tecs.gfx.particles.InitialOptions.color field

Caller-writable. Sets the color every particle starts with, multiplied by the gradient and by the emitter's tint. Defaults to opaque white.

tecs.gfx.particles.InitialOptions.color: Color

tecs.gfx.particles.ParticleEmitter record

Controls an effect's playback state and per-instance scales.

Deliberately small. If an instance could override every effect field then effects would stop being reusable GPU data and every emitter would grow to hold a copy. The effect owns capacity because it determines the pool slot range when the emitter starts. Read-only. Exposes the particle-emitter component.

record tecs.gfx.particles.ParticleEmitter is ecs.Component
    effect: Effect
    state: EmitterState
    seed: number
    rateScale: number
    sizeScale: number
    timeScale: number
    tint: Color

    burst: function(self, count: number)
    clear: function(self)
    estimatedCount: function(self): integer
    finished: function(self): boolean
    pause: function(self)
    play: function(self)
    restart: function(self)
    stop: function(self)
end

Interfaces

Interface
ecs.Component

tecs.gfx.particles.ParticleEmitter.effect field

Caller-writable. Selects the effect before spawning the emitter. It cannot change after spawn because the slot range is the effect's capacity, and changing it would mean reallocating. Despawn and spawn again, which is cheap.

tecs.gfx.particles.ParticleEmitter.effect: Effect

tecs.gfx.particles.ParticleEmitter.state field

Caller-writable. Reports whether the emitter plays, pauses, or stops. Set it through play, pause and stop rather than by assignment: those move the cycle's start step and the random generation with it, and a bare write leaves both where the previous state left them, so an emitter written back to "playing" resumes part way through its schedule instead of starting it again.

tecs.gfx.particles.ParticleEmitter.state: EmitterState

tecs.gfx.particles.ParticleEmitter.seed field

Caller-writable. Selects the random sequence. Two emitters with one seed produce one field.

tecs.gfx.particles.ParticleEmitter.seed: number

tecs.gfx.particles.ParticleEmitter.rateScale field

Caller-writable. Multiplies the effect's emission rate.

tecs.gfx.particles.ParticleEmitter.rateScale: number

tecs.gfx.particles.ParticleEmitter.sizeScale field

Caller-writable. Multiplies every particle's size.

tecs.gfx.particles.ParticleEmitter.sizeScale: number

tecs.gfx.particles.ParticleEmitter.timeScale field

Caller-writable. Multiplies the speed of the effect clock.

tecs.gfx.particles.ParticleEmitter.timeScale: number

tecs.gfx.particles.ParticleEmitter.tint field

Caller-writable. Multiplies every particle's color, alpha included, so a tint is how one emitter of a blended effect fades against another. An effect whose render.blend is "opaque" ignores the alpha.

tecs.gfx.particles.ParticleEmitter.tint: Color

tecs.gfx.particles.ParticleEmitter:burst Instance

Emits count particles at the next step boundary.

A stopped emitter ignores the call. The emitter truncates a count beyond its free capacity to the number that fits.

function tecs.gfx.particles.ParticleEmitter.burst(self, count: number)
Arguments
Name Type Description
self ParticleEmitter
count number
Returns

None.

tecs.gfx.particles.ParticleEmitter:clear Instance

Kills every live particle immediately.

Use this for teardown and state transitions. It leaves emission unchanged, so a playing emitter starts filling again on the next step.

function tecs.gfx.particles.ParticleEmitter.clear(self)
Arguments
Name Type Description
self ParticleEmitter
Returns

None.

tecs.gfx.particles.ParticleEmitter:estimatedCount Instance

Returns an estimate of how many particles remain alive.

Integrates the schedule over the effect's meanLifetime and treats every emission older than that as expired. An effect whose initial.lifetime is one number has a mean equal to that lifetime, so the answer is exact but for the step the emitter is part way through; a lifetime range makes it an expectation, because the host does not know which particles drew the short lifetimes. A truncated burst or an overflow replacement moves it either way. Reading the exact count back from the GPU would stall the pipeline.

function tecs.gfx.particles.ParticleEmitter.estimatedCount(
    self
): integer
Arguments
Name Type Description
self ParticleEmitter
Returns
Type Description
integer

tecs.gfx.particles.ParticleEmitter:finished Instance

Returns whether this emitter has stopped emitting and its last particle has died.

Exact, and it costs nothing: it is the schedule plus the longest lifetime against the emitter's own clock, all of which the host holds. This is what almost every "has the explosion finished" question is actually asking, and unlike a count it needs nothing read back from the GPU.

function tecs.gfx.particles.ParticleEmitter.finished(self): boolean
Arguments
Name Type Description
self ParticleEmitter
Returns
Type Description
boolean

tecs.gfx.particles.ParticleEmitter:pause Instance

Stops emission and holds. The schedule and the particles both keep their state, and neither ages while this is on.

function tecs.gfx.particles.ParticleEmitter.pause(self)
Arguments
Name Type Description
self ParticleEmitter
Returns

None.

tecs.gfx.particles.ParticleEmitter:play Instance

Starts or resumes emission.

From stopped this begins the cycle again; from paused it carries on from where it was, with the steps spent paused not counted against the schedule.

function tecs.gfx.particles.ParticleEmitter.play(self)
Arguments
Name Type Description
self ParticleEmitter
Returns

None.

tecs.gfx.particles.ParticleEmitter:restart Instance

Resets the schedule and the random sequence, and starts playing.

Reproducible: a restart of an emitter with the same seed produces the same field, because every particle's draws are a pure function of the seed, the generation, the serial and the property, and all four start over.

function tecs.gfx.particles.ParticleEmitter.restart(self)
Arguments
Name Type Description
self ParticleEmitter
Returns

None.

tecs.gfx.particles.ParticleEmitter:stop Instance

Stops emission and resets the schedule. Live particles drain.

The next play starts the cycle from its beginning with a fresh random sequence, which is what makes it reproducible. Killing the field instead is clear, and the two are deliberately separate.

function tecs.gfx.particles.ParticleEmitter.stop(self)
Arguments
Name Type Description
self ParticleEmitter
Returns

None.

tecs.gfx.particles.Pool record

Represents a world's installed particle pool.

record tecs.gfx.particles.Pool
    capacity: integer

    active: function(self): boolean
    blended: function(self): integer
    casting: function(self): integer
    count: function(self): integer
    destroy: function(self)
    record: function(
        self, frame: Frame, instances: Buffer, bounds: Buffer
    )
    takeDirty: function(self): {integer}
    write: function(
        self,
        floats: loader.CArray,
        bounds: loader.CArray,
        base: integer,
        first: integer,
        last: integer
    )
end

tecs.gfx.particles.Pool.capacity field

Read-only. Reports how many live particles the pool can hold.

tecs.gfx.particles.Pool.capacity: integer

tecs.gfx.particles.Pool:active Instance

Whether there is anything to dispatch over.

Published rather than derived, and this is the hazard the design named: an emitter dirties nothing, ever, while its whole field moves every frame. There is no archetype bit to read and no component write to observe, so a gate written as "nothing is dirty" would conclude wrongly every frame an emitter was running. The pool counts its own live emitters instead, and holds the gate open one frame past the last of them so the pass that hides their slots is the one that actually runs.

function tecs.gfx.particles.Pool.active(self): boolean
Arguments
Name Type Description
self Pool
Returns
Type Description
boolean

tecs.gfx.particles.Pool:blended Instance

Slots the forward pass may find a particle in.

Reserved slots rather than live particles, and it has to be: what is alive lives in a buffer the CPU never reads, so the honest answer here is the only one that cannot be too small. Too small is the failure that matters, because the backend skips the whole forward lane on a frame nothing said was blended and every particle of every blended effect would then go missing.

A slot with no live particle in it writes the hidden bound, so over-reporting costs the lane five compute passes over slots the mark pass has already rejected, and never a draw.

function tecs.gfx.particles.Pool.blended(self): integer
Arguments
Name Type Description
self Pool
Returns
Type Description
integer

tecs.gfx.particles.Pool:casting Instance

Particles never write caster lane signs into their bounds.

function tecs.gfx.particles.Pool.casting(self): integer
Arguments
Name Type Description
self Pool
Returns
Type Description
integer

tecs.gfx.particles.Pool:count Instance

function tecs.gfx.particles.Pool.count(self): integer
Arguments
Name Type Description
self Pool
Returns
Type Description
integer

tecs.gfx.particles.Pool:destroy Instance

Releases everything the pool owns. Safe to call more than once.

function tecs.gfx.particles.Pool.destroy(self)
Arguments
Name Type Description
self Pool
Returns

None.

tecs.gfx.particles.Pool:record Instance

function tecs.gfx.particles.Pool.record(
    self, frame: Frame, instances: Buffer, bounds: Buffer
)
Arguments
Name Type Description
self Pool
frame Frame
instances Buffer
bounds Buffer
Returns

None.

tecs.gfx.particles.Pool:takeDirty Instance

function tecs.gfx.particles.Pool.takeDirty(self): {integer}
Arguments
Name Type Description
self Pool
Returns
Type Description
{integer}

tecs.gfx.particles.Pool:write Instance

Hides the run and remembers where it landed.

Only ever called on a frame the layout moved, since nothing else is ever dirty. What it writes is the hidden slot every reserved run writes, and the simulate pass overwrites the live ones later on the same frame: the flush is recorded before the dispatch, so a relayout cannot lose a particle.

function tecs.gfx.particles.Pool.write(
    self,
    floats: loader.CArray,
    bounds: loader.CArray,
    base: integer,
    first: integer,
    last: integer
)
Arguments
Name Type Description
self Pool
floats loader.CArray
bounds loader.CArray
base integer
first integer
last integer
Returns

None.

tecs.gfx.particles.PoolOptions record

Configures plugin.

record tecs.gfx.particles.PoolOptions
    renderer: Renderer
    capacity: integer
    maxEmitters: integer
end

tecs.gfx.particles.PoolOptions.renderer field

Caller-writable. Selects the renderer whose instance buffer the pool uses and whose device its buffers and pipelines are built on. Required, and an absent one raises.

tecs.gfx.particles.PoolOptions.renderer: Renderer

tecs.gfx.particles.PoolOptions.capacity field

Caller-writable. Sets how many live particles the pool holds across the world. Defaults to 16384. The value remains fixed at install: changing it would move the run, and moving the run lays out the whole scene again.

tecs.gfx.particles.PoolOptions.capacity: integer

tecs.gfx.particles.PoolOptions.maxEmitters field

Caller-writable. Sets how many emitters the pool holds at once. Defaults to 256. An emitter that arrives past the limit draws nothing and logs the refusal.

tecs.gfx.particles.PoolOptions.maxEmitters: integer

tecs.gfx.particles.Range record

The two-bound form of a Value.

record tecs.gfx.particles.Range
    min: number
    max: number
end

tecs.gfx.particles.Range.min field

Caller-writable. Sets the inclusive lower bound.

tecs.gfx.particles.Range.min: number

tecs.gfx.particles.Range.max field

Caller-writable. Sets the inclusive upper bound.

tecs.gfx.particles.Range.max: number

tecs.gfx.particles.RenderOptions record

Configures how particles draw.

global record tecs.gfx.particles.RenderOptions
    material: string
    materialParam: number
    sprite: Sprite
    sheet: Sheet
    tag: string
    layer: integer
    pivotX: number
    pivotY: number
    alignment: string
    stretch: number
    blend: string
    clip: integer
end

tecs.gfx.particles.RenderOptions.material field

Caller-writable. Selects the material that shades the quad by name. An absent value samples the image array and covers the whole quad.

tecs.gfx.particles.RenderOptions.material: string

tecs.gfx.particles.RenderOptions.materialParam field

Caller-writable. Passes one number to the material, clamped to zero through 0.999 and ignored unless material names one. The instance carries the material as id + param in one float, and the vertex shader reads the integer part as the selector and the fraction as the parameter, so 1.0 would arrive as the next material's id with a parameter of zero. Defaults to zero.

tecs.gfx.particles.RenderOptions.materialParam: number

tecs.gfx.particles.RenderOptions.sprite field

Caller-writable. Selects a registered image from renderer.sprites:sprite. An absent value draws a white quad.

tecs.gfx.particles.RenderOptions.sprite: Sprite

tecs.gfx.particles.RenderOptions.sheet field

Caller-writable. Selects a sheet to animate instead of a still region. The cycle is played exactly once over each particle's own life and clamps at the end, so a randomized lifetime randomizes the playback speed.

Uses the same frame table an animated entity does, so per-frame durations, reverse and pingpong all arrive already spent.

tecs.gfx.particles.RenderOptions.sheet: Sheet

tecs.gfx.particles.RenderOptions.tag field

Caller-writable. Selects the sheet tag to animate. An absent value animates the whole sheet. A name the sheet does not carry animates the whole sheet too, and the sheet reports it at error level under the tecs.gfx logger, naming the tags it does carry. Defining the effect still succeeds, so a sheet re-exported without a tag keeps drawing.

tecs.gfx.particles.RenderOptions.tag: string

tecs.gfx.particles.RenderOptions.layer field

Caller-writable. Selects the render layer from one to layers.MAX. Defaults to one, and anything outside the range raises.

tecs.gfx.particles.RenderOptions.layer: integer

tecs.gfx.particles.RenderOptions.pivotX field

Caller-writable. Sets the horizontal pivot as a fraction of the frame from its top left. Defaults to 0.5, the middle.

tecs.gfx.particles.RenderOptions.pivotX: number

tecs.gfx.particles.RenderOptions.pivotY field

Caller-writable. Sets the vertical pivot as a fraction of the frame from its top edge. Defaults to 0.5, the middle.

tecs.gfx.particles.RenderOptions.pivotY: number

tecs.gfx.particles.RenderOptions.alignment field

Caller-writable. Selects "fixed" or "velocity" alignment. Velocity alignment adds the path's angle to the particle's own rotation, so a spinning spark aligned to its path still spins. Defaults to "fixed", and anything else raises.

tecs.gfx.particles.RenderOptions.alignment: string

tecs.gfx.particles.RenderOptions.stretch field

Caller-writable. Multiplies the along-path axis when aligned to velocity. Defaults to one.

tecs.gfx.particles.RenderOptions.stretch: number

tecs.gfx.particles.RenderOptions.blend field

Caller-writable. Selects "alpha", "additive", or "opaque". Defaults to "alpha", and anything else raises.

"alpha" and "additive" draw in the forward pass over the composited image, depth tested and not written, so a particle is still hidden by geometry in front of it. "alpha" keeps that much less of what is behind it and "additive" adds to it, so a dark additive particle changes nothing and overlapping ones brighten.

"opaque" draws into the G-buffer instead. What it gives up is alpha, which the G-buffer is written with replace and has nowhere to put: a fade to transparent writes opaque over what was behind it, and the only fade left is a size curve reaching zero. What it buys is everything that reads the G-buffer, which is the occluder mask and being shadowed by one.

tecs.gfx.particles.RenderOptions.blend: string

tecs.gfx.particles.RenderOptions.clip field

Caller-writable. Selects the clip region that contains the fragments. Defaults to zero, which is no clipping.

tecs.gfx.particles.RenderOptions.clip: integer

tecs.gfx.particles.ScheduleOptions record

Configures when particles emit.

global record tecs.gfx.particles.ScheduleOptions
    rate: number
    duration: number
    looping: boolean
    delay: number
    bursts: {ScheduleBurst}
end

tecs.gfx.particles.ScheduleOptions.rate field

Caller-writable. Sets the continuous emission rate in particles per second. Defaults to zero, which emits only what bursts asks for.

tecs.gfx.particles.ScheduleOptions.rate: number

tecs.gfx.particles.ScheduleOptions.duration field

Caller-writable. Sets the emission cycle duration in seconds. Defaults to zero, which leaves the cycle open.

tecs.gfx.particles.ScheduleOptions.duration: number

tecs.gfx.particles.ScheduleOptions.looping field

Caller-writable. Controls whether the emission cycle repeats. Defaults to false.

tecs.gfx.particles.ScheduleOptions.looping: boolean

tecs.gfx.particles.ScheduleOptions.delay field

Caller-writable. Sets the delay before emission starts in seconds. Defaults to zero.

tecs.gfx.particles.ScheduleOptions.delay: number

tecs.gfx.particles.ScheduleOptions.bursts field

Caller-writable. Defines at most four timed burst counts measured from the start of the cycle. A muzzle flash is one burst at time zero. Defaults to none, and a fifth raises.

tecs.gfx.particles.ScheduleOptions.bursts: {ScheduleBurst}

tecs.gfx.particles.SpawnOptions record

Configures where particles spawn.

global record tecs.gfx.particles.SpawnOptions
    shape: string
    width: number
    height: number
    arc: number
    rotation: number
    distribution: string
    direction: number
    spread: number
    outward: boolean
    space: string
    inheritVelocity: number
end

tecs.gfx.particles.SpawnOptions.shape field

Caller-writable. Selects "point", "line", "rectangle", "rectangleEdge", "disc", "ring", or "cone". Defaults to "point", and anything else raises.

tecs.gfx.particles.SpawnOptions.shape: string

tecs.gfx.particles.SpawnOptions.width field

Caller-writable. Sets width for a line or rectangle and radius for a disc, ring, or cone. Defaults to zero.

tecs.gfx.particles.SpawnOptions.width: number

tecs.gfx.particles.SpawnOptions.height field

Caller-writable. Sets height for a rectangle. Every other shape ignores it. Defaults to zero.

tecs.gfx.particles.SpawnOptions.height: number

tecs.gfx.particles.SpawnOptions.arc field

Caller-writable. Sets the sampled sector of a disc, ring, or cone in radians. Defaults to a full turn.

tecs.gfx.particles.SpawnOptions.arc: number

tecs.gfx.particles.SpawnOptions.rotation field

Caller-writable. Rotates the sampled area in radians, independently of launch direction. Defaults to zero.

tecs.gfx.particles.SpawnOptions.rotation: number

tecs.gfx.particles.SpawnOptions.distribution field

Caller-writable. Selects "uniform" or "normal" sampling. A gaussian spread makes a column read as a column rather than as a slab. Defaults to "uniform", and anything else raises.

tecs.gfx.particles.SpawnOptions.distribution: string

tecs.gfx.particles.SpawnOptions.direction field

Caller-writable. Sets the launch direction in radians and centers the full cone on it, so a particle leaves within plus or minus half of spread. Half-angle is the other plausible reading of spread and a mismatch is silent, so it is said here. Defaults to zero.

tecs.gfx.particles.SpawnOptions.direction: number

tecs.gfx.particles.SpawnOptions.spread field

Caller-writable. Sets the full cone width in radians. Defaults to zero, which launches every particle along direction.

tecs.gfx.particles.SpawnOptions.spread: number

tecs.gfx.particles.SpawnOptions.outward field

Caller-writable. Launches along the shape's outward normal rather than along direction. Meaningful for the shapes that have one: disc, ring, cone and a rectangle's edge. Defaults to false.

tecs.gfx.particles.SpawnOptions.outward: boolean

tecs.gfx.particles.SpawnOptions.space field

Caller-writable. Selects "local" or "world" space. Local particles follow the emitter's transform for their whole life; world particles read it once, at spawn, and then move independently. Neither is a good universal default, so it is explicit and defaults to "world". Anything else raises.

tecs.gfx.particles.SpawnOptions.space: string

tecs.gfx.particles.SpawnOptions.inheritVelocity field

Caller-writable. Sets how much emitter velocity a particle inherits, from zero to one. Defaults to zero, and world space is the only space it means anything in: a local particle is already carried by the emitter, so inheriting would carry it twice.

tecs.gfx.particles.SpawnOptions.inheritVelocity: number

tecs.gfx.particles.UpdateOptions record

Configures how a particle changes over its normalized lifetime.

global record tecs.gfx.particles.UpdateOptions
    drag: number
    radialAcceleration: Value
    tangentialAcceleration: Value
    size: Curve
    color: Gradient
end

tecs.gfx.particles.UpdateOptions.drag field

Caller-writable. Sets damping in units of one over seconds, applied as velocity = velocity / (1 + drag * dt), so it composes with any step. Defaults to zero.

tecs.gfx.particles.UpdateOptions.drag: number

tecs.gfx.particles.UpdateOptions.radialAcceleration field

Caller-writable. Sets acceleration away from the emitter. This and tangentialAcceleration are the difference between a fountain and a vortex. Defaults to zero.

tecs.gfx.particles.UpdateOptions.tangentialAcceleration field

Caller-writable. Sets acceleration at right angles to the emitter. Defaults to zero.

tecs.gfx.particles.UpdateOptions.size field

Caller-writable. Multiplies each particle's size over its life. An absent curve multiplies by one, so leaving it out costs nothing.

tecs.gfx.particles.UpdateOptions.size: Curve

tecs.gfx.particles.UpdateOptions.color field

Caller-writable. Multiplies each particle's color over its life. An absent gradient multiplies by white, so leaving it out costs nothing. Its alpha fades a blended effect and is ignored by an effect whose render.blend is "opaque".

tecs.gfx.particles.UpdateOptions.color: Gradient

tecs.gfx.particles.Value type

Accepted wherever a property may vary per particle.

type tecs.gfx.particles.Value = Value

Functions

tecs.gfx.particles.find Static

Returns the effect registered under name, or nil when none exists.

function tecs.gfx.particles.find(name: string): Effect

Arguments

Name Type Description
name string Nil answers nil rather than raising, so a restore holding no name takes the same path as one holding an unknown name.

Returns

Type Description
Effect The registered effect itself, not a copy, so two callers finding one name share it. Nil when nothing has that name.

tecs.gfx.particles.names Static

Returns every registered effect name in registration order.

function tecs.gfx.particles.names(): {string}

Arguments

None.

Returns

Type Description
{string} A fresh table each call, the caller's to keep and to modify.

tecs.gfx.particles.plugin Static

Installs the pool on a world. Without it, Tecs allocates and dispatches no particle work.

function tecs.gfx.particles.plugin(options: PoolOptions): function(
    World
)

Arguments

Name Type Description
options PoolOptions Requires renderer; both capacities have defaults.

Returns

Type Description
function(World) Returns the plugin to install on a world. Installing it on a world that already has a pool does nothing, and installing it on two worlds gives each its own run of the one renderer's buffer.

tecs.gfx.particles.poolOf Static

Returns the pool installed on a world, or nil. Tests and tooling use this function.

function tecs.gfx.particles.poolOf(world: World): Pool

Arguments

Name Type Description
world World Accepts a world with or without the plugin.

Returns

Type Description
Pool Returns the world's live pool, or nil when the plugin was never installed. The function does not copy the pool.

tecs.gfx.particles.reset Static

Forgets every registered effect, so a spec can register a set again. Every handle already handed out goes stale with it.

function tecs.gfx.particles.reset()

Arguments

None.

Returns

None.

Values

tecs.gfx.particles.CURVE_SAMPLES variable

Read-only. Reports how many samples one compiled curve or gradient holds.

tecs.gfx.particles.CURVE_SAMPLES: integer

tecs.gfx.particles.EFFECT variable

Read-only. Reports each field's offset within an effect record.

Published because the shaders state the same offsets and a disagreement between the two is silent: it draws something, just not the right thing. spec/particles_spec.lua reads both and holds them to each other, which is the only reason this is on the surface.

tecs.gfx.particles.EFFECT: {string: integer}

tecs.gfx.particles.EFFECT_FLOATS variable

Read-only. Reports how many floats each effect record contains.

tecs.gfx.particles.EFFECT_FLOATS: integer

tecs.gfx.particles.EMITTER variable

Read-only. Reports each field's float offset within an emitter record, counting from zero.

tecs.gfx.particles.EMITTER: {string: integer}

tecs.gfx.particles.EMITTER_FLOATS variable

Read-only. Reports how many floats each emitter record contains. An emitter record is much the smaller of the two.

tecs.gfx.particles.EMITTER_FLOATS: integer

tecs.gfx.particles.STATE_FLOATS variable

Read-only. Reports how many GPU state floats one particle carries.

tecs.gfx.particles.STATE_FLOATS: integer