Core concepts
The handful of ideas behind every Pylinka effect.
Project → System → Graph
A project is the top-level document. It holds one or more systems, a set of project-wide knobs, and any texture assets. Each system is one particle system: a graph, an emitter, a pool capacity, and a blend mode.
A graph is a typed, acyclic dataflow of nodes joined by
edges. Ports carry one of five types — f32, vec2,
vec4, color, bool. A system is well-formed when it has
exactly one output.spawnPosition and one output.initLife.
Values vs. structure
Every edit falls into one of two buckets, and this is what makes Pylinka feel instant:
| Kind of edit | Example | Cost |
|---|---|---|
| Value | drag a number, change a color, move a knob | one uniform write, next frame |
| Structure | add/remove a node or edge, change an ease | recompile (~30 ms, debounced) |
Unconnected input values are lowered to GPU uniforms, so scrubbing them never recompiles. Only changes to the shape of the generated code do.
Knobs
Any value can be promoted to a knob — a named control like windPower
with a range and a linear or log scale. Knobs are shared across a project and driven at runtime:
pylinka.params.set('windPower', 40); // fans out to every system using it Promotion is free: the uniform slot already exists, so it never triggers a recompile.
World space & moving emitters
Particles live in the view's local space. The emitter position is consumed only at spawn, so moving the emitter changes where new particles are born while existing ones stay put — a coin flying across the board leaves sparks that hang and fade in place.
This is why you attach the view to a static layer and let the target drive the emitter
via follow(). Reparenting the view onto a moving sprite transforms every live
particle and defeats world-space simulation.
Emission
Emitters run in one of three modes:
- flow — a steady
rate(particles/second), optionally plusrateOverDistance(particles per pixel the emitter travels) for gap-free trails. - burst — a
counteveryintervalseconds. - once — a single burst on start.
Sub-frame spawn interpolation places particles along the emitter's path within a frame, so fast trails come out continuous rather than clumped.
Pools & lifetime
Each system pre-allocates a fixed capacity of particle slots. When a particle's age
reaches its life it dies automatically — there is no kill node for end-of-life. If emission
outruns capacity, new particles are dropped (an overflowCount ticks up); the editor
warns when rate × maxLife exceeds capacity.
Determinism
Pylinka uses an integer-hash PRNG. Runs are bit-reproducible on the same device, driver, and fixed-step mode. Across different GPUs, float math diverges — expect statistical similarity, not identical trajectories. There are no cross-client sync guarantees.