The VJ's EFFECT surface is a small renderer of its own. An effect is a
`.splash` DOCUMENT — engine choice, stages, fields, parameters and now the
SHADERS THEMSELVES live in the document rather than in Rust. That is the
shape this commit introduces (dev has never seen an intermediate one): a
document is the whole effect, and the Rust side is the engine families that
documents draw with.
The engine families: particles and emitters, GPU sim swarms and fluid, static
meshes (firefly synchrony, harmonograph loom, domino liturgy), tiles, flock,
clouds, city, pipes, stock charts, an SDF raymarcher with a subclassable
`scene_sdf`, and a mountain-jet endless range with a beat-pulsed fighter.
Three things make them a system rather than a demo reel:
- SIM FIELDS — a float simulation-texture primitive, so an engine can carry
GPU state across frames (wind, particles, fluid) instead of being a pure
function of the clock.
- CONTENT COUPLING — `content:` in a document and `input0` on every engine's
tex0, so an effect can consume the program video: drapes, backdrops,
mirrors, billboards, chrome, frescoes, canopy, glass, silk, wall, swarm,
pen and mosaic families all take the picture playing behind them.
- MUSIC BINDING — shaders read the beat, so the whole library moves with the
track rather than on its own clock.
Roughly a hundred seeded preset documents ship with it, each with a lazily
rendered animated thumbnail (rendered 4x and box-downscaled, 16-tap SSAA);
the thumbnail pass went from four minutes to eleven seconds. CONTRACT.md is
the document contract and its verify recipe; IDEAS.md is the campaign tracker.
90 lines
3.3 KiB
Text
90 lines
3.3 KiB
Text
// BULL TERMINAL — the green-on-black trading floor. A random-walk OHLC
|
|
// tape commits ONE candle per beat (beats detected from the phase wrap —
|
|
// no wiring needed), the live candle ticks every frame with volatility
|
|
// that spikes on the beat envelope, and the whole terminal is mono
|
|
// phosphor: bright green up-candles, dim green down-candles, scanlines,
|
|
// a glowing crosshair riding the live price. NO text — the effect stack
|
|
// has no glyph path, so the axis is dash ticks, which is the terminal
|
|
// anyway. Pattern taught: per_beat is the tape clock; drift > 0 = bull.
|
|
{
|
|
name: "Bull Terminal"
|
|
engine: "stockcharts"
|
|
seed: 21
|
|
|
|
candles: 110
|
|
per_beat: 1
|
|
vol: 1.1
|
|
drift: 0.15
|
|
spike: 1.6
|
|
ma: 14
|
|
grid_x: 4
|
|
grid_y: 7
|
|
scan: 16
|
|
width: 15
|
|
height: 7.5
|
|
|
|
beat_pulse: 0.5
|
|
p0: 0.15 // phosphor gain
|
|
p1: "0.3 + 0.7*pulse" // crosshair pumps on the beat
|
|
|
|
// PERFORMANCE DIALS — the chart engine's own p-levers: GAIN drives
|
|
// the phosphor, XHAIR holds the crosshair pump when touched, PANIC
|
|
// floods the board red.
|
|
dials: [
|
|
{name: "GAIN", bind: "p0", default: 0.15},
|
|
{name: "XHAIR", bind: "p1", default: 0.5},
|
|
{name: "PANIC", bind: "p2", default: 0.0}
|
|
]
|
|
|
|
color_bg: #x020604
|
|
color_a: #x39ff6a
|
|
color_b: #x117a34
|
|
color_c: #xd8ffe4
|
|
|
|
cam_orbit: 0.0
|
|
cam_fov: 40
|
|
fog: 0.0
|
|
glow: 1.2
|
|
|
|
stages: [
|
|
{kind: "bloom", threshold: 0.4, strength: 1.3, levels: 2}
|
|
]
|
|
|
|
// THE LOOK LIVES HERE. This block is not a reference to the
|
|
// engine's shader — it IS the pixel math this effect runs.
|
|
// Rewrite it and the effect looks different; everything else
|
|
// (geometry, motion, the content coupling, the beat) keeps
|
|
// working, because the block SUBCLASSES the engine shader.
|
|
// Inputs: t = candle age 0..1, attr = (element class, age, up/down, move size).
|
|
// Signals: self.time_beat (time, beat, phase, pulse),
|
|
// self.sig (bar, bpm, energy, dt), self.user (p0..p3),
|
|
// self.col_a/b/c/bg, self.fog (density, glow, content, -).
|
|
shader: draw.DrawVjFxCharts {
|
|
fx_color: fn(t: float, attr: vec4, normal: vec3, wpos: vec3) -> vec4 {
|
|
let class = attr.x
|
|
if class < 0.5 {
|
|
// Candle body: col_a up / col_b down, big moves burn.
|
|
let base = self.col_b.mix(self.col_a, step(0.5, attr.z))
|
|
let burn = 0.75 + attr.w * 0.7 + self.time_beat.w * (1.0 - t) * 0.6
|
|
return vec4(base.xyz * burn, 1.0 - t * 0.55)
|
|
}
|
|
if class < 1.5 {
|
|
let base = self.col_b.mix(self.col_a, step(0.5, attr.z))
|
|
return vec4(base.xyz * 0.55, (1.0 - t * 0.55) * 0.8)
|
|
}
|
|
if class < 2.5 {
|
|
return vec4(self.col_a.xyz * 0.16, 0.5)
|
|
}
|
|
if class < 3.5 {
|
|
// Kept modest: the bloom stage amplifies this line, and an
|
|
// over-gained crosshair blows out into a white bar.
|
|
let g = 0.55 + self.user.y * 0.5 + self.time_beat.w * 0.25
|
|
return vec4(self.col_c.xyz * g, 0.8)
|
|
}
|
|
if class < 4.5 {
|
|
return vec4(self.col_c.xyz * 0.5 + self.col_a.xyz * 0.4, 0.85)
|
|
}
|
|
return vec4(self.col_a.xyz * 0.6, 0.9)
|
|
}
|
|
}
|
|
}
|