Speed and Prewarm are arithmetic on the clock, not a second loop (#87)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rootandClaude Opus 5 committed 2026-09-14 07:31:14 +02:00
1 parent 02c74bba02
commit 198483b155
3 files changed
+10 -4

No files matched your search

+4 -1
@@ -105,7 +105,10 @@ of 500 particles is reproducible, holds still for a check, and needs no state be
source stood when each one was born. Both are pure and tested without a browser. The other side of
that choice: **freezing such a thing means moving its origin, not pausing a counter** — the clock
belongs to the library and keeps running, so a preview let go after a minute on hold has to be
rebased to the second it was stopped at, or it resumes a minute further on.
rebased to the second it was stopped at, or it resumes a minute further on. The good side: a row
that changes *when* rather than *what* costs nothing but a term on that one argument — a particle
source's `Time scale` is a factor on it and `Prewarm` a summand, so neither needs a frame of
simulation nor a line in the drawing code.
Comments state contracts
and reasons, not what the code does: a `/** */` on every export whose name does not say it all,
`//` only for a decision or gotcha.
+2 -2
@@ -12,7 +12,7 @@ npm run check # typecheck + lint + format:check + tests, the gate CI runs
# code is lost (tail's is reported), so a red run reads as green
npm run build # writes dist/ and regenerates index.html
./build.sh # the same under `set -o pipefail`, for callers that pipe the output
node tools/seed-e2e.mjs && node tools/e2e.mjs # 816 editor checks, screenshots in /tmp/gm-e2e-*.png
node tools/seed-e2e.mjs && node tools/e2e.mjs # 821 editor checks, screenshots in /tmp/gm-e2e-*.png
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step
./e2e.sh start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result
@@ -156,4 +156,4 @@ widths to find click points. The **render device is not in this repo** — `/dev
and a 1 GB `/dev/shm` come from `container.options` in the runner's own `config.yaml`, which
applies to every job on that runner. Without them chromium falls back to llvmpipe, which gets
WebGL 1.0 only. Measured in the job container: `ANGLE (Intel, Vulkan 1.3.230, HD Graphics 630)`,
WebGL 2.0, and the same 816 checks as here.
WebGL 2.0, and the same 821 checks as here.
+4 -1
@@ -27,7 +27,8 @@ nothing.
Right of it, past a divider that drags, the **preview** panel emits the asset's own particles from
its middle, wearing **the rows the asset itself carries** — edit `Rate` or `Lifetime` on the asset
and the panel changes with them, including how far the slider reaches (three lifetimes). Rows the
and the panel changes with them, including how far the slider reaches (three lifetimes, which a
`Time scale` of 2 covers in half the seconds). Rows the
asset leaves alone fall back to the type's defaults: 20 a second, one second each, a plain 16 px
square. It stands beside the graph rather than under it because a graph is read across: width is
what it can spare, height is not. `Hold` stops the clock where it is and turns into `Run`; the
@@ -78,6 +79,8 @@ mirror), so hanging one on a moving ship turns the whole spray with it. Its own
| **Rate** | 20 | Particles per second, for a continuous source. |
| **Burst count** | 20 | How many go out per burst. |
| **Lifetime** | 1 s | How long one particle lives — **the scale that gives `Age` its meaning**. |
| **Time scale** | 1 | How fast the source's own clock runs: 2 is twice the speed, so it emits twice as often and a particle is gone in half the time. Every row above is read on that clock, which is why none of them has to be touched to slow a whole effect down. 0 holds the picture where it is. |
| **Prewarm** | off | Start one lifetime in, which is the second a stream first stands full. Without it a fire lit by a scene fade-in begins as a single spark and takes a lifetime to look like a fire. Nothing to a *Burst*, whose particles are all gone by that second. |
| **Max particles** | 500 | The most alive at once; past it the oldest are dropped. |
| **Size** | 16 | The particle's size in pixels, which the graph's `scaleX`/`scaleY` multiply. |
| **Texture** | none | The picture each particle is drawn with. Without one it is a plain square. |