merge(particle): integrate flow-field as default Particle Mode + ParticleBackend

# Conflicts:
#	manifold/src/console/flow-field.ts
This commit is contained in:
monkey-w1n5t0n 2026-06-28 04:32:08 +02:00
commit cba7de6a4b
5 changed files with 56 additions and 6 deletions

View file

@ -18,6 +18,7 @@ export { OscBridgeBackend } from './osc-backend';
export type { OscBackendConfig } from './osc-backend';
export { NispsOscClient } from './osc-client';
export { PassthroughBackend } from './passthrough-backend';
export { ParticleBackend } from './particle-backend';
export {
useBackendManager,
} from './useBackendManager';

View file

@ -21,6 +21,7 @@ import type { BackendId } from '../dock/output-state';
import { WebMidiBackend } from './midi-backend';
import { OscBridgeBackend } from './osc-backend';
import { PassthroughBackend } from './passthrough-backend';
import { ParticleBackend } from './particle-backend';
/** The slice of EngineApi the manager depends on (keeps it decoupled/testable). */
export interface ManagerEngine {
@ -47,7 +48,7 @@ export class BackendManager {
['midi', backends?.midi ?? new WebMidiBackend()],
['osc', backends?.osc ?? new OscBridgeBackend()],
['synth', backends?.synth ?? new PassthroughBackend('synth', 'Built-in Synth — audio plays in the engine')],
['particles', backends?.particles ?? new PassthroughBackend('particles', 'Particle visualiser')],
['particles', backends?.particles ?? new ParticleBackend()],
['cvgate', backends?.cvgate ?? new PassthroughBackend('cvgate', 'CV / gate (via VCV bridge)')],
['vcv', backends?.vcv ?? new PassthroughBackend('vcv', 'VCV bridge')],
]);

View file

@ -0,0 +1,35 @@
/**
* ParticleBackend the output backend for the Particle System mode (the DEFAULT
* mode).
*
* Transport is a NO-OP: the flow-field visualiser (console/flow-field.ts, driven
* by ParticleStage) is a SEPARATE consumer of the engine spine it reads
* `engine.getOutputs()` directly in its own requestAnimationFrame loop and maps
* the first 20 outputs onto the visual field. So there is nothing for the
* BackendManager to "send" here.
*
* What this backend DOES contribute:
* - It is a non-synth backend, so selecting it makes the BackendManager gate
* audio (`engine.audio.setMuted(true)`) the synth is silenced while the
* particles are on screen, exactly like the MIDI / OSC modes.
* - It reports a sensible "ready" status for the Outputs panel.
*
* It extends PassthroughBackend (the shared no-op sink) purely to keep the
* empty-`send` / nothing-to-start behaviour in one place; the audio gate lives
* in the manager (`setActive`), not here.
*/
import { PassthroughBackend } from './passthrough-backend';
import type { BackendStatus } from './backend';
export class ParticleBackend extends PassthroughBackend {
constructor() {
super('particles', 'Particle visualiser — outputs drive the flow field (no audio)');
}
override status(): BackendStatus {
return {
state: 'ready',
message: 'Particle visualiser — outputs drive the flow field (no audio)',
};
}
}

View file

@ -27,10 +27,10 @@ import { useEngine, useEngineVersion } from '../engine';
import { MF_MODES, modeEngineId, seededGradient, shapeValues } from './model';
import type { MFParam } from './model';
import { CompositeStage } from './CompositeStage';
import { ParticleStage } from './ParticleStage';
import { SplitStage } from './SplitStage';
import { OutputStage } from './OutputStage';
import { InputMini } from './InputMini';
import { ParticleStage } from './ParticleStage';
import { Manifold } from './Manifold';
import { ReadoutStrip } from './ReadoutStrip';
import { VerdictCluster } from './VerdictCluster';

View file

@ -1,10 +1,23 @@
/**
* flow-field.ts Canvas2D flow-field particle system, a faithful TypeScript
* port of the a-immersive playground visualiser (`js/ui/visualizer.js`).
* port of the a-immersive playground visualiser
* (`/home/w1n5t0n/deployments/meml-aimmersive/js/ui/visualizer.js`).
*
* Driven by the first 20 model outputs (each [0,1]); `setParams` maps them to
* the visual ranges. The simulation advances on its own clock, so particles keep
* flowing between inferences only the *field* changes when the MLP outputs do.
* FAITHFULNESS (verified 2026-06-28 against the original):
* 400 particles, identical permutation-table value noise (`PERM`/`noise2D`).
* The first 20 model outputs (each [0,1]) map to the visual ranges exactly
* as the original `setParams` (p0..p19 see the per-line comments below).
* The advection/attractor/dispersion/repulsor integration in `draw()` is a
* line-for-line port of the original.
* Deliberate improvements over the original (NOT drift):
* - `resize()` resets the transform before `scale(dpr)` so repeated resizes
* don't compound the device-pixel-ratio scale (the original double-scaled
* on every resize); it also repaints the background so a resize doesn't
* leave stale trails.
* - `draw()` early-returns while the canvas has zero size (pre-layout).
*
* The simulation advances on its own clock, so particles keep flowing between
* inferences only the *field* changes when the MLP outputs do.
*/
// Simple value noise (no dependencies) — identical permutation scheme to the