# 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. ```teal local sparks = 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`](/modules/gfx/particles/#tecs.gfx.particles.Effect) describes schedule, spawn, initial state, updates, and rendering. A [`ParticleEmitter`](/modules/gfx/particles/#tecs.gfx.particles.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`](/modules/gfx/particles/#tecs.gfx.particles.newCurve) | Compiles keyframes into a curve over normalized age. | | [`newEffect`](/modules/gfx/particles/#tecs.gfx.particles.newEffect) | Registers an immutable effect under options.name and shares it among every emitter using that name. | | [`newGradient`](/modules/gfx/particles/#tecs.gfx.particles.newGradient) | Compiles keyframes into a color gradient over normalized age. | ### Types | Type | Kind | Description | | --- | --- | --- | | [`Color`](/modules/gfx/particles/#tecs.gfx.particles.Color) | type | Represents a color accepted by particle authoring fields. | | [`Curve`](/modules/gfx/particles/#tecs.gfx.particles.Curve) | record | Stores a compiled scalar curve. | | [`Draining`](/modules/gfx/particles/#tecs.gfx.particles.Draining) | record | A slot range whose emitter is gone, held until its last particle can no longer be alive. | | [`Effect`](/modules/gfx/particles/#tecs.gfx.particles.Effect) | record | The handle newEffect returns and a ParticleEmitter names. | | [`EffectOptions`](/modules/gfx/particles/#tecs.gfx.particles.EffectOptions) | record | Configures newEffect. | | [`EmitterOptions`](/modules/gfx/particles/#tecs.gfx.particles.EmitterOptions) | record | Configures a ParticleEmitter. | | [`EmitterState`](/modules/gfx/particles/#tecs.gfx.particles.EmitterState) | enum | The three values an emitter's state takes. | | [`Gradient`](/modules/gfx/particles/#tecs.gfx.particles.Gradient) | record | Stores a compiled color gradient. | | [`Holding`](/modules/gfx/particles/#tecs.gfx.particles.Holding) | record | One emitter's place in the pool, as the pool remembers it. | | [`InitialOptions`](/modules/gfx/particles/#tecs.gfx.particles.InitialOptions) | record | Configures a particle's initial properties. | | [`ParticleEmitter`](/modules/gfx/particles/#tecs.gfx.particles.ParticleEmitter) | record | Controls an effect's playback state and per-instance scales. | | [`Pool`](/modules/gfx/particles/#tecs.gfx.particles.Pool) | record | Represents a world's installed particle pool. | | [`PoolOptions`](/modules/gfx/particles/#tecs.gfx.particles.PoolOptions) | record | Configures plugin. | | [`Range`](/modules/gfx/particles/#tecs.gfx.particles.Range) | record | The two-bound form of a Value. | | [`RenderOptions`](/modules/gfx/particles/#tecs.gfx.particles.RenderOptions) | record | Configures how particles draw. | | [`ScheduleOptions`](/modules/gfx/particles/#tecs.gfx.particles.ScheduleOptions) | record | Configures when particles emit. | | [`SpawnOptions`](/modules/gfx/particles/#tecs.gfx.particles.SpawnOptions) | record | Configures where particles spawn. | | [`UpdateOptions`](/modules/gfx/particles/#tecs.gfx.particles.UpdateOptions) | record | Configures how a particle changes over its normalized lifetime. | | [`Value`](/modules/gfx/particles/#tecs.gfx.particles.Value) | type | Accepted wherever a property may vary per particle. | ### Functions | Function | Kind | Description | | --- | --- | --- | | [`find`](/modules/gfx/particles/#tecs.gfx.particles.find) | Static | Returns the effect registered under name, or nil when none exists. | | [`names`](/modules/gfx/particles/#tecs.gfx.particles.names) | Static | Returns every registered effect name in registration order. | | [`plugin`](/modules/gfx/particles/#tecs.gfx.particles.plugin) | Static | Installs the pool on a world. | | [`poolOf`](/modules/gfx/particles/#tecs.gfx.particles.poolOf) | Static | Returns the pool installed on a world, or nil. | | [`reset`](/modules/gfx/particles/#tecs.gfx.particles.reset) | Static | Forgets every registered effect, so a spec can register a set again. | ### Values | Value | Type | Description | | --- | --- | --- | | [`CURVE_SAMPLES`](/modules/gfx/particles/#tecs.gfx.particles.CURVE_SAMPLES) | `integer` | Read-only. Reports how many samples one compiled curve or gradient holds. | | [`EFFECT`](/modules/gfx/particles/#tecs.gfx.particles.EFFECT) | `{string : integer}` | Read-only. Reports each field's offset within an effect record. | | [`EFFECT_FLOATS`](/modules/gfx/particles/#tecs.gfx.particles.EFFECT_FLOATS) | `integer` | Read-only. Reports how many floats each effect record contains. | | [`EMITTER`](/modules/gfx/particles/#tecs.gfx.particles.EMITTER) | `{string : integer}` | Read-only. Reports each field's float offset within an emitter record, counting from zero. | | [`EMITTER_FLOATS`](/modules/gfx/particles/#tecs.gfx.particles.EMITTER_FLOATS) | `integer` | Read-only. Reports how many floats each emitter record contains. | | [`STATE_FLOATS`](/modules/gfx/particles/#tecs.gfx.particles.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. ```teal 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`](/modules/gfx/particles/#tecs.gfx.particles.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. ```teal function tecs.gfx.particles.newEffect(options: EffectOptions): Effect ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `options` | [`EffectOptions`](/modules/gfx/particles/#tecs.gfx.particles.EffectOptions) | Every section but `name` is optional and defaults to something drawable. | #### Returns | Type | Description | | --- | --- | | [`Effect`](/modules/gfx/particles/#tecs.gfx.particles.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. ```teal 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`](/modules/gfx/particles/#tecs.gfx.particles.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. ```teal type tecs.gfx.particles.Color = Color ``` ### tecs.gfx.particles.Curve record Stores a compiled scalar curve. ```teal 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. ```teal 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. ```teal 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. ```teal tecs.gfx.particles.Draining.base: integer ``` #### tecs.gfx.particles.Draining.count field Engine-owned. Stores the number of particle slots awaiting release. ```teal tecs.gfx.particles.Draining.count: integer ``` #### tecs.gfx.particles.Draining.releaseAt field Engine-owned. Stores the time when the slots become reusable. ```teal 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. ```teal tecs.gfx.particles.Draining.blended: boolean ``` ### tecs.gfx.particles.Effect record The handle `newEffect` returns and a [`ParticleEmitter`](/modules/gfx/particles/#tecs.gfx.particles.ParticleEmitter) names. ```teal 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. ```teal 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. ```teal tecs.gfx.particles.Effect.index: integer ``` #### tecs.gfx.particles.Effect.capacity field Read-only. Reports how many live particles one emitter can hold. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal tecs.gfx.particles.Effect.emitFor: number ``` #### tecs.gfx.particles.Effect.rate field Read-only. Reports the continuous emission rate used by `finished` and `estimatedCount`. ```teal tecs.gfx.particles.Effect.rate: number ``` #### tecs.gfx.particles.Effect.delay field Read-only. Reports the emission delay in seconds. ```teal tecs.gfx.particles.Effect.delay: number ``` #### tecs.gfx.particles.Effect.duration field Read-only. Reports the emission duration in seconds. ```teal tecs.gfx.particles.Effect.duration: number ``` #### tecs.gfx.particles.Effect.looping field Read-only. Reports whether the emission cycle repeats. ```teal tecs.gfx.particles.Effect.looping: boolean ``` #### tecs.gfx.particles.Effect.bursts field Read-only. Provides the compiled burst schedule. ```teal tecs.gfx.particles.Effect.bursts: {number} ``` ### tecs.gfx.particles.EffectOptions record Configures `newEffect`. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal tecs.gfx.particles.EffectOptions.schedule: ScheduleOptions ``` #### tecs.gfx.particles.EffectOptions.spawn field Caller-writable. Configures where the effect spawns particles. An absent section takes every default below it. ```teal 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. ```teal tecs.gfx.particles.EffectOptions.initial: InitialOptions ``` #### 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. ```teal 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. ```teal tecs.gfx.particles.EffectOptions.render: RenderOptions ``` ### tecs.gfx.particles.EmitterOptions record Configures a [`ParticleEmitter`](/modules/gfx/particles/#tecs.gfx.particles.ParticleEmitter). ```teal 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. ```teal 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. ```teal tecs.gfx.particles.EmitterOptions.state: EmitterState ``` #### tecs.gfx.particles.EmitterOptions.seed field Caller-writable. Sets the random seed. Defaults to one. ```teal tecs.gfx.particles.EmitterOptions.seed: number ``` #### tecs.gfx.particles.EmitterOptions.rateScale field Caller-writable. Multiplies the effect's emission rate. Defaults to one. ```teal tecs.gfx.particles.EmitterOptions.rateScale: number ``` #### tecs.gfx.particles.EmitterOptions.sizeScale field Caller-writable. Multiplies every particle's size. Defaults to one. ```teal tecs.gfx.particles.EmitterOptions.sizeScale: number ``` #### tecs.gfx.particles.EmitterOptions.timeScale field Caller-writable. Multiplies the speed of the effect clock. Defaults to one. ```teal 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. ```teal tecs.gfx.particles.EmitterOptions.tint: Color ``` ### tecs.gfx.particles.EmitterState enum The three values an emitter's `state` takes. ```teal global enum tecs.gfx.particles.EmitterState "paused" "playing" "stopped" end ``` ### tecs.gfx.particles.Gradient record Stores a compiled color gradient. ```teal 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. ```teal tecs.gfx.particles.Gradient.samples: {number} ``` ### tecs.gfx.particles.Holding record One emitter's place in the pool, as the pool remembers it. ```teal 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. ```teal tecs.gfx.particles.Holding.item: ParticleEmitter ``` #### tecs.gfx.particles.Holding.index field Engine-owned. Stores the reusable emitter index. ```teal tecs.gfx.particles.Holding.index: integer ``` #### tecs.gfx.particles.Holding.base field Engine-owned. Stores the first particle slot. ```teal tecs.gfx.particles.Holding.base: integer ``` #### tecs.gfx.particles.Holding.count field Engine-owned. Stores the number of particle slots. ```teal 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. ```teal tecs.gfx.particles.Holding.tint: Color ``` #### tecs.gfx.particles.Holding.tintR field Engine-owned. Stores the parsed red tint channel. ```teal tecs.gfx.particles.Holding.tintR: number ``` #### tecs.gfx.particles.Holding.tintG field Engine-owned. Stores the parsed green tint channel. ```teal tecs.gfx.particles.Holding.tintG: number ``` #### tecs.gfx.particles.Holding.tintB field Engine-owned. Stores the parsed blue tint channel. ```teal tecs.gfx.particles.Holding.tintB: number ``` #### tecs.gfx.particles.Holding.tintA field Engine-owned. Stores the parsed alpha tint channel. ```teal tecs.gfx.particles.Holding.tintA: number ``` ### tecs.gfx.particles.InitialOptions record Configures a particle's initial properties. ```teal 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`. ```teal 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. ```teal tecs.gfx.particles.InitialOptions.speed: Value ``` #### tecs.gfx.particles.InitialOptions.size field Caller-writable. Sets initial size in world units. Defaults to one. ```teal tecs.gfx.particles.InitialOptions.size: Value ``` #### tecs.gfx.particles.InitialOptions.rotation field Caller-writable. Sets initial rotation in radians. Defaults to zero. ```teal 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. ```teal tecs.gfx.particles.InitialOptions.angularVelocity: Value ``` #### 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. ```teal tecs.gfx.particles.InitialOptions.accelerationX: Value ``` #### tecs.gfx.particles.InitialOptions.accelerationY field Caller-writable. Sets constant vertical acceleration. Defaults to zero. ```teal 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. ```teal 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. ```teal 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`](/modules/ecs/#tecs.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. ```teal 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. ```teal 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. ```teal tecs.gfx.particles.ParticleEmitter.seed: number ``` #### tecs.gfx.particles.ParticleEmitter.rateScale field Caller-writable. Multiplies the effect's emission rate. ```teal tecs.gfx.particles.ParticleEmitter.rateScale: number ``` #### tecs.gfx.particles.ParticleEmitter.sizeScale field Caller-writable. Multiplies every particle's size. ```teal tecs.gfx.particles.ParticleEmitter.sizeScale: number ``` #### tecs.gfx.particles.ParticleEmitter.timeScale field Caller-writable. Multiplies the speed of the effect clock. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal function tecs.gfx.particles.Pool.casting(self): integer ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Pool` | | ##### Returns | Type | Description | | --- | --- | | `integer` | | #### tecs.gfx.particles.Pool:count Instance ```teal 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. ```teal function tecs.gfx.particles.Pool.destroy(self) ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Pool` | | ##### Returns None. #### tecs.gfx.particles.Pool:record Instance ```teal 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 ```teal 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. ```teal 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`. ```teal 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. ```teal 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. ```teal 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. ```teal tecs.gfx.particles.PoolOptions.maxEmitters: integer ``` ### tecs.gfx.particles.Range record The two-bound form of a `Value`. ```teal record tecs.gfx.particles.Range min: number max: number end ``` #### tecs.gfx.particles.Range.min field Caller-writable. Sets the inclusive lower bound. ```teal tecs.gfx.particles.Range.min: number ``` #### tecs.gfx.particles.Range.max field Caller-writable. Sets the inclusive upper bound. ```teal tecs.gfx.particles.Range.max: number ``` ### tecs.gfx.particles.RenderOptions record Configures how particles draw. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal tecs.gfx.particles.RenderOptions.clip: integer ``` ### tecs.gfx.particles.ScheduleOptions record Configures when particles emit. ```teal 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. ```teal 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. ```teal tecs.gfx.particles.ScheduleOptions.duration: number ``` #### tecs.gfx.particles.ScheduleOptions.looping field Caller-writable. Controls whether the emission cycle repeats. Defaults to false. ```teal 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. ```teal 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. ```teal tecs.gfx.particles.ScheduleOptions.bursts: {ScheduleBurst} ``` ### tecs.gfx.particles.SpawnOptions record Configures where particles spawn. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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`. ```teal 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. ```teal 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. ```teal 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. ```teal tecs.gfx.particles.SpawnOptions.inheritVelocity: number ``` ### tecs.gfx.particles.UpdateOptions record Configures how a particle changes over its normalized lifetime. ```teal 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. ```teal 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. ```teal tecs.gfx.particles.UpdateOptions.radialAcceleration: Value ``` #### tecs.gfx.particles.UpdateOptions.tangentialAcceleration field Caller-writable. Sets acceleration at right angles to the emitter. Defaults to zero. ```teal tecs.gfx.particles.UpdateOptions.tangentialAcceleration: Value ``` #### 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. ```teal 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"`. ```teal tecs.gfx.particles.UpdateOptions.color: Gradient ``` ### tecs.gfx.particles.Value type Accepted wherever a property may vary per particle. ```teal type tecs.gfx.particles.Value = Value ``` ## Functions ### tecs.gfx.particles.find Static Returns the effect registered under `name`, or nil when none exists. ```teal 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`](/modules/gfx/particles/#tecs.gfx.particles.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. ```teal 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. ```teal function tecs.gfx.particles.plugin(options: PoolOptions): function( World ) ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `options` | [`PoolOptions`](/modules/gfx/particles/#tecs.gfx.particles.PoolOptions) | Requires `renderer`; both capacities have defaults. | #### Returns | Type | Description | | --- | --- | | `function(`[`World`](/modules/ecs/#tecs.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. ```teal function tecs.gfx.particles.poolOf(world: World): Pool ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `world` | [`World`](/modules/ecs/#tecs.World) | Accepts a world with or without the plugin. | #### Returns | Type | Description | | --- | --- | | [`Pool`](/modules/gfx/particles/#tecs.gfx.particles.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. ```teal 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. ```teal 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. ```teal tecs.gfx.particles.EFFECT: {string: integer} ``` ### tecs.gfx.particles.EFFECT_FLOATS variable Read-only. Reports how many floats each effect record contains. ```teal 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. ```teal 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. ```teal tecs.gfx.particles.EMITTER_FLOATS: integer ``` ### tecs.gfx.particles.STATE_FLOATS variable Read-only. Reports how many GPU state floats one particle carries. ```teal tecs.gfx.particles.STATE_FLOATS: integer ```