A particle is a function of its age, not a thing that is stepped (#73)
game-maker / suite (push) Failing after 4m22s

The asset type `particles` is placeable and carries a graph at the same time —
`behavior` was the blueprint. Because a continuous emitter gives birth to particle n
at time n/rate, the whole field is a function of the clock rather than of the frames:
no ring-buffer bookkeeping, exact time jumps, reproducibility as a measurable property.

compile.ts follows compileShader's frame but emits JS closures into a Float64Array
instead of GLSL, and without eval; a node with two consumers is evaluated once.
Drawing is one Pixi ParticleContainer per source, so one draw call.

Collision, wind and braking in flight are out for good — that is the price of the
formula model, not an omission.

The tab count goes from nine to ten, which is exactly the case #84 was built for one
day earlier: at 1280 px the row no longer holds every tab, so guard 14b now asks for
hidden.length > 0 instead of === 0.
This commit is contained in:
root committed 2026-09-14 05:44:25 +02:00
1 parent f83c568999
commit 0f9e1611d1
39 files changed
+2386 -32

No files matched your search

+5
View File
@@ -26,6 +26,11 @@ export interface EntityHandle {
z: number;
props: PropertyBag;
parent: EntityHandle | null;
/**
* Counts the Emit pulses a particle source has been given. Kept off `props` for the reason the
* animation clock is: what the world writes back to the model must not grow an override per frame.
*/
emits?: number;
}
/** Position, rotation, scale and mirror of every ancestor applied, nearest last. */
+4 -3
View File
@@ -19,10 +19,11 @@ export interface PortDef {
}
/**
* `state` and `shader` are the categories no behavior graph offers: their nodes are the states of
* a state machine and the GLSL of a shader asset.
* `state`, `shader` and `particles` are the categories no behavior graph offers: their nodes are the
* states of a state machine, the GLSL of a shader asset and the motion of one particle.
*/
export type NodeCategory = 'event' | 'flow' | 'data' | 'variable' | 'action' | 'state' | 'shader';
export type NodeCategory =
'event' | 'flow' | 'data' | 'variable' | 'action' | 'state' | 'shader' | 'particles';
/**
* How a loop node asks for one of its exec outputs to be run repeatedly: `port` carries the body,
+19
View File
@@ -0,0 +1,19 @@
import type { NodeDef } from '../../NodeRegistry';
import { asEntity } from '../../values';
import { exec, obj } from '../shared';
export const Emit: NodeDef = {
type: 'Emit',
label: 'Emit',
category: 'action',
description:
'Starts a particle source over: a Burst source throws another burst, a Continuous one begins its stream again at the first particle.',
inputs: [exec(), obj('source')],
outputs: [exec()],
params: [],
run: (_ctx, inputs) => {
const target = asEntity(inputs.source);
if (target) target.emits = (target.emits ?? 0) + 1;
return { next: 'exec' };
},
};
+6 -2
View File
@@ -1,5 +1,6 @@
import { type NodeDef, registerNode } from '../NodeRegistry';
import { Destroy } from './action/Destroy';
import { Emit } from './action/Emit';
import { Log } from './action/Log';
import { Move } from './action/Move';
import { RestartGame } from './action/RestartGame';
@@ -88,6 +89,7 @@ import { RunBehavior } from './flow/RunBehavior';
import { Sequence } from './flow/Sequence';
import { SetState } from './flow/SetState';
import { StateBranch } from './flow/StateBranch';
import { PARTICLE_NODES } from '../../particles/nodes';
import { SHADER_NODES } from '../../shader/nodes';
import { State } from '../stateMachine';
import { Variables } from './variable/Variables';
@@ -182,13 +184,15 @@ export const BUILT_IN_NODES: readonly NodeDef[] = [
SetPosition,
Spawn,
Destroy,
Emit,
Log,
StopGame,
RestartGame,
];
for (const def of BUILT_IN_NODES) registerNode(def);
// Registered so a machine's or a shader's graph loads and draws, but not built in: no behavior
// graph offers them.
// Registered so a machine's, a shader's or a particle graph loads and draws, but not built in: no
// behavior graph offers them.
registerNode(State);
for (const def of SHADER_NODES) registerNode(def);
for (const def of PARTICLE_NODES) registerNode(def);