VFXWeaverСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачать
ВЕРСИИ (88)
release
VFXWeaver API 2.5.0 (1.21.11)
2.5.022 окт. 2026 г.75Minecraft: 1.21.11fabric
Что нового
### Added
- **`hex_grid`: a triangular grid coloured by the screen position.** A new screen effect draws a triangular lattice over the frame - three line directions (two at 60 degrees, one vertical) on a `cell_size` cell (46 px by default) anchored at the screen centre - and paints the lines with the prism ramp picked by the position (`prism7(x * 0.6 + y * 0.4 + time * speed)`, drifting at 0.5 per second): the lines at 28 % of that colour over near-black `(0.01, 0.008, 0.02)`, and the highlights of the picture (brighter than `core`, 0.68 by default - absolute, not local, so no outline appears at a brightness step) in white; `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it) and no param has an upper cap - only the lower guards that avoid degenerate maths (`cell_size >= 1` px, `width >= 0.0001`). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/hex-grid.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`spectral`: a rainbow prism look over the frame.** A new screen effect draws the picture through a seven-stop prism ramp - red `(1.0, 0.07, 0.1)`, orange `(1.0, 0.42, 0.03)`, yellow `(1.0, 0.88, 0.06)`, green `(0.1, 0.95, 0.28)`, blue `(0.06, 0.48, 1.0)`, violet `(0.28, 0.14, 1.0)`, magenta `(0.74, 0.16, 1.0)` - swept by the angle around `center_x`/`center_y`, the distance from it (0.9 per screen height) and the clock (0.7 per second); `mode` picks the variant: **0** the prism duotone (the frame is split into a dark and a bright half by its own brightness, `mid` 0.5 by default - absolute, not local, so no outline appears at a brightness step; the bright half takes the prism colour and the highlights, `core` 0.68, go white), **1** the chromatic smear (seven taps along the outward direction, spread by `0.0035 + 0.004 * distance`, every tap's edge `smoothstep(0.02, 0.30)` painted with its own prism stop - a wide ramp, so it stays a smear and not a drawn outline) and **2** the spectral ring (the luminance picks a band `floor(luma * 6.999)`, the distance picks a ring `floor(distance * 9 - time * 6)`, the two together pick a spectral stop and the highlights (`core` 0.68) go white; `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it), `mode` is an enum clamped to 0..2 and no other param has an upper cap. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/spectral.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`torn_edge`: a ragged tear with a glowing line down it.** A new screen effect cuts the frame along a straight line through `center_x`/`center_y` whose angle is re-rolled every beat (`speed`, 20 per second by default): a value-noise ripple of `ragged` (0.02 of the screen height, 38 cycles across the tear) makes the edge wander, the two sides of the cut are drawn at opposite brightness (a pixel brighter than `mid`, 0.5 by default - the split is absolute, not local, so no outline appears at a brightness step), and the tear itself is a white line (an exponential falloff of `glow`, 0.0035 of the screen height, scaled by 1.6); `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it), and no param has an upper cap - only the lower guards that avoid degenerate maths (`speed >= 0`, `glow >= 0.0005`). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/torn-edge.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`datamosh`: the frame is decoded against a reference that never arrives, and the movement search runs once per cell instead of once per pixel.** A new screen effect is a *feedback* effect: it keeps the picture it produced last frame in the frame's history buffer, and that history is the reference the decoder gets instead of a key frame. **The key frame never arrives**: the history only refreshes through the per-piece I-frame below, so the decoder keeps painting the screen as it was half a second ago. The movement is estimated the way a codec searches its reference frame for the matching block: one Lucas-Kanade step between the live picture and the reference (the reference carries the previous live frame's luma in its alpha) gives the velocity of the content, and a greedy search over 8, 4, 2 and 1 pixel finds the integer vector that best matches it, with a penalty that gives exactly zero where nothing moves. The search used to run per pixel (about 174 texture reads per pixel, a full-resolution pass the frame rate could not afford): every pixel of a piece needs the same vector, so the search now runs in its own pass at 1/8 resolution - one texel per 8x8 cell - and the update pass reads the result back. The picture is unchanged, the cost is about 6.5 reads per pixel instead of 174. The decoder then carries the pixels over as they are (point sampled, no filter, no leftover of the live frame), so a moving object drags sharp pixels of the old background with it; the luma is taken at the measured vector and the colour at `1 + chroma` times it, the way a 4:2:0 stream smears colour when it is decoded against the wrong reference - every colour is the picture's own, there is no random noise. The pieces come from a static hash quadtree (8 to 64 px), so neighbouring pieces differ in size and the layout never changes, and each piece returns to the real picture on its own phase every `iframe` seconds, so there is no global flash. `smear` (1) is the vector multiplier and `chroma` (0.5) the colour pull; only the lower guards exist (`chroma` and `amount` clamped to 0..1, `smear` and `iframe` not negative), no param has an upper cap. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Honest limits, stated on the page: the melt lives in the same history buffer as `afterimage`, so the two chain when both run and each overwrites the alpha the other needs; the reference is the screen's own past rather than another shot; the movement needs some structure in the picture, so in flat areas (sky, a plain wall) nothing is dragged; and the motion field is one texel per 8x8 cell, so a slower movement than 8 px is not resolved. Documented in the [guide](guide/effects/screen/datamosh.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1`, `scripts/check-post-fusion.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`heartbeat`: a heartbeat positioned in the world.** A new audio effect plays a recording of one lub-dub that ships inside the mod (`rate` beat rate in bpm, default `60`; `volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it; `pitch` playback-speed multiplier, default `1` = as recorded) straight into the OS mixer — no resource pack needed, identical on every Minecraft line. Every beat restarts the recording, and the recording is time-scaled to the tempo: at 60 bpm it plays as recorded (0.85 s in a 1 s beat) and at 140 bpm it is compressed to about 0.36 s in a 0.43 s beat, with the pause inside the lub-dub closing up in the same proportion. The scaling keeps the tone where it was, so a fast heart is a fast recording on the same pitch rather than a higher one; `pitch` is a separate tone multiplier that does move the pitch; the pause between beats is whatever the recording leaves free, not a separate re-timing. A cut playback is faded over 5 ms so a restart never clicks, and `rate` and `pitch` are read fresh every tick, so keyframes/`expr`/bindings ride them live and the phase integrates per sample, so a rate change glides. Positioning, distance falloff (full within 8 blocks, silent past 48), clock semantics (pause freezes, seek resyncs silently), fade-out on single stop and immediate `stopAll`/world exit/client shutdown close all mirror the `hum`/`noise` voices; the same honest limits apply (vanilla mixer bypassed, no-audio-device stays silent with a one-time warning). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/heartbeat.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`volume_duck`: scale a vanilla sound category down while active.** A new global (non-positional) audio effect sets one game-options category level to its pre-duck level times `volume` (`0..1`, neutral `1.0` so `fade_ticks` fades the duck in and out), selected by the `channel` param as a case-insensitive name (`master`, `music`, `records`, `weather`, `blocks`, `hostile`, `neutral`, `players`, `ambient`, `voice`, `ui`) or the historical `SoundSource` ordinal (`0..10`, back-compat); an unknown name warns once and safely no-ops instead of ducking the wrong category. Each frame only ducked channels are written (base x last-writer-wins scale across stacked ducks); the captured level is restored the frame a channel's last duck ends and on single stop, replay seek, `stopAll`, world exit and client shutdown, and nothing is ever written to `options.txt`. Honest limits, stated on the page: the options slider shows the scaled level and a mid-duck slider drag on that channel is overwritten, then lost on restore. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/volume-duck.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`noise`: generated white noise positioned in the world.** A new audio effect synthesises mono white noise per effect instance (`volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it; `brightness` crisp/soft blend `0..1`, default `0.5`; `jitter` random-walk step with `jitter_rate` speed multiplier for a playback-rate and volume wander; `seed` reseeds the texture) straight into the OS mixer — no audio file in any pack, identical on every Minecraft line. Positioning, distance falloff (full within 8 blocks, silent past 48), clock semantics (pause freezes, seek resyncs silently), fade-out on single stop and immediate `stopAll`/world exit/client shutdown close all mirror the `hum` voice; the same honest limits apply (vanilla mixer bypassed, no-audio-device stays silent with a one-time warning). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/noise.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`hum`: a generated sine hum positioned in the world.** A new audio effect synthesises a mono sine tone per effect instance (`frequency` in Hz, default `440`; `volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it) straight into the OS mixer — no audio file in any pack, identical on every Minecraft line. The voice is positioned like a world overlay (move position, first static `positions` entry, first entity anchor, authored `pos_x/y/z`, else the local-player snapshot) with a linear distance falloff (full within 8 blocks, silent past 48); it follows the shared effect clock, so pause freezes it and a replay seek resyncs silently. Stopping one instance fades it out; `stopAll`/world exit/client shutdown closes the voices at once. Honest limits, stated on the page: the vanilla mixer is bypassed (volume sliders do not apply — use `volume`), and a machine with no audio device stays silent with a one-time warning. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/hum.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
### Fixed
- **The frame rate no longer reaches the sound: the voices moved to their own audio thread.** The `hum`, `noise` and `heartbeat` voices were synthesised on the render thread, one chunk per frame, so the frame rate leaked into the sound: the gain ramps were one frame long, a long frame could starve the line's buffer (that is the crackling), and the heartbeat's time-scale work spiked the frame. The voices now live on a dedicated audio thread that generates fixed 5 ms blocks from its own wall clock and keeps every line about 60 ms ahead of its playhead, so a slow frame, a lost frame or an unfocused window cannot starve a line or stretch a ramp - the sound is the same at 30 fps and at 200 fps. The render thread only publishes the parameters and the shared effect clock; the voice state belongs to the audio thread, so there is nothing to lock. A paused game (or a paused replay) is still the one thing that stops it: the shared effect clock freezes and the thread renders silence until it moves again. The heartbeat's time-scale work moved to the audio thread with the rest. No param renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on the [hum](guide/effects/audio/hum.md), [noise](guide/effects/audio/noise.md) and [heartbeat](guide/effects/audio/heartbeat.md) pages. **Not verified in game**.
- **The synthesised voices no longer crackle, and a heartbeat plays out instead of being cut.** Three click sources are gone. A voice that resumes after a silence gap (a pause, a game hitch, a fade) now fades in over 5 ms instead of starting at full amplitude. A voice whose effect ended is no longer cut at the playhead: the `hum` and the `noise` fade out and their line is released once it has drained, so the stop is a fade and not a step. And the last 5 ms of a heartbeat recording fade out, so a beat ends without a click. The heartbeat's `duration` now decides when the sound stops *repeating*: when the instance ends, the thump stops repeating right there and the beat that is sounding plays to its end instead of being cut in the middle. No param renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on the [heartbeat](guide/effects/audio/heartbeat.md) page. **Not verified in game**.
- **Synthesised voices no longer stutter or crackle on slow frames.** The `hum`/`noise`/`heartbeat` voices counted samples from frame ticks, so an unfocused window (a few frames per second) pushed each frame past the 4-tick cap and the voice emitted nothing (chunk-gap-chunk), and the 0.25 s line buffer underran whenever the game loop stalled. Each voice now counts samples on its own steady wall clock gated by the effect clock: slow frames and replay seeks emit the wall-clock debt sized to the line's free buffer space (the write never blocks the frame), phases stay continuous so there are no bursts or boundary clicks, and pause (or a paused replay) still freezes synthesis exactly as before. The buffer grows to 0.5 s. No param renamed, no UBO or wire change, `PROTOCOL_VERSION` unchanged. Documented on the [hum](guide/effects/audio/hum.md), [noise](guide/effects/audio/noise.md) and [heartbeat](guide/effects/audio/heartbeat.md) pages; audible output cannot be verified headless here. **Not verified in game**.
- **Local-contrast halos around geometry in `halftone`, `comic` and `accent_glow` — local averaging removed.** The 8-tap averaging ring made the tone mapping a de-facto edge detector: near any luminance step the local mean skewed and the tone saturated into flat paper-coloured bands with no dots, which reads as glowing outlines and is useless for halftones. All three effects now map absolute pixel luminance instead (`halftone` keeps its adjustable `gain` contrast around mid grey, default `2.5` = neutral; `comic` gains a tunable `mid` split point, default `0.5`; `accent_glow` gains tunable `mid`/`core` thresholds, defaults `0.5`/`0.68`), so halos cannot form by construction — plus 8 fewer texture fetches per pixel. Dot grids grow from the screen centre instead of a corner, so changing `cell_size` rescales around the middle of the frame. Three new optional params (`mid` on `comic`; `mid`, `core` on `accent_glow`), no UBO or wire change, `PROTOCOL_VERSION` unchanged. **Not verified in game**.
release
VFXWeaver API 2.5.0 (1.21.11, NeoForge)
2.5.022 окт. 2026 г.61Minecraft: 1.21.11neoforge
Что нового
### Added
- **`hex_grid`: a triangular grid coloured by the screen position.** A new screen effect draws a triangular lattice over the frame - three line directions (two at 60 degrees, one vertical) on a `cell_size` cell (46 px by default) anchored at the screen centre - and paints the lines with the prism ramp picked by the position (`prism7(x * 0.6 + y * 0.4 + time * speed)`, drifting at 0.5 per second): the lines at 28 % of that colour over near-black `(0.01, 0.008, 0.02)`, and the highlights of the picture (brighter than `core`, 0.68 by default - absolute, not local, so no outline appears at a brightness step) in white; `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it) and no param has an upper cap - only the lower guards that avoid degenerate maths (`cell_size >= 1` px, `width >= 0.0001`). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/hex-grid.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`spectral`: a rainbow prism look over the frame.** A new screen effect draws the picture through a seven-stop prism ramp - red `(1.0, 0.07, 0.1)`, orange `(1.0, 0.42, 0.03)`, yellow `(1.0, 0.88, 0.06)`, green `(0.1, 0.95, 0.28)`, blue `(0.06, 0.48, 1.0)`, violet `(0.28, 0.14, 1.0)`, magenta `(0.74, 0.16, 1.0)` - swept by the angle around `center_x`/`center_y`, the distance from it (0.9 per screen height) and the clock (0.7 per second); `mode` picks the variant: **0** the prism duotone (the frame is split into a dark and a bright half by its own brightness, `mid` 0.5 by default - absolute, not local, so no outline appears at a brightness step; the bright half takes the prism colour and the highlights, `core` 0.68, go white), **1** the chromatic smear (seven taps along the outward direction, spread by `0.0035 + 0.004 * distance`, every tap's edge `smoothstep(0.02, 0.30)` painted with its own prism stop - a wide ramp, so it stays a smear and not a drawn outline) and **2** the spectral ring (the luminance picks a band `floor(luma * 6.999)`, the distance picks a ring `floor(distance * 9 - time * 6)`, the two together pick a spectral stop and the highlights (`core` 0.68) go white; `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it), `mode` is an enum clamped to 0..2 and no other param has an upper cap. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/spectral.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`torn_edge`: a ragged tear with a glowing line down it.** A new screen effect cuts the frame along a straight line through `center_x`/`center_y` whose angle is re-rolled every beat (`speed`, 20 per second by default): a value-noise ripple of `ragged` (0.02 of the screen height, 38 cycles across the tear) makes the edge wander, the two sides of the cut are drawn at opposite brightness (a pixel brighter than `mid`, 0.5 by default - the split is absolute, not local, so no outline appears at a brightness step), and the tear itself is a white line (an exponential falloff of `glow`, 0.0035 of the screen height, scaled by 1.6); `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it), and no param has an upper cap - only the lower guards that avoid degenerate maths (`speed >= 0`, `glow >= 0.0005`). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/torn-edge.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`datamosh`: the frame is decoded against a reference that never arrives, and the movement search runs once per cell instead of once per pixel.** A new screen effect is a *feedback* effect: it keeps the picture it produced last frame in the frame's history buffer, and that history is the reference the decoder gets instead of a key frame. **The key frame never arrives**: the history only refreshes through the per-piece I-frame below, so the decoder keeps painting the screen as it was half a second ago. The movement is estimated the way a codec searches its reference frame for the matching block: one Lucas-Kanade step between the live picture and the reference (the reference carries the previous live frame's luma in its alpha) gives the velocity of the content, and a greedy search over 8, 4, 2 and 1 pixel finds the integer vector that best matches it, with a penalty that gives exactly zero where nothing moves. The search used to run per pixel (about 174 texture reads per pixel, a full-resolution pass the frame rate could not afford): every pixel of a piece needs the same vector, so the search now runs in its own pass at 1/8 resolution - one texel per 8x8 cell - and the update pass reads the result back. The picture is unchanged, the cost is about 6.5 reads per pixel instead of 174. The decoder then carries the pixels over as they are (point sampled, no filter, no leftover of the live frame), so a moving object drags sharp pixels of the old background with it; the luma is taken at the measured vector and the colour at `1 + chroma` times it, the way a 4:2:0 stream smears colour when it is decoded against the wrong reference - every colour is the picture's own, there is no random noise. The pieces come from a static hash quadtree (8 to 64 px), so neighbouring pieces differ in size and the layout never changes, and each piece returns to the real picture on its own phase every `iframe` seconds, so there is no global flash. `smear` (1) is the vector multiplier and `chroma` (0.5) the colour pull; only the lower guards exist (`chroma` and `amount` clamped to 0..1, `smear` and `iframe` not negative), no param has an upper cap. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Honest limits, stated on the page: the melt lives in the same history buffer as `afterimage`, so the two chain when both run and each overwrites the alpha the other needs; the reference is the screen's own past rather than another shot; the movement needs some structure in the picture, so in flat areas (sky, a plain wall) nothing is dragged; and the motion field is one texel per 8x8 cell, so a slower movement than 8 px is not resolved. Documented in the [guide](guide/effects/screen/datamosh.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1`, `scripts/check-post-fusion.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`heartbeat`: a heartbeat positioned in the world.** A new audio effect plays a recording of one lub-dub that ships inside the mod (`rate` beat rate in bpm, default `60`; `volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it; `pitch` playback-speed multiplier, default `1` = as recorded) straight into the OS mixer — no resource pack needed, identical on every Minecraft line. Every beat restarts the recording, and the recording is time-scaled to the tempo: at 60 bpm it plays as recorded (0.85 s in a 1 s beat) and at 140 bpm it is compressed to about 0.36 s in a 0.43 s beat, with the pause inside the lub-dub closing up in the same proportion. The scaling keeps the tone where it was, so a fast heart is a fast recording on the same pitch rather than a higher one; `pitch` is a separate tone multiplier that does move the pitch; the pause between beats is whatever the recording leaves free, not a separate re-timing. A cut playback is faded over 5 ms so a restart never clicks, and `rate` and `pitch` are read fresh every tick, so keyframes/`expr`/bindings ride them live and the phase integrates per sample, so a rate change glides. Positioning, distance falloff (full within 8 blocks, silent past 48), clock semantics (pause freezes, seek resyncs silently), fade-out on single stop and immediate `stopAll`/world exit/client shutdown close all mirror the `hum`/`noise` voices; the same honest limits apply (vanilla mixer bypassed, no-audio-device stays silent with a one-time warning). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/heartbeat.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`volume_duck`: scale a vanilla sound category down while active.** A new global (non-positional) audio effect sets one game-options category level to its pre-duck level times `volume` (`0..1`, neutral `1.0` so `fade_ticks` fades the duck in and out), selected by the `channel` param as a case-insensitive name (`master`, `music`, `records`, `weather`, `blocks`, `hostile`, `neutral`, `players`, `ambient`, `voice`, `ui`) or the historical `SoundSource` ordinal (`0..10`, back-compat); an unknown name warns once and safely no-ops instead of ducking the wrong category. Each frame only ducked channels are written (base x last-writer-wins scale across stacked ducks); the captured level is restored the frame a channel's last duck ends and on single stop, replay seek, `stopAll`, world exit and client shutdown, and nothing is ever written to `options.txt`. Honest limits, stated on the page: the options slider shows the scaled level and a mid-duck slider drag on that channel is overwritten, then lost on restore. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/volume-duck.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`noise`: generated white noise positioned in the world.** A new audio effect synthesises mono white noise per effect instance (`volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it; `brightness` crisp/soft blend `0..1`, default `0.5`; `jitter` random-walk step with `jitter_rate` speed multiplier for a playback-rate and volume wander; `seed` reseeds the texture) straight into the OS mixer — no audio file in any pack, identical on every Minecraft line. Positioning, distance falloff (full within 8 blocks, silent past 48), clock semantics (pause freezes, seek resyncs silently), fade-out on single stop and immediate `stopAll`/world exit/client shutdown close all mirror the `hum` voice; the same honest limits apply (vanilla mixer bypassed, no-audio-device stays silent with a one-time warning). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/noise.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`hum`: a generated sine hum positioned in the world.** A new audio effect synthesises a mono sine tone per effect instance (`frequency` in Hz, default `440`; `volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it) straight into the OS mixer — no audio file in any pack, identical on every Minecraft line. The voice is positioned like a world overlay (move position, first static `positions` entry, first entity anchor, authored `pos_x/y/z`, else the local-player snapshot) with a linear distance falloff (full within 8 blocks, silent past 48); it follows the shared effect clock, so pause freezes it and a replay seek resyncs silently. Stopping one instance fades it out; `stopAll`/world exit/client shutdown closes the voices at once. Honest limits, stated on the page: the vanilla mixer is bypassed (volume sliders do not apply — use `volume`), and a machine with no audio device stays silent with a one-time warning. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/hum.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
### Fixed
- **The frame rate no longer reaches the sound: the voices moved to their own audio thread.** The `hum`, `noise` and `heartbeat` voices were synthesised on the render thread, one chunk per frame, so the frame rate leaked into the sound: the gain ramps were one frame long, a long frame could starve the line's buffer (that is the crackling), and the heartbeat's time-scale work spiked the frame. The voices now live on a dedicated audio thread that generates fixed 5 ms blocks from its own wall clock and keeps every line about 60 ms ahead of its playhead, so a slow frame, a lost frame or an unfocused window cannot starve a line or stretch a ramp - the sound is the same at 30 fps and at 200 fps. The render thread only publishes the parameters and the shared effect clock; the voice state belongs to the audio thread, so there is nothing to lock. A paused game (or a paused replay) is still the one thing that stops it: the shared effect clock freezes and the thread renders silence until it moves again. The heartbeat's time-scale work moved to the audio thread with the rest. No param renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on the [hum](guide/effects/audio/hum.md), [noise](guide/effects/audio/noise.md) and [heartbeat](guide/effects/audio/heartbeat.md) pages. **Not verified in game**.
- **The synthesised voices no longer crackle, and a heartbeat plays out instead of being cut.** Three click sources are gone. A voice that resumes after a silence gap (a pause, a game hitch, a fade) now fades in over 5 ms instead of starting at full amplitude. A voice whose effect ended is no longer cut at the playhead: the `hum` and the `noise` fade out and their line is released once it has drained, so the stop is a fade and not a step. And the last 5 ms of a heartbeat recording fade out, so a beat ends without a click. The heartbeat's `duration` now decides when the sound stops *repeating*: when the instance ends, the thump stops repeating right there and the beat that is sounding plays to its end instead of being cut in the middle. No param renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on the [heartbeat](guide/effects/audio/heartbeat.md) page. **Not verified in game**.
- **Synthesised voices no longer stutter or crackle on slow frames.** The `hum`/`noise`/`heartbeat` voices counted samples from frame ticks, so an unfocused window (a few frames per second) pushed each frame past the 4-tick cap and the voice emitted nothing (chunk-gap-chunk), and the 0.25 s line buffer underran whenever the game loop stalled. Each voice now counts samples on its own steady wall clock gated by the effect clock: slow frames and replay seeks emit the wall-clock debt sized to the line's free buffer space (the write never blocks the frame), phases stay continuous so there are no bursts or boundary clicks, and pause (or a paused replay) still freezes synthesis exactly as before. The buffer grows to 0.5 s. No param renamed, no UBO or wire change, `PROTOCOL_VERSION` unchanged. Documented on the [hum](guide/effects/audio/hum.md), [noise](guide/effects/audio/noise.md) and [heartbeat](guide/effects/audio/heartbeat.md) pages; audible output cannot be verified headless here. **Not verified in game**.
- **Local-contrast halos around geometry in `halftone`, `comic` and `accent_glow` — local averaging removed.** The 8-tap averaging ring made the tone mapping a de-facto edge detector: near any luminance step the local mean skewed and the tone saturated into flat paper-coloured bands with no dots, which reads as glowing outlines and is useless for halftones. All three effects now map absolute pixel luminance instead (`halftone` keeps its adjustable `gain` contrast around mid grey, default `2.5` = neutral; `comic` gains a tunable `mid` split point, default `0.5`; `accent_glow` gains tunable `mid`/`core` thresholds, defaults `0.5`/`0.68`), so halos cannot form by construction — plus 8 fewer texture fetches per pixel. Dot grids grow from the screen centre instead of a corner, so changing `cell_size` rescales around the middle of the frame. Three new optional params (`mid` on `comic`; `mid`, `core` on `accent_glow`), no UBO or wire change, `PROTOCOL_VERSION` unchanged. **Not verified in game**.
release
VFXWeaver API 2.5.0 (26.1.x)
2.5.022 окт. 2026 г.29Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
### Added
- **`hex_grid`: a triangular grid coloured by the screen position.** A new screen effect draws a triangular lattice over the frame - three line directions (two at 60 degrees, one vertical) on a `cell_size` cell (46 px by default) anchored at the screen centre - and paints the lines with the prism ramp picked by the position (`prism7(x * 0.6 + y * 0.4 + time * speed)`, drifting at 0.5 per second): the lines at 28 % of that colour over near-black `(0.01, 0.008, 0.02)`, and the highlights of the picture (brighter than `core`, 0.68 by default - absolute, not local, so no outline appears at a brightness step) in white; `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it) and no param has an upper cap - only the lower guards that avoid degenerate maths (`cell_size >= 1` px, `width >= 0.0001`). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/hex-grid.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`spectral`: a rainbow prism look over the frame.** A new screen effect draws the picture through a seven-stop prism ramp - red `(1.0, 0.07, 0.1)`, orange `(1.0, 0.42, 0.03)`, yellow `(1.0, 0.88, 0.06)`, green `(0.1, 0.95, 0.28)`, blue `(0.06, 0.48, 1.0)`, violet `(0.28, 0.14, 1.0)`, magenta `(0.74, 0.16, 1.0)` - swept by the angle around `center_x`/`center_y`, the distance from it (0.9 per screen height) and the clock (0.7 per second); `mode` picks the variant: **0** the prism duotone (the frame is split into a dark and a bright half by its own brightness, `mid` 0.5 by default - absolute, not local, so no outline appears at a brightness step; the bright half takes the prism colour and the highlights, `core` 0.68, go white), **1** the chromatic smear (seven taps along the outward direction, spread by `0.0035 + 0.004 * distance`, every tap's edge `smoothstep(0.02, 0.30)` painted with its own prism stop - a wide ramp, so it stays a smear and not a drawn outline) and **2** the spectral ring (the luminance picks a band `floor(luma * 6.999)`, the distance picks a ring `floor(distance * 9 - time * 6)`, the two together pick a spectral stop and the highlights (`core` 0.68) go white; `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it), `mode` is an enum clamped to 0..2 and no other param has an upper cap. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/spectral.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`torn_edge`: a ragged tear with a glowing line down it.** A new screen effect cuts the frame along a straight line through `center_x`/`center_y` whose angle is re-rolled every beat (`speed`, 20 per second by default): a value-noise ripple of `ragged` (0.02 of the screen height, 38 cycles across the tear) makes the edge wander, the two sides of the cut are drawn at opposite brightness (a pixel brighter than `mid`, 0.5 by default - the split is absolute, not local, so no outline appears at a brightness step), and the tear itself is a white line (an exponential falloff of `glow`, 0.0035 of the screen height, scaled by 1.6); `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it), and no param has an upper cap - only the lower guards that avoid degenerate maths (`speed >= 0`, `glow >= 0.0005`). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/torn-edge.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`datamosh`: the frame is decoded against a reference that never arrives, and the movement search runs once per cell instead of once per pixel.** A new screen effect is a *feedback* effect: it keeps the picture it produced last frame in the frame's history buffer, and that history is the reference the decoder gets instead of a key frame. **The key frame never arrives**: the history only refreshes through the per-piece I-frame below, so the decoder keeps painting the screen as it was half a second ago. The movement is estimated the way a codec searches its reference frame for the matching block: one Lucas-Kanade step between the live picture and the reference (the reference carries the previous live frame's luma in its alpha) gives the velocity of the content, and a greedy search over 8, 4, 2 and 1 pixel finds the integer vector that best matches it, with a penalty that gives exactly zero where nothing moves. The search used to run per pixel (about 174 texture reads per pixel, a full-resolution pass the frame rate could not afford): every pixel of a piece needs the same vector, so the search now runs in its own pass at 1/8 resolution - one texel per 8x8 cell - and the update pass reads the result back. The picture is unchanged, the cost is about 6.5 reads per pixel instead of 174. The decoder then carries the pixels over as they are (point sampled, no filter, no leftover of the live frame), so a moving object drags sharp pixels of the old background with it; the luma is taken at the measured vector and the colour at `1 + chroma` times it, the way a 4:2:0 stream smears colour when it is decoded against the wrong reference - every colour is the picture's own, there is no random noise. The pieces come from a static hash quadtree (8 to 64 px), so neighbouring pieces differ in size and the layout never changes, and each piece returns to the real picture on its own phase every `iframe` seconds, so there is no global flash. `smear` (1) is the vector multiplier and `chroma` (0.5) the colour pull; only the lower guards exist (`chroma` and `amount` clamped to 0..1, `smear` and `iframe` not negative), no param has an upper cap. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Honest limits, stated on the page: the melt lives in the same history buffer as `afterimage`, so the two chain when both run and each overwrites the alpha the other needs; the reference is the screen's own past rather than another shot; the movement needs some structure in the picture, so in flat areas (sky, a plain wall) nothing is dragged; and the motion field is one texel per 8x8 cell, so a slower movement than 8 px is not resolved. Documented in the [guide](guide/effects/screen/datamosh.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1`, `scripts/check-post-fusion.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`heartbeat`: a heartbeat positioned in the world.** A new audio effect plays a recording of one lub-dub that ships inside the mod (`rate` beat rate in bpm, default `60`; `volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it; `pitch` playback-speed multiplier, default `1` = as recorded) straight into the OS mixer — no resource pack needed, identical on every Minecraft line. Every beat restarts the recording, and the recording is time-scaled to the tempo: at 60 bpm it plays as recorded (0.85 s in a 1 s beat) and at 140 bpm it is compressed to about 0.36 s in a 0.43 s beat, with the pause inside the lub-dub closing up in the same proportion. The scaling keeps the tone where it was, so a fast heart is a fast recording on the same pitch rather than a higher one; `pitch` is a separate tone multiplier that does move the pitch; the pause between beats is whatever the recording leaves free, not a separate re-timing. A cut playback is faded over 5 ms so a restart never clicks, and `rate` and `pitch` are read fresh every tick, so keyframes/`expr`/bindings ride them live and the phase integrates per sample, so a rate change glides. Positioning, distance falloff (full within 8 blocks, silent past 48), clock semantics (pause freezes, seek resyncs silently), fade-out on single stop and immediate `stopAll`/world exit/client shutdown close all mirror the `hum`/`noise` voices; the same honest limits apply (vanilla mixer bypassed, no-audio-device stays silent with a one-time warning). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/heartbeat.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`volume_duck`: scale a vanilla sound category down while active.** A new global (non-positional) audio effect sets one game-options category level to its pre-duck level times `volume` (`0..1`, neutral `1.0` so `fade_ticks` fades the duck in and out), selected by the `channel` param as a case-insensitive name (`master`, `music`, `records`, `weather`, `blocks`, `hostile`, `neutral`, `players`, `ambient`, `voice`, `ui`) or the historical `SoundSource` ordinal (`0..10`, back-compat); an unknown name warns once and safely no-ops instead of ducking the wrong category. Each frame only ducked channels are written (base x last-writer-wins scale across stacked ducks); the captured level is restored the frame a channel's last duck ends and on single stop, replay seek, `stopAll`, world exit and client shutdown, and nothing is ever written to `options.txt`. Honest limits, stated on the page: the options slider shows the scaled level and a mid-duck slider drag on that channel is overwritten, then lost on restore. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/volume-duck.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`noise`: generated white noise positioned in the world.** A new audio effect synthesises mono white noise per effect instance (`volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it; `brightness` crisp/soft blend `0..1`, default `0.5`; `jitter` random-walk step with `jitter_rate` speed multiplier for a playback-rate and volume wander; `seed` reseeds the texture) straight into the OS mixer — no audio file in any pack, identical on every Minecraft line. Positioning, distance falloff (full within 8 blocks, silent past 48), clock semantics (pause freezes, seek resyncs silently), fade-out on single stop and immediate `stopAll`/world exit/client shutdown close all mirror the `hum` voice; the same honest limits apply (vanilla mixer bypassed, no-audio-device stays silent with a one-time warning). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/noise.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`hum`: a generated sine hum positioned in the world.** A new audio effect synthesises a mono sine tone per effect instance (`frequency` in Hz, default `440`; `volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it) straight into the OS mixer — no audio file in any pack, identical on every Minecraft line. The voice is positioned like a world overlay (move position, first static `positions` entry, first entity anchor, authored `pos_x/y/z`, else the local-player snapshot) with a linear distance falloff (full within 8 blocks, silent past 48); it follows the shared effect clock, so pause freezes it and a replay seek resyncs silently. Stopping one instance fades it out; `stopAll`/world exit/client shutdown closes the voices at once. Honest limits, stated on the page: the vanilla mixer is bypassed (volume sliders do not apply — use `volume`), and a machine with no audio device stays silent with a one-time warning. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/hum.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
### Fixed
- **The frame rate no longer reaches the sound: the voices moved to their own audio thread.** The `hum`, `noise` and `heartbeat` voices were synthesised on the render thread, one chunk per frame, so the frame rate leaked into the sound: the gain ramps were one frame long, a long frame could starve the line's buffer (that is the crackling), and the heartbeat's time-scale work spiked the frame. The voices now live on a dedicated audio thread that generates fixed 5 ms blocks from its own wall clock and keeps every line about 60 ms ahead of its playhead, so a slow frame, a lost frame or an unfocused window cannot starve a line or stretch a ramp - the sound is the same at 30 fps and at 200 fps. The render thread only publishes the parameters and the shared effect clock; the voice state belongs to the audio thread, so there is nothing to lock. A paused game (or a paused replay) is still the one thing that stops it: the shared effect clock freezes and the thread renders silence until it moves again. The heartbeat's time-scale work moved to the audio thread with the rest. No param renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on the [hum](guide/effects/audio/hum.md), [noise](guide/effects/audio/noise.md) and [heartbeat](guide/effects/audio/heartbeat.md) pages. **Not verified in game**.
- **The synthesised voices no longer crackle, and a heartbeat plays out instead of being cut.** Three click sources are gone. A voice that resumes after a silence gap (a pause, a game hitch, a fade) now fades in over 5 ms instead of starting at full amplitude. A voice whose effect ended is no longer cut at the playhead: the `hum` and the `noise` fade out and their line is released once it has drained, so the stop is a fade and not a step. And the last 5 ms of a heartbeat recording fade out, so a beat ends without a click. The heartbeat's `duration` now decides when the sound stops *repeating*: when the instance ends, the thump stops repeating right there and the beat that is sounding plays to its end instead of being cut in the middle. No param renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on the [heartbeat](guide/effects/audio/heartbeat.md) page. **Not verified in game**.
- **Synthesised voices no longer stutter or crackle on slow frames.** The `hum`/`noise`/`heartbeat` voices counted samples from frame ticks, so an unfocused window (a few frames per second) pushed each frame past the 4-tick cap and the voice emitted nothing (chunk-gap-chunk), and the 0.25 s line buffer underran whenever the game loop stalled. Each voice now counts samples on its own steady wall clock gated by the effect clock: slow frames and replay seeks emit the wall-clock debt sized to the line's free buffer space (the write never blocks the frame), phases stay continuous so there are no bursts or boundary clicks, and pause (or a paused replay) still freezes synthesis exactly as before. The buffer grows to 0.5 s. No param renamed, no UBO or wire change, `PROTOCOL_VERSION` unchanged. Documented on the [hum](guide/effects/audio/hum.md), [noise](guide/effects/audio/noise.md) and [heartbeat](guide/effects/audio/heartbeat.md) pages; audible output cannot be verified headless here. **Not verified in game**.
- **Local-contrast halos around geometry in `halftone`, `comic` and `accent_glow` — local averaging removed.** The 8-tap averaging ring made the tone mapping a de-facto edge detector: near any luminance step the local mean skewed and the tone saturated into flat paper-coloured bands with no dots, which reads as glowing outlines and is useless for halftones. All three effects now map absolute pixel luminance instead (`halftone` keeps its adjustable `gain` contrast around mid grey, default `2.5` = neutral; `comic` gains a tunable `mid` split point, default `0.5`; `accent_glow` gains tunable `mid`/`core` thresholds, defaults `0.5`/`0.68`), so halos cannot form by construction — plus 8 fewer texture fetches per pixel. Dot grids grow from the screen centre instead of a corner, so changing `cell_size` rescales around the middle of the frame. Three new optional params (`mid` on `comic`; `mid`, `core` on `accent_glow`), no UBO or wire change, `PROTOCOL_VERSION` unchanged. **Not verified in game**.
release
VFXWeaver API 2.5.0 (26.1.x, NeoForge)
2.5.022 окт. 2026 г.29Minecraft: 26.1, 26.1.1, 26.1.2neoforge
Что нового
### Added
- **`hex_grid`: a triangular grid coloured by the screen position.** A new screen effect draws a triangular lattice over the frame - three line directions (two at 60 degrees, one vertical) on a `cell_size` cell (46 px by default) anchored at the screen centre - and paints the lines with the prism ramp picked by the position (`prism7(x * 0.6 + y * 0.4 + time * speed)`, drifting at 0.5 per second): the lines at 28 % of that colour over near-black `(0.01, 0.008, 0.02)`, and the highlights of the picture (brighter than `core`, 0.68 by default - absolute, not local, so no outline appears at a brightness step) in white; `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it) and no param has an upper cap - only the lower guards that avoid degenerate maths (`cell_size >= 1` px, `width >= 0.0001`). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/hex-grid.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`spectral`: a rainbow prism look over the frame.** A new screen effect draws the picture through a seven-stop prism ramp - red `(1.0, 0.07, 0.1)`, orange `(1.0, 0.42, 0.03)`, yellow `(1.0, 0.88, 0.06)`, green `(0.1, 0.95, 0.28)`, blue `(0.06, 0.48, 1.0)`, violet `(0.28, 0.14, 1.0)`, magenta `(0.74, 0.16, 1.0)` - swept by the angle around `center_x`/`center_y`, the distance from it (0.9 per screen height) and the clock (0.7 per second); `mode` picks the variant: **0** the prism duotone (the frame is split into a dark and a bright half by its own brightness, `mid` 0.5 by default - absolute, not local, so no outline appears at a brightness step; the bright half takes the prism colour and the highlights, `core` 0.68, go white), **1** the chromatic smear (seven taps along the outward direction, spread by `0.0035 + 0.004 * distance`, every tap's edge `smoothstep(0.02, 0.30)` painted with its own prism stop - a wide ramp, so it stays a smear and not a drawn outline) and **2** the spectral ring (the luminance picks a band `floor(luma * 6.999)`, the distance picks a ring `floor(distance * 9 - time * 6)`, the two together pick a spectral stop and the highlights (`core` 0.68) go white; `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it), `mode` is an enum clamped to 0..2 and no other param has an upper cap. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/spectral.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`torn_edge`: a ragged tear with a glowing line down it.** A new screen effect cuts the frame along a straight line through `center_x`/`center_y` whose angle is re-rolled every beat (`speed`, 20 per second by default): a value-noise ripple of `ragged` (0.02 of the screen height, 38 cycles across the tear) makes the edge wander, the two sides of the cut are drawn at opposite brightness (a pixel brighter than `mid`, 0.5 by default - the split is absolute, not local, so no outline appears at a brightness step), and the tear itself is a white line (an exponential falloff of `glow`, 0.0035 of the screen height, scaled by 1.6); `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it), and no param has an upper cap - only the lower guards that avoid degenerate maths (`speed >= 0`, `glow >= 0.0005`). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/torn-edge.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`datamosh`: the frame is decoded against a reference that never arrives, and the movement search runs once per cell instead of once per pixel.** A new screen effect is a *feedback* effect: it keeps the picture it produced last frame in the frame's history buffer, and that history is the reference the decoder gets instead of a key frame. **The key frame never arrives**: the history only refreshes through the per-piece I-frame below, so the decoder keeps painting the screen as it was half a second ago. The movement is estimated the way a codec searches its reference frame for the matching block: one Lucas-Kanade step between the live picture and the reference (the reference carries the previous live frame's luma in its alpha) gives the velocity of the content, and a greedy search over 8, 4, 2 and 1 pixel finds the integer vector that best matches it, with a penalty that gives exactly zero where nothing moves. The search used to run per pixel (about 174 texture reads per pixel, a full-resolution pass the frame rate could not afford): every pixel of a piece needs the same vector, so the search now runs in its own pass at 1/8 resolution - one texel per 8x8 cell - and the update pass reads the result back. The picture is unchanged, the cost is about 6.5 reads per pixel instead of 174. The decoder then carries the pixels over as they are (point sampled, no filter, no leftover of the live frame), so a moving object drags sharp pixels of the old background with it; the luma is taken at the measured vector and the colour at `1 + chroma` times it, the way a 4:2:0 stream smears colour when it is decoded against the wrong reference - every colour is the picture's own, there is no random noise. The pieces come from a static hash quadtree (8 to 64 px), so neighbouring pieces differ in size and the layout never changes, and each piece returns to the real picture on its own phase every `iframe` seconds, so there is no global flash. `smear` (1) is the vector multiplier and `chroma` (0.5) the colour pull; only the lower guards exist (`chroma` and `amount` clamped to 0..1, `smear` and `iframe` not negative), no param has an upper cap. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Honest limits, stated on the page: the melt lives in the same history buffer as `afterimage`, so the two chain when both run and each overwrites the alpha the other needs; the reference is the screen's own past rather than another shot; the movement needs some structure in the picture, so in flat areas (sky, a plain wall) nothing is dragged; and the motion field is one texel per 8x8 cell, so a slower movement than 8 px is not resolved. Documented in the [guide](guide/effects/screen/datamosh.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1`, `scripts/check-post-fusion.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`heartbeat`: a heartbeat positioned in the world.** A new audio effect plays a recording of one lub-dub that ships inside the mod (`rate` beat rate in bpm, default `60`; `volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it; `pitch` playback-speed multiplier, default `1` = as recorded) straight into the OS mixer — no resource pack needed, identical on every Minecraft line. Every beat restarts the recording, and the recording is time-scaled to the tempo: at 60 bpm it plays as recorded (0.85 s in a 1 s beat) and at 140 bpm it is compressed to about 0.36 s in a 0.43 s beat, with the pause inside the lub-dub closing up in the same proportion. The scaling keeps the tone where it was, so a fast heart is a fast recording on the same pitch rather than a higher one; `pitch` is a separate tone multiplier that does move the pitch; the pause between beats is whatever the recording leaves free, not a separate re-timing. A cut playback is faded over 5 ms so a restart never clicks, and `rate` and `pitch` are read fresh every tick, so keyframes/`expr`/bindings ride them live and the phase integrates per sample, so a rate change glides. Positioning, distance falloff (full within 8 blocks, silent past 48), clock semantics (pause freezes, seek resyncs silently), fade-out on single stop and immediate `stopAll`/world exit/client shutdown close all mirror the `hum`/`noise` voices; the same honest limits apply (vanilla mixer bypassed, no-audio-device stays silent with a one-time warning). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/heartbeat.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`volume_duck`: scale a vanilla sound category down while active.** A new global (non-positional) audio effect sets one game-options category level to its pre-duck level times `volume` (`0..1`, neutral `1.0` so `fade_ticks` fades the duck in and out), selected by the `channel` param as a case-insensitive name (`master`, `music`, `records`, `weather`, `blocks`, `hostile`, `neutral`, `players`, `ambient`, `voice`, `ui`) or the historical `SoundSource` ordinal (`0..10`, back-compat); an unknown name warns once and safely no-ops instead of ducking the wrong category. Each frame only ducked channels are written (base x last-writer-wins scale across stacked ducks); the captured level is restored the frame a channel's last duck ends and on single stop, replay seek, `stopAll`, world exit and client shutdown, and nothing is ever written to `options.txt`. Honest limits, stated on the page: the options slider shows the scaled level and a mid-duck slider drag on that channel is overwritten, then lost on restore. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/volume-duck.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`noise`: generated white noise positioned in the world.** A new audio effect synthesises mono white noise per effect instance (`volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it; `brightness` crisp/soft blend `0..1`, default `0.5`; `jitter` random-walk step with `jitter_rate` speed multiplier for a playback-rate and volume wander; `seed` reseeds the texture) straight into the OS mixer — no audio file in any pack, identical on every Minecraft line. Positioning, distance falloff (full within 8 blocks, silent past 48), clock semantics (pause freezes, seek resyncs silently), fade-out on single stop and immediate `stopAll`/world exit/client shutdown close all mirror the `hum` voice; the same honest limits apply (vanilla mixer bypassed, no-audio-device stays silent with a one-time warning). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/noise.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`hum`: a generated sine hum positioned in the world.** A new audio effect synthesises a mono sine tone per effect instance (`frequency` in Hz, default `440`; `volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it) straight into the OS mixer — no audio file in any pack, identical on every Minecraft line. The voice is positioned like a world overlay (move position, first static `positions` entry, first entity anchor, authored `pos_x/y/z`, else the local-player snapshot) with a linear distance falloff (full within 8 blocks, silent past 48); it follows the shared effect clock, so pause freezes it and a replay seek resyncs silently. Stopping one instance fades it out; `stopAll`/world exit/client shutdown closes the voices at once. Honest limits, stated on the page: the vanilla mixer is bypassed (volume sliders do not apply — use `volume`), and a machine with no audio device stays silent with a one-time warning. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/hum.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
### Fixed
- **The frame rate no longer reaches the sound: the voices moved to their own audio thread.** The `hum`, `noise` and `heartbeat` voices were synthesised on the render thread, one chunk per frame, so the frame rate leaked into the sound: the gain ramps were one frame long, a long frame could starve the line's buffer (that is the crackling), and the heartbeat's time-scale work spiked the frame. The voices now live on a dedicated audio thread that generates fixed 5 ms blocks from its own wall clock and keeps every line about 60 ms ahead of its playhead, so a slow frame, a lost frame or an unfocused window cannot starve a line or stretch a ramp - the sound is the same at 30 fps and at 200 fps. The render thread only publishes the parameters and the shared effect clock; the voice state belongs to the audio thread, so there is nothing to lock. A paused game (or a paused replay) is still the one thing that stops it: the shared effect clock freezes and the thread renders silence until it moves again. The heartbeat's time-scale work moved to the audio thread with the rest. No param renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on the [hum](guide/effects/audio/hum.md), [noise](guide/effects/audio/noise.md) and [heartbeat](guide/effects/audio/heartbeat.md) pages. **Not verified in game**.
- **The synthesised voices no longer crackle, and a heartbeat plays out instead of being cut.** Three click sources are gone. A voice that resumes after a silence gap (a pause, a game hitch, a fade) now fades in over 5 ms instead of starting at full amplitude. A voice whose effect ended is no longer cut at the playhead: the `hum` and the `noise` fade out and their line is released once it has drained, so the stop is a fade and not a step. And the last 5 ms of a heartbeat recording fade out, so a beat ends without a click. The heartbeat's `duration` now decides when the sound stops *repeating*: when the instance ends, the thump stops repeating right there and the beat that is sounding plays to its end instead of being cut in the middle. No param renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on the [heartbeat](guide/effects/audio/heartbeat.md) page. **Not verified in game**.
- **Synthesised voices no longer stutter or crackle on slow frames.** The `hum`/`noise`/`heartbeat` voices counted samples from frame ticks, so an unfocused window (a few frames per second) pushed each frame past the 4-tick cap and the voice emitted nothing (chunk-gap-chunk), and the 0.25 s line buffer underran whenever the game loop stalled. Each voice now counts samples on its own steady wall clock gated by the effect clock: slow frames and replay seeks emit the wall-clock debt sized to the line's free buffer space (the write never blocks the frame), phases stay continuous so there are no bursts or boundary clicks, and pause (or a paused replay) still freezes synthesis exactly as before. The buffer grows to 0.5 s. No param renamed, no UBO or wire change, `PROTOCOL_VERSION` unchanged. Documented on the [hum](guide/effects/audio/hum.md), [noise](guide/effects/audio/noise.md) and [heartbeat](guide/effects/audio/heartbeat.md) pages; audible output cannot be verified headless here. **Not verified in game**.
- **Local-contrast halos around geometry in `halftone`, `comic` and `accent_glow` — local averaging removed.** The 8-tap averaging ring made the tone mapping a de-facto edge detector: near any luminance step the local mean skewed and the tone saturated into flat paper-coloured bands with no dots, which reads as glowing outlines and is useless for halftones. All three effects now map absolute pixel luminance instead (`halftone` keeps its adjustable `gain` contrast around mid grey, default `2.5` = neutral; `comic` gains a tunable `mid` split point, default `0.5`; `accent_glow` gains tunable `mid`/`core` thresholds, defaults `0.5`/`0.68`), so halos cannot form by construction — plus 8 fewer texture fetches per pixel. Dot grids grow from the screen centre instead of a corner, so changing `cell_size` rescales around the middle of the frame. Three new optional params (`mid` on `comic`; `mid`, `core` on `accent_glow`), no UBO or wire change, `PROTOCOL_VERSION` unchanged. **Not verified in game**.
release
VFXWeaver API 2.5.0 (26.2)
2.5.022 окт. 2026 г.87Minecraft: 26.2fabric
Что нового
### Added
- **`hex_grid`: a triangular grid coloured by the screen position.** A new screen effect draws a triangular lattice over the frame - three line directions (two at 60 degrees, one vertical) on a `cell_size` cell (46 px by default) anchored at the screen centre - and paints the lines with the prism ramp picked by the position (`prism7(x * 0.6 + y * 0.4 + time * speed)`, drifting at 0.5 per second): the lines at 28 % of that colour over near-black `(0.01, 0.008, 0.02)`, and the highlights of the picture (brighter than `core`, 0.68 by default - absolute, not local, so no outline appears at a brightness step) in white; `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it) and no param has an upper cap - only the lower guards that avoid degenerate maths (`cell_size >= 1` px, `width >= 0.0001`). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/hex-grid.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`spectral`: a rainbow prism look over the frame.** A new screen effect draws the picture through a seven-stop prism ramp - red `(1.0, 0.07, 0.1)`, orange `(1.0, 0.42, 0.03)`, yellow `(1.0, 0.88, 0.06)`, green `(0.1, 0.95, 0.28)`, blue `(0.06, 0.48, 1.0)`, violet `(0.28, 0.14, 1.0)`, magenta `(0.74, 0.16, 1.0)` - swept by the angle around `center_x`/`center_y`, the distance from it (0.9 per screen height) and the clock (0.7 per second); `mode` picks the variant: **0** the prism duotone (the frame is split into a dark and a bright half by its own brightness, `mid` 0.5 by default - absolute, not local, so no outline appears at a brightness step; the bright half takes the prism colour and the highlights, `core` 0.68, go white), **1** the chromatic smear (seven taps along the outward direction, spread by `0.0035 + 0.004 * distance`, every tap's edge `smoothstep(0.02, 0.30)` painted with its own prism stop - a wide ramp, so it stays a smear and not a drawn outline) and **2** the spectral ring (the luminance picks a band `floor(luma * 6.999)`, the distance picks a ring `floor(distance * 9 - time * 6)`, the two together pick a spectral stop and the highlights (`core` 0.68) go white; `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it), `mode` is an enum clamped to 0..2 and no other param has an upper cap. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/spectral.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`torn_edge`: a ragged tear with a glowing line down it.** A new screen effect cuts the frame along a straight line through `center_x`/`center_y` whose angle is re-rolled every beat (`speed`, 20 per second by default): a value-noise ripple of `ragged` (0.02 of the screen height, 38 cycles across the tear) makes the edge wander, the two sides of the cut are drawn at opposite brightness (a pixel brighter than `mid`, 0.5 by default - the split is absolute, not local, so no outline appears at a brightness step), and the tear itself is a white line (an exponential falloff of `glow`, 0.0035 of the screen height, scaled by 1.6); `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it), and no param has an upper cap - only the lower guards that avoid degenerate maths (`speed >= 0`, `glow >= 0.0005`). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/torn-edge.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`datamosh`: the frame is decoded against a reference that never arrives, and the movement search runs once per cell instead of once per pixel.** A new screen effect is a *feedback* effect: it keeps the picture it produced last frame in the frame's history buffer, and that history is the reference the decoder gets instead of a key frame. **The key frame never arrives**: the history only refreshes through the per-piece I-frame below, so the decoder keeps painting the screen as it was half a second ago. The movement is estimated the way a codec searches its reference frame for the matching block: one Lucas-Kanade step between the live picture and the reference (the reference carries the previous live frame's luma in its alpha) gives the velocity of the content, and a greedy search over 8, 4, 2 and 1 pixel finds the integer vector that best matches it, with a penalty that gives exactly zero where nothing moves. The search used to run per pixel (about 174 texture reads per pixel, a full-resolution pass the frame rate could not afford): every pixel of a piece needs the same vector, so the search now runs in its own pass at 1/8 resolution - one texel per 8x8 cell - and the update pass reads the result back. The picture is unchanged, the cost is about 6.5 reads per pixel instead of 174. The decoder then carries the pixels over as they are (point sampled, no filter, no leftover of the live frame), so a moving object drags sharp pixels of the old background with it; the luma is taken at the measured vector and the colour at `1 + chroma` times it, the way a 4:2:0 stream smears colour when it is decoded against the wrong reference - every colour is the picture's own, there is no random noise. The pieces come from a static hash quadtree (8 to 64 px), so neighbouring pieces differ in size and the layout never changes, and each piece returns to the real picture on its own phase every `iframe` seconds, so there is no global flash. `smear` (1) is the vector multiplier and `chroma` (0.5) the colour pull; only the lower guards exist (`chroma` and `amount` clamped to 0..1, `smear` and `iframe` not negative), no param has an upper cap. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Honest limits, stated on the page: the melt lives in the same history buffer as `afterimage`, so the two chain when both run and each overwrites the alpha the other needs; the reference is the screen's own past rather than another shot; the movement needs some structure in the picture, so in flat areas (sky, a plain wall) nothing is dragged; and the motion field is one texel per 8x8 cell, so a slower movement than 8 px is not resolved. Documented in the [guide](guide/effects/screen/datamosh.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1`, `scripts/check-post-fusion.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`heartbeat`: a heartbeat positioned in the world.** A new audio effect plays a recording of one lub-dub that ships inside the mod (`rate` beat rate in bpm, default `60`; `volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it; `pitch` playback-speed multiplier, default `1` = as recorded) straight into the OS mixer — no resource pack needed, identical on every Minecraft line. Every beat restarts the recording, and the recording is time-scaled to the tempo: at 60 bpm it plays as recorded (0.85 s in a 1 s beat) and at 140 bpm it is compressed to about 0.36 s in a 0.43 s beat, with the pause inside the lub-dub closing up in the same proportion. The scaling keeps the tone where it was, so a fast heart is a fast recording on the same pitch rather than a higher one; `pitch` is a separate tone multiplier that does move the pitch; the pause between beats is whatever the recording leaves free, not a separate re-timing. A cut playback is faded over 5 ms so a restart never clicks, and `rate` and `pitch` are read fresh every tick, so keyframes/`expr`/bindings ride them live and the phase integrates per sample, so a rate change glides. Positioning, distance falloff (full within 8 blocks, silent past 48), clock semantics (pause freezes, seek resyncs silently), fade-out on single stop and immediate `stopAll`/world exit/client shutdown close all mirror the `hum`/`noise` voices; the same honest limits apply (vanilla mixer bypassed, no-audio-device stays silent with a one-time warning). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/heartbeat.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`volume_duck`: scale a vanilla sound category down while active.** A new global (non-positional) audio effect sets one game-options category level to its pre-duck level times `volume` (`0..1`, neutral `1.0` so `fade_ticks` fades the duck in and out), selected by the `channel` param as a case-insensitive name (`master`, `music`, `records`, `weather`, `blocks`, `hostile`, `neutral`, `players`, `ambient`, `voice`, `ui`) or the historical `SoundSource` ordinal (`0..10`, back-compat); an unknown name warns once and safely no-ops instead of ducking the wrong category. Each frame only ducked channels are written (base x last-writer-wins scale across stacked ducks); the captured level is restored the frame a channel's last duck ends and on single stop, replay seek, `stopAll`, world exit and client shutdown, and nothing is ever written to `options.txt`. Honest limits, stated on the page: the options slider shows the scaled level and a mid-duck slider drag on that channel is overwritten, then lost on restore. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/volume-duck.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`noise`: generated white noise positioned in the world.** A new audio effect synthesises mono white noise per effect instance (`volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it; `brightness` crisp/soft blend `0..1`, default `0.5`; `jitter` random-walk step with `jitter_rate` speed multiplier for a playback-rate and volume wander; `seed` reseeds the texture) straight into the OS mixer — no audio file in any pack, identical on every Minecraft line. Positioning, distance falloff (full within 8 blocks, silent past 48), clock semantics (pause freezes, seek resyncs silently), fade-out on single stop and immediate `stopAll`/world exit/client shutdown close all mirror the `hum` voice; the same honest limits apply (vanilla mixer bypassed, no-audio-device stays silent with a one-time warning). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/noise.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`hum`: a generated sine hum positioned in the world.** A new audio effect synthesises a mono sine tone per effect instance (`frequency` in Hz, default `440`; `volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it) straight into the OS mixer — no audio file in any pack, identical on every Minecraft line. The voice is positioned like a world overlay (move position, first static `positions` entry, first entity anchor, authored `pos_x/y/z`, else the local-player snapshot) with a linear distance falloff (full within 8 blocks, silent past 48); it follows the shared effect clock, so pause freezes it and a replay seek resyncs silently. Stopping one instance fades it out; `stopAll`/world exit/client shutdown closes the voices at once. Honest limits, stated on the page: the vanilla mixer is bypassed (volume sliders do not apply — use `volume`), and a machine with no audio device stays silent with a one-time warning. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/hum.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
### Fixed
- **The frame rate no longer reaches the sound: the voices moved to their own audio thread.** The `hum`, `noise` and `heartbeat` voices were synthesised on the render thread, one chunk per frame, so the frame rate leaked into the sound: the gain ramps were one frame long, a long frame could starve the line's buffer (that is the crackling), and the heartbeat's time-scale work spiked the frame. The voices now live on a dedicated audio thread that generates fixed 5 ms blocks from its own wall clock and keeps every line about 60 ms ahead of its playhead, so a slow frame, a lost frame or an unfocused window cannot starve a line or stretch a ramp - the sound is the same at 30 fps and at 200 fps. The render thread only publishes the parameters and the shared effect clock; the voice state belongs to the audio thread, so there is nothing to lock. A paused game (or a paused replay) is still the one thing that stops it: the shared effect clock freezes and the thread renders silence until it moves again. The heartbeat's time-scale work moved to the audio thread with the rest. No param renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on the [hum](guide/effects/audio/hum.md), [noise](guide/effects/audio/noise.md) and [heartbeat](guide/effects/audio/heartbeat.md) pages. **Not verified in game**.
- **The synthesised voices no longer crackle, and a heartbeat plays out instead of being cut.** Three click sources are gone. A voice that resumes after a silence gap (a pause, a game hitch, a fade) now fades in over 5 ms instead of starting at full amplitude. A voice whose effect ended is no longer cut at the playhead: the `hum` and the `noise` fade out and their line is released once it has drained, so the stop is a fade and not a step. And the last 5 ms of a heartbeat recording fade out, so a beat ends without a click. The heartbeat's `duration` now decides when the sound stops *repeating*: when the instance ends, the thump stops repeating right there and the beat that is sounding plays to its end instead of being cut in the middle. No param renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on the [heartbeat](guide/effects/audio/heartbeat.md) page. **Not verified in game**.
- **Synthesised voices no longer stutter or crackle on slow frames.** The `hum`/`noise`/`heartbeat` voices counted samples from frame ticks, so an unfocused window (a few frames per second) pushed each frame past the 4-tick cap and the voice emitted nothing (chunk-gap-chunk), and the 0.25 s line buffer underran whenever the game loop stalled. Each voice now counts samples on its own steady wall clock gated by the effect clock: slow frames and replay seeks emit the wall-clock debt sized to the line's free buffer space (the write never blocks the frame), phases stay continuous so there are no bursts or boundary clicks, and pause (or a paused replay) still freezes synthesis exactly as before. The buffer grows to 0.5 s. No param renamed, no UBO or wire change, `PROTOCOL_VERSION` unchanged. Documented on the [hum](guide/effects/audio/hum.md), [noise](guide/effects/audio/noise.md) and [heartbeat](guide/effects/audio/heartbeat.md) pages; audible output cannot be verified headless here. **Not verified in game**.
- **Local-contrast halos around geometry in `halftone`, `comic` and `accent_glow` — local averaging removed.** The 8-tap averaging ring made the tone mapping a de-facto edge detector: near any luminance step the local mean skewed and the tone saturated into flat paper-coloured bands with no dots, which reads as glowing outlines and is useless for halftones. All three effects now map absolute pixel luminance instead (`halftone` keeps its adjustable `gain` contrast around mid grey, default `2.5` = neutral; `comic` gains a tunable `mid` split point, default `0.5`; `accent_glow` gains tunable `mid`/`core` thresholds, defaults `0.5`/`0.68`), so halos cannot form by construction — plus 8 fewer texture fetches per pixel. Dot grids grow from the screen centre instead of a corner, so changing `cell_size` rescales around the middle of the frame. Three new optional params (`mid` on `comic`; `mid`, `core` on `accent_glow`), no UBO or wire change, `PROTOCOL_VERSION` unchanged. **Not verified in game**.
release
VFXWeaver API 2.5.0 (26.2, NeoForge)
2.5.022 окт. 2026 г.29Minecraft: 26.2neoforge
Что нового
### Added
- **`hex_grid`: a triangular grid coloured by the screen position.** A new screen effect draws a triangular lattice over the frame - three line directions (two at 60 degrees, one vertical) on a `cell_size` cell (46 px by default) anchored at the screen centre - and paints the lines with the prism ramp picked by the position (`prism7(x * 0.6 + y * 0.4 + time * speed)`, drifting at 0.5 per second): the lines at 28 % of that colour over near-black `(0.01, 0.008, 0.02)`, and the highlights of the picture (brighter than `core`, 0.68 by default - absolute, not local, so no outline appears at a brightness step) in white; `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it) and no param has an upper cap - only the lower guards that avoid degenerate maths (`cell_size >= 1` px, `width >= 0.0001`). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/hex-grid.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`spectral`: a rainbow prism look over the frame.** A new screen effect draws the picture through a seven-stop prism ramp - red `(1.0, 0.07, 0.1)`, orange `(1.0, 0.42, 0.03)`, yellow `(1.0, 0.88, 0.06)`, green `(0.1, 0.95, 0.28)`, blue `(0.06, 0.48, 1.0)`, violet `(0.28, 0.14, 1.0)`, magenta `(0.74, 0.16, 1.0)` - swept by the angle around `center_x`/`center_y`, the distance from it (0.9 per screen height) and the clock (0.7 per second); `mode` picks the variant: **0** the prism duotone (the frame is split into a dark and a bright half by its own brightness, `mid` 0.5 by default - absolute, not local, so no outline appears at a brightness step; the bright half takes the prism colour and the highlights, `core` 0.68, go white), **1** the chromatic smear (seven taps along the outward direction, spread by `0.0035 + 0.004 * distance`, every tap's edge `smoothstep(0.02, 0.30)` painted with its own prism stop - a wide ramp, so it stays a smear and not a drawn outline) and **2** the spectral ring (the luminance picks a band `floor(luma * 6.999)`, the distance picks a ring `floor(distance * 9 - time * 6)`, the two together pick a spectral stop and the highlights (`core` 0.68) go white; `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it), `mode` is an enum clamped to 0..2 and no other param has an upper cap. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/spectral.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`torn_edge`: a ragged tear with a glowing line down it.** A new screen effect cuts the frame along a straight line through `center_x`/`center_y` whose angle is re-rolled every beat (`speed`, 20 per second by default): a value-noise ripple of `ragged` (0.02 of the screen height, 38 cycles across the tear) makes the edge wander, the two sides of the cut are drawn at opposite brightness (a pixel brighter than `mid`, 0.5 by default - the split is absolute, not local, so no outline appears at a brightness step), and the tear itself is a white line (an exponential falloff of `glow`, 0.0035 of the screen height, scaled by 1.6); `amount` cross-fades the whole look (default `1.0`, neutral `0.0` so `fade_ticks` fades it), and no param has an upper cap - only the lower guards that avoid degenerate maths (`speed >= 0`, `glow >= 0.0005`). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/torn-edge.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`datamosh`: the frame is decoded against a reference that never arrives, and the movement search runs once per cell instead of once per pixel.** A new screen effect is a *feedback* effect: it keeps the picture it produced last frame in the frame's history buffer, and that history is the reference the decoder gets instead of a key frame. **The key frame never arrives**: the history only refreshes through the per-piece I-frame below, so the decoder keeps painting the screen as it was half a second ago. The movement is estimated the way a codec searches its reference frame for the matching block: one Lucas-Kanade step between the live picture and the reference (the reference carries the previous live frame's luma in its alpha) gives the velocity of the content, and a greedy search over 8, 4, 2 and 1 pixel finds the integer vector that best matches it, with a penalty that gives exactly zero where nothing moves. The search used to run per pixel (about 174 texture reads per pixel, a full-resolution pass the frame rate could not afford): every pixel of a piece needs the same vector, so the search now runs in its own pass at 1/8 resolution - one texel per 8x8 cell - and the update pass reads the result back. The picture is unchanged, the cost is about 6.5 reads per pixel instead of 174. The decoder then carries the pixels over as they are (point sampled, no filter, no leftover of the live frame), so a moving object drags sharp pixels of the old background with it; the luma is taken at the measured vector and the colour at `1 + chroma` times it, the way a 4:2:0 stream smears colour when it is decoded against the wrong reference - every colour is the picture's own, there is no random noise. The pieces come from a static hash quadtree (8 to 64 px), so neighbouring pieces differ in size and the layout never changes, and each piece returns to the real picture on its own phase every `iframe` seconds, so there is no global flash. `smear` (1) is the vector multiplier and `chroma` (0.5) the colour pull; only the lower guards exist (`chroma` and `amount` clamped to 0..1, `smear` and `iframe` not negative), no param has an upper cap. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Honest limits, stated on the page: the melt lives in the same history buffer as `afterimage`, so the two chain when both run and each overwrites the alpha the other needs; the reference is the screen's own past rather than another shot; the movement needs some structure in the picture, so in flat areas (sky, a plain wall) nothing is dragged; and the motion field is one texel per 8x8 cell, so a slower movement than 8 px is not resolved. Documented in the [guide](guide/effects/screen/datamosh.md). Asserted by `scripts/check-vfx-schema.ps1`, `scripts/check-shader-identifiers.ps1`, `scripts/check-post-fusion.ps1` and `scripts/check-builtins-parse.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`heartbeat`: a heartbeat positioned in the world.** A new audio effect plays a recording of one lub-dub that ships inside the mod (`rate` beat rate in bpm, default `60`; `volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it; `pitch` playback-speed multiplier, default `1` = as recorded) straight into the OS mixer — no resource pack needed, identical on every Minecraft line. Every beat restarts the recording, and the recording is time-scaled to the tempo: at 60 bpm it plays as recorded (0.85 s in a 1 s beat) and at 140 bpm it is compressed to about 0.36 s in a 0.43 s beat, with the pause inside the lub-dub closing up in the same proportion. The scaling keeps the tone where it was, so a fast heart is a fast recording on the same pitch rather than a higher one; `pitch` is a separate tone multiplier that does move the pitch; the pause between beats is whatever the recording leaves free, not a separate re-timing. A cut playback is faded over 5 ms so a restart never clicks, and `rate` and `pitch` are read fresh every tick, so keyframes/`expr`/bindings ride them live and the phase integrates per sample, so a rate change glides. Positioning, distance falloff (full within 8 blocks, silent past 48), clock semantics (pause freezes, seek resyncs silently), fade-out on single stop and immediate `stopAll`/world exit/client shutdown close all mirror the `hum`/`noise` voices; the same honest limits apply (vanilla mixer bypassed, no-audio-device stays silent with a one-time warning). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/heartbeat.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`volume_duck`: scale a vanilla sound category down while active.** A new global (non-positional) audio effect sets one game-options category level to its pre-duck level times `volume` (`0..1`, neutral `1.0` so `fade_ticks` fades the duck in and out), selected by the `channel` param as a case-insensitive name (`master`, `music`, `records`, `weather`, `blocks`, `hostile`, `neutral`, `players`, `ambient`, `voice`, `ui`) or the historical `SoundSource` ordinal (`0..10`, back-compat); an unknown name warns once and safely no-ops instead of ducking the wrong category. Each frame only ducked channels are written (base x last-writer-wins scale across stacked ducks); the captured level is restored the frame a channel's last duck ends and on single stop, replay seek, `stopAll`, world exit and client shutdown, and nothing is ever written to `options.txt`. Honest limits, stated on the page: the options slider shows the scaled level and a mid-duck slider drag on that channel is overwritten, then lost on restore. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/volume-duck.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`noise`: generated white noise positioned in the world.** A new audio effect synthesises mono white noise per effect instance (`volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it; `brightness` crisp/soft blend `0..1`, default `0.5`; `jitter` random-walk step with `jitter_rate` speed multiplier for a playback-rate and volume wander; `seed` reseeds the texture) straight into the OS mixer — no audio file in any pack, identical on every Minecraft line. Positioning, distance falloff (full within 8 blocks, silent past 48), clock semantics (pause freezes, seek resyncs silently), fade-out on single stop and immediate `stopAll`/world exit/client shutdown close all mirror the `hum` voice; the same honest limits apply (vanilla mixer bypassed, no-audio-device stays silent with a one-time warning). Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/noise.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
- **`hum`: a generated sine hum positioned in the world.** A new audio effect synthesises a mono sine tone per effect instance (`frequency` in Hz, default `440`; `volume` linear gain `0..1`, neutral `0.0` so `fade_ticks` fades it) straight into the OS mixer — no audio file in any pack, identical on every Minecraft line. The voice is positioned like a world overlay (move position, first static `positions` entry, first entity anchor, authored `pos_x/y/z`, else the local-player snapshot) with a linear distance falloff (full within 8 blocks, silent past 48); it follows the shared effect clock, so pause freezes it and a replay seek resyncs silently. Stopping one instance fades it out; `stopAll`/world exit/client shutdown closes the voices at once. Honest limits, stated on the page: the vanilla mixer is bypassed (volume sliders do not apply — use `volume`), and a machine with no audio device stays silent with a one-time warning. Additive: one new effect type and its params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged, no new dependency. Documented in the [guide](guide/effects/audio/hum.md). Asserted by `scripts/check-vfx-schema.ps1` and `scripts/check-builtins-parse.ps1`; audible output cannot be verified headless here. **Not verified in game**.
### Fixed
- **The frame rate no longer reaches the sound: the voices moved to their own audio thread.** The `hum`, `noise` and `heartbeat` voices were synthesised on the render thread, one chunk per frame, so the frame rate leaked into the sound: the gain ramps were one frame long, a long frame could starve the line's buffer (that is the crackling), and the heartbeat's time-scale work spiked the frame. The voices now live on a dedicated audio thread that generates fixed 5 ms blocks from its own wall clock and keeps every line about 60 ms ahead of its playhead, so a slow frame, a lost frame or an unfocused window cannot starve a line or stretch a ramp - the sound is the same at 30 fps and at 200 fps. The render thread only publishes the parameters and the shared effect clock; the voice state belongs to the audio thread, so there is nothing to lock. A paused game (or a paused replay) is still the one thing that stops it: the shared effect clock freezes and the thread renders silence until it moves again. The heartbeat's time-scale work moved to the audio thread with the rest. No param renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on the [hum](guide/effects/audio/hum.md), [noise](guide/effects/audio/noise.md) and [heartbeat](guide/effects/audio/heartbeat.md) pages. **Not verified in game**.
- **The synthesised voices no longer crackle, and a heartbeat plays out instead of being cut.** Three click sources are gone. A voice that resumes after a silence gap (a pause, a game hitch, a fade) now fades in over 5 ms instead of starting at full amplitude. A voice whose effect ended is no longer cut at the playhead: the `hum` and the `noise` fade out and their line is released once it has drained, so the stop is a fade and not a step. And the last 5 ms of a heartbeat recording fade out, so a beat ends without a click. The heartbeat's `duration` now decides when the sound stops *repeating*: when the instance ends, the thump stops repeating right there and the beat that is sounding plays to its end instead of being cut in the middle. No param renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on the [heartbeat](guide/effects/audio/heartbeat.md) page. **Not verified in game**.
- **Synthesised voices no longer stutter or crackle on slow frames.** The `hum`/`noise`/`heartbeat` voices counted samples from frame ticks, so an unfocused window (a few frames per second) pushed each frame past the 4-tick cap and the voice emitted nothing (chunk-gap-chunk), and the 0.25 s line buffer underran whenever the game loop stalled. Each voice now counts samples on its own steady wall clock gated by the effect clock: slow frames and replay seeks emit the wall-clock debt sized to the line's free buffer space (the write never blocks the frame), phases stay continuous so there are no bursts or boundary clicks, and pause (or a paused replay) still freezes synthesis exactly as before. The buffer grows to 0.5 s. No param renamed, no UBO or wire change, `PROTOCOL_VERSION` unchanged. Documented on the [hum](guide/effects/audio/hum.md), [noise](guide/effects/audio/noise.md) and [heartbeat](guide/effects/audio/heartbeat.md) pages; audible output cannot be verified headless here. **Not verified in game**.
- **Local-contrast halos around geometry in `halftone`, `comic` and `accent_glow` — local averaging removed.** The 8-tap averaging ring made the tone mapping a de-facto edge detector: near any luminance step the local mean skewed and the tone saturated into flat paper-coloured bands with no dots, which reads as glowing outlines and is useless for halftones. All three effects now map absolute pixel luminance instead (`halftone` keeps its adjustable `gain` contrast around mid grey, default `2.5` = neutral; `comic` gains a tunable `mid` split point, default `0.5`; `accent_glow` gains tunable `mid`/`core` thresholds, defaults `0.5`/`0.68`), so halos cannot form by construction — plus 8 fewer texture fetches per pixel. Dot grids grow from the screen centre instead of a corner, so changing `cell_size` rescales around the middle of the frame. Three new optional params (`mid` on `comic`; `mid`, `core` on `accent_glow`), no UBO or wire change, `PROTOCOL_VERSION` unchanged. **Not verified in game**.
release
VFXWeaver API 2.4.1 (1.21.11, NeoForge)
2.4.114 окт. 2026 г.39Minecraft: 1.21.11neoforge
Что нового
### Added
- **`particles` shape params, `dust` size, `block_chain` spacing/scale, outline widths, `light_beam` top_scale, `pulse_ring` thickness, `guide_line` width/dashes, spark speed/size and `entity_displace` scale no longer have an upper cap.** Every lifted param keeps only its lower guard (`particles`: `radius >= 0.05`, `height >= 0.5`, `turns >= 0.25`, `speed/accel/lifetime >= 0`, `vel_y` unbounded, `dust size >= 0.05`; `block_chain`: `spacing >= 0.25`, `scale >= 0.1`; outlines/ring/guide-line widths and thickness/gap `>= 0`, `dash_length >= 0.1`; `light_beam top_scale >= 0.1`; spark `speed >= 0`, `size >= 0.01`; `entity_displace scale >= 0.5` mirroring the renderer floor), and the editor schema mirrors the same floor-only bounds. The floors that remain are the ones that avoid degenerate geometry or division by zero (spacing, dash_length, displace scale); no geometry loop count depends on any lifted param, so the caps had no perf basis. No datapack field renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on [particles](guide/effects/world/particles.md), [block_chain](guide/effects/world/block-chain.md), [light_beam](guide/effects/world/light-beam.md), [entity_displace](guide/effects/entity/entity-displace.md) and [spark presets](guide/datapack/sparks.md).
- **`light_beam` radius/height and `pulse_ring` radius no longer have an upper cap.** `light_beam` keeps its lower guards (`radius >= 0.1`, `height >= 1`) and `pulse_ring` keeps `radius >= 0` (degenerate/inverted geometry guard), but the `16`/`256` and `64` caps are gone: the geometry cost is constant (a fixed shell/slice count), so radius/height only scale positions. No datapack field renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on [light_beam](guide/effects/world/light-beam.md) and [pulse_ring](guide/effects/world/pulse-ring.md).
release
VFXWeaver API 2.4.1 (1.21.11)
2.4.114 окт. 2026 г.51Minecraft: 1.21.11fabric
Что нового
### Added
- **`particles` shape params, `dust` size, `block_chain` spacing/scale, outline widths, `light_beam` top_scale, `pulse_ring` thickness, `guide_line` width/dashes, spark speed/size and `entity_displace` scale no longer have an upper cap.** Every lifted param keeps only its lower guard (`particles`: `radius >= 0.05`, `height >= 0.5`, `turns >= 0.25`, `speed/accel/lifetime >= 0`, `vel_y` unbounded, `dust size >= 0.05`; `block_chain`: `spacing >= 0.25`, `scale >= 0.1`; outlines/ring/guide-line widths and thickness/gap `>= 0`, `dash_length >= 0.1`; `light_beam top_scale >= 0.1`; spark `speed >= 0`, `size >= 0.01`; `entity_displace scale >= 0.5` mirroring the renderer floor), and the editor schema mirrors the same floor-only bounds. The floors that remain are the ones that avoid degenerate geometry or division by zero (spacing, dash_length, displace scale); no geometry loop count depends on any lifted param, so the caps had no perf basis. No datapack field renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on [particles](guide/effects/world/particles.md), [block_chain](guide/effects/world/block-chain.md), [light_beam](guide/effects/world/light-beam.md), [entity_displace](guide/effects/entity/entity-displace.md) and [spark presets](guide/datapack/sparks.md).
- **`light_beam` radius/height and `pulse_ring` radius no longer have an upper cap.** `light_beam` keeps its lower guards (`radius >= 0.1`, `height >= 1`) and `pulse_ring` keeps `radius >= 0` (degenerate/inverted geometry guard), but the `16`/`256` and `64` caps are gone: the geometry cost is constant (a fixed shell/slice count), so radius/height only scale positions. No datapack field renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on [light_beam](guide/effects/world/light-beam.md) and [pulse_ring](guide/effects/world/pulse-ring.md).
release
VFXWeaver API 2.4.1 (26.1.x, NeoForge)
2.4.114 окт. 2026 г.32Minecraft: 26.1, 26.1.1, 26.1.2neoforge
Что нового
### Added
- **`particles` shape params, `dust` size, `block_chain` spacing/scale, outline widths, `light_beam` top_scale, `pulse_ring` thickness, `guide_line` width/dashes, spark speed/size and `entity_displace` scale no longer have an upper cap.** Every lifted param keeps only its lower guard (`particles`: `radius >= 0.05`, `height >= 0.5`, `turns >= 0.25`, `speed/accel/lifetime >= 0`, `vel_y` unbounded, `dust size >= 0.05`; `block_chain`: `spacing >= 0.25`, `scale >= 0.1`; outlines/ring/guide-line widths and thickness/gap `>= 0`, `dash_length >= 0.1`; `light_beam top_scale >= 0.1`; spark `speed >= 0`, `size >= 0.01`; `entity_displace scale >= 0.5` mirroring the renderer floor), and the editor schema mirrors the same floor-only bounds. The floors that remain are the ones that avoid degenerate geometry or division by zero (spacing, dash_length, displace scale); no geometry loop count depends on any lifted param, so the caps had no perf basis. No datapack field renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on [particles](guide/effects/world/particles.md), [block_chain](guide/effects/world/block-chain.md), [light_beam](guide/effects/world/light-beam.md), [entity_displace](guide/effects/entity/entity-displace.md) and [spark presets](guide/datapack/sparks.md).
- **`light_beam` radius/height and `pulse_ring` radius no longer have an upper cap.** `light_beam` keeps its lower guards (`radius >= 0.1`, `height >= 1`) and `pulse_ring` keeps `radius >= 0` (degenerate/inverted geometry guard), but the `16`/`256` and `64` caps are gone: the geometry cost is constant (a fixed shell/slice count), so radius/height only scale positions. No datapack field renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on [light_beam](guide/effects/world/light-beam.md) and [pulse_ring](guide/effects/world/pulse-ring.md).
release
VFXWeaver API 2.4.1 (26.1.x)
2.4.114 окт. 2026 г.107Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
### Added
- **`particles` shape params, `dust` size, `block_chain` spacing/scale, outline widths, `light_beam` top_scale, `pulse_ring` thickness, `guide_line` width/dashes, spark speed/size and `entity_displace` scale no longer have an upper cap.** Every lifted param keeps only its lower guard (`particles`: `radius >= 0.05`, `height >= 0.5`, `turns >= 0.25`, `speed/accel/lifetime >= 0`, `vel_y` unbounded, `dust size >= 0.05`; `block_chain`: `spacing >= 0.25`, `scale >= 0.1`; outlines/ring/guide-line widths and thickness/gap `>= 0`, `dash_length >= 0.1`; `light_beam top_scale >= 0.1`; spark `speed >= 0`, `size >= 0.01`; `entity_displace scale >= 0.5` mirroring the renderer floor), and the editor schema mirrors the same floor-only bounds. The floors that remain are the ones that avoid degenerate geometry or division by zero (spacing, dash_length, displace scale); no geometry loop count depends on any lifted param, so the caps had no perf basis. No datapack field renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on [particles](guide/effects/world/particles.md), [block_chain](guide/effects/world/block-chain.md), [light_beam](guide/effects/world/light-beam.md), [entity_displace](guide/effects/entity/entity-displace.md) and [spark presets](guide/datapack/sparks.md).
- **`light_beam` radius/height and `pulse_ring` radius no longer have an upper cap.** `light_beam` keeps its lower guards (`radius >= 0.1`, `height >= 1`) and `pulse_ring` keeps `radius >= 0` (degenerate/inverted geometry guard), but the `16`/`256` and `64` caps are gone: the geometry cost is constant (a fixed shell/slice count), so radius/height only scale positions. No datapack field renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on [light_beam](guide/effects/world/light-beam.md) and [pulse_ring](guide/effects/world/pulse-ring.md).
release
VFXWeaver API 2.4.1 (26.2, NeoForge)
2.4.114 окт. 2026 г.31Minecraft: 26.2neoforge
Что нового
### Added
- **`particles` shape params, `dust` size, `block_chain` spacing/scale, outline widths, `light_beam` top_scale, `pulse_ring` thickness, `guide_line` width/dashes, spark speed/size and `entity_displace` scale no longer have an upper cap.** Every lifted param keeps only its lower guard (`particles`: `radius >= 0.05`, `height >= 0.5`, `turns >= 0.25`, `speed/accel/lifetime >= 0`, `vel_y` unbounded, `dust size >= 0.05`; `block_chain`: `spacing >= 0.25`, `scale >= 0.1`; outlines/ring/guide-line widths and thickness/gap `>= 0`, `dash_length >= 0.1`; `light_beam top_scale >= 0.1`; spark `speed >= 0`, `size >= 0.01`; `entity_displace scale >= 0.5` mirroring the renderer floor), and the editor schema mirrors the same floor-only bounds. The floors that remain are the ones that avoid degenerate geometry or division by zero (spacing, dash_length, displace scale); no geometry loop count depends on any lifted param, so the caps had no perf basis. No datapack field renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on [particles](guide/effects/world/particles.md), [block_chain](guide/effects/world/block-chain.md), [light_beam](guide/effects/world/light-beam.md), [entity_displace](guide/effects/entity/entity-displace.md) and [spark presets](guide/datapack/sparks.md).
- **`light_beam` radius/height and `pulse_ring` radius no longer have an upper cap.** `light_beam` keeps its lower guards (`radius >= 0.1`, `height >= 1`) and `pulse_ring` keeps `radius >= 0` (degenerate/inverted geometry guard), but the `16`/`256` and `64` caps are gone: the geometry cost is constant (a fixed shell/slice count), so radius/height only scale positions. No datapack field renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on [light_beam](guide/effects/world/light-beam.md) and [pulse_ring](guide/effects/world/pulse-ring.md).
release
VFXWeaver API 2.4.1 (26.2)
2.4.114 окт. 2026 г.98Minecraft: 26.2fabric
Что нового
### Added
- **`particles` shape params, `dust` size, `block_chain` spacing/scale, outline widths, `light_beam` top_scale, `pulse_ring` thickness, `guide_line` width/dashes, spark speed/size and `entity_displace` scale no longer have an upper cap.** Every lifted param keeps only its lower guard (`particles`: `radius >= 0.05`, `height >= 0.5`, `turns >= 0.25`, `speed/accel/lifetime >= 0`, `vel_y` unbounded, `dust size >= 0.05`; `block_chain`: `spacing >= 0.25`, `scale >= 0.1`; outlines/ring/guide-line widths and thickness/gap `>= 0`, `dash_length >= 0.1`; `light_beam top_scale >= 0.1`; spark `speed >= 0`, `size >= 0.01`; `entity_displace scale >= 0.5` mirroring the renderer floor), and the editor schema mirrors the same floor-only bounds. The floors that remain are the ones that avoid degenerate geometry or division by zero (spacing, dash_length, displace scale); no geometry loop count depends on any lifted param, so the caps had no perf basis. No datapack field renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on [particles](guide/effects/world/particles.md), [block_chain](guide/effects/world/block-chain.md), [light_beam](guide/effects/world/light-beam.md), [entity_displace](guide/effects/entity/entity-displace.md) and [spark presets](guide/datapack/sparks.md).
- **`light_beam` radius/height and `pulse_ring` radius no longer have an upper cap.** `light_beam` keeps its lower guards (`radius >= 0.1`, `height >= 1`) and `pulse_ring` keeps `radius >= 0` (degenerate/inverted geometry guard), but the `16`/`256` and `64` caps are gone: the geometry cost is constant (a fixed shell/slice count), so radius/height only scale positions. No datapack field renamed, no wire, UBO or API change, `PROTOCOL_VERSION` unchanged. Documented on [light_beam](guide/effects/world/light-beam.md) and [pulse_ring](guide/effects/world/pulse-ring.md).
release
VFXWeaver API 2.4.0 (1.21.11, NeoForge)
2.4.014 окт. 2026 г.16Minecraft: 1.21.11neoforge
Что нового
### Added
- **Visual node editor v1.1 "quick wins".** A tab may now be left **empty** (delete the effect node; a **+ Add effect** placeholder retypes it); undo/redo is **snapshot-based** (one drag or one action = one step, up to 100) and recompiles the whole effect; a broad set of keyboard shortcuts was added (`Del`, `Ctrl+C/V/D/A`, `Ctrl+Z`, `Space`, `R`, `K`, `F`/`Home`, `Ctrl+S`, `Tab`, `Ctrl+G`, `[`/`]`, `?`); the timeline gained a **tick ruler**, **keyframe clamping to `[0, duration]`** on drag with an out-of-schema-range red highlight, and a draggable **duration bar**; numeric fields edit **inline (select-on-focus)** with no floating popup, and wired slots/params render **read-only with a source badge** and a "go to node" affordance; the `bind` value node shows a binding dropdown and labels its output by the binding's unit. `COLLECTION` is no longer offered by the schema the editor palette is built from. Editor-only: no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md). **Not verified in game**.
- **Visual node editor: build, live-play and export effects in a local web UI.** `/vfxeditor` starts (and a second call stops) a loopback HTTP/SSE bridge that serves a vendored LiteGraph editor from the jar and drives the running client: a schema-driven palette, one tab per effect, an inspector (constant / start-end / keyframes / binding / `expr`), a timeline with draggable keys and play/stop/loop transport, and live incremental edits gated by a monotonic `revision`. It exports a real datapack to a world `datapacks` folder, a loose folder or a zip (per-Minecraft-line `pack.mcmeta`, plus a sidecar holding the graph layout), imports a folder or zip back, reconnects the event stream with exponential backoff (1 s/2 s/4 s…) and re-sends the full effect when the revision diverges, and undoes by restoring the model snapshot then recompiling the whole effect (never an incremental game undo). Client-only, no new dependency, no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md). **Not verified in game**.
- **Authored preview mode in the visual editor.** The transport switches Exact/Authored: Authored plays the collection root with authored timings, so fades, loops and durations read as they will in game. Editor-only: no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md).
- **Reference nodes and nested collections.** A tab can instance another tab (cycle-guarded, nesting depth capped at 4); Exact preview recursively expands nested collections with accumulating delays. Editor-only, no wire change. Documented in the [guide](guide/editor.md).
- **Scrub is pause; export v2; editor QoL.** Scrubbing the playhead pauses instead of auto-resuming; export carries `vfx_curves`/`vfx_particles` blocks verbatim, writes multi-namespace packs in one pass, and migrates the sidecar to v2; socket groups start collapsed, effect nodes gained a canvas solo badge, and `Ctrl+G` groups persist as named frames. Editor-only, no wire change. Documented in the [guide](guide/editor.md).
release
VFXWeaver API 2.4.0 (1.21.11)
2.4.014 окт. 2026 г.18Minecraft: 1.21.11fabric
Что нового
### Added
- **Visual node editor v1.1 "quick wins".** A tab may now be left **empty** (delete the effect node; a **+ Add effect** placeholder retypes it); undo/redo is **snapshot-based** (one drag or one action = one step, up to 100) and recompiles the whole effect; a broad set of keyboard shortcuts was added (`Del`, `Ctrl+C/V/D/A`, `Ctrl+Z`, `Space`, `R`, `K`, `F`/`Home`, `Ctrl+S`, `Tab`, `Ctrl+G`, `[`/`]`, `?`); the timeline gained a **tick ruler**, **keyframe clamping to `[0, duration]`** on drag with an out-of-schema-range red highlight, and a draggable **duration bar**; numeric fields edit **inline (select-on-focus)** with no floating popup, and wired slots/params render **read-only with a source badge** and a "go to node" affordance; the `bind` value node shows a binding dropdown and labels its output by the binding's unit. `COLLECTION` is no longer offered by the schema the editor palette is built from. Editor-only: no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md). **Not verified in game**.
- **Visual node editor: build, live-play and export effects in a local web UI.** `/vfxeditor` starts (and a second call stops) a loopback HTTP/SSE bridge that serves a vendored LiteGraph editor from the jar and drives the running client: a schema-driven palette, one tab per effect, an inspector (constant / start-end / keyframes / binding / `expr`), a timeline with draggable keys and play/stop/loop transport, and live incremental edits gated by a monotonic `revision`. It exports a real datapack to a world `datapacks` folder, a loose folder or a zip (per-Minecraft-line `pack.mcmeta`, plus a sidecar holding the graph layout), imports a folder or zip back, reconnects the event stream with exponential backoff (1 s/2 s/4 s…) and re-sends the full effect when the revision diverges, and undoes by restoring the model snapshot then recompiling the whole effect (never an incremental game undo). Client-only, no new dependency, no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md). **Not verified in game**.
- **Authored preview mode in the visual editor.** The transport switches Exact/Authored: Authored plays the collection root with authored timings, so fades, loops and durations read as they will in game. Editor-only: no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md).
- **Reference nodes and nested collections.** A tab can instance another tab (cycle-guarded, nesting depth capped at 4); Exact preview recursively expands nested collections with accumulating delays. Editor-only, no wire change. Documented in the [guide](guide/editor.md).
- **Scrub is pause; export v2; editor QoL.** Scrubbing the playhead pauses instead of auto-resuming; export carries `vfx_curves`/`vfx_particles` blocks verbatim, writes multi-namespace packs in one pass, and migrates the sidecar to v2; socket groups start collapsed, effect nodes gained a canvas solo badge, and `Ctrl+G` groups persist as named frames. Editor-only, no wire change. Documented in the [guide](guide/editor.md).
release
VFXWeaver API 2.4.0 (26.1.x, NeoForge)
2.4.014 окт. 2026 г.10Minecraft: 26.1, 26.1.1, 26.1.2neoforge
Что нового
### Added
- **Visual node editor v1.1 "quick wins".** A tab may now be left **empty** (delete the effect node; a **+ Add effect** placeholder retypes it); undo/redo is **snapshot-based** (one drag or one action = one step, up to 100) and recompiles the whole effect; a broad set of keyboard shortcuts was added (`Del`, `Ctrl+C/V/D/A`, `Ctrl+Z`, `Space`, `R`, `K`, `F`/`Home`, `Ctrl+S`, `Tab`, `Ctrl+G`, `[`/`]`, `?`); the timeline gained a **tick ruler**, **keyframe clamping to `[0, duration]`** on drag with an out-of-schema-range red highlight, and a draggable **duration bar**; numeric fields edit **inline (select-on-focus)** with no floating popup, and wired slots/params render **read-only with a source badge** and a "go to node" affordance; the `bind` value node shows a binding dropdown and labels its output by the binding's unit. `COLLECTION` is no longer offered by the schema the editor palette is built from. Editor-only: no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md). **Not verified in game**.
- **Visual node editor: build, live-play and export effects in a local web UI.** `/vfxeditor` starts (and a second call stops) a loopback HTTP/SSE bridge that serves a vendored LiteGraph editor from the jar and drives the running client: a schema-driven palette, one tab per effect, an inspector (constant / start-end / keyframes / binding / `expr`), a timeline with draggable keys and play/stop/loop transport, and live incremental edits gated by a monotonic `revision`. It exports a real datapack to a world `datapacks` folder, a loose folder or a zip (per-Minecraft-line `pack.mcmeta`, plus a sidecar holding the graph layout), imports a folder or zip back, reconnects the event stream with exponential backoff (1 s/2 s/4 s…) and re-sends the full effect when the revision diverges, and undoes by restoring the model snapshot then recompiling the whole effect (never an incremental game undo). Client-only, no new dependency, no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md). **Not verified in game**.
- **Authored preview mode in the visual editor.** The transport switches Exact/Authored: Authored plays the collection root with authored timings, so fades, loops and durations read as they will in game. Editor-only: no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md).
- **Reference nodes and nested collections.** A tab can instance another tab (cycle-guarded, nesting depth capped at 4); Exact preview recursively expands nested collections with accumulating delays. Editor-only, no wire change. Documented in the [guide](guide/editor.md).
- **Scrub is pause; export v2; editor QoL.** Scrubbing the playhead pauses instead of auto-resuming; export carries `vfx_curves`/`vfx_particles` blocks verbatim, writes multi-namespace packs in one pass, and migrates the sidecar to v2; socket groups start collapsed, effect nodes gained a canvas solo badge, and `Ctrl+G` groups persist as named frames. Editor-only, no wire change. Documented in the [guide](guide/editor.md).
release
VFXWeaver API 2.4.0 (26.1.x)
2.4.014 окт. 2026 г.51Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
### Added
- **Visual node editor v1.1 "quick wins".** A tab may now be left **empty** (delete the effect node; a **+ Add effect** placeholder retypes it); undo/redo is **snapshot-based** (one drag or one action = one step, up to 100) and recompiles the whole effect; a broad set of keyboard shortcuts was added (`Del`, `Ctrl+C/V/D/A`, `Ctrl+Z`, `Space`, `R`, `K`, `F`/`Home`, `Ctrl+S`, `Tab`, `Ctrl+G`, `[`/`]`, `?`); the timeline gained a **tick ruler**, **keyframe clamping to `[0, duration]`** on drag with an out-of-schema-range red highlight, and a draggable **duration bar**; numeric fields edit **inline (select-on-focus)** with no floating popup, and wired slots/params render **read-only with a source badge** and a "go to node" affordance; the `bind` value node shows a binding dropdown and labels its output by the binding's unit. `COLLECTION` is no longer offered by the schema the editor palette is built from. Editor-only: no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md). **Not verified in game**.
- **Visual node editor: build, live-play and export effects in a local web UI.** `/vfxeditor` starts (and a second call stops) a loopback HTTP/SSE bridge that serves a vendored LiteGraph editor from the jar and drives the running client: a schema-driven palette, one tab per effect, an inspector (constant / start-end / keyframes / binding / `expr`), a timeline with draggable keys and play/stop/loop transport, and live incremental edits gated by a monotonic `revision`. It exports a real datapack to a world `datapacks` folder, a loose folder or a zip (per-Minecraft-line `pack.mcmeta`, plus a sidecar holding the graph layout), imports a folder or zip back, reconnects the event stream with exponential backoff (1 s/2 s/4 s…) and re-sends the full effect when the revision diverges, and undoes by restoring the model snapshot then recompiling the whole effect (never an incremental game undo). Client-only, no new dependency, no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md). **Not verified in game**.
- **Authored preview mode in the visual editor.** The transport switches Exact/Authored: Authored plays the collection root with authored timings, so fades, loops and durations read as they will in game. Editor-only: no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md).
- **Reference nodes and nested collections.** A tab can instance another tab (cycle-guarded, nesting depth capped at 4); Exact preview recursively expands nested collections with accumulating delays. Editor-only, no wire change. Documented in the [guide](guide/editor.md).
- **Scrub is pause; export v2; editor QoL.** Scrubbing the playhead pauses instead of auto-resuming; export carries `vfx_curves`/`vfx_particles` blocks verbatim, writes multi-namespace packs in one pass, and migrates the sidecar to v2; socket groups start collapsed, effect nodes gained a canvas solo badge, and `Ctrl+G` groups persist as named frames. Editor-only, no wire change. Documented in the [guide](guide/editor.md).
release
VFXWeaver API 2.4.0 (26.2, NeoForge)
2.4.014 окт. 2026 г.14Minecraft: 26.2neoforge
Что нового
### Added
- **Visual node editor v1.1 "quick wins".** A tab may now be left **empty** (delete the effect node; a **+ Add effect** placeholder retypes it); undo/redo is **snapshot-based** (one drag or one action = one step, up to 100) and recompiles the whole effect; a broad set of keyboard shortcuts was added (`Del`, `Ctrl+C/V/D/A`, `Ctrl+Z`, `Space`, `R`, `K`, `F`/`Home`, `Ctrl+S`, `Tab`, `Ctrl+G`, `[`/`]`, `?`); the timeline gained a **tick ruler**, **keyframe clamping to `[0, duration]`** on drag with an out-of-schema-range red highlight, and a draggable **duration bar**; numeric fields edit **inline (select-on-focus)** with no floating popup, and wired slots/params render **read-only with a source badge** and a "go to node" affordance; the `bind` value node shows a binding dropdown and labels its output by the binding's unit. `COLLECTION` is no longer offered by the schema the editor palette is built from. Editor-only: no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md). **Not verified in game**.
- **Visual node editor: build, live-play and export effects in a local web UI.** `/vfxeditor` starts (and a second call stops) a loopback HTTP/SSE bridge that serves a vendored LiteGraph editor from the jar and drives the running client: a schema-driven palette, one tab per effect, an inspector (constant / start-end / keyframes / binding / `expr`), a timeline with draggable keys and play/stop/loop transport, and live incremental edits gated by a monotonic `revision`. It exports a real datapack to a world `datapacks` folder, a loose folder or a zip (per-Minecraft-line `pack.mcmeta`, plus a sidecar holding the graph layout), imports a folder or zip back, reconnects the event stream with exponential backoff (1 s/2 s/4 s…) and re-sends the full effect when the revision diverges, and undoes by restoring the model snapshot then recompiling the whole effect (never an incremental game undo). Client-only, no new dependency, no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md). **Not verified in game**.
- **Authored preview mode in the visual editor.** The transport switches Exact/Authored: Authored plays the collection root with authored timings, so fades, loops and durations read as they will in game. Editor-only: no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md).
- **Reference nodes and nested collections.** A tab can instance another tab (cycle-guarded, nesting depth capped at 4); Exact preview recursively expands nested collections with accumulating delays. Editor-only, no wire change. Documented in the [guide](guide/editor.md).
- **Scrub is pause; export v2; editor QoL.** Scrubbing the playhead pauses instead of auto-resuming; export carries `vfx_curves`/`vfx_particles` blocks verbatim, writes multi-namespace packs in one pass, and migrates the sidecar to v2; socket groups start collapsed, effect nodes gained a canvas solo badge, and `Ctrl+G` groups persist as named frames. Editor-only, no wire change. Documented in the [guide](guide/editor.md).
release
VFXWeaver API 2.4.0 (26.2)
2.4.014 окт. 2026 г.28Minecraft: 26.2fabric
Что нового
### Added
- **Visual node editor v1.1 "quick wins".** A tab may now be left **empty** (delete the effect node; a **+ Add effect** placeholder retypes it); undo/redo is **snapshot-based** (one drag or one action = one step, up to 100) and recompiles the whole effect; a broad set of keyboard shortcuts was added (`Del`, `Ctrl+C/V/D/A`, `Ctrl+Z`, `Space`, `R`, `K`, `F`/`Home`, `Ctrl+S`, `Tab`, `Ctrl+G`, `[`/`]`, `?`); the timeline gained a **tick ruler**, **keyframe clamping to `[0, duration]`** on drag with an out-of-schema-range red highlight, and a draggable **duration bar**; numeric fields edit **inline (select-on-focus)** with no floating popup, and wired slots/params render **read-only with a source badge** and a "go to node" affordance; the `bind` value node shows a binding dropdown and labels its output by the binding's unit. `COLLECTION` is no longer offered by the schema the editor palette is built from. Editor-only: no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md). **Not verified in game**.
- **Visual node editor: build, live-play and export effects in a local web UI.** `/vfxeditor` starts (and a second call stops) a loopback HTTP/SSE bridge that serves a vendored LiteGraph editor from the jar and drives the running client: a schema-driven palette, one tab per effect, an inspector (constant / start-end / keyframes / binding / `expr`), a timeline with draggable keys and play/stop/loop transport, and live incremental edits gated by a monotonic `revision`. It exports a real datapack to a world `datapacks` folder, a loose folder or a zip (per-Minecraft-line `pack.mcmeta`, plus a sidecar holding the graph layout), imports a folder or zip back, reconnects the event stream with exponential backoff (1 s/2 s/4 s…) and re-sends the full effect when the revision diverges, and undoes by restoring the model snapshot then recompiling the whole effect (never an incremental game undo). Client-only, no new dependency, no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md). **Not verified in game**.
- **Authored preview mode in the visual editor.** The transport switches Exact/Authored: Authored plays the collection root with authored timings, so fades, loops and durations read as they will in game. Editor-only: no datapack field, UBO, API or wire change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/editor.md).
- **Reference nodes and nested collections.** A tab can instance another tab (cycle-guarded, nesting depth capped at 4); Exact preview recursively expands nested collections with accumulating delays. Editor-only, no wire change. Documented in the [guide](guide/editor.md).
- **Scrub is pause; export v2; editor QoL.** Scrubbing the playhead pauses instead of auto-resuming; export carries `vfx_curves`/`vfx_particles` blocks verbatim, writes multi-namespace packs in one pass, and migrates the sidecar to v2; socket groups start collapsed, effect nodes gained a canvas solo badge, and `Ctrl+G` groups persist as named frames. Editor-only, no wire change. Documented in the [guide](guide/editor.md).
release
VFXWeaver API 2.3.0 (1.21.11, NeoForge)
2.3.04 окт. 2026 г.13Minecraft: 1.21.11neoforge
Что нового
### Added
- **`screen_image`: draw a picture over the game frame.** A new screen effect composites a resource-pack PNG - or an image a mod supplied from code with `VFXAPI.registerImage` - onto the game's own framebuffer inside an animatable rectangle. `pos_x`/`pos_y` place it, `size_w`/`size_h` size it and animate the picture's own proportions (`size_w:size_h` is exactly the on-screen width:height, so equal values are a square on any window), `opacity` fades it, and `frames`/`frame_time` step a strip or a vanilla `.png.mcmeta` animation. The canvas is the **Minecraft window**, not the monitor, so the picture scales with the window and nothing outside the game frame is drawn; `screen_layer` picks whether it draws under the GUI (the default), above the first-person hand, or over everything including chat and hotbar. A `.mcmeta` sibling animation wins over `frames`/`frame_time`; `interpolate: true` is not supported. An id that does not resolve draws nothing and warns once instead of blanking the frame. Additive: one new effect type and its optional params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/screen-image.md). Asserted by `scripts/check-screen-image.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`VFXAPI.registerImage`/`unregisterImage`: show an image a mod renders itself, anywhere a texture is accepted.** A mod hands over ARGB pixels (row-major, row 0 the top row, `0xAARRGGBB`, the order `NativeImage.getPixels()` returns) under a resource id, and that id then works exactly like a packed texture id: effect fields (`surface_pattern`, `sky_pattern`, field textures) resolve it through the game's texture manager, and a `window_create` picture uploads it into the aux window's own GL context. This is the missing piece for an item icon, a live preview or a generated texture that does not exist as a `.png` in any pack. The registry is a client singleton bounded at 256 images; registering an id again replaces it (the previous GPU texture is released). Additive: `default` methods on `VFXLocalDispatcher` (a dispatcher compiled before this still links), no wire or UBO change, `PROTOCOL_VERSION` unchanged. Documented in the [API](API.md#vfxapi). Asserted by `scripts/check-image-registry.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **Two effects that open a real picture window on the desktop: `window_create` and `window_control`.** A `window_create` opens a named **frameless, transparent, click-through** window covering the work area of the monitor the game is on, and shows a picture in it; `window_control` then moves, resizes, fades and retitles that window by name. Neither is a post-processing pass nor world geometry, and nothing about the Minecraft window changes. The window is undecorated, non-resizable, always on top and click-through (`GLFW_MOUSE_PASSTHROUGH`) at all times, and never takes focus, so it cannot steal input from the game. It is created hidden, sized to the work area and shown last, and is **never moved or resized afterwards** - position and size animate the picture's rectangle *inside* the canvas, so an animation costs no OS call per frame. Up to **8** windows may be live at once; a second `window_create` under a name that is already live is a no-op (first one wins). `titles` plus an animated `title_index` pick the title (clamped), `opacity` animates, and a frame sheet steps on `frame_time`. Wayland is best-effort. Additive: two new effect types and their optional params, no `VFXAPI` change, no wire or UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/window/custom-windows.md). **Not verified in game** - in particular the game must keep presenting normally on 26.2 with an aux GL window open, which a green build cannot prove.
release
VFXWeaver API 2.3.0 (1.21.11)
2.3.04 окт. 2026 г.70Minecraft: 1.21.11fabric
Что нового
### Added
- **`screen_image`: draw a picture over the game frame.** A new screen effect composites a resource-pack PNG - or an image a mod supplied from code with `VFXAPI.registerImage` - onto the game's own framebuffer inside an animatable rectangle. `pos_x`/`pos_y` place it, `size_w`/`size_h` size it and animate the picture's own proportions (`size_w:size_h` is exactly the on-screen width:height, so equal values are a square on any window), `opacity` fades it, and `frames`/`frame_time` step a strip or a vanilla `.png.mcmeta` animation. The canvas is the **Minecraft window**, not the monitor, so the picture scales with the window and nothing outside the game frame is drawn; `screen_layer` picks whether it draws under the GUI (the default), above the first-person hand, or over everything including chat and hotbar. A `.mcmeta` sibling animation wins over `frames`/`frame_time`; `interpolate: true` is not supported. An id that does not resolve draws nothing and warns once instead of blanking the frame. Additive: one new effect type and its optional params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/screen-image.md). Asserted by `scripts/check-screen-image.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`VFXAPI.registerImage`/`unregisterImage`: show an image a mod renders itself, anywhere a texture is accepted.** A mod hands over ARGB pixels (row-major, row 0 the top row, `0xAARRGGBB`, the order `NativeImage.getPixels()` returns) under a resource id, and that id then works exactly like a packed texture id: effect fields (`surface_pattern`, `sky_pattern`, field textures) resolve it through the game's texture manager, and a `window_create` picture uploads it into the aux window's own GL context. This is the missing piece for an item icon, a live preview or a generated texture that does not exist as a `.png` in any pack. The registry is a client singleton bounded at 256 images; registering an id again replaces it (the previous GPU texture is released). Additive: `default` methods on `VFXLocalDispatcher` (a dispatcher compiled before this still links), no wire or UBO change, `PROTOCOL_VERSION` unchanged. Documented in the [API](API.md#vfxapi). Asserted by `scripts/check-image-registry.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **Two effects that open a real picture window on the desktop: `window_create` and `window_control`.** A `window_create` opens a named **frameless, transparent, click-through** window covering the work area of the monitor the game is on, and shows a picture in it; `window_control` then moves, resizes, fades and retitles that window by name. Neither is a post-processing pass nor world geometry, and nothing about the Minecraft window changes. The window is undecorated, non-resizable, always on top and click-through (`GLFW_MOUSE_PASSTHROUGH`) at all times, and never takes focus, so it cannot steal input from the game. It is created hidden, sized to the work area and shown last, and is **never moved or resized afterwards** - position and size animate the picture's rectangle *inside* the canvas, so an animation costs no OS call per frame. Up to **8** windows may be live at once; a second `window_create` under a name that is already live is a no-op (first one wins). `titles` plus an animated `title_index` pick the title (clamped), `opacity` animates, and a frame sheet steps on `frame_time`. Wayland is best-effort. Additive: two new effect types and their optional params, no `VFXAPI` change, no wire or UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/window/custom-windows.md). **Not verified in game** - in particular the game must keep presenting normally on 26.2 with an aux GL window open, which a green build cannot prove.
release
VFXWeaver API 2.3.0 (26.1.x, NeoForge)
2.3.04 окт. 2026 г.4Minecraft: 26.1, 26.1.1, 26.1.2neoforge
Что нового
### Added
- **`screen_image`: draw a picture over the game frame.** A new screen effect composites a resource-pack PNG - or an image a mod supplied from code with `VFXAPI.registerImage` - onto the game's own framebuffer inside an animatable rectangle. `pos_x`/`pos_y` place it, `size_w`/`size_h` size it and animate the picture's own proportions (`size_w:size_h` is exactly the on-screen width:height, so equal values are a square on any window), `opacity` fades it, and `frames`/`frame_time` step a strip or a vanilla `.png.mcmeta` animation. The canvas is the **Minecraft window**, not the monitor, so the picture scales with the window and nothing outside the game frame is drawn; `screen_layer` picks whether it draws under the GUI (the default), above the first-person hand, or over everything including chat and hotbar. A `.mcmeta` sibling animation wins over `frames`/`frame_time`; `interpolate: true` is not supported. An id that does not resolve draws nothing and warns once instead of blanking the frame. Additive: one new effect type and its optional params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/screen-image.md). Asserted by `scripts/check-screen-image.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`VFXAPI.registerImage`/`unregisterImage`: show an image a mod renders itself, anywhere a texture is accepted.** A mod hands over ARGB pixels (row-major, row 0 the top row, `0xAARRGGBB`, the order `NativeImage.getPixels()` returns) under a resource id, and that id then works exactly like a packed texture id: effect fields (`surface_pattern`, `sky_pattern`, field textures) resolve it through the game's texture manager, and a `window_create` picture uploads it into the aux window's own GL context. This is the missing piece for an item icon, a live preview or a generated texture that does not exist as a `.png` in any pack. The registry is a client singleton bounded at 256 images; registering an id again replaces it (the previous GPU texture is released). Additive: `default` methods on `VFXLocalDispatcher` (a dispatcher compiled before this still links), no wire or UBO change, `PROTOCOL_VERSION` unchanged. Documented in the [API](API.md#vfxapi). Asserted by `scripts/check-image-registry.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **Two effects that open a real picture window on the desktop: `window_create` and `window_control`.** A `window_create` opens a named **frameless, transparent, click-through** window covering the work area of the monitor the game is on, and shows a picture in it; `window_control` then moves, resizes, fades and retitles that window by name. Neither is a post-processing pass nor world geometry, and nothing about the Minecraft window changes. The window is undecorated, non-resizable, always on top and click-through (`GLFW_MOUSE_PASSTHROUGH`) at all times, and never takes focus, so it cannot steal input from the game. It is created hidden, sized to the work area and shown last, and is **never moved or resized afterwards** - position and size animate the picture's rectangle *inside* the canvas, so an animation costs no OS call per frame. Up to **8** windows may be live at once; a second `window_create` under a name that is already live is a no-op (first one wins). `titles` plus an animated `title_index` pick the title (clamped), `opacity` animates, and a frame sheet steps on `frame_time`. Wayland is best-effort. Additive: two new effect types and their optional params, no `VFXAPI` change, no wire or UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/window/custom-windows.md). **Not verified in game** - in particular the game must keep presenting normally on 26.2 with an aux GL window open, which a green build cannot prove.
release
VFXWeaver API 2.3.0 (26.1.x)
2.3.04 окт. 2026 г.90Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
### Added
- **`screen_image`: draw a picture over the game frame.** A new screen effect composites a resource-pack PNG - or an image a mod supplied from code with `VFXAPI.registerImage` - onto the game's own framebuffer inside an animatable rectangle. `pos_x`/`pos_y` place it, `size_w`/`size_h` size it and animate the picture's own proportions (`size_w:size_h` is exactly the on-screen width:height, so equal values are a square on any window), `opacity` fades it, and `frames`/`frame_time` step a strip or a vanilla `.png.mcmeta` animation. The canvas is the **Minecraft window**, not the monitor, so the picture scales with the window and nothing outside the game frame is drawn; `screen_layer` picks whether it draws under the GUI (the default), above the first-person hand, or over everything including chat and hotbar. A `.mcmeta` sibling animation wins over `frames`/`frame_time`; `interpolate: true` is not supported. An id that does not resolve draws nothing and warns once instead of blanking the frame. Additive: one new effect type and its optional params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/screen-image.md). Asserted by `scripts/check-screen-image.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`VFXAPI.registerImage`/`unregisterImage`: show an image a mod renders itself, anywhere a texture is accepted.** A mod hands over ARGB pixels (row-major, row 0 the top row, `0xAARRGGBB`, the order `NativeImage.getPixels()` returns) under a resource id, and that id then works exactly like a packed texture id: effect fields (`surface_pattern`, `sky_pattern`, field textures) resolve it through the game's texture manager, and a `window_create` picture uploads it into the aux window's own GL context. This is the missing piece for an item icon, a live preview or a generated texture that does not exist as a `.png` in any pack. The registry is a client singleton bounded at 256 images; registering an id again replaces it (the previous GPU texture is released). Additive: `default` methods on `VFXLocalDispatcher` (a dispatcher compiled before this still links), no wire or UBO change, `PROTOCOL_VERSION` unchanged. Documented in the [API](API.md#vfxapi). Asserted by `scripts/check-image-registry.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **Two effects that open a real picture window on the desktop: `window_create` and `window_control`.** A `window_create` opens a named **frameless, transparent, click-through** window covering the work area of the monitor the game is on, and shows a picture in it; `window_control` then moves, resizes, fades and retitles that window by name. Neither is a post-processing pass nor world geometry, and nothing about the Minecraft window changes. The window is undecorated, non-resizable, always on top and click-through (`GLFW_MOUSE_PASSTHROUGH`) at all times, and never takes focus, so it cannot steal input from the game. It is created hidden, sized to the work area and shown last, and is **never moved or resized afterwards** - position and size animate the picture's rectangle *inside* the canvas, so an animation costs no OS call per frame. Up to **8** windows may be live at once; a second `window_create` under a name that is already live is a no-op (first one wins). `titles` plus an animated `title_index` pick the title (clamped), `opacity` animates, and a frame sheet steps on `frame_time`. Wayland is best-effort. Additive: two new effect types and their optional params, no `VFXAPI` change, no wire or UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/window/custom-windows.md). **Not verified in game** - in particular the game must keep presenting normally on 26.2 with an aux GL window open, which a green build cannot prove.
release
VFXWeaver API 2.3.0 (26.2, NeoForge)
2.3.04 окт. 2026 г.16Minecraft: 26.2neoforge
Что нового
### Added
- **`screen_image`: draw a picture over the game frame.** A new screen effect composites a resource-pack PNG - or an image a mod supplied from code with `VFXAPI.registerImage` - onto the game's own framebuffer inside an animatable rectangle. `pos_x`/`pos_y` place it, `size_w`/`size_h` size it and animate the picture's own proportions (`size_w:size_h` is exactly the on-screen width:height, so equal values are a square on any window), `opacity` fades it, and `frames`/`frame_time` step a strip or a vanilla `.png.mcmeta` animation. The canvas is the **Minecraft window**, not the monitor, so the picture scales with the window and nothing outside the game frame is drawn; `screen_layer` picks whether it draws under the GUI (the default), above the first-person hand, or over everything including chat and hotbar. A `.mcmeta` sibling animation wins over `frames`/`frame_time`; `interpolate: true` is not supported. An id that does not resolve draws nothing and warns once instead of blanking the frame. Additive: one new effect type and its optional params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/screen-image.md). Asserted by `scripts/check-screen-image.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`VFXAPI.registerImage`/`unregisterImage`: show an image a mod renders itself, anywhere a texture is accepted.** A mod hands over ARGB pixels (row-major, row 0 the top row, `0xAARRGGBB`, the order `NativeImage.getPixels()` returns) under a resource id, and that id then works exactly like a packed texture id: effect fields (`surface_pattern`, `sky_pattern`, field textures) resolve it through the game's texture manager, and a `window_create` picture uploads it into the aux window's own GL context. This is the missing piece for an item icon, a live preview or a generated texture that does not exist as a `.png` in any pack. The registry is a client singleton bounded at 256 images; registering an id again replaces it (the previous GPU texture is released). Additive: `default` methods on `VFXLocalDispatcher` (a dispatcher compiled before this still links), no wire or UBO change, `PROTOCOL_VERSION` unchanged. Documented in the [API](API.md#vfxapi). Asserted by `scripts/check-image-registry.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **Two effects that open a real picture window on the desktop: `window_create` and `window_control`.** A `window_create` opens a named **frameless, transparent, click-through** window covering the work area of the monitor the game is on, and shows a picture in it; `window_control` then moves, resizes, fades and retitles that window by name. Neither is a post-processing pass nor world geometry, and nothing about the Minecraft window changes. The window is undecorated, non-resizable, always on top and click-through (`GLFW_MOUSE_PASSTHROUGH`) at all times, and never takes focus, so it cannot steal input from the game. It is created hidden, sized to the work area and shown last, and is **never moved or resized afterwards** - position and size animate the picture's rectangle *inside* the canvas, so an animation costs no OS call per frame. Up to **8** windows may be live at once; a second `window_create` under a name that is already live is a no-op (first one wins). `titles` plus an animated `title_index` pick the title (clamped), `opacity` animates, and a frame sheet steps on `frame_time`. Wayland is best-effort. Additive: two new effect types and their optional params, no `VFXAPI` change, no wire or UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/window/custom-windows.md). **Not verified in game** - in particular the game must keep presenting normally on 26.2 with an aux GL window open, which a green build cannot prove.
release
VFXWeaver API 2.3.0 (26.2)
2.3.04 окт. 2026 г.77Minecraft: 26.2fabric
Что нового
### Added
- **`screen_image`: draw a picture over the game frame.** A new screen effect composites a resource-pack PNG - or an image a mod supplied from code with `VFXAPI.registerImage` - onto the game's own framebuffer inside an animatable rectangle. `pos_x`/`pos_y` place it, `size_w`/`size_h` size it and animate the picture's own proportions (`size_w:size_h` is exactly the on-screen width:height, so equal values are a square on any window), `opacity` fades it, and `frames`/`frame_time` step a strip or a vanilla `.png.mcmeta` animation. The canvas is the **Minecraft window**, not the monitor, so the picture scales with the window and nothing outside the game frame is drawn; `screen_layer` picks whether it draws under the GUI (the default), above the first-person hand, or over everything including chat and hotbar. A `.mcmeta` sibling animation wins over `frames`/`frame_time`; `interpolate: true` is not supported. An id that does not resolve draws nothing and warns once instead of blanking the frame. Additive: one new effect type and its optional params, no existing datapack field renamed, no UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/screen/screen-image.md). Asserted by `scripts/check-screen-image.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **`VFXAPI.registerImage`/`unregisterImage`: show an image a mod renders itself, anywhere a texture is accepted.** A mod hands over ARGB pixels (row-major, row 0 the top row, `0xAARRGGBB`, the order `NativeImage.getPixels()` returns) under a resource id, and that id then works exactly like a packed texture id: effect fields (`surface_pattern`, `sky_pattern`, field textures) resolve it through the game's texture manager, and a `window_create` picture uploads it into the aux window's own GL context. This is the missing piece for an item icon, a live preview or a generated texture that does not exist as a `.png` in any pack. The registry is a client singleton bounded at 256 images; registering an id again replaces it (the previous GPU texture is released). Additive: `default` methods on `VFXLocalDispatcher` (a dispatcher compiled before this still links), no wire or UBO change, `PROTOCOL_VERSION` unchanged. Documented in the [API](API.md#vfxapi). Asserted by `scripts/check-image-registry.ps1`; the GLSL/pixels cannot be verified headless here. **Not verified in game**.
- **Two effects that open a real picture window on the desktop: `window_create` and `window_control`.** A `window_create` opens a named **frameless, transparent, click-through** window covering the work area of the monitor the game is on, and shows a picture in it; `window_control` then moves, resizes, fades and retitles that window by name. Neither is a post-processing pass nor world geometry, and nothing about the Minecraft window changes. The window is undecorated, non-resizable, always on top and click-through (`GLFW_MOUSE_PASSTHROUGH`) at all times, and never takes focus, so it cannot steal input from the game. It is created hidden, sized to the work area and shown last, and is **never moved or resized afterwards** - position and size animate the picture's rectangle *inside* the canvas, so an animation costs no OS call per frame. Up to **8** windows may be live at once; a second `window_create` under a name that is already live is a no-op (first one wins). `titles` plus an animated `title_index` pick the title (clamped), `opacity` animates, and a frame sheet steps on `frame_time`. Wayland is best-effort. Additive: two new effect types and their optional params, no `VFXAPI` change, no wire or UBO layout change, `PROTOCOL_VERSION` unchanged. Documented in the [guide](guide/effects/window/custom-windows.md). **Not verified in game** - in particular the game must keep presenting normally on 26.2 with an aux GL window open, which a green build cannot prove.
release
VFXWeaver API 2.2.1 (1.21.11, NeoForge)
2.2.11 окт. 2026 г.11Minecraft: 1.21.11neoforge
Что нового
### Added
- **Two optional box bounds for a GLSL mask shape â a containment box and a skip box.** A world
plugin leaf on `"volume": "aura"` may now declare an axis-aligned box as its broad phase, in two
further optional functions (`vfx_shape_custom_bounds_box`, `vfx_shape_custom_skip_bounds`).
The **containment box** must contain the whole region where the field is `<= 0`; the **skip box**
must lie entirely inside a region where the field is `> 0` and be *inscribed* in that hole â the
AABB of a set of circles does not qualify, because its corners sit in the coverage region and
would skip real coverage. Getting the two the wrong way round is a declaration bug rather than a
cull: it buys nothing, because the coverage region outside such a box is unbounded. A miss on the
containment box's padded interval culls the leaf with no march at all, and a camera inside the skip
box starts the march at its far side instead of at the camera. The slab test is padded by
`softness / 2` and the skip keeps the last `softness` before the range limit sampled, so no cliff
appears at a box face and the plugin never has to know `softness`. Both are optional and consulted
per leaf (skip, then box, then the existing sphere, then no bounds), so a plugin that declares none
of them renders exactly as before. Additive: two default methods on `VFXMaskShapeGlsl`, no new
`VFXAPI` overload, no datapack field, no wire or UBO layout change, `PROTOCOL_VERSION` unchanged.
See [API](API.md). **Not verified in game**; the contracts and the arithmetic
are asserted by `scripts/check-aura-bounds.ps1` â the GLSL cannot compile headless here.
- **`"aura_steps"` on a custom aura mask leaf â an optional per-leaf cap on the march.** The march
budget is an upper bound a typical ray never approaches (it leaves early on a saturated sample, on
the dive bound and on the range limit, and spends 1 to 20 of the loop's 40 + 16 iterations on the
check's ray classes), so the new number is a **cap, not a spend** â a last resort for a leaf that
really does burn the budget, and a declared broad phase is the real lever. Accepted only on a custom
leaf with `"volume": "aura"` (a parse error naming the shape anywhere else, since only a plugin
leaf has a march to bound); a negative value is a parse error, and a value above the compiled
budget is clamped to it. Absent, or `0`, means no cap and the leaf is identical to today. A
truncated march is best-effort and never saturates; because a march cut short while still inside
meets the shipped `tExit = tLimit` rule, a low cap can read slightly *higher* than uncapped
(measured: +0.23 at a cap of 2, bounded by the uncapped silhouette factor). Additive: one new
optional leaf field on an existing reserved slot, no UBO or Config field added, no field renamed,
`PROTOCOL_VERSION` unchanged. See [masks](guide/datapack/masks.md). **Not verified in game**; asserted by `scripts/check-aura-bounds.ps1` â the GLSL cannot compile headless
here.
release
VFXWeaver API 2.2.1 (26.1.x, NeoForge)
2.2.11 окт. 2026 г.56Minecraft: 26.1, 26.1.1, 26.1.2neoforge
Что нового
### Added
- **Two optional box bounds for a GLSL mask shape â a containment box and a skip box.** A world
plugin leaf on `"volume": "aura"` may now declare an axis-aligned box as its broad phase, in two
further optional functions (`vfx_shape_custom_bounds_box`, `vfx_shape_custom_skip_bounds`).
The **containment box** must contain the whole region where the field is `<= 0`; the **skip box**
must lie entirely inside a region where the field is `> 0` and be *inscribed* in that hole â the
AABB of a set of circles does not qualify, because its corners sit in the coverage region and
would skip real coverage. Getting the two the wrong way round is a declaration bug rather than a
cull: it buys nothing, because the coverage region outside such a box is unbounded. A miss on the
containment box's padded interval culls the leaf with no march at all, and a camera inside the skip
box starts the march at its far side instead of at the camera. The slab test is padded by
`softness / 2` and the skip keeps the last `softness` before the range limit sampled, so no cliff
appears at a box face and the plugin never has to know `softness`. Both are optional and consulted
per leaf (skip, then box, then the existing sphere, then no bounds), so a plugin that declares none
of them renders exactly as before. Additive: two default methods on `VFXMaskShapeGlsl`, no new
`VFXAPI` overload, no datapack field, no wire or UBO layout change, `PROTOCOL_VERSION` unchanged.
See [API](API.md). **Not verified in game**; the contracts and the arithmetic
are asserted by `scripts/check-aura-bounds.ps1` â the GLSL cannot compile headless here.
- **`"aura_steps"` on a custom aura mask leaf â an optional per-leaf cap on the march.** The march
budget is an upper bound a typical ray never approaches (it leaves early on a saturated sample, on
the dive bound and on the range limit, and spends 1 to 20 of the loop's 40 + 16 iterations on the
check's ray classes), so the new number is a **cap, not a spend** â a last resort for a leaf that
really does burn the budget, and a declared broad phase is the real lever. Accepted only on a custom
leaf with `"volume": "aura"` (a parse error naming the shape anywhere else, since only a plugin
leaf has a march to bound); a negative value is a parse error, and a value above the compiled
budget is clamped to it. Absent, or `0`, means no cap and the leaf is identical to today. A
truncated march is best-effort and never saturates; because a march cut short while still inside
meets the shipped `tExit = tLimit` rule, a low cap can read slightly *higher* than uncapped
(measured: +0.23 at a cap of 2, bounded by the uncapped silhouette factor). Additive: one new
optional leaf field on an existing reserved slot, no UBO or Config field added, no field renamed,
`PROTOCOL_VERSION` unchanged. See [masks](guide/datapack/masks.md). **Not verified in game**; asserted by `scripts/check-aura-bounds.ps1` â the GLSL cannot compile headless
here.
release
VFXWeaver API 2.2.1 (26.2, NeoForge)
2.2.11 окт. 2026 г.13Minecraft: 26.2neoforge
Что нового
### Added
- **Two optional box bounds for a GLSL mask shape â a containment box and a skip box.** A world
plugin leaf on `"volume": "aura"` may now declare an axis-aligned box as its broad phase, in two
further optional functions (`vfx_shape_custom_bounds_box`, `vfx_shape_custom_skip_bounds`).
The **containment box** must contain the whole region where the field is `<= 0`; the **skip box**
must lie entirely inside a region where the field is `> 0` and be *inscribed* in that hole â the
AABB of a set of circles does not qualify, because its corners sit in the coverage region and
would skip real coverage. Getting the two the wrong way round is a declaration bug rather than a
cull: it buys nothing, because the coverage region outside such a box is unbounded. A miss on the
containment box's padded interval culls the leaf with no march at all, and a camera inside the skip
box starts the march at its far side instead of at the camera. The slab test is padded by
`softness / 2` and the skip keeps the last `softness` before the range limit sampled, so no cliff
appears at a box face and the plugin never has to know `softness`. Both are optional and consulted
per leaf (skip, then box, then the existing sphere, then no bounds), so a plugin that declares none
of them renders exactly as before. Additive: two default methods on `VFXMaskShapeGlsl`, no new
`VFXAPI` overload, no datapack field, no wire or UBO layout change, `PROTOCOL_VERSION` unchanged.
See [API](API.md). **Not verified in game**; the contracts and the arithmetic
are asserted by `scripts/check-aura-bounds.ps1` â the GLSL cannot compile headless here.
- **`"aura_steps"` on a custom aura mask leaf â an optional per-leaf cap on the march.** The march
budget is an upper bound a typical ray never approaches (it leaves early on a saturated sample, on
the dive bound and on the range limit, and spends 1 to 20 of the loop's 40 + 16 iterations on the
check's ray classes), so the new number is a **cap, not a spend** â a last resort for a leaf that
really does burn the budget, and a declared broad phase is the real lever. Accepted only on a custom
leaf with `"volume": "aura"` (a parse error naming the shape anywhere else, since only a plugin
leaf has a march to bound); a negative value is a parse error, and a value above the compiled
budget is clamped to it. Absent, or `0`, means no cap and the leaf is identical to today. A
truncated march is best-effort and never saturates; because a march cut short while still inside
meets the shipped `tExit = tLimit` rule, a low cap can read slightly *higher* than uncapped
(measured: +0.23 at a cap of 2, bounded by the uncapped silhouette factor). Additive: one new
optional leaf field on an existing reserved slot, no UBO or Config field added, no field renamed,
`PROTOCOL_VERSION` unchanged. See [masks](guide/datapack/masks.md). **Not verified in game**; asserted by `scripts/check-aura-bounds.ps1` â the GLSL cannot compile headless
here.
release
VFXWeaver API 2.2.1 (1.21.11)
2.2.11 окт. 2026 г.19Minecraft: 1.21.11fabric
Что нового
### Added
- **Two optional box bounds for a GLSL mask shape â a containment box and a skip box.** A world
plugin leaf on `"volume": "aura"` may now declare an axis-aligned box as its broad phase, in two
further optional functions (`vfx_shape_custom_bounds_box`, `vfx_shape_custom_skip_bounds`).
The **containment box** must contain the whole region where the field is `<= 0`; the **skip box**
must lie entirely inside a region where the field is `> 0` and be *inscribed* in that hole â the
AABB of a set of circles does not qualify, because its corners sit in the coverage region and
would skip real coverage. Getting the two the wrong way round is a declaration bug rather than a
cull: it buys nothing, because the coverage region outside such a box is unbounded. A miss on the
containment box's padded interval culls the leaf with no march at all, and a camera inside the skip
box starts the march at its far side instead of at the camera. The slab test is padded by
`softness / 2` and the skip keeps the last `softness` before the range limit sampled, so no cliff
appears at a box face and the plugin never has to know `softness`. Both are optional and consulted
per leaf (skip, then box, then the existing sphere, then no bounds), so a plugin that declares none
of them renders exactly as before. Additive: two default methods on `VFXMaskShapeGlsl`, no new
`VFXAPI` overload, no datapack field, no wire or UBO layout change, `PROTOCOL_VERSION` unchanged.
See [API](API.md). **Not verified in game**; the contracts and the arithmetic
are asserted by `scripts/check-aura-bounds.ps1` â the GLSL cannot compile headless here.
- **`"aura_steps"` on a custom aura mask leaf â an optional per-leaf cap on the march.** The march
budget is an upper bound a typical ray never approaches (it leaves early on a saturated sample, on
the dive bound and on the range limit, and spends 1 to 20 of the loop's 40 + 16 iterations on the
check's ray classes), so the new number is a **cap, not a spend** â a last resort for a leaf that
really does burn the budget, and a declared broad phase is the real lever. Accepted only on a custom
leaf with `"volume": "aura"` (a parse error naming the shape anywhere else, since only a plugin
leaf has a march to bound); a negative value is a parse error, and a value above the compiled
budget is clamped to it. Absent, or `0`, means no cap and the leaf is identical to today. A
truncated march is best-effort and never saturates; because a march cut short while still inside
meets the shipped `tExit = tLimit` rule, a low cap can read slightly *higher* than uncapped
(measured: +0.23 at a cap of 2, bounded by the uncapped silhouette factor). Additive: one new
optional leaf field on an existing reserved slot, no UBO or Config field added, no field renamed,
`PROTOCOL_VERSION` unchanged. See [masks](guide/datapack/masks.md). **Not verified in game**; asserted by `scripts/check-aura-bounds.ps1` â the GLSL cannot compile headless
here.
release
VFXWeaver API 2.2.1 (26.1.x)
2.2.11 окт. 2026 г.35Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
### Added
- **Two optional box bounds for a GLSL mask shape â a containment box and a skip box.** A world
plugin leaf on `"volume": "aura"` may now declare an axis-aligned box as its broad phase, in two
further optional functions (`vfx_shape_custom_bounds_box`, `vfx_shape_custom_skip_bounds`).
The **containment box** must contain the whole region where the field is `<= 0`; the **skip box**
must lie entirely inside a region where the field is `> 0` and be *inscribed* in that hole â the
AABB of a set of circles does not qualify, because its corners sit in the coverage region and
would skip real coverage. Getting the two the wrong way round is a declaration bug rather than a
cull: it buys nothing, because the coverage region outside such a box is unbounded. A miss on the
containment box's padded interval culls the leaf with no march at all, and a camera inside the skip
box starts the march at its far side instead of at the camera. The slab test is padded by
`softness / 2` and the skip keeps the last `softness` before the range limit sampled, so no cliff
appears at a box face and the plugin never has to know `softness`. Both are optional and consulted
per leaf (skip, then box, then the existing sphere, then no bounds), so a plugin that declares none
of them renders exactly as before. Additive: two default methods on `VFXMaskShapeGlsl`, no new
`VFXAPI` overload, no datapack field, no wire or UBO layout change, `PROTOCOL_VERSION` unchanged.
See [API](API.md). **Not verified in game**; the contracts and the arithmetic
are asserted by `scripts/check-aura-bounds.ps1` â the GLSL cannot compile headless here.
- **`"aura_steps"` on a custom aura mask leaf â an optional per-leaf cap on the march.** The march
budget is an upper bound a typical ray never approaches (it leaves early on a saturated sample, on
the dive bound and on the range limit, and spends 1 to 20 of the loop's 40 + 16 iterations on the
check's ray classes), so the new number is a **cap, not a spend** â a last resort for a leaf that
really does burn the budget, and a declared broad phase is the real lever. Accepted only on a custom
leaf with `"volume": "aura"` (a parse error naming the shape anywhere else, since only a plugin
leaf has a march to bound); a negative value is a parse error, and a value above the compiled
budget is clamped to it. Absent, or `0`, means no cap and the leaf is identical to today. A
truncated march is best-effort and never saturates; because a march cut short while still inside
meets the shipped `tExit = tLimit` rule, a low cap can read slightly *higher* than uncapped
(measured: +0.23 at a cap of 2, bounded by the uncapped silhouette factor). Additive: one new
optional leaf field on an existing reserved slot, no UBO or Config field added, no field renamed,
`PROTOCOL_VERSION` unchanged. See [masks](guide/datapack/masks.md). **Not verified in game**; asserted by `scripts/check-aura-bounds.ps1` â the GLSL cannot compile headless
here.
release
VFXWeaver API 2.2.1 (26.2)
2.2.11 окт. 2026 г.20Minecraft: 26.2fabric
Что нового
### Added
- **Two optional box bounds for a GLSL mask shape â a containment box and a skip box.** A world
plugin leaf on `"volume": "aura"` may now declare an axis-aligned box as its broad phase, in two
further optional functions (`vfx_shape_custom_bounds_box`, `vfx_shape_custom_skip_bounds`).
The **containment box** must contain the whole region where the field is `<= 0`; the **skip box**
must lie entirely inside a region where the field is `> 0` and be *inscribed* in that hole â the
AABB of a set of circles does not qualify, because its corners sit in the coverage region and
would skip real coverage. Getting the two the wrong way round is a declaration bug rather than a
cull: it buys nothing, because the coverage region outside such a box is unbounded. A miss on the
containment box's padded interval culls the leaf with no march at all, and a camera inside the skip
box starts the march at its far side instead of at the camera. The slab test is padded by
`softness / 2` and the skip keeps the last `softness` before the range limit sampled, so no cliff
appears at a box face and the plugin never has to know `softness`. Both are optional and consulted
per leaf (skip, then box, then the existing sphere, then no bounds), so a plugin that declares none
of them renders exactly as before. Additive: two default methods on `VFXMaskShapeGlsl`, no new
`VFXAPI` overload, no datapack field, no wire or UBO layout change, `PROTOCOL_VERSION` unchanged.
See [API](API.md). **Not verified in game**; the contracts and the arithmetic
are asserted by `scripts/check-aura-bounds.ps1` â the GLSL cannot compile headless here.
- **`"aura_steps"` on a custom aura mask leaf â an optional per-leaf cap on the march.** The march
budget is an upper bound a typical ray never approaches (it leaves early on a saturated sample, on
the dive bound and on the range limit, and spends 1 to 20 of the loop's 40 + 16 iterations on the
check's ray classes), so the new number is a **cap, not a spend** â a last resort for a leaf that
really does burn the budget, and a declared broad phase is the real lever. Accepted only on a custom
leaf with `"volume": "aura"` (a parse error naming the shape anywhere else, since only a plugin
leaf has a march to bound); a negative value is a parse error, and a value above the compiled
budget is clamped to it. Absent, or `0`, means no cap and the leaf is identical to today. A
truncated march is best-effort and never saturates; because a march cut short while still inside
meets the shipped `tExit = tLimit` rule, a low cap can read slightly *higher* than uncapped
(measured: +0.23 at a cap of 2, bounded by the uncapped silhouette factor). Additive: one new
optional leaf field on an existing reserved slot, no UBO or Config field added, no field renamed,
`PROTOCOL_VERSION` unchanged. See [masks](guide/datapack/masks.md). **Not verified in game**; asserted by `scripts/check-aura-bounds.ps1` â the GLSL cannot compile headless
here.
release
VFXWeaver API 2.2.0 (1.21.11, NeoForge)
2.2.030 сент. 2026 г.13Minecraft: 1.21.11neoforge
Что нового
## 2.2.0 - 2026-09-29
### Added
- **`/vfxconfig` — an in-game settings screen.** A vanilla screen (no Cloth Config, Mod Menu or
YACL, no new dependency) edits the three post-chain settings: **chain resolution** (Full / Half,
default **Half**), **fusion** (default **On**) and the **remap** (default **On**, with a
description label). Changes apply without a restart and persist to `config/vfxweaver.json`; a JVM
system property still overrides the file. Client-side and available to every player (no operator
rights): the command is registered on the client and never reaches the server, so it works on any
server, including a dedicated one. No datapack, API, wire or UBO change.
- **Post-chain fusion: a run of pointwise screen effects renders as one shader pass.** Adjacent
`POINT` screen effects (`color_grade`, `screen_flash`, `vignette`, `invert`, `posterize`,
`scanlines`, `film_grain`) and each masked effect's coverage consumer are compiled into a single
generated program, so a chain of four masked pointwise effects costs one pass instead of twelve.
`blur`-class effects (anything that samples neighbours, including `bloom`, `vhs` and `motion_blur`),
`afterimage`/`stop_motion` and every unannotated effect cut the chain and render exactly as before.
Output is re-quantised per stage so a fused chain matches the unfused one. On by default;
`-Dvfxweaver.fusion=false` disables it. The one-tap remaps (`distortion`, `vortex`, `pixelate`,
`noise_warp`, `slice_shift`) are fused too, on by default now that the owner's in-game A/B showed
no visible difference; `-Dvfxweaver.fusion.remap=false` disables them. No datapack, API, wire or
UBO change.
- **Half-resolution effect runs.** A run of the heavy multi-tap screen passes (`blur`'s two passes,
`bloom`, `depth_of_field`, `vhs` and `digital_glitch`) can render at half resolution, wrapped in an
exact downsample/upsample pair, once the run's summed tap count reaches 8 (`vhs` and
`digital_glitch` carry four taps each, so they scale only beside another scalable pass). Nothing
else about the chain changes: pointwise and fused stages, mask consumers and their coverage, the
pre-effect image, `afterimage`/`stop_motion` and every unlisted effect render exactly as before.
On by default; `-Dvfxweaver.chainres=0.5` is the default and `-Dvfxweaver.chainres=1.0` disables
it. Only those two values are accepted - anything else warns once and is treated as `0.5`. Inside a
scaled pass the output is slightly softer, which is the point. No datapack, API, wire or UBO
change.
release
VFXWeaver API 2.2.0 (26.1.x, NeoForge)
2.2.030 сент. 2026 г.19Minecraft: 26.1, 26.1.1, 26.1.2neoforge
Что нового
## 2.2.0 - 2026-09-29
### Added
- **`/vfxconfig` — an in-game settings screen.** A vanilla screen (no Cloth Config, Mod Menu or
YACL, no new dependency) edits the three post-chain settings: **chain resolution** (Full / Half,
default **Half**), **fusion** (default **On**) and the **remap** (default **On**, with a
description label). Changes apply without a restart and persist to `config/vfxweaver.json`; a JVM
system property still overrides the file. Client-side and available to every player (no operator
rights): the command is registered on the client and never reaches the server, so it works on any
server, including a dedicated one. No datapack, API, wire or UBO change.
- **Post-chain fusion: a run of pointwise screen effects renders as one shader pass.** Adjacent
`POINT` screen effects (`color_grade`, `screen_flash`, `vignette`, `invert`, `posterize`,
`scanlines`, `film_grain`) and each masked effect's coverage consumer are compiled into a single
generated program, so a chain of four masked pointwise effects costs one pass instead of twelve.
`blur`-class effects (anything that samples neighbours, including `bloom`, `vhs` and `motion_blur`),
`afterimage`/`stop_motion` and every unannotated effect cut the chain and render exactly as before.
Output is re-quantised per stage so a fused chain matches the unfused one. On by default;
`-Dvfxweaver.fusion=false` disables it. The one-tap remaps (`distortion`, `vortex`, `pixelate`,
`noise_warp`, `slice_shift`) are fused too, on by default now that the owner's in-game A/B showed
no visible difference; `-Dvfxweaver.fusion.remap=false` disables them. No datapack, API, wire or
UBO change.
- **Half-resolution effect runs.** A run of the heavy multi-tap screen passes (`blur`'s two passes,
`bloom`, `depth_of_field`, `vhs` and `digital_glitch`) can render at half resolution, wrapped in an
exact downsample/upsample pair, once the run's summed tap count reaches 8 (`vhs` and
`digital_glitch` carry four taps each, so they scale only beside another scalable pass). Nothing
else about the chain changes: pointwise and fused stages, mask consumers and their coverage, the
pre-effect image, `afterimage`/`stop_motion` and every unlisted effect render exactly as before.
On by default; `-Dvfxweaver.chainres=0.5` is the default and `-Dvfxweaver.chainres=1.0` disables
it. Only those two values are accepted - anything else warns once and is treated as `0.5`. Inside a
scaled pass the output is slightly softer, which is the point. No datapack, API, wire or UBO
change.
release
VFXWeaver API 2.2.0 (26.2, NeoForge)
2.2.030 сент. 2026 г.12Minecraft: 26.2neoforge
Что нового
## 2.2.0 - 2026-09-29
### Added
- **`/vfxconfig` — an in-game settings screen.** A vanilla screen (no Cloth Config, Mod Menu or
YACL, no new dependency) edits the three post-chain settings: **chain resolution** (Full / Half,
default **Half**), **fusion** (default **On**) and the **remap** (default **On**, with a
description label). Changes apply without a restart and persist to `config/vfxweaver.json`; a JVM
system property still overrides the file. Client-side and available to every player (no operator
rights): the command is registered on the client and never reaches the server, so it works on any
server, including a dedicated one. No datapack, API, wire or UBO change.
- **Post-chain fusion: a run of pointwise screen effects renders as one shader pass.** Adjacent
`POINT` screen effects (`color_grade`, `screen_flash`, `vignette`, `invert`, `posterize`,
`scanlines`, `film_grain`) and each masked effect's coverage consumer are compiled into a single
generated program, so a chain of four masked pointwise effects costs one pass instead of twelve.
`blur`-class effects (anything that samples neighbours, including `bloom`, `vhs` and `motion_blur`),
`afterimage`/`stop_motion` and every unannotated effect cut the chain and render exactly as before.
Output is re-quantised per stage so a fused chain matches the unfused one. On by default;
`-Dvfxweaver.fusion=false` disables it. The one-tap remaps (`distortion`, `vortex`, `pixelate`,
`noise_warp`, `slice_shift`) are fused too, on by default now that the owner's in-game A/B showed
no visible difference; `-Dvfxweaver.fusion.remap=false` disables them. No datapack, API, wire or
UBO change.
- **Half-resolution effect runs.** A run of the heavy multi-tap screen passes (`blur`'s two passes,
`bloom`, `depth_of_field`, `vhs` and `digital_glitch`) can render at half resolution, wrapped in an
exact downsample/upsample pair, once the run's summed tap count reaches 8 (`vhs` and
`digital_glitch` carry four taps each, so they scale only beside another scalable pass). Nothing
else about the chain changes: pointwise and fused stages, mask consumers and their coverage, the
pre-effect image, `afterimage`/`stop_motion` and every unlisted effect render exactly as before.
On by default; `-Dvfxweaver.chainres=0.5` is the default and `-Dvfxweaver.chainres=1.0` disables
it. Only those two values are accepted - anything else warns once and is treated as `0.5`. Inside a
scaled pass the output is slightly softer, which is the point. No datapack, API, wire or UBO
change.
release
VFXWeaver API 2.2.0 (1.21.11)
2.2.030 сент. 2026 г.16Minecraft: 1.21.11fabric
Что нового
## 2.2.0 - 2026-09-29
### Added
- **`/vfxconfig` — an in-game settings screen.** A vanilla screen (no Cloth Config, Mod Menu or
YACL, no new dependency) edits the three post-chain settings: **chain resolution** (Full / Half,
default **Half**), **fusion** (default **On**) and the **remap** (default **On**, with a
description label). Changes apply without a restart and persist to `config/vfxweaver.json`; a JVM
system property still overrides the file. Client-side and available to every player (no operator
rights): the command is registered on the client and never reaches the server, so it works on any
server, including a dedicated one. No datapack, API, wire or UBO change.
- **Post-chain fusion: a run of pointwise screen effects renders as one shader pass.** Adjacent
`POINT` screen effects (`color_grade`, `screen_flash`, `vignette`, `invert`, `posterize`,
`scanlines`, `film_grain`) and each masked effect's coverage consumer are compiled into a single
generated program, so a chain of four masked pointwise effects costs one pass instead of twelve.
`blur`-class effects (anything that samples neighbours, including `bloom`, `vhs` and `motion_blur`),
`afterimage`/`stop_motion` and every unannotated effect cut the chain and render exactly as before.
Output is re-quantised per stage so a fused chain matches the unfused one. On by default;
`-Dvfxweaver.fusion=false` disables it. The one-tap remaps (`distortion`, `vortex`, `pixelate`,
`noise_warp`, `slice_shift`) are fused too, on by default now that the owner's in-game A/B showed
no visible difference; `-Dvfxweaver.fusion.remap=false` disables them. No datapack, API, wire or
UBO change.
- **Half-resolution effect runs.** A run of the heavy multi-tap screen passes (`blur`'s two passes,
`bloom`, `depth_of_field`, `vhs` and `digital_glitch`) can render at half resolution, wrapped in an
exact downsample/upsample pair, once the run's summed tap count reaches 8 (`vhs` and
`digital_glitch` carry four taps each, so they scale only beside another scalable pass). Nothing
else about the chain changes: pointwise and fused stages, mask consumers and their coverage, the
pre-effect image, `afterimage`/`stop_motion` and every unlisted effect render exactly as before.
On by default; `-Dvfxweaver.chainres=0.5` is the default and `-Dvfxweaver.chainres=1.0` disables
it. Only those two values are accepted - anything else warns once and is treated as `0.5`. Inside a
scaled pass the output is slightly softer, which is the point. No datapack, API, wire or UBO
change.
release
VFXWeaver API 2.2.0 (26.1.x)
2.2.030 сент. 2026 г.29Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
## 2.2.0 - 2026-09-29
### Added
- **`/vfxconfig` — an in-game settings screen.** A vanilla screen (no Cloth Config, Mod Menu or
YACL, no new dependency) edits the three post-chain settings: **chain resolution** (Full / Half,
default **Half**), **fusion** (default **On**) and the **remap** (default **On**, with a
description label). Changes apply without a restart and persist to `config/vfxweaver.json`; a JVM
system property still overrides the file. Client-side and available to every player (no operator
rights): the command is registered on the client and never reaches the server, so it works on any
server, including a dedicated one. No datapack, API, wire or UBO change.
- **Post-chain fusion: a run of pointwise screen effects renders as one shader pass.** Adjacent
`POINT` screen effects (`color_grade`, `screen_flash`, `vignette`, `invert`, `posterize`,
`scanlines`, `film_grain`) and each masked effect's coverage consumer are compiled into a single
generated program, so a chain of four masked pointwise effects costs one pass instead of twelve.
`blur`-class effects (anything that samples neighbours, including `bloom`, `vhs` and `motion_blur`),
`afterimage`/`stop_motion` and every unannotated effect cut the chain and render exactly as before.
Output is re-quantised per stage so a fused chain matches the unfused one. On by default;
`-Dvfxweaver.fusion=false` disables it. The one-tap remaps (`distortion`, `vortex`, `pixelate`,
`noise_warp`, `slice_shift`) are fused too, on by default now that the owner's in-game A/B showed
no visible difference; `-Dvfxweaver.fusion.remap=false` disables them. No datapack, API, wire or
UBO change.
- **Half-resolution effect runs.** A run of the heavy multi-tap screen passes (`blur`'s two passes,
`bloom`, `depth_of_field`, `vhs` and `digital_glitch`) can render at half resolution, wrapped in an
exact downsample/upsample pair, once the run's summed tap count reaches 8 (`vhs` and
`digital_glitch` carry four taps each, so they scale only beside another scalable pass). Nothing
else about the chain changes: pointwise and fused stages, mask consumers and their coverage, the
pre-effect image, `afterimage`/`stop_motion` and every unlisted effect render exactly as before.
On by default; `-Dvfxweaver.chainres=0.5` is the default and `-Dvfxweaver.chainres=1.0` disables
it. Only those two values are accepted - anything else warns once and is treated as `0.5`. Inside a
scaled pass the output is slightly softer, which is the point. No datapack, API, wire or UBO
change.
release
VFXWeaver API 2.2.0 (26.2)
2.2.030 сент. 2026 г.28Minecraft: 26.2fabric
Что нового
## 2.2.0 - 2026-09-29
### Added
- **`/vfxconfig` — an in-game settings screen.** A vanilla screen (no Cloth Config, Mod Menu or
YACL, no new dependency) edits the three post-chain settings: **chain resolution** (Full / Half,
default **Half**), **fusion** (default **On**) and the **remap** (default **On**, with a
description label). Changes apply without a restart and persist to `config/vfxweaver.json`; a JVM
system property still overrides the file. Client-side and available to every player (no operator
rights): the command is registered on the client and never reaches the server, so it works on any
server, including a dedicated one. No datapack, API, wire or UBO change.
- **Post-chain fusion: a run of pointwise screen effects renders as one shader pass.** Adjacent
`POINT` screen effects (`color_grade`, `screen_flash`, `vignette`, `invert`, `posterize`,
`scanlines`, `film_grain`) and each masked effect's coverage consumer are compiled into a single
generated program, so a chain of four masked pointwise effects costs one pass instead of twelve.
`blur`-class effects (anything that samples neighbours, including `bloom`, `vhs` and `motion_blur`),
`afterimage`/`stop_motion` and every unannotated effect cut the chain and render exactly as before.
Output is re-quantised per stage so a fused chain matches the unfused one. On by default;
`-Dvfxweaver.fusion=false` disables it. The one-tap remaps (`distortion`, `vortex`, `pixelate`,
`noise_warp`, `slice_shift`) are fused too, on by default now that the owner's in-game A/B showed
no visible difference; `-Dvfxweaver.fusion.remap=false` disables them. No datapack, API, wire or
UBO change.
- **Half-resolution effect runs.** A run of the heavy multi-tap screen passes (`blur`'s two passes,
`bloom`, `depth_of_field`, `vhs` and `digital_glitch`) can render at half resolution, wrapped in an
exact downsample/upsample pair, once the run's summed tap count reaches 8 (`vhs` and
`digital_glitch` carry four taps each, so they scale only beside another scalable pass). Nothing
else about the chain changes: pointwise and fused stages, mask consumers and their coverage, the
pre-effect image, `afterimage`/`stop_motion` and every unlisted effect render exactly as before.
On by default; `-Dvfxweaver.chainres=0.5` is the default and `-Dvfxweaver.chainres=1.0` disables
it. Only those two values are accepted - anything else warns once and is treated as `0.5`. Inside a
scaled pass the output is slightly softer, which is the point. No datapack, API, wire or UBO
change.
release
VFXWeaver API 2.1.1 (1.21.11, NeoForge)
2.1.127 сент. 2026 г.12Minecraft: 1.21.11neoforge
Что нового
## 2.1.1 - 2026-09-27
### Added
- **`shape: "depth"` — a mask leaf driven by the scene distance.** Coverage is 0.5 exactly at the
leaf's `distance` and falls to 0 at `distance + softness/2` (the leaf's own `softness` is the
falloff width, so there is one knob, not two), and 1 nearer than that. Bind a post effect to it and
you get depth-of-field masking: sharp up close, blurred past the focus distance, with the sky on the
uncovered side unless you compose it away. `invert` flips it, and a `difference` of two depth leaves
is a band around one distance. It has no geometry of its own and needs no per-frame driver, so it is
the camera-relative alternative to a sphere leaf bound to the player. See
[masks](guide/datapack/masks.md).
- **`"occlusion": false` on an aura leaf — a fill that ignores the depth buffer.** Translucent
surfaces (stained glass, ice, water) write depth, so a wall of aura behind a glass window was
correctly-but-unhelpfully treated as occluded and the fill disappeared. The flag turns the occlusion
term off, so the volume shows through glass. It is an aura-only leaf field, defaults to the current
behaviour, and costs no extra sampling; being all-or-nothing it also draws the volume over nearer
terrain and over anything else in front of it. See [masks](guide/datapack/masks.md).
- **`"occlusion_softness"` on an aura leaf — the occlusion width as its own knob.** The occlusion ramp
used to be denominated in the leaf's own `softness`, so a deliberately soft silhouette also decided
how softly the fill was hidden by terrain, and a blocky hillside (a 1-block staircase) turned into a
stack of ~45 bands along the silhouette. The new field is that width in world blocks: 0.5 where the
volume's entry sits on the surface, falling to 0 or 1 over the given distance. Absent, the ramp keeps
following `softness` exactly as before, animation included, so no existing mask changes. A sub-block
value gives a crisp silhouette that follows the terrain instead of a 45-band fade.
See [masks](guide/datapack/masks.md).
- **`order` - the compositing order of screen effects inside a layer.** Screen effects accept an
`order` param next to `screen_layer`: a float, lower runs earlier, default `0`, and effects that
leave it alone keep the order they were started in, so no existing pack changes. `screen_layer` stays
the outer, semantic axis (which part of the frame the pass belongs to) and `order` only sequences the
effects that layer already selected; being fractional, `10.5` slots an effect between two at `10` and
`11`. It is an ordinary param, so the datapack, the command param-map, `setParam` and keyframes all
reach it, and the wire format, UBO and `PROTOCOL_VERSION` are untouched. See
[screen effects](guide/effects/index.md).
- **A plugin can declare its field's gradient bound.** `VFXAPI.registerMaskShapeGlsl(id, plugin, lipschitz)`
is a new overload: the plugin field must be a conservative distance (`|grad d| <= 1`), and a field that
is perturbed (noise, a biased blend) can no longer stay silent about it — either scale the value down
or declare a true upper bound, which the aura march and the envelope then respect. A variant of
several plugins compiles against the largest declared bound. The mask guide now states the rule next
to the plugin contract. Additive: an existing registration compiles exactly as before.
See [masks](guide/datapack/masks.md) and [the API](API.md#vfxapi).
### Fixed
- **`volume: "aura"` on a GLSL-plugin mask leaf: a smooth soft edge instead of a banded ramp with a hard
cliff.** A wide, soft plugin volume rendered its silhouette in visible steps and dropped from ~50%
coverage straight to zero within a fraction of a degree. The plugin aura estimated the ray's deep
point as the *sampled* minimum of its march, so the edge resolved into sample-sized steps, and any
entered ray scored at least 0.5 while a miss scored exactly zero — a built-in ~0.5 jump at the
silhouette. Both are gone by construction: the deep point is now a Lipschitz **cone-envelope** of the
samples already taken (continuous in screen space, exact for creased `max`/`min` fields, never thinner
than the truth), a ray that misses the volume fades over the leaf's own `softness` instead of
vanishing, and the entry distance is bracketed by false-position, which also dequantises the
occlusion ramp. The edge lands in the same place, the interior still saturates to full coverage, and
the cost is unchanged - no extra SDF evaluations. Standing **inside** the volume keeps filling the
view, growing towards full coverage with depth. See
[masks](guide/datapack/masks.md).
- **The aura march grades a ray by its deepest penetration, not its first exit.** Entry and exit were
two marches and the second stopped at the first `d > 0`, so on a wall with a bump the solid mass
*behind* the bump was never sampled and the silhouette reported the bump's shallow depth - the
coverage dipped across it (the reported arch at the bottom of a wall). One pooled loop over the same
40 + 16 steps now runs to a real termination: the cone bound stays global over the whole ray (so the
mass behind a bump is what gets measured), the first entry still drives the occlusion ramp and the
last exit brackets the horizon fade. Saturation stays a sample certificate and budget exhaustion
deliberately does *not* saturate - a long shallow corridor would light up. An additive anti-crawl step
keeps a grazing approach or the gap behind a bump from eating the budget; it is bounded, which is why
a ray that never enters can now reach at most 0.6 rather than 0.5 of coverage. Measured: the bump
profile goes from `1.00 ×9, 0.58 0.70 0.80 0.85 0.80 0.70 0.58, 1.00 ×9` to a flat `1.00`, the
fixture's flat wings drop from 22.0 to 8.9 SDF calls per ray, and a receding ray terminates in 5
instead of burning all 40. See [masks](guide/datapack/masks.md).
- **The occlusion ramp is kept at least 48 pixels wide on screen.** A depth-driven edge inherits the
depth buffer's granularity: blocky terrain steps by a block at every block edge, and at a grazing
view of the ground those steps land less than a pixel apart, so the mask's silhouette aliased into a
comb. The ramp is widened with the per-pixel depth slope wherever the depth moves fast, capped at 4x
the authored width so a depth discontinuity (a cliff edge) cannot smear the edge; the authored
`occlusion_softness` stays the floor, so a near face-on occluder is unchanged. Measured at 2.8 blocks
per pixel: the ramp runs 44.8 → 134.4 blocks and the coverage step per terrain block edge falls from
0.0223 to 0.0074 - below what an 8-bit target can show. No uniform, no UBO/wire change, no new knob.
See [masks](guide/datapack/masks.md).
release
VFXWeaver API 2.1.1 (26.1.x, NeoForge)
2.1.127 сент. 2026 г.18Minecraft: 26.1, 26.1.1, 26.1.2neoforge
Что нового
## 2.1.1 - 2026-09-27
### Added
- **`shape: "depth"` — a mask leaf driven by the scene distance.** Coverage is 0.5 exactly at the
leaf's `distance` and falls to 0 at `distance + softness/2` (the leaf's own `softness` is the
falloff width, so there is one knob, not two), and 1 nearer than that. Bind a post effect to it and
you get depth-of-field masking: sharp up close, blurred past the focus distance, with the sky on the
uncovered side unless you compose it away. `invert` flips it, and a `difference` of two depth leaves
is a band around one distance. It has no geometry of its own and needs no per-frame driver, so it is
the camera-relative alternative to a sphere leaf bound to the player. See
[masks](guide/datapack/masks.md).
- **`"occlusion": false` on an aura leaf — a fill that ignores the depth buffer.** Translucent
surfaces (stained glass, ice, water) write depth, so a wall of aura behind a glass window was
correctly-but-unhelpfully treated as occluded and the fill disappeared. The flag turns the occlusion
term off, so the volume shows through glass. It is an aura-only leaf field, defaults to the current
behaviour, and costs no extra sampling; being all-or-nothing it also draws the volume over nearer
terrain and over anything else in front of it. See [masks](guide/datapack/masks.md).
- **`"occlusion_softness"` on an aura leaf — the occlusion width as its own knob.** The occlusion ramp
used to be denominated in the leaf's own `softness`, so a deliberately soft silhouette also decided
how softly the fill was hidden by terrain, and a blocky hillside (a 1-block staircase) turned into a
stack of ~45 bands along the silhouette. The new field is that width in world blocks: 0.5 where the
volume's entry sits on the surface, falling to 0 or 1 over the given distance. Absent, the ramp keeps
following `softness` exactly as before, animation included, so no existing mask changes. A sub-block
value gives a crisp silhouette that follows the terrain instead of a 45-band fade.
See [masks](guide/datapack/masks.md).
- **`order` - the compositing order of screen effects inside a layer.** Screen effects accept an
`order` param next to `screen_layer`: a float, lower runs earlier, default `0`, and effects that
leave it alone keep the order they were started in, so no existing pack changes. `screen_layer` stays
the outer, semantic axis (which part of the frame the pass belongs to) and `order` only sequences the
effects that layer already selected; being fractional, `10.5` slots an effect between two at `10` and
`11`. It is an ordinary param, so the datapack, the command param-map, `setParam` and keyframes all
reach it, and the wire format, UBO and `PROTOCOL_VERSION` are untouched. See
[screen effects](guide/effects/index.md).
- **A plugin can declare its field's gradient bound.** `VFXAPI.registerMaskShapeGlsl(id, plugin, lipschitz)`
is a new overload: the plugin field must be a conservative distance (`|grad d| <= 1`), and a field that
is perturbed (noise, a biased blend) can no longer stay silent about it — either scale the value down
or declare a true upper bound, which the aura march and the envelope then respect. A variant of
several plugins compiles against the largest declared bound. The mask guide now states the rule next
to the plugin contract. Additive: an existing registration compiles exactly as before.
See [masks](guide/datapack/masks.md) and [the API](API.md#vfxapi).
### Fixed
- **`volume: "aura"` on a GLSL-plugin mask leaf: a smooth soft edge instead of a banded ramp with a hard
cliff.** A wide, soft plugin volume rendered its silhouette in visible steps and dropped from ~50%
coverage straight to zero within a fraction of a degree. The plugin aura estimated the ray's deep
point as the *sampled* minimum of its march, so the edge resolved into sample-sized steps, and any
entered ray scored at least 0.5 while a miss scored exactly zero — a built-in ~0.5 jump at the
silhouette. Both are gone by construction: the deep point is now a Lipschitz **cone-envelope** of the
samples already taken (continuous in screen space, exact for creased `max`/`min` fields, never thinner
than the truth), a ray that misses the volume fades over the leaf's own `softness` instead of
vanishing, and the entry distance is bracketed by false-position, which also dequantises the
occlusion ramp. The edge lands in the same place, the interior still saturates to full coverage, and
the cost is unchanged - no extra SDF evaluations. Standing **inside** the volume keeps filling the
view, growing towards full coverage with depth. See
[masks](guide/datapack/masks.md).
- **The aura march grades a ray by its deepest penetration, not its first exit.** Entry and exit were
two marches and the second stopped at the first `d > 0`, so on a wall with a bump the solid mass
*behind* the bump was never sampled and the silhouette reported the bump's shallow depth - the
coverage dipped across it (the reported arch at the bottom of a wall). One pooled loop over the same
40 + 16 steps now runs to a real termination: the cone bound stays global over the whole ray (so the
mass behind a bump is what gets measured), the first entry still drives the occlusion ramp and the
last exit brackets the horizon fade. Saturation stays a sample certificate and budget exhaustion
deliberately does *not* saturate - a long shallow corridor would light up. An additive anti-crawl step
keeps a grazing approach or the gap behind a bump from eating the budget; it is bounded, which is why
a ray that never enters can now reach at most 0.6 rather than 0.5 of coverage. Measured: the bump
profile goes from `1.00 ×9, 0.58 0.70 0.80 0.85 0.80 0.70 0.58, 1.00 ×9` to a flat `1.00`, the
fixture's flat wings drop from 22.0 to 8.9 SDF calls per ray, and a receding ray terminates in 5
instead of burning all 40. See [masks](guide/datapack/masks.md).
- **The occlusion ramp is kept at least 48 pixels wide on screen.** A depth-driven edge inherits the
depth buffer's granularity: blocky terrain steps by a block at every block edge, and at a grazing
view of the ground those steps land less than a pixel apart, so the mask's silhouette aliased into a
comb. The ramp is widened with the per-pixel depth slope wherever the depth moves fast, capped at 4x
the authored width so a depth discontinuity (a cliff edge) cannot smear the edge; the authored
`occlusion_softness` stays the floor, so a near face-on occluder is unchanged. Measured at 2.8 blocks
per pixel: the ramp runs 44.8 → 134.4 blocks and the coverage step per terrain block edge falls from
0.0223 to 0.0074 - below what an 8-bit target can show. No uniform, no UBO/wire change, no new knob.
See [masks](guide/datapack/masks.md).
release
VFXWeaver API 2.1.1 (26.2, NeoForge)
2.1.127 сент. 2026 г.11Minecraft: 26.2neoforge
Что нового
## 2.1.1 - 2026-09-27
### Added
- **`shape: "depth"` — a mask leaf driven by the scene distance.** Coverage is 0.5 exactly at the
leaf's `distance` and falls to 0 at `distance + softness/2` (the leaf's own `softness` is the
falloff width, so there is one knob, not two), and 1 nearer than that. Bind a post effect to it and
you get depth-of-field masking: sharp up close, blurred past the focus distance, with the sky on the
uncovered side unless you compose it away. `invert` flips it, and a `difference` of two depth leaves
is a band around one distance. It has no geometry of its own and needs no per-frame driver, so it is
the camera-relative alternative to a sphere leaf bound to the player. See
[masks](guide/datapack/masks.md).
- **`"occlusion": false` on an aura leaf — a fill that ignores the depth buffer.** Translucent
surfaces (stained glass, ice, water) write depth, so a wall of aura behind a glass window was
correctly-but-unhelpfully treated as occluded and the fill disappeared. The flag turns the occlusion
term off, so the volume shows through glass. It is an aura-only leaf field, defaults to the current
behaviour, and costs no extra sampling; being all-or-nothing it also draws the volume over nearer
terrain and over anything else in front of it. See [masks](guide/datapack/masks.md).
- **`"occlusion_softness"` on an aura leaf — the occlusion width as its own knob.** The occlusion ramp
used to be denominated in the leaf's own `softness`, so a deliberately soft silhouette also decided
how softly the fill was hidden by terrain, and a blocky hillside (a 1-block staircase) turned into a
stack of ~45 bands along the silhouette. The new field is that width in world blocks: 0.5 where the
volume's entry sits on the surface, falling to 0 or 1 over the given distance. Absent, the ramp keeps
following `softness` exactly as before, animation included, so no existing mask changes. A sub-block
value gives a crisp silhouette that follows the terrain instead of a 45-band fade.
See [masks](guide/datapack/masks.md).
- **`order` - the compositing order of screen effects inside a layer.** Screen effects accept an
`order` param next to `screen_layer`: a float, lower runs earlier, default `0`, and effects that
leave it alone keep the order they were started in, so no existing pack changes. `screen_layer` stays
the outer, semantic axis (which part of the frame the pass belongs to) and `order` only sequences the
effects that layer already selected; being fractional, `10.5` slots an effect between two at `10` and
`11`. It is an ordinary param, so the datapack, the command param-map, `setParam` and keyframes all
reach it, and the wire format, UBO and `PROTOCOL_VERSION` are untouched. See
[screen effects](guide/effects/index.md).
- **A plugin can declare its field's gradient bound.** `VFXAPI.registerMaskShapeGlsl(id, plugin, lipschitz)`
is a new overload: the plugin field must be a conservative distance (`|grad d| <= 1`), and a field that
is perturbed (noise, a biased blend) can no longer stay silent about it — either scale the value down
or declare a true upper bound, which the aura march and the envelope then respect. A variant of
several plugins compiles against the largest declared bound. The mask guide now states the rule next
to the plugin contract. Additive: an existing registration compiles exactly as before.
See [masks](guide/datapack/masks.md) and [the API](API.md#vfxapi).
### Fixed
- **`volume: "aura"` on a GLSL-plugin mask leaf: a smooth soft edge instead of a banded ramp with a hard
cliff.** A wide, soft plugin volume rendered its silhouette in visible steps and dropped from ~50%
coverage straight to zero within a fraction of a degree. The plugin aura estimated the ray's deep
point as the *sampled* minimum of its march, so the edge resolved into sample-sized steps, and any
entered ray scored at least 0.5 while a miss scored exactly zero — a built-in ~0.5 jump at the
silhouette. Both are gone by construction: the deep point is now a Lipschitz **cone-envelope** of the
samples already taken (continuous in screen space, exact for creased `max`/`min` fields, never thinner
than the truth), a ray that misses the volume fades over the leaf's own `softness` instead of
vanishing, and the entry distance is bracketed by false-position, which also dequantises the
occlusion ramp. The edge lands in the same place, the interior still saturates to full coverage, and
the cost is unchanged - no extra SDF evaluations. Standing **inside** the volume keeps filling the
view, growing towards full coverage with depth. See
[masks](guide/datapack/masks.md).
- **The aura march grades a ray by its deepest penetration, not its first exit.** Entry and exit were
two marches and the second stopped at the first `d > 0`, so on a wall with a bump the solid mass
*behind* the bump was never sampled and the silhouette reported the bump's shallow depth - the
coverage dipped across it (the reported arch at the bottom of a wall). One pooled loop over the same
40 + 16 steps now runs to a real termination: the cone bound stays global over the whole ray (so the
mass behind a bump is what gets measured), the first entry still drives the occlusion ramp and the
last exit brackets the horizon fade. Saturation stays a sample certificate and budget exhaustion
deliberately does *not* saturate - a long shallow corridor would light up. An additive anti-crawl step
keeps a grazing approach or the gap behind a bump from eating the budget; it is bounded, which is why
a ray that never enters can now reach at most 0.6 rather than 0.5 of coverage. Measured: the bump
profile goes from `1.00 ×9, 0.58 0.70 0.80 0.85 0.80 0.70 0.58, 1.00 ×9` to a flat `1.00`, the
fixture's flat wings drop from 22.0 to 8.9 SDF calls per ray, and a receding ray terminates in 5
instead of burning all 40. See [masks](guide/datapack/masks.md).
- **The occlusion ramp is kept at least 48 pixels wide on screen.** A depth-driven edge inherits the
depth buffer's granularity: blocky terrain steps by a block at every block edge, and at a grazing
view of the ground those steps land less than a pixel apart, so the mask's silhouette aliased into a
comb. The ramp is widened with the per-pixel depth slope wherever the depth moves fast, capped at 4x
the authored width so a depth discontinuity (a cliff edge) cannot smear the edge; the authored
`occlusion_softness` stays the floor, so a near face-on occluder is unchanged. Measured at 2.8 blocks
per pixel: the ramp runs 44.8 → 134.4 blocks and the coverage step per terrain block edge falls from
0.0223 to 0.0074 - below what an 8-bit target can show. No uniform, no UBO/wire change, no new knob.
See [masks](guide/datapack/masks.md).
release
VFXWeaver API 2.1.1 (1.21.11)
2.1.127 сент. 2026 г.22Minecraft: 1.21.11fabric
Что нового
## 2.1.1 - 2026-09-27
### Added
- **`shape: "depth"` — a mask leaf driven by the scene distance.** Coverage is 0.5 exactly at the
leaf's `distance` and falls to 0 at `distance + softness/2` (the leaf's own `softness` is the
falloff width, so there is one knob, not two), and 1 nearer than that. Bind a post effect to it and
you get depth-of-field masking: sharp up close, blurred past the focus distance, with the sky on the
uncovered side unless you compose it away. `invert` flips it, and a `difference` of two depth leaves
is a band around one distance. It has no geometry of its own and needs no per-frame driver, so it is
the camera-relative alternative to a sphere leaf bound to the player. See
[masks](guide/datapack/masks.md).
- **`"occlusion": false` on an aura leaf — a fill that ignores the depth buffer.** Translucent
surfaces (stained glass, ice, water) write depth, so a wall of aura behind a glass window was
correctly-but-unhelpfully treated as occluded and the fill disappeared. The flag turns the occlusion
term off, so the volume shows through glass. It is an aura-only leaf field, defaults to the current
behaviour, and costs no extra sampling; being all-or-nothing it also draws the volume over nearer
terrain and over anything else in front of it. See [masks](guide/datapack/masks.md).
- **`"occlusion_softness"` on an aura leaf — the occlusion width as its own knob.** The occlusion ramp
used to be denominated in the leaf's own `softness`, so a deliberately soft silhouette also decided
how softly the fill was hidden by terrain, and a blocky hillside (a 1-block staircase) turned into a
stack of ~45 bands along the silhouette. The new field is that width in world blocks: 0.5 where the
volume's entry sits on the surface, falling to 0 or 1 over the given distance. Absent, the ramp keeps
following `softness` exactly as before, animation included, so no existing mask changes. A sub-block
value gives a crisp silhouette that follows the terrain instead of a 45-band fade.
See [masks](guide/datapack/masks.md).
- **`order` - the compositing order of screen effects inside a layer.** Screen effects accept an
`order` param next to `screen_layer`: a float, lower runs earlier, default `0`, and effects that
leave it alone keep the order they were started in, so no existing pack changes. `screen_layer` stays
the outer, semantic axis (which part of the frame the pass belongs to) and `order` only sequences the
effects that layer already selected; being fractional, `10.5` slots an effect between two at `10` and
`11`. It is an ordinary param, so the datapack, the command param-map, `setParam` and keyframes all
reach it, and the wire format, UBO and `PROTOCOL_VERSION` are untouched. See
[screen effects](guide/effects/index.md).
- **A plugin can declare its field's gradient bound.** `VFXAPI.registerMaskShapeGlsl(id, plugin, lipschitz)`
is a new overload: the plugin field must be a conservative distance (`|grad d| <= 1`), and a field that
is perturbed (noise, a biased blend) can no longer stay silent about it — either scale the value down
or declare a true upper bound, which the aura march and the envelope then respect. A variant of
several plugins compiles against the largest declared bound. The mask guide now states the rule next
to the plugin contract. Additive: an existing registration compiles exactly as before.
See [masks](guide/datapack/masks.md) and [the API](API.md#vfxapi).
### Fixed
- **`volume: "aura"` on a GLSL-plugin mask leaf: a smooth soft edge instead of a banded ramp with a hard
cliff.** A wide, soft plugin volume rendered its silhouette in visible steps and dropped from ~50%
coverage straight to zero within a fraction of a degree. The plugin aura estimated the ray's deep
point as the *sampled* minimum of its march, so the edge resolved into sample-sized steps, and any
entered ray scored at least 0.5 while a miss scored exactly zero — a built-in ~0.5 jump at the
silhouette. Both are gone by construction: the deep point is now a Lipschitz **cone-envelope** of the
samples already taken (continuous in screen space, exact for creased `max`/`min` fields, never thinner
than the truth), a ray that misses the volume fades over the leaf's own `softness` instead of
vanishing, and the entry distance is bracketed by false-position, which also dequantises the
occlusion ramp. The edge lands in the same place, the interior still saturates to full coverage, and
the cost is unchanged - no extra SDF evaluations. Standing **inside** the volume keeps filling the
view, growing towards full coverage with depth. See
[masks](guide/datapack/masks.md).
- **The aura march grades a ray by its deepest penetration, not its first exit.** Entry and exit were
two marches and the second stopped at the first `d > 0`, so on a wall with a bump the solid mass
*behind* the bump was never sampled and the silhouette reported the bump's shallow depth - the
coverage dipped across it (the reported arch at the bottom of a wall). One pooled loop over the same
40 + 16 steps now runs to a real termination: the cone bound stays global over the whole ray (so the
mass behind a bump is what gets measured), the first entry still drives the occlusion ramp and the
last exit brackets the horizon fade. Saturation stays a sample certificate and budget exhaustion
deliberately does *not* saturate - a long shallow corridor would light up. An additive anti-crawl step
keeps a grazing approach or the gap behind a bump from eating the budget; it is bounded, which is why
a ray that never enters can now reach at most 0.6 rather than 0.5 of coverage. Measured: the bump
profile goes from `1.00 ×9, 0.58 0.70 0.80 0.85 0.80 0.70 0.58, 1.00 ×9` to a flat `1.00`, the
fixture's flat wings drop from 22.0 to 8.9 SDF calls per ray, and a receding ray terminates in 5
instead of burning all 40. See [masks](guide/datapack/masks.md).
- **The occlusion ramp is kept at least 48 pixels wide on screen.** A depth-driven edge inherits the
depth buffer's granularity: blocky terrain steps by a block at every block edge, and at a grazing
view of the ground those steps land less than a pixel apart, so the mask's silhouette aliased into a
comb. The ramp is widened with the per-pixel depth slope wherever the depth moves fast, capped at 4x
the authored width so a depth discontinuity (a cliff edge) cannot smear the edge; the authored
`occlusion_softness` stays the floor, so a near face-on occluder is unchanged. Measured at 2.8 blocks
per pixel: the ramp runs 44.8 → 134.4 blocks and the coverage step per terrain block edge falls from
0.0223 to 0.0074 - below what an 8-bit target can show. No uniform, no UBO/wire change, no new knob.
See [masks](guide/datapack/masks.md).
release
VFXWeaver API 2.1.1 (26.1.x)
2.1.127 сент. 2026 г.32Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
## 2.1.1 - 2026-09-27
### Added
- **`shape: "depth"` — a mask leaf driven by the scene distance.** Coverage is 0.5 exactly at the
leaf's `distance` and falls to 0 at `distance + softness/2` (the leaf's own `softness` is the
falloff width, so there is one knob, not two), and 1 nearer than that. Bind a post effect to it and
you get depth-of-field masking: sharp up close, blurred past the focus distance, with the sky on the
uncovered side unless you compose it away. `invert` flips it, and a `difference` of two depth leaves
is a band around one distance. It has no geometry of its own and needs no per-frame driver, so it is
the camera-relative alternative to a sphere leaf bound to the player. See
[masks](guide/datapack/masks.md).
- **`"occlusion": false` on an aura leaf — a fill that ignores the depth buffer.** Translucent
surfaces (stained glass, ice, water) write depth, so a wall of aura behind a glass window was
correctly-but-unhelpfully treated as occluded and the fill disappeared. The flag turns the occlusion
term off, so the volume shows through glass. It is an aura-only leaf field, defaults to the current
behaviour, and costs no extra sampling; being all-or-nothing it also draws the volume over nearer
terrain and over anything else in front of it. See [masks](guide/datapack/masks.md).
- **`"occlusion_softness"` on an aura leaf — the occlusion width as its own knob.** The occlusion ramp
used to be denominated in the leaf's own `softness`, so a deliberately soft silhouette also decided
how softly the fill was hidden by terrain, and a blocky hillside (a 1-block staircase) turned into a
stack of ~45 bands along the silhouette. The new field is that width in world blocks: 0.5 where the
volume's entry sits on the surface, falling to 0 or 1 over the given distance. Absent, the ramp keeps
following `softness` exactly as before, animation included, so no existing mask changes. A sub-block
value gives a crisp silhouette that follows the terrain instead of a 45-band fade.
See [masks](guide/datapack/masks.md).
- **`order` - the compositing order of screen effects inside a layer.** Screen effects accept an
`order` param next to `screen_layer`: a float, lower runs earlier, default `0`, and effects that
leave it alone keep the order they were started in, so no existing pack changes. `screen_layer` stays
the outer, semantic axis (which part of the frame the pass belongs to) and `order` only sequences the
effects that layer already selected; being fractional, `10.5` slots an effect between two at `10` and
`11`. It is an ordinary param, so the datapack, the command param-map, `setParam` and keyframes all
reach it, and the wire format, UBO and `PROTOCOL_VERSION` are untouched. See
[screen effects](guide/effects/index.md).
- **A plugin can declare its field's gradient bound.** `VFXAPI.registerMaskShapeGlsl(id, plugin, lipschitz)`
is a new overload: the plugin field must be a conservative distance (`|grad d| <= 1`), and a field that
is perturbed (noise, a biased blend) can no longer stay silent about it — either scale the value down
or declare a true upper bound, which the aura march and the envelope then respect. A variant of
several plugins compiles against the largest declared bound. The mask guide now states the rule next
to the plugin contract. Additive: an existing registration compiles exactly as before.
See [masks](guide/datapack/masks.md) and [the API](API.md#vfxapi).
### Fixed
- **`volume: "aura"` on a GLSL-plugin mask leaf: a smooth soft edge instead of a banded ramp with a hard
cliff.** A wide, soft plugin volume rendered its silhouette in visible steps and dropped from ~50%
coverage straight to zero within a fraction of a degree. The plugin aura estimated the ray's deep
point as the *sampled* minimum of its march, so the edge resolved into sample-sized steps, and any
entered ray scored at least 0.5 while a miss scored exactly zero — a built-in ~0.5 jump at the
silhouette. Both are gone by construction: the deep point is now a Lipschitz **cone-envelope** of the
samples already taken (continuous in screen space, exact for creased `max`/`min` fields, never thinner
than the truth), a ray that misses the volume fades over the leaf's own `softness` instead of
vanishing, and the entry distance is bracketed by false-position, which also dequantises the
occlusion ramp. The edge lands in the same place, the interior still saturates to full coverage, and
the cost is unchanged - no extra SDF evaluations. Standing **inside** the volume keeps filling the
view, growing towards full coverage with depth. See
[masks](guide/datapack/masks.md).
- **The aura march grades a ray by its deepest penetration, not its first exit.** Entry and exit were
two marches and the second stopped at the first `d > 0`, so on a wall with a bump the solid mass
*behind* the bump was never sampled and the silhouette reported the bump's shallow depth - the
coverage dipped across it (the reported arch at the bottom of a wall). One pooled loop over the same
40 + 16 steps now runs to a real termination: the cone bound stays global over the whole ray (so the
mass behind a bump is what gets measured), the first entry still drives the occlusion ramp and the
last exit brackets the horizon fade. Saturation stays a sample certificate and budget exhaustion
deliberately does *not* saturate - a long shallow corridor would light up. An additive anti-crawl step
keeps a grazing approach or the gap behind a bump from eating the budget; it is bounded, which is why
a ray that never enters can now reach at most 0.6 rather than 0.5 of coverage. Measured: the bump
profile goes from `1.00 ×9, 0.58 0.70 0.80 0.85 0.80 0.70 0.58, 1.00 ×9` to a flat `1.00`, the
fixture's flat wings drop from 22.0 to 8.9 SDF calls per ray, and a receding ray terminates in 5
instead of burning all 40. See [masks](guide/datapack/masks.md).
- **The occlusion ramp is kept at least 48 pixels wide on screen.** A depth-driven edge inherits the
depth buffer's granularity: blocky terrain steps by a block at every block edge, and at a grazing
view of the ground those steps land less than a pixel apart, so the mask's silhouette aliased into a
comb. The ramp is widened with the per-pixel depth slope wherever the depth moves fast, capped at 4x
the authored width so a depth discontinuity (a cliff edge) cannot smear the edge; the authored
`occlusion_softness` stays the floor, so a near face-on occluder is unchanged. Measured at 2.8 blocks
per pixel: the ramp runs 44.8 → 134.4 blocks and the coverage step per terrain block edge falls from
0.0223 to 0.0074 - below what an 8-bit target can show. No uniform, no UBO/wire change, no new knob.
See [masks](guide/datapack/masks.md).
release
VFXWeaver API 2.1.1 (26.2)
2.1.127 сент. 2026 г.34Minecraft: 26.2fabric
Что нового
## 2.1.1 - 2026-09-27
### Added
- **`shape: "depth"` — a mask leaf driven by the scene distance.** Coverage is 0.5 exactly at the
leaf's `distance` and falls to 0 at `distance + softness/2` (the leaf's own `softness` is the
falloff width, so there is one knob, not two), and 1 nearer than that. Bind a post effect to it and
you get depth-of-field masking: sharp up close, blurred past the focus distance, with the sky on the
uncovered side unless you compose it away. `invert` flips it, and a `difference` of two depth leaves
is a band around one distance. It has no geometry of its own and needs no per-frame driver, so it is
the camera-relative alternative to a sphere leaf bound to the player. See
[masks](guide/datapack/masks.md).
- **`"occlusion": false` on an aura leaf — a fill that ignores the depth buffer.** Translucent
surfaces (stained glass, ice, water) write depth, so a wall of aura behind a glass window was
correctly-but-unhelpfully treated as occluded and the fill disappeared. The flag turns the occlusion
term off, so the volume shows through glass. It is an aura-only leaf field, defaults to the current
behaviour, and costs no extra sampling; being all-or-nothing it also draws the volume over nearer
terrain and over anything else in front of it. See [masks](guide/datapack/masks.md).
- **`"occlusion_softness"` on an aura leaf — the occlusion width as its own knob.** The occlusion ramp
used to be denominated in the leaf's own `softness`, so a deliberately soft silhouette also decided
how softly the fill was hidden by terrain, and a blocky hillside (a 1-block staircase) turned into a
stack of ~45 bands along the silhouette. The new field is that width in world blocks: 0.5 where the
volume's entry sits on the surface, falling to 0 or 1 over the given distance. Absent, the ramp keeps
following `softness` exactly as before, animation included, so no existing mask changes. A sub-block
value gives a crisp silhouette that follows the terrain instead of a 45-band fade.
See [masks](guide/datapack/masks.md).
- **`order` - the compositing order of screen effects inside a layer.** Screen effects accept an
`order` param next to `screen_layer`: a float, lower runs earlier, default `0`, and effects that
leave it alone keep the order they were started in, so no existing pack changes. `screen_layer` stays
the outer, semantic axis (which part of the frame the pass belongs to) and `order` only sequences the
effects that layer already selected; being fractional, `10.5` slots an effect between two at `10` and
`11`. It is an ordinary param, so the datapack, the command param-map, `setParam` and keyframes all
reach it, and the wire format, UBO and `PROTOCOL_VERSION` are untouched. See
[screen effects](guide/effects/index.md).
- **A plugin can declare its field's gradient bound.** `VFXAPI.registerMaskShapeGlsl(id, plugin, lipschitz)`
is a new overload: the plugin field must be a conservative distance (`|grad d| <= 1`), and a field that
is perturbed (noise, a biased blend) can no longer stay silent about it — either scale the value down
or declare a true upper bound, which the aura march and the envelope then respect. A variant of
several plugins compiles against the largest declared bound. The mask guide now states the rule next
to the plugin contract. Additive: an existing registration compiles exactly as before.
See [masks](guide/datapack/masks.md) and [the API](API.md#vfxapi).
### Fixed
- **`volume: "aura"` on a GLSL-plugin mask leaf: a smooth soft edge instead of a banded ramp with a hard
cliff.** A wide, soft plugin volume rendered its silhouette in visible steps and dropped from ~50%
coverage straight to zero within a fraction of a degree. The plugin aura estimated the ray's deep
point as the *sampled* minimum of its march, so the edge resolved into sample-sized steps, and any
entered ray scored at least 0.5 while a miss scored exactly zero — a built-in ~0.5 jump at the
silhouette. Both are gone by construction: the deep point is now a Lipschitz **cone-envelope** of the
samples already taken (continuous in screen space, exact for creased `max`/`min` fields, never thinner
than the truth), a ray that misses the volume fades over the leaf's own `softness` instead of
vanishing, and the entry distance is bracketed by false-position, which also dequantises the
occlusion ramp. The edge lands in the same place, the interior still saturates to full coverage, and
the cost is unchanged - no extra SDF evaluations. Standing **inside** the volume keeps filling the
view, growing towards full coverage with depth. See
[masks](guide/datapack/masks.md).
- **The aura march grades a ray by its deepest penetration, not its first exit.** Entry and exit were
two marches and the second stopped at the first `d > 0`, so on a wall with a bump the solid mass
*behind* the bump was never sampled and the silhouette reported the bump's shallow depth - the
coverage dipped across it (the reported arch at the bottom of a wall). One pooled loop over the same
40 + 16 steps now runs to a real termination: the cone bound stays global over the whole ray (so the
mass behind a bump is what gets measured), the first entry still drives the occlusion ramp and the
last exit brackets the horizon fade. Saturation stays a sample certificate and budget exhaustion
deliberately does *not* saturate - a long shallow corridor would light up. An additive anti-crawl step
keeps a grazing approach or the gap behind a bump from eating the budget; it is bounded, which is why
a ray that never enters can now reach at most 0.6 rather than 0.5 of coverage. Measured: the bump
profile goes from `1.00 ×9, 0.58 0.70 0.80 0.85 0.80 0.70 0.58, 1.00 ×9` to a flat `1.00`, the
fixture's flat wings drop from 22.0 to 8.9 SDF calls per ray, and a receding ray terminates in 5
instead of burning all 40. See [masks](guide/datapack/masks.md).
- **The occlusion ramp is kept at least 48 pixels wide on screen.** A depth-driven edge inherits the
depth buffer's granularity: blocky terrain steps by a block at every block edge, and at a grazing
view of the ground those steps land less than a pixel apart, so the mask's silhouette aliased into a
comb. The ramp is widened with the per-pixel depth slope wherever the depth moves fast, capped at 4x
the authored width so a depth discontinuity (a cliff edge) cannot smear the edge; the authored
`occlusion_softness` stays the floor, so a near face-on occluder is unchanged. Measured at 2.8 blocks
per pixel: the ramp runs 44.8 → 134.4 blocks and the coverage step per terrain block edge falls from
0.0223 to 0.0074 - below what an 8-bit target can show. No uniform, no UBO/wire change, no new knob.
See [masks](guide/datapack/masks.md).
release
VFXWeaver API 2.1.0 (1.21.11, NeoForge)
2.1.025 сент. 2026 г.38Minecraft: 1.21.11neoforge
Что нового
## 2.1.0 — 2026-09-25
### Added
- **`film_grain` can be coloured (`chroma`).** A new `chroma` param (`0`, monochrome, default; `1`,
fully coloured) mixes an independent per-channel grain into the shared monochrome grain and stops
halfway, so at `1` the grain keeps its luminance structure instead of dissolving into plain colour
noise. Animated like any other param (`keyframes`, `expr`, `setParam`, `fade_ticks`). Additive: absent
= exactly the previous monochrome look, no datapack field renamed, no wire change. See
[film_grain](guide/effects/screen/film-grain.md).
- **`fog_modifier` — change the vanilla fog.** A new value-modifier effect (like `fov_modifier`, not
a post pass): `fog_start_scale`/`fog_end_scale` multiply the vanilla fog start/end distance
(neutral `1.0`; `>1` sees further, `<1` pulls the fog closer) and optional `fog_r`/`fog_g`/`fog_b`
(neutral "unset") override the fog colour. `fog_color_amount` (`0..1`, neutral `1`/unset) scales how
far the colour moves from vanilla — animating it ramps smoothly from the dimension's own fog colour
to the authored one, and `0` is a true no-op that never writes the colour. The mod rewrites the fog
at the **return of vanilla `FogRenderer.setupFog`**, mutating the instance the game produced in
place before anything consumes it: the same values then reach the fog uniforms (`FogEnvironmental`/
`FogRenderDistance` start+end, `FogSkyEnd`/`FogCloudsEnd` and `FogColor`), the level renderer and
the sky/cloud shaders, so vanilla terrain, entities, the **horizon band** and the entity-effect
geometry all see the change; absent params leave the fog bit-for-bit untouched. Scales combine
additively around neutral (`1 + Σ((s−1)·weight)`) and the colour is a weighted average blended from
vanilla (order-independent, clamped; each effect's colour weight is its `weight·fog_color_amount`),
so overlapping effects stack predictably. All six params are ordinary animatable params, so
`keyframes`, `expr`, graphs, `setParam` and `fade_ticks` work. The start/end scales apply to every
distance-based fog on all three nodes — overworld, **nether** (`fog_start`/`fog_end` distances) and
End, plus water/lava/snow/blindness/darkness, and `fog_end_scale` also scales the sky/cloud fade
ends; these versions have **no** density-fog field or attribute, so nothing is left out. **Honest
limits:** under an **Iris shaderpack** this effect is a **no-op** (a pack computes its own fog and
ignores the vanilla values); and the `block_tint`/`block_outline`/`glow`/spark overlays use a
fog-free shader, so only the entity-effect geometry inherits the change. No shader change, no UBO
change, no new dependency; `PROTOCOL_VERSION` unchanged. The mixin hooks the return of vanilla
`FogRenderer.setupFog` at `priority 900` (before Sodium's fog mixin) and mutates the returned
instance in place, so the fog uniforms, the level renderer and the sky/cloud shaders all see it.
**Not verified in game:** the visual result; the static contract — the per-node
`FogRenderer.setupFog` hook point (return of `setupFog`, mutated in place), the colour-amount
combination maths and the type/neutral/isPostProcessing wiring — is asserted by
`scripts/check-fog-modifier.ps1`.
- **`sky_pattern` — follow the sun, the moon or the stars (`anchor`).** The overnight sky body is a
new optional top-level `anchor` string: omitted (or `"dome"`) is the world-fixed
`anchor_yaw`/`anchor_pitch` authoring; `"sun"` and `"moon"` make the pattern track the vanilla
body; `"stars"` locks the pattern to the rotating star sphere (the shader inverts vanilla's star
pose exactly — see Fixed below). The body angles are read from the client's own sky render state
**on the CPU** and fed to the existing `anchor_yaw`/`anchor_pitch` uniforms (plus two internal
star fields), so there is **no mixin** — which is what keeps it working under an Iris shaderpack
(the world time, hence the angles, is still computed by the game). When the sky state is unavailable (no overworld sky, or the render
state is not ready) the effect **contributes nothing** instead of painting at a guessed spot, and
warns once. Additive: a new top-level field only, no existing effect changed, no datapack field
renamed, `PROTOCOL_VERSION` unchanged, and no UBO/Config change. Honest limits: the effect is an
**overlay**, so the vanilla sun/moon are still drawn underneath (size the pattern to cover them:
sun ≈33° apparent width, moon ≈24°), and because the vanilla star colour is not reached without a
`SkyRenderer` mixin, `"stars"` positions a pattern at the star sphere rather than recolouring the
vanilla stars. The rendering is not verified in game by the author.
- **`sky_pattern` — a datapack figure or texture drawn on the sky dome.** A new screen post effect
and the sky sibling of `surface_pattern`: the same structural `pattern` block, shared shape
library and `pattern.texture` addressing, but the pixel's view ray is projected onto the dome
instead of a depth-reconstructed world surface (the projection is chosen by `sky_mode` — see the
`sky_mode` entry below). The dome anchor is authored as
`anchor_yaw`/`anchor_pitch` (degrees) and the image can be spun about world Y with `dome_rotation`;
`repeat: [3, 3]` draws nine dots with no texture, and a `pattern.texture` with a `sheet` plus the
animatable `frame` param draws an animated sky. The pass is gated on the depth sky test, so it
never touches terrain, the hand or the GUI. Additive: a new effect type only, no existing effect
changed, no datapack field renamed, `PROTOCOL_VERSION` unchanged, and no existing shader's UBO
layout touched. The rendering is not verified in game by the author;
like every post effect it composes over an Iris shaderpack's sky.
- **`light_beam` can end on a chosen point.** Three optional params, `end_at_x`/`end_at_y`/`end_at_z`,
name a world point the **far end** of the beam lands on: the anchor is derived from it
(`end − axis × height`), so the geometry finishes exactly there — with the default axis the beam
hangs straight down from `height` above the point, which is how a beam comes out of the sky. They
are ordinary animatable params, so the landing point can be keyframed, driven by an expression or
bound, and a beam from A to B is `end_at = B` with the axis and length computed between the two
points. Unset, the beam behaves exactly as before. No shader, pipeline, UBO or wire change.
- **`light_beam` can point in any direction.** Three new params, `dir_x`/`dir_y`/`dir_z` (defaults
`0`/`1`/`0`), replace the fixed vertical axis, so a beam can be aimed at an angle — e.g. from a
point on the sky. They are ordinary animatable params, so they can be keyframed, driven by an
expression or bound, including to the camera look (`{"bind": "look_x"}` etc.). With the defaults the
beam renders exactly as before. No shader, pipeline, UBO or wire change.
- **Sky masks.** Two new mask leaves address the sky instead of the world: `sky` (the whole visible
sky, no parameters) and a 2D shape (`circle`/`ellipse`/`rect`/`polygon`) in the new
`space: "dome"`, which places it on an equirectangular map of the sky with the centre authored as
`[yaw, pitch]` degrees. A dome leaf is sky-occluded by construction — a hill or a wall occupying a
dome direction is never tinted — and both fail closed (zero coverage) when no trustworthy depth is
available. This is what lets any existing screen effect be restricted to the sky, e.g. a green or
glitching sky. Additive: no datapack field was renamed, the wire format and `PROTOCOL_VERSION` are
unchanged and the coverage UBO layout did not change. The far-depth test is verified in game on all
three lines (26.2, 26.1.2, 1.21.11). The sun, the moon and the clouds are drawn without depth, so a
`sky`/`dome` leaf covers them too (a green sky turns the sun green); the horizon fog is baked into
the terrain, so it is not recoloured and a strong tint can leave a seam there.
- **`volume: "aura"` on a world GLSL-plugin mask leaf.** A custom mask leaf registered with
`VFXAPI.registerMaskShapeGlsl` can now be a real volume instead of a surface-only shape: the pixel's
view ray is sphere-traced through the plugin's own distance field and the effect fills the volume —
air and sky included — wherever the scene does not occlude it, with the same silhouette, edge fade
and occlusion maths as a built-in `sphere`/`box` aura. A composed custom leaf (no raw SDF to march)
and a screen-space plugin leaf are per-file parse errors naming the reason rather than silent
fallbacks. An optional `vec4 vfx_shape_custom_bounds()` in the plugin source (world centre and
radius) acts as an analytic broad phase: a ray that misses it is rejected without evaluating the SDF,
which makes the average cost proportional to the volume's screen area instead of the whole frame. A
plugin that declares neither function compiles and behaves exactly as before; the wire format,
`PROTOCOL_VERSION` and the coverage UBO layout are unchanged.
### Changed
- **`sky_pattern` addresses the sky with `patch` or `fill` — the legacy equirectangular `dome` mode
was removed before release.** A single global chart of a sphere is topologically unable to tile it
cleanly: `u` is undefined at the poles (a whole-sky pattern winds into a funnel at the zenith) and
`u` must wrap at ±180° yaw (a mandatory visible seam / mirror axis) — properties of the chart, not
formula bugs, so no formula fixes them. The effect therefore offers only local-chart modes:
**`patch`** (default, `0`) is one gnomonic decal at the anchor (no pole, no seam, clean discard
past the decal horizon) and **`fill`** (`1`) covers the whole sphere with three orthographic charts
blended by a sharpened partition of unity (no pole convergence, no seam anywhere; the charts'
coverage and colour are blended, never their UVs). `"dome"` and any other unknown value are now a
per-file parse error naming the accepted values. `sky_mode` is appended last to the Config UBO, so
no existing shader's layout changes; no datapack field was renamed and `PROTOCOL_VERSION` is
unchanged. The effect is not in any release yet, so removing `dome` breaks nothing. A full skybox
**cube** remains a separate future feature.
- **A custom mask leaf in `surface` mode no longer tints the sky.** The surface path classifies only
what the depth buffer contains, so a sky pixel contributes no coverage. Built-in shapes already
behaved this way (their bounded distance field puts a far-plane point outside the shape); the gate is
now explicit for custom leaves, whose SDF may be intentionally unbounded. A plugin that relied on
painting the sky from a `surface` leaf will no longer do so.
- **A built-in world GLSL mask plugin, `vfxweaver:blobs_glsl`.** The only shipped GLSL plugin was
`vfxweaver:ringed_glsl`, a screen-space 2D figure, so `space: "world"` plugin leaves had no volume
to march and the dynamic-data demo could not show the data driving anything. `vfxweaver:blobs_glsl`
is a genuine world-space SDF — a union of up to eight spheres, negative inside — whose sphere
centres/radii come from the leaf's dynamic data (`mask.p<N>.d0..d31`, four floats per sphere) and
whose `p0..p7` are global controls (offset, radius scale/bias, count). It also declares the optional
`vfx_shape_custom_bounds()` broad phase over the same data+params extent, so `volume: "aura"` with
it is cheap. It works for both `surface` and `aura`; the three mask demos use it. The plugin leaf
shape is inert on the `1.21.11` node (no shader-source hook). Additive: `vfxweaver:ringed_glsl`
stays the screen reference, no existing pass's UBO/wire layout changes, `PROTOCOL_VERSION` unchanged.
See [masks](guide/datapack/masks.md).
### Fixed
- **`film_grain` noise no longer repeats in a fixed pattern.** The cells were hashed with a
`fract(sin(dot(...)))` hash, which is only well distributed for large inputs: on the small integers a
pixel cell produces it repeats in a visible lattice, so the frame read as one small tile stamped over
and over. Cells are now hashed with an integer bit-mix, and the per-frame offset is an integer tick
mix rather than a `fract()`-scaled float.
- **`film_grain` and `pixelate` cell grids grow from the centre, not the corner.** Both derived their
grid straight from the UV, so an animated cell size expanded the pattern out of the bottom-left
corner; the grid is now measured from the screen centre. Effects whose pattern is per-scanline
(`scanlines`, `vhs`, `digital_glitch`) are deliberately unchanged — those bands run from the top edge
and have no centre to grow from.
- **`sky_pattern` `anchor: "stars"` really tracks the star field.** The anchor previously drove
`dome_rotation`, a world-**Y** spin, while vanilla rotates the star sphere about a different axis
(its pose is `Ry(-90°) · Rx(starAngle)`), so a `stars` pattern was only an approximation and turned
about the wrong axis. The sky shader now takes the sampled direction into the star sphere's own
frame with the exact inverse, so the pattern is locked to the star material. `dome_rotation` is not
hijacked — it stays the author's world-Y spin, applied first. Two internal Config fields are
appended after `sky_mode`; no datapack field was renamed and `PROTOCOL_VERSION` is unchanged. See
[sky_pattern](guide/effects/screen/sky-pattern.md).
- **`sky_pattern` `patch` decals have no built-in clip, and the ring demo no longer circles the
player.** A `patch` is a flat sign on the tangent plane: it is evaluated out to the tangent-plane
horizon, where a pixel behind the decal (`dot(dir, anchor) <= 0`) is discarded - no wrapping, no
pole convergence and no built-in clip. A large patch can therefore show that horizon as a straight
cut; restrict the effect with a mask if you need a clean edge. (An earlier unit-disc bound was
removed: it visibly cut the figure.) The demos use each projection for what it is for:
`show_sky_pattern_texture` (a whole-sky texture) and `sky_cracks` are `fill`, while
`show_sky_pattern` (the ring) and `nine_red_pixels` are small `patch` decals. The ring demo no
longer animates `dome_rotation` - in `patch` mode that moves the decal along its latitude, around
the sky, so it now animates only `tile_scale` and `opacity` and stays at its spot. The effect is
not in any release yet; no datapack field was renamed and `PROTOCOL_VERSION` is unchanged.
release
VFXWeaver API 2.1.0 (26.1.x, NeoForge)
2.1.025 сент. 2026 г.17Minecraft: 26.1, 26.1.1, 26.1.2neoforge
Что нового
## 2.1.0 — 2026-09-25
### Added
- **`film_grain` can be coloured (`chroma`).** A new `chroma` param (`0`, monochrome, default; `1`,
fully coloured) mixes an independent per-channel grain into the shared monochrome grain and stops
halfway, so at `1` the grain keeps its luminance structure instead of dissolving into plain colour
noise. Animated like any other param (`keyframes`, `expr`, `setParam`, `fade_ticks`). Additive: absent
= exactly the previous monochrome look, no datapack field renamed, no wire change. See
[film_grain](guide/effects/screen/film-grain.md).
- **`fog_modifier` — change the vanilla fog.** A new value-modifier effect (like `fov_modifier`, not
a post pass): `fog_start_scale`/`fog_end_scale` multiply the vanilla fog start/end distance
(neutral `1.0`; `>1` sees further, `<1` pulls the fog closer) and optional `fog_r`/`fog_g`/`fog_b`
(neutral "unset") override the fog colour. `fog_color_amount` (`0..1`, neutral `1`/unset) scales how
far the colour moves from vanilla — animating it ramps smoothly from the dimension's own fog colour
to the authored one, and `0` is a true no-op that never writes the colour. The mod rewrites the fog
at the **return of vanilla `FogRenderer.setupFog`**, mutating the instance the game produced in
place before anything consumes it: the same values then reach the fog uniforms (`FogEnvironmental`/
`FogRenderDistance` start+end, `FogSkyEnd`/`FogCloudsEnd` and `FogColor`), the level renderer and
the sky/cloud shaders, so vanilla terrain, entities, the **horizon band** and the entity-effect
geometry all see the change; absent params leave the fog bit-for-bit untouched. Scales combine
additively around neutral (`1 + Σ((s−1)·weight)`) and the colour is a weighted average blended from
vanilla (order-independent, clamped; each effect's colour weight is its `weight·fog_color_amount`),
so overlapping effects stack predictably. All six params are ordinary animatable params, so
`keyframes`, `expr`, graphs, `setParam` and `fade_ticks` work. The start/end scales apply to every
distance-based fog on all three nodes — overworld, **nether** (`fog_start`/`fog_end` distances) and
End, plus water/lava/snow/blindness/darkness, and `fog_end_scale` also scales the sky/cloud fade
ends; these versions have **no** density-fog field or attribute, so nothing is left out. **Honest
limits:** under an **Iris shaderpack** this effect is a **no-op** (a pack computes its own fog and
ignores the vanilla values); and the `block_tint`/`block_outline`/`glow`/spark overlays use a
fog-free shader, so only the entity-effect geometry inherits the change. No shader change, no UBO
change, no new dependency; `PROTOCOL_VERSION` unchanged. The mixin hooks the return of vanilla
`FogRenderer.setupFog` at `priority 900` (before Sodium's fog mixin) and mutates the returned
instance in place, so the fog uniforms, the level renderer and the sky/cloud shaders all see it.
**Not verified in game:** the visual result; the static contract — the per-node
`FogRenderer.setupFog` hook point (return of `setupFog`, mutated in place), the colour-amount
combination maths and the type/neutral/isPostProcessing wiring — is asserted by
`scripts/check-fog-modifier.ps1`.
- **`sky_pattern` — follow the sun, the moon or the stars (`anchor`).** The overnight sky body is a
new optional top-level `anchor` string: omitted (or `"dome"`) is the world-fixed
`anchor_yaw`/`anchor_pitch` authoring; `"sun"` and `"moon"` make the pattern track the vanilla
body; `"stars"` locks the pattern to the rotating star sphere (the shader inverts vanilla's star
pose exactly — see Fixed below). The body angles are read from the client's own sky render state
**on the CPU** and fed to the existing `anchor_yaw`/`anchor_pitch` uniforms (plus two internal
star fields), so there is **no mixin** — which is what keeps it working under an Iris shaderpack
(the world time, hence the angles, is still computed by the game). When the sky state is unavailable (no overworld sky, or the render
state is not ready) the effect **contributes nothing** instead of painting at a guessed spot, and
warns once. Additive: a new top-level field only, no existing effect changed, no datapack field
renamed, `PROTOCOL_VERSION` unchanged, and no UBO/Config change. Honest limits: the effect is an
**overlay**, so the vanilla sun/moon are still drawn underneath (size the pattern to cover them:
sun ≈33° apparent width, moon ≈24°), and because the vanilla star colour is not reached without a
`SkyRenderer` mixin, `"stars"` positions a pattern at the star sphere rather than recolouring the
vanilla stars. The rendering is not verified in game by the author.
- **`sky_pattern` — a datapack figure or texture drawn on the sky dome.** A new screen post effect
and the sky sibling of `surface_pattern`: the same structural `pattern` block, shared shape
library and `pattern.texture` addressing, but the pixel's view ray is projected onto the dome
instead of a depth-reconstructed world surface (the projection is chosen by `sky_mode` — see the
`sky_mode` entry below). The dome anchor is authored as
`anchor_yaw`/`anchor_pitch` (degrees) and the image can be spun about world Y with `dome_rotation`;
`repeat: [3, 3]` draws nine dots with no texture, and a `pattern.texture` with a `sheet` plus the
animatable `frame` param draws an animated sky. The pass is gated on the depth sky test, so it
never touches terrain, the hand or the GUI. Additive: a new effect type only, no existing effect
changed, no datapack field renamed, `PROTOCOL_VERSION` unchanged, and no existing shader's UBO
layout touched. The rendering is not verified in game by the author;
like every post effect it composes over an Iris shaderpack's sky.
- **`light_beam` can end on a chosen point.** Three optional params, `end_at_x`/`end_at_y`/`end_at_z`,
name a world point the **far end** of the beam lands on: the anchor is derived from it
(`end − axis × height`), so the geometry finishes exactly there — with the default axis the beam
hangs straight down from `height` above the point, which is how a beam comes out of the sky. They
are ordinary animatable params, so the landing point can be keyframed, driven by an expression or
bound, and a beam from A to B is `end_at = B` with the axis and length computed between the two
points. Unset, the beam behaves exactly as before. No shader, pipeline, UBO or wire change.
- **`light_beam` can point in any direction.** Three new params, `dir_x`/`dir_y`/`dir_z` (defaults
`0`/`1`/`0`), replace the fixed vertical axis, so a beam can be aimed at an angle — e.g. from a
point on the sky. They are ordinary animatable params, so they can be keyframed, driven by an
expression or bound, including to the camera look (`{"bind": "look_x"}` etc.). With the defaults the
beam renders exactly as before. No shader, pipeline, UBO or wire change.
- **Sky masks.** Two new mask leaves address the sky instead of the world: `sky` (the whole visible
sky, no parameters) and a 2D shape (`circle`/`ellipse`/`rect`/`polygon`) in the new
`space: "dome"`, which places it on an equirectangular map of the sky with the centre authored as
`[yaw, pitch]` degrees. A dome leaf is sky-occluded by construction — a hill or a wall occupying a
dome direction is never tinted — and both fail closed (zero coverage) when no trustworthy depth is
available. This is what lets any existing screen effect be restricted to the sky, e.g. a green or
glitching sky. Additive: no datapack field was renamed, the wire format and `PROTOCOL_VERSION` are
unchanged and the coverage UBO layout did not change. The far-depth test is verified in game on all
three lines (26.2, 26.1.2, 1.21.11). The sun, the moon and the clouds are drawn without depth, so a
`sky`/`dome` leaf covers them too (a green sky turns the sun green); the horizon fog is baked into
the terrain, so it is not recoloured and a strong tint can leave a seam there.
- **`volume: "aura"` on a world GLSL-plugin mask leaf.** A custom mask leaf registered with
`VFXAPI.registerMaskShapeGlsl` can now be a real volume instead of a surface-only shape: the pixel's
view ray is sphere-traced through the plugin's own distance field and the effect fills the volume —
air and sky included — wherever the scene does not occlude it, with the same silhouette, edge fade
and occlusion maths as a built-in `sphere`/`box` aura. A composed custom leaf (no raw SDF to march)
and a screen-space plugin leaf are per-file parse errors naming the reason rather than silent
fallbacks. An optional `vec4 vfx_shape_custom_bounds()` in the plugin source (world centre and
radius) acts as an analytic broad phase: a ray that misses it is rejected without evaluating the SDF,
which makes the average cost proportional to the volume's screen area instead of the whole frame. A
plugin that declares neither function compiles and behaves exactly as before; the wire format,
`PROTOCOL_VERSION` and the coverage UBO layout are unchanged.
### Changed
- **`sky_pattern` addresses the sky with `patch` or `fill` — the legacy equirectangular `dome` mode
was removed before release.** A single global chart of a sphere is topologically unable to tile it
cleanly: `u` is undefined at the poles (a whole-sky pattern winds into a funnel at the zenith) and
`u` must wrap at ±180° yaw (a mandatory visible seam / mirror axis) — properties of the chart, not
formula bugs, so no formula fixes them. The effect therefore offers only local-chart modes:
**`patch`** (default, `0`) is one gnomonic decal at the anchor (no pole, no seam, clean discard
past the decal horizon) and **`fill`** (`1`) covers the whole sphere with three orthographic charts
blended by a sharpened partition of unity (no pole convergence, no seam anywhere; the charts'
coverage and colour are blended, never their UVs). `"dome"` and any other unknown value are now a
per-file parse error naming the accepted values. `sky_mode` is appended last to the Config UBO, so
no existing shader's layout changes; no datapack field was renamed and `PROTOCOL_VERSION` is
unchanged. The effect is not in any release yet, so removing `dome` breaks nothing. A full skybox
**cube** remains a separate future feature.
- **A custom mask leaf in `surface` mode no longer tints the sky.** The surface path classifies only
what the depth buffer contains, so a sky pixel contributes no coverage. Built-in shapes already
behaved this way (their bounded distance field puts a far-plane point outside the shape); the gate is
now explicit for custom leaves, whose SDF may be intentionally unbounded. A plugin that relied on
painting the sky from a `surface` leaf will no longer do so.
- **A built-in world GLSL mask plugin, `vfxweaver:blobs_glsl`.** The only shipped GLSL plugin was
`vfxweaver:ringed_glsl`, a screen-space 2D figure, so `space: "world"` plugin leaves had no volume
to march and the dynamic-data demo could not show the data driving anything. `vfxweaver:blobs_glsl`
is a genuine world-space SDF — a union of up to eight spheres, negative inside — whose sphere
centres/radii come from the leaf's dynamic data (`mask.p<N>.d0..d31`, four floats per sphere) and
whose `p0..p7` are global controls (offset, radius scale/bias, count). It also declares the optional
`vfx_shape_custom_bounds()` broad phase over the same data+params extent, so `volume: "aura"` with
it is cheap. It works for both `surface` and `aura`; the three mask demos use it. The plugin leaf
shape is inert on the `1.21.11` node (no shader-source hook). Additive: `vfxweaver:ringed_glsl`
stays the screen reference, no existing pass's UBO/wire layout changes, `PROTOCOL_VERSION` unchanged.
See [masks](guide/datapack/masks.md).
### Fixed
- **`film_grain` noise no longer repeats in a fixed pattern.** The cells were hashed with a
`fract(sin(dot(...)))` hash, which is only well distributed for large inputs: on the small integers a
pixel cell produces it repeats in a visible lattice, so the frame read as one small tile stamped over
and over. Cells are now hashed with an integer bit-mix, and the per-frame offset is an integer tick
mix rather than a `fract()`-scaled float.
- **`film_grain` and `pixelate` cell grids grow from the centre, not the corner.** Both derived their
grid straight from the UV, so an animated cell size expanded the pattern out of the bottom-left
corner; the grid is now measured from the screen centre. Effects whose pattern is per-scanline
(`scanlines`, `vhs`, `digital_glitch`) are deliberately unchanged — those bands run from the top edge
and have no centre to grow from.
- **`sky_pattern` `anchor: "stars"` really tracks the star field.** The anchor previously drove
`dome_rotation`, a world-**Y** spin, while vanilla rotates the star sphere about a different axis
(its pose is `Ry(-90°) · Rx(starAngle)`), so a `stars` pattern was only an approximation and turned
about the wrong axis. The sky shader now takes the sampled direction into the star sphere's own
frame with the exact inverse, so the pattern is locked to the star material. `dome_rotation` is not
hijacked — it stays the author's world-Y spin, applied first. Two internal Config fields are
appended after `sky_mode`; no datapack field was renamed and `PROTOCOL_VERSION` is unchanged. See
[sky_pattern](guide/effects/screen/sky-pattern.md).
- **`sky_pattern` `patch` decals have no built-in clip, and the ring demo no longer circles the
player.** A `patch` is a flat sign on the tangent plane: it is evaluated out to the tangent-plane
horizon, where a pixel behind the decal (`dot(dir, anchor) <= 0`) is discarded - no wrapping, no
pole convergence and no built-in clip. A large patch can therefore show that horizon as a straight
cut; restrict the effect with a mask if you need a clean edge. (An earlier unit-disc bound was
removed: it visibly cut the figure.) The demos use each projection for what it is for:
`show_sky_pattern_texture` (a whole-sky texture) and `sky_cracks` are `fill`, while
`show_sky_pattern` (the ring) and `nine_red_pixels` are small `patch` decals. The ring demo no
longer animates `dome_rotation` - in `patch` mode that moves the decal along its latitude, around
the sky, so it now animates only `tile_scale` and `opacity` and stays at its spot. The effect is
not in any release yet; no datapack field was renamed and `PROTOCOL_VERSION` is unchanged.
release
VFXWeaver API 2.1.0 (26.2, NeoForge)
2.1.025 сент. 2026 г.18Minecraft: 26.2neoforge
Что нового
## 2.1.0 — 2026-09-25
### Added
- **`film_grain` can be coloured (`chroma`).** A new `chroma` param (`0`, monochrome, default; `1`,
fully coloured) mixes an independent per-channel grain into the shared monochrome grain and stops
halfway, so at `1` the grain keeps its luminance structure instead of dissolving into plain colour
noise. Animated like any other param (`keyframes`, `expr`, `setParam`, `fade_ticks`). Additive: absent
= exactly the previous monochrome look, no datapack field renamed, no wire change. See
[film_grain](guide/effects/screen/film-grain.md).
- **`fog_modifier` — change the vanilla fog.** A new value-modifier effect (like `fov_modifier`, not
a post pass): `fog_start_scale`/`fog_end_scale` multiply the vanilla fog start/end distance
(neutral `1.0`; `>1` sees further, `<1` pulls the fog closer) and optional `fog_r`/`fog_g`/`fog_b`
(neutral "unset") override the fog colour. `fog_color_amount` (`0..1`, neutral `1`/unset) scales how
far the colour moves from vanilla — animating it ramps smoothly from the dimension's own fog colour
to the authored one, and `0` is a true no-op that never writes the colour. The mod rewrites the fog
at the **return of vanilla `FogRenderer.setupFog`**, mutating the instance the game produced in
place before anything consumes it: the same values then reach the fog uniforms (`FogEnvironmental`/
`FogRenderDistance` start+end, `FogSkyEnd`/`FogCloudsEnd` and `FogColor`), the level renderer and
the sky/cloud shaders, so vanilla terrain, entities, the **horizon band** and the entity-effect
geometry all see the change; absent params leave the fog bit-for-bit untouched. Scales combine
additively around neutral (`1 + Σ((s−1)·weight)`) and the colour is a weighted average blended from
vanilla (order-independent, clamped; each effect's colour weight is its `weight·fog_color_amount`),
so overlapping effects stack predictably. All six params are ordinary animatable params, so
`keyframes`, `expr`, graphs, `setParam` and `fade_ticks` work. The start/end scales apply to every
distance-based fog on all three nodes — overworld, **nether** (`fog_start`/`fog_end` distances) and
End, plus water/lava/snow/blindness/darkness, and `fog_end_scale` also scales the sky/cloud fade
ends; these versions have **no** density-fog field or attribute, so nothing is left out. **Honest
limits:** under an **Iris shaderpack** this effect is a **no-op** (a pack computes its own fog and
ignores the vanilla values); and the `block_tint`/`block_outline`/`glow`/spark overlays use a
fog-free shader, so only the entity-effect geometry inherits the change. No shader change, no UBO
change, no new dependency; `PROTOCOL_VERSION` unchanged. The mixin hooks the return of vanilla
`FogRenderer.setupFog` at `priority 900` (before Sodium's fog mixin) and mutates the returned
instance in place, so the fog uniforms, the level renderer and the sky/cloud shaders all see it.
**Not verified in game:** the visual result; the static contract — the per-node
`FogRenderer.setupFog` hook point (return of `setupFog`, mutated in place), the colour-amount
combination maths and the type/neutral/isPostProcessing wiring — is asserted by
`scripts/check-fog-modifier.ps1`.
- **`sky_pattern` — follow the sun, the moon or the stars (`anchor`).** The overnight sky body is a
new optional top-level `anchor` string: omitted (or `"dome"`) is the world-fixed
`anchor_yaw`/`anchor_pitch` authoring; `"sun"` and `"moon"` make the pattern track the vanilla
body; `"stars"` locks the pattern to the rotating star sphere (the shader inverts vanilla's star
pose exactly — see Fixed below). The body angles are read from the client's own sky render state
**on the CPU** and fed to the existing `anchor_yaw`/`anchor_pitch` uniforms (plus two internal
star fields), so there is **no mixin** — which is what keeps it working under an Iris shaderpack
(the world time, hence the angles, is still computed by the game). When the sky state is unavailable (no overworld sky, or the render
state is not ready) the effect **contributes nothing** instead of painting at a guessed spot, and
warns once. Additive: a new top-level field only, no existing effect changed, no datapack field
renamed, `PROTOCOL_VERSION` unchanged, and no UBO/Config change. Honest limits: the effect is an
**overlay**, so the vanilla sun/moon are still drawn underneath (size the pattern to cover them:
sun ≈33° apparent width, moon ≈24°), and because the vanilla star colour is not reached without a
`SkyRenderer` mixin, `"stars"` positions a pattern at the star sphere rather than recolouring the
vanilla stars. The rendering is not verified in game by the author.
- **`sky_pattern` — a datapack figure or texture drawn on the sky dome.** A new screen post effect
and the sky sibling of `surface_pattern`: the same structural `pattern` block, shared shape
library and `pattern.texture` addressing, but the pixel's view ray is projected onto the dome
instead of a depth-reconstructed world surface (the projection is chosen by `sky_mode` — see the
`sky_mode` entry below). The dome anchor is authored as
`anchor_yaw`/`anchor_pitch` (degrees) and the image can be spun about world Y with `dome_rotation`;
`repeat: [3, 3]` draws nine dots with no texture, and a `pattern.texture` with a `sheet` plus the
animatable `frame` param draws an animated sky. The pass is gated on the depth sky test, so it
never touches terrain, the hand or the GUI. Additive: a new effect type only, no existing effect
changed, no datapack field renamed, `PROTOCOL_VERSION` unchanged, and no existing shader's UBO
layout touched. The rendering is not verified in game by the author;
like every post effect it composes over an Iris shaderpack's sky.
- **`light_beam` can end on a chosen point.** Three optional params, `end_at_x`/`end_at_y`/`end_at_z`,
name a world point the **far end** of the beam lands on: the anchor is derived from it
(`end − axis × height`), so the geometry finishes exactly there — with the default axis the beam
hangs straight down from `height` above the point, which is how a beam comes out of the sky. They
are ordinary animatable params, so the landing point can be keyframed, driven by an expression or
bound, and a beam from A to B is `end_at = B` with the axis and length computed between the two
points. Unset, the beam behaves exactly as before. No shader, pipeline, UBO or wire change.
- **`light_beam` can point in any direction.** Three new params, `dir_x`/`dir_y`/`dir_z` (defaults
`0`/`1`/`0`), replace the fixed vertical axis, so a beam can be aimed at an angle — e.g. from a
point on the sky. They are ordinary animatable params, so they can be keyframed, driven by an
expression or bound, including to the camera look (`{"bind": "look_x"}` etc.). With the defaults the
beam renders exactly as before. No shader, pipeline, UBO or wire change.
- **Sky masks.** Two new mask leaves address the sky instead of the world: `sky` (the whole visible
sky, no parameters) and a 2D shape (`circle`/`ellipse`/`rect`/`polygon`) in the new
`space: "dome"`, which places it on an equirectangular map of the sky with the centre authored as
`[yaw, pitch]` degrees. A dome leaf is sky-occluded by construction — a hill or a wall occupying a
dome direction is never tinted — and both fail closed (zero coverage) when no trustworthy depth is
available. This is what lets any existing screen effect be restricted to the sky, e.g. a green or
glitching sky. Additive: no datapack field was renamed, the wire format and `PROTOCOL_VERSION` are
unchanged and the coverage UBO layout did not change. The far-depth test is verified in game on all
three lines (26.2, 26.1.2, 1.21.11). The sun, the moon and the clouds are drawn without depth, so a
`sky`/`dome` leaf covers them too (a green sky turns the sun green); the horizon fog is baked into
the terrain, so it is not recoloured and a strong tint can leave a seam there.
- **`volume: "aura"` on a world GLSL-plugin mask leaf.** A custom mask leaf registered with
`VFXAPI.registerMaskShapeGlsl` can now be a real volume instead of a surface-only shape: the pixel's
view ray is sphere-traced through the plugin's own distance field and the effect fills the volume —
air and sky included — wherever the scene does not occlude it, with the same silhouette, edge fade
and occlusion maths as a built-in `sphere`/`box` aura. A composed custom leaf (no raw SDF to march)
and a screen-space plugin leaf are per-file parse errors naming the reason rather than silent
fallbacks. An optional `vec4 vfx_shape_custom_bounds()` in the plugin source (world centre and
radius) acts as an analytic broad phase: a ray that misses it is rejected without evaluating the SDF,
which makes the average cost proportional to the volume's screen area instead of the whole frame. A
plugin that declares neither function compiles and behaves exactly as before; the wire format,
`PROTOCOL_VERSION` and the coverage UBO layout are unchanged.
### Changed
- **`sky_pattern` addresses the sky with `patch` or `fill` — the legacy equirectangular `dome` mode
was removed before release.** A single global chart of a sphere is topologically unable to tile it
cleanly: `u` is undefined at the poles (a whole-sky pattern winds into a funnel at the zenith) and
`u` must wrap at ±180° yaw (a mandatory visible seam / mirror axis) — properties of the chart, not
formula bugs, so no formula fixes them. The effect therefore offers only local-chart modes:
**`patch`** (default, `0`) is one gnomonic decal at the anchor (no pole, no seam, clean discard
past the decal horizon) and **`fill`** (`1`) covers the whole sphere with three orthographic charts
blended by a sharpened partition of unity (no pole convergence, no seam anywhere; the charts'
coverage and colour are blended, never their UVs). `"dome"` and any other unknown value are now a
per-file parse error naming the accepted values. `sky_mode` is appended last to the Config UBO, so
no existing shader's layout changes; no datapack field was renamed and `PROTOCOL_VERSION` is
unchanged. The effect is not in any release yet, so removing `dome` breaks nothing. A full skybox
**cube** remains a separate future feature.
- **A custom mask leaf in `surface` mode no longer tints the sky.** The surface path classifies only
what the depth buffer contains, so a sky pixel contributes no coverage. Built-in shapes already
behaved this way (their bounded distance field puts a far-plane point outside the shape); the gate is
now explicit for custom leaves, whose SDF may be intentionally unbounded. A plugin that relied on
painting the sky from a `surface` leaf will no longer do so.
- **A built-in world GLSL mask plugin, `vfxweaver:blobs_glsl`.** The only shipped GLSL plugin was
`vfxweaver:ringed_glsl`, a screen-space 2D figure, so `space: "world"` plugin leaves had no volume
to march and the dynamic-data demo could not show the data driving anything. `vfxweaver:blobs_glsl`
is a genuine world-space SDF — a union of up to eight spheres, negative inside — whose sphere
centres/radii come from the leaf's dynamic data (`mask.p<N>.d0..d31`, four floats per sphere) and
whose `p0..p7` are global controls (offset, radius scale/bias, count). It also declares the optional
`vfx_shape_custom_bounds()` broad phase over the same data+params extent, so `volume: "aura"` with
it is cheap. It works for both `surface` and `aura`; the three mask demos use it. The plugin leaf
shape is inert on the `1.21.11` node (no shader-source hook). Additive: `vfxweaver:ringed_glsl`
stays the screen reference, no existing pass's UBO/wire layout changes, `PROTOCOL_VERSION` unchanged.
See [masks](guide/datapack/masks.md).
### Fixed
- **`film_grain` noise no longer repeats in a fixed pattern.** The cells were hashed with a
`fract(sin(dot(...)))` hash, which is only well distributed for large inputs: on the small integers a
pixel cell produces it repeats in a visible lattice, so the frame read as one small tile stamped over
and over. Cells are now hashed with an integer bit-mix, and the per-frame offset is an integer tick
mix rather than a `fract()`-scaled float.
- **`film_grain` and `pixelate` cell grids grow from the centre, not the corner.** Both derived their
grid straight from the UV, so an animated cell size expanded the pattern out of the bottom-left
corner; the grid is now measured from the screen centre. Effects whose pattern is per-scanline
(`scanlines`, `vhs`, `digital_glitch`) are deliberately unchanged — those bands run from the top edge
and have no centre to grow from.
- **`sky_pattern` `anchor: "stars"` really tracks the star field.** The anchor previously drove
`dome_rotation`, a world-**Y** spin, while vanilla rotates the star sphere about a different axis
(its pose is `Ry(-90°) · Rx(starAngle)`), so a `stars` pattern was only an approximation and turned
about the wrong axis. The sky shader now takes the sampled direction into the star sphere's own
frame with the exact inverse, so the pattern is locked to the star material. `dome_rotation` is not
hijacked — it stays the author's world-Y spin, applied first. Two internal Config fields are
appended after `sky_mode`; no datapack field was renamed and `PROTOCOL_VERSION` is unchanged. See
[sky_pattern](guide/effects/screen/sky-pattern.md).
- **`sky_pattern` `patch` decals have no built-in clip, and the ring demo no longer circles the
player.** A `patch` is a flat sign on the tangent plane: it is evaluated out to the tangent-plane
horizon, where a pixel behind the decal (`dot(dir, anchor) <= 0`) is discarded - no wrapping, no
pole convergence and no built-in clip. A large patch can therefore show that horizon as a straight
cut; restrict the effect with a mask if you need a clean edge. (An earlier unit-disc bound was
removed: it visibly cut the figure.) The demos use each projection for what it is for:
`show_sky_pattern_texture` (a whole-sky texture) and `sky_cracks` are `fill`, while
`show_sky_pattern` (the ring) and `nine_red_pixels` are small `patch` decals. The ring demo no
longer animates `dome_rotation` - in `patch` mode that moves the decal along its latitude, around
the sky, so it now animates only `tile_scale` and `opacity` and stays at its spot. The effect is
not in any release yet; no datapack field was renamed and `PROTOCOL_VERSION` is unchanged.
release
VFXWeaver API 2.1.0 (1.21.11)
2.1.025 сент. 2026 г.16Minecraft: 1.21.11fabric
Что нового
## 2.1.0 — 2026-09-25
### Added
- **`film_grain` can be coloured (`chroma`).** A new `chroma` param (`0`, monochrome, default; `1`,
fully coloured) mixes an independent per-channel grain into the shared monochrome grain and stops
halfway, so at `1` the grain keeps its luminance structure instead of dissolving into plain colour
noise. Animated like any other param (`keyframes`, `expr`, `setParam`, `fade_ticks`). Additive: absent
= exactly the previous monochrome look, no datapack field renamed, no wire change. See
[film_grain](guide/effects/screen/film-grain.md).
- **`fog_modifier` — change the vanilla fog.** A new value-modifier effect (like `fov_modifier`, not
a post pass): `fog_start_scale`/`fog_end_scale` multiply the vanilla fog start/end distance
(neutral `1.0`; `>1` sees further, `<1` pulls the fog closer) and optional `fog_r`/`fog_g`/`fog_b`
(neutral "unset") override the fog colour. `fog_color_amount` (`0..1`, neutral `1`/unset) scales how
far the colour moves from vanilla — animating it ramps smoothly from the dimension's own fog colour
to the authored one, and `0` is a true no-op that never writes the colour. The mod rewrites the fog
at the **return of vanilla `FogRenderer.setupFog`**, mutating the instance the game produced in
place before anything consumes it: the same values then reach the fog uniforms (`FogEnvironmental`/
`FogRenderDistance` start+end, `FogSkyEnd`/`FogCloudsEnd` and `FogColor`), the level renderer and
the sky/cloud shaders, so vanilla terrain, entities, the **horizon band** and the entity-effect
geometry all see the change; absent params leave the fog bit-for-bit untouched. Scales combine
additively around neutral (`1 + Σ((s−1)·weight)`) and the colour is a weighted average blended from
vanilla (order-independent, clamped; each effect's colour weight is its `weight·fog_color_amount`),
so overlapping effects stack predictably. All six params are ordinary animatable params, so
`keyframes`, `expr`, graphs, `setParam` and `fade_ticks` work. The start/end scales apply to every
distance-based fog on all three nodes — overworld, **nether** (`fog_start`/`fog_end` distances) and
End, plus water/lava/snow/blindness/darkness, and `fog_end_scale` also scales the sky/cloud fade
ends; these versions have **no** density-fog field or attribute, so nothing is left out. **Honest
limits:** under an **Iris shaderpack** this effect is a **no-op** (a pack computes its own fog and
ignores the vanilla values); and the `block_tint`/`block_outline`/`glow`/spark overlays use a
fog-free shader, so only the entity-effect geometry inherits the change. No shader change, no UBO
change, no new dependency; `PROTOCOL_VERSION` unchanged. The mixin hooks the return of vanilla
`FogRenderer.setupFog` at `priority 900` (before Sodium's fog mixin) and mutates the returned
instance in place, so the fog uniforms, the level renderer and the sky/cloud shaders all see it.
**Not verified in game:** the visual result; the static contract — the per-node
`FogRenderer.setupFog` hook point (return of `setupFog`, mutated in place), the colour-amount
combination maths and the type/neutral/isPostProcessing wiring — is asserted by
`scripts/check-fog-modifier.ps1`.
- **`sky_pattern` — follow the sun, the moon or the stars (`anchor`).** The overnight sky body is a
new optional top-level `anchor` string: omitted (or `"dome"`) is the world-fixed
`anchor_yaw`/`anchor_pitch` authoring; `"sun"` and `"moon"` make the pattern track the vanilla
body; `"stars"` locks the pattern to the rotating star sphere (the shader inverts vanilla's star
pose exactly — see Fixed below). The body angles are read from the client's own sky render state
**on the CPU** and fed to the existing `anchor_yaw`/`anchor_pitch` uniforms (plus two internal
star fields), so there is **no mixin** — which is what keeps it working under an Iris shaderpack
(the world time, hence the angles, is still computed by the game). When the sky state is unavailable (no overworld sky, or the render
state is not ready) the effect **contributes nothing** instead of painting at a guessed spot, and
warns once. Additive: a new top-level field only, no existing effect changed, no datapack field
renamed, `PROTOCOL_VERSION` unchanged, and no UBO/Config change. Honest limits: the effect is an
**overlay**, so the vanilla sun/moon are still drawn underneath (size the pattern to cover them:
sun ≈33° apparent width, moon ≈24°), and because the vanilla star colour is not reached without a
`SkyRenderer` mixin, `"stars"` positions a pattern at the star sphere rather than recolouring the
vanilla stars. The rendering is not verified in game by the author.
- **`sky_pattern` — a datapack figure or texture drawn on the sky dome.** A new screen post effect
and the sky sibling of `surface_pattern`: the same structural `pattern` block, shared shape
library and `pattern.texture` addressing, but the pixel's view ray is projected onto the dome
instead of a depth-reconstructed world surface (the projection is chosen by `sky_mode` — see the
`sky_mode` entry below). The dome anchor is authored as
`anchor_yaw`/`anchor_pitch` (degrees) and the image can be spun about world Y with `dome_rotation`;
`repeat: [3, 3]` draws nine dots with no texture, and a `pattern.texture` with a `sheet` plus the
animatable `frame` param draws an animated sky. The pass is gated on the depth sky test, so it
never touches terrain, the hand or the GUI. Additive: a new effect type only, no existing effect
changed, no datapack field renamed, `PROTOCOL_VERSION` unchanged, and no existing shader's UBO
layout touched. The rendering is not verified in game by the author;
like every post effect it composes over an Iris shaderpack's sky.
- **`light_beam` can end on a chosen point.** Three optional params, `end_at_x`/`end_at_y`/`end_at_z`,
name a world point the **far end** of the beam lands on: the anchor is derived from it
(`end − axis × height`), so the geometry finishes exactly there — with the default axis the beam
hangs straight down from `height` above the point, which is how a beam comes out of the sky. They
are ordinary animatable params, so the landing point can be keyframed, driven by an expression or
bound, and a beam from A to B is `end_at = B` with the axis and length computed between the two
points. Unset, the beam behaves exactly as before. No shader, pipeline, UBO or wire change.
- **`light_beam` can point in any direction.** Three new params, `dir_x`/`dir_y`/`dir_z` (defaults
`0`/`1`/`0`), replace the fixed vertical axis, so a beam can be aimed at an angle — e.g. from a
point on the sky. They are ordinary animatable params, so they can be keyframed, driven by an
expression or bound, including to the camera look (`{"bind": "look_x"}` etc.). With the defaults the
beam renders exactly as before. No shader, pipeline, UBO or wire change.
- **Sky masks.** Two new mask leaves address the sky instead of the world: `sky` (the whole visible
sky, no parameters) and a 2D shape (`circle`/`ellipse`/`rect`/`polygon`) in the new
`space: "dome"`, which places it on an equirectangular map of the sky with the centre authored as
`[yaw, pitch]` degrees. A dome leaf is sky-occluded by construction — a hill or a wall occupying a
dome direction is never tinted — and both fail closed (zero coverage) when no trustworthy depth is
available. This is what lets any existing screen effect be restricted to the sky, e.g. a green or
glitching sky. Additive: no datapack field was renamed, the wire format and `PROTOCOL_VERSION` are
unchanged and the coverage UBO layout did not change. The far-depth test is verified in game on all
three lines (26.2, 26.1.2, 1.21.11). The sun, the moon and the clouds are drawn without depth, so a
`sky`/`dome` leaf covers them too (a green sky turns the sun green); the horizon fog is baked into
the terrain, so it is not recoloured and a strong tint can leave a seam there.
- **`volume: "aura"` on a world GLSL-plugin mask leaf.** A custom mask leaf registered with
`VFXAPI.registerMaskShapeGlsl` can now be a real volume instead of a surface-only shape: the pixel's
view ray is sphere-traced through the plugin's own distance field and the effect fills the volume —
air and sky included — wherever the scene does not occlude it, with the same silhouette, edge fade
and occlusion maths as a built-in `sphere`/`box` aura. A composed custom leaf (no raw SDF to march)
and a screen-space plugin leaf are per-file parse errors naming the reason rather than silent
fallbacks. An optional `vec4 vfx_shape_custom_bounds()` in the plugin source (world centre and
radius) acts as an analytic broad phase: a ray that misses it is rejected without evaluating the SDF,
which makes the average cost proportional to the volume's screen area instead of the whole frame. A
plugin that declares neither function compiles and behaves exactly as before; the wire format,
`PROTOCOL_VERSION` and the coverage UBO layout are unchanged.
### Changed
- **`sky_pattern` addresses the sky with `patch` or `fill` — the legacy equirectangular `dome` mode
was removed before release.** A single global chart of a sphere is topologically unable to tile it
cleanly: `u` is undefined at the poles (a whole-sky pattern winds into a funnel at the zenith) and
`u` must wrap at ±180° yaw (a mandatory visible seam / mirror axis) — properties of the chart, not
formula bugs, so no formula fixes them. The effect therefore offers only local-chart modes:
**`patch`** (default, `0`) is one gnomonic decal at the anchor (no pole, no seam, clean discard
past the decal horizon) and **`fill`** (`1`) covers the whole sphere with three orthographic charts
blended by a sharpened partition of unity (no pole convergence, no seam anywhere; the charts'
coverage and colour are blended, never their UVs). `"dome"` and any other unknown value are now a
per-file parse error naming the accepted values. `sky_mode` is appended last to the Config UBO, so
no existing shader's layout changes; no datapack field was renamed and `PROTOCOL_VERSION` is
unchanged. The effect is not in any release yet, so removing `dome` breaks nothing. A full skybox
**cube** remains a separate future feature.
- **A custom mask leaf in `surface` mode no longer tints the sky.** The surface path classifies only
what the depth buffer contains, so a sky pixel contributes no coverage. Built-in shapes already
behaved this way (their bounded distance field puts a far-plane point outside the shape); the gate is
now explicit for custom leaves, whose SDF may be intentionally unbounded. A plugin that relied on
painting the sky from a `surface` leaf will no longer do so.
- **A built-in world GLSL mask plugin, `vfxweaver:blobs_glsl`.** The only shipped GLSL plugin was
`vfxweaver:ringed_glsl`, a screen-space 2D figure, so `space: "world"` plugin leaves had no volume
to march and the dynamic-data demo could not show the data driving anything. `vfxweaver:blobs_glsl`
is a genuine world-space SDF — a union of up to eight spheres, negative inside — whose sphere
centres/radii come from the leaf's dynamic data (`mask.p<N>.d0..d31`, four floats per sphere) and
whose `p0..p7` are global controls (offset, radius scale/bias, count). It also declares the optional
`vfx_shape_custom_bounds()` broad phase over the same data+params extent, so `volume: "aura"` with
it is cheap. It works for both `surface` and `aura`; the three mask demos use it. The plugin leaf
shape is inert on the `1.21.11` node (no shader-source hook). Additive: `vfxweaver:ringed_glsl`
stays the screen reference, no existing pass's UBO/wire layout changes, `PROTOCOL_VERSION` unchanged.
See [masks](guide/datapack/masks.md).
### Fixed
- **`film_grain` noise no longer repeats in a fixed pattern.** The cells were hashed with a
`fract(sin(dot(...)))` hash, which is only well distributed for large inputs: on the small integers a
pixel cell produces it repeats in a visible lattice, so the frame read as one small tile stamped over
and over. Cells are now hashed with an integer bit-mix, and the per-frame offset is an integer tick
mix rather than a `fract()`-scaled float.
- **`film_grain` and `pixelate` cell grids grow from the centre, not the corner.** Both derived their
grid straight from the UV, so an animated cell size expanded the pattern out of the bottom-left
corner; the grid is now measured from the screen centre. Effects whose pattern is per-scanline
(`scanlines`, `vhs`, `digital_glitch`) are deliberately unchanged — those bands run from the top edge
and have no centre to grow from.
- **`sky_pattern` `anchor: "stars"` really tracks the star field.** The anchor previously drove
`dome_rotation`, a world-**Y** spin, while vanilla rotates the star sphere about a different axis
(its pose is `Ry(-90°) · Rx(starAngle)`), so a `stars` pattern was only an approximation and turned
about the wrong axis. The sky shader now takes the sampled direction into the star sphere's own
frame with the exact inverse, so the pattern is locked to the star material. `dome_rotation` is not
hijacked — it stays the author's world-Y spin, applied first. Two internal Config fields are
appended after `sky_mode`; no datapack field was renamed and `PROTOCOL_VERSION` is unchanged. See
[sky_pattern](guide/effects/screen/sky-pattern.md).
- **`sky_pattern` `patch` decals have no built-in clip, and the ring demo no longer circles the
player.** A `patch` is a flat sign on the tangent plane: it is evaluated out to the tangent-plane
horizon, where a pixel behind the decal (`dot(dir, anchor) <= 0`) is discarded - no wrapping, no
pole convergence and no built-in clip. A large patch can therefore show that horizon as a straight
cut; restrict the effect with a mask if you need a clean edge. (An earlier unit-disc bound was
removed: it visibly cut the figure.) The demos use each projection for what it is for:
`show_sky_pattern_texture` (a whole-sky texture) and `sky_cracks` are `fill`, while
`show_sky_pattern` (the ring) and `nine_red_pixels` are small `patch` decals. The ring demo no
longer animates `dome_rotation` - in `patch` mode that moves the decal along its latitude, around
the sky, so it now animates only `tile_scale` and `opacity` and stays at its spot. The effect is
not in any release yet; no datapack field was renamed and `PROTOCOL_VERSION` is unchanged.
release
VFXWeaver API 2.1.0 (26.1.x)
2.1.025 сент. 2026 г.16Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
## 2.1.0 — 2026-09-25
### Added
- **`film_grain` can be coloured (`chroma`).** A new `chroma` param (`0`, monochrome, default; `1`,
fully coloured) mixes an independent per-channel grain into the shared monochrome grain and stops
halfway, so at `1` the grain keeps its luminance structure instead of dissolving into plain colour
noise. Animated like any other param (`keyframes`, `expr`, `setParam`, `fade_ticks`). Additive: absent
= exactly the previous monochrome look, no datapack field renamed, no wire change. See
[film_grain](guide/effects/screen/film-grain.md).
- **`fog_modifier` — change the vanilla fog.** A new value-modifier effect (like `fov_modifier`, not
a post pass): `fog_start_scale`/`fog_end_scale` multiply the vanilla fog start/end distance
(neutral `1.0`; `>1` sees further, `<1` pulls the fog closer) and optional `fog_r`/`fog_g`/`fog_b`
(neutral "unset") override the fog colour. `fog_color_amount` (`0..1`, neutral `1`/unset) scales how
far the colour moves from vanilla — animating it ramps smoothly from the dimension's own fog colour
to the authored one, and `0` is a true no-op that never writes the colour. The mod rewrites the fog
at the **return of vanilla `FogRenderer.setupFog`**, mutating the instance the game produced in
place before anything consumes it: the same values then reach the fog uniforms (`FogEnvironmental`/
`FogRenderDistance` start+end, `FogSkyEnd`/`FogCloudsEnd` and `FogColor`), the level renderer and
the sky/cloud shaders, so vanilla terrain, entities, the **horizon band** and the entity-effect
geometry all see the change; absent params leave the fog bit-for-bit untouched. Scales combine
additively around neutral (`1 + Σ((s−1)·weight)`) and the colour is a weighted average blended from
vanilla (order-independent, clamped; each effect's colour weight is its `weight·fog_color_amount`),
so overlapping effects stack predictably. All six params are ordinary animatable params, so
`keyframes`, `expr`, graphs, `setParam` and `fade_ticks` work. The start/end scales apply to every
distance-based fog on all three nodes — overworld, **nether** (`fog_start`/`fog_end` distances) and
End, plus water/lava/snow/blindness/darkness, and `fog_end_scale` also scales the sky/cloud fade
ends; these versions have **no** density-fog field or attribute, so nothing is left out. **Honest
limits:** under an **Iris shaderpack** this effect is a **no-op** (a pack computes its own fog and
ignores the vanilla values); and the `block_tint`/`block_outline`/`glow`/spark overlays use a
fog-free shader, so only the entity-effect geometry inherits the change. No shader change, no UBO
change, no new dependency; `PROTOCOL_VERSION` unchanged. The mixin hooks the return of vanilla
`FogRenderer.setupFog` at `priority 900` (before Sodium's fog mixin) and mutates the returned
instance in place, so the fog uniforms, the level renderer and the sky/cloud shaders all see it.
**Not verified in game:** the visual result; the static contract — the per-node
`FogRenderer.setupFog` hook point (return of `setupFog`, mutated in place), the colour-amount
combination maths and the type/neutral/isPostProcessing wiring — is asserted by
`scripts/check-fog-modifier.ps1`.
- **`sky_pattern` — follow the sun, the moon or the stars (`anchor`).** The overnight sky body is a
new optional top-level `anchor` string: omitted (or `"dome"`) is the world-fixed
`anchor_yaw`/`anchor_pitch` authoring; `"sun"` and `"moon"` make the pattern track the vanilla
body; `"stars"` locks the pattern to the rotating star sphere (the shader inverts vanilla's star
pose exactly — see Fixed below). The body angles are read from the client's own sky render state
**on the CPU** and fed to the existing `anchor_yaw`/`anchor_pitch` uniforms (plus two internal
star fields), so there is **no mixin** — which is what keeps it working under an Iris shaderpack
(the world time, hence the angles, is still computed by the game). When the sky state is unavailable (no overworld sky, or the render
state is not ready) the effect **contributes nothing** instead of painting at a guessed spot, and
warns once. Additive: a new top-level field only, no existing effect changed, no datapack field
renamed, `PROTOCOL_VERSION` unchanged, and no UBO/Config change. Honest limits: the effect is an
**overlay**, so the vanilla sun/moon are still drawn underneath (size the pattern to cover them:
sun ≈33° apparent width, moon ≈24°), and because the vanilla star colour is not reached without a
`SkyRenderer` mixin, `"stars"` positions a pattern at the star sphere rather than recolouring the
vanilla stars. The rendering is not verified in game by the author.
- **`sky_pattern` — a datapack figure or texture drawn on the sky dome.** A new screen post effect
and the sky sibling of `surface_pattern`: the same structural `pattern` block, shared shape
library and `pattern.texture` addressing, but the pixel's view ray is projected onto the dome
instead of a depth-reconstructed world surface (the projection is chosen by `sky_mode` — see the
`sky_mode` entry below). The dome anchor is authored as
`anchor_yaw`/`anchor_pitch` (degrees) and the image can be spun about world Y with `dome_rotation`;
`repeat: [3, 3]` draws nine dots with no texture, and a `pattern.texture` with a `sheet` plus the
animatable `frame` param draws an animated sky. The pass is gated on the depth sky test, so it
never touches terrain, the hand or the GUI. Additive: a new effect type only, no existing effect
changed, no datapack field renamed, `PROTOCOL_VERSION` unchanged, and no existing shader's UBO
layout touched. The rendering is not verified in game by the author;
like every post effect it composes over an Iris shaderpack's sky.
- **`light_beam` can end on a chosen point.** Three optional params, `end_at_x`/`end_at_y`/`end_at_z`,
name a world point the **far end** of the beam lands on: the anchor is derived from it
(`end − axis × height`), so the geometry finishes exactly there — with the default axis the beam
hangs straight down from `height` above the point, which is how a beam comes out of the sky. They
are ordinary animatable params, so the landing point can be keyframed, driven by an expression or
bound, and a beam from A to B is `end_at = B` with the axis and length computed between the two
points. Unset, the beam behaves exactly as before. No shader, pipeline, UBO or wire change.
- **`light_beam` can point in any direction.** Three new params, `dir_x`/`dir_y`/`dir_z` (defaults
`0`/`1`/`0`), replace the fixed vertical axis, so a beam can be aimed at an angle — e.g. from a
point on the sky. They are ordinary animatable params, so they can be keyframed, driven by an
expression or bound, including to the camera look (`{"bind": "look_x"}` etc.). With the defaults the
beam renders exactly as before. No shader, pipeline, UBO or wire change.
- **Sky masks.** Two new mask leaves address the sky instead of the world: `sky` (the whole visible
sky, no parameters) and a 2D shape (`circle`/`ellipse`/`rect`/`polygon`) in the new
`space: "dome"`, which places it on an equirectangular map of the sky with the centre authored as
`[yaw, pitch]` degrees. A dome leaf is sky-occluded by construction — a hill or a wall occupying a
dome direction is never tinted — and both fail closed (zero coverage) when no trustworthy depth is
available. This is what lets any existing screen effect be restricted to the sky, e.g. a green or
glitching sky. Additive: no datapack field was renamed, the wire format and `PROTOCOL_VERSION` are
unchanged and the coverage UBO layout did not change. The far-depth test is verified in game on all
three lines (26.2, 26.1.2, 1.21.11). The sun, the moon and the clouds are drawn without depth, so a
`sky`/`dome` leaf covers them too (a green sky turns the sun green); the horizon fog is baked into
the terrain, so it is not recoloured and a strong tint can leave a seam there.
- **`volume: "aura"` on a world GLSL-plugin mask leaf.** A custom mask leaf registered with
`VFXAPI.registerMaskShapeGlsl` can now be a real volume instead of a surface-only shape: the pixel's
view ray is sphere-traced through the plugin's own distance field and the effect fills the volume —
air and sky included — wherever the scene does not occlude it, with the same silhouette, edge fade
and occlusion maths as a built-in `sphere`/`box` aura. A composed custom leaf (no raw SDF to march)
and a screen-space plugin leaf are per-file parse errors naming the reason rather than silent
fallbacks. An optional `vec4 vfx_shape_custom_bounds()` in the plugin source (world centre and
radius) acts as an analytic broad phase: a ray that misses it is rejected without evaluating the SDF,
which makes the average cost proportional to the volume's screen area instead of the whole frame. A
plugin that declares neither function compiles and behaves exactly as before; the wire format,
`PROTOCOL_VERSION` and the coverage UBO layout are unchanged.
### Changed
- **`sky_pattern` addresses the sky with `patch` or `fill` — the legacy equirectangular `dome` mode
was removed before release.** A single global chart of a sphere is topologically unable to tile it
cleanly: `u` is undefined at the poles (a whole-sky pattern winds into a funnel at the zenith) and
`u` must wrap at ±180° yaw (a mandatory visible seam / mirror axis) — properties of the chart, not
formula bugs, so no formula fixes them. The effect therefore offers only local-chart modes:
**`patch`** (default, `0`) is one gnomonic decal at the anchor (no pole, no seam, clean discard
past the decal horizon) and **`fill`** (`1`) covers the whole sphere with three orthographic charts
blended by a sharpened partition of unity (no pole convergence, no seam anywhere; the charts'
coverage and colour are blended, never their UVs). `"dome"` and any other unknown value are now a
per-file parse error naming the accepted values. `sky_mode` is appended last to the Config UBO, so
no existing shader's layout changes; no datapack field was renamed and `PROTOCOL_VERSION` is
unchanged. The effect is not in any release yet, so removing `dome` breaks nothing. A full skybox
**cube** remains a separate future feature.
- **A custom mask leaf in `surface` mode no longer tints the sky.** The surface path classifies only
what the depth buffer contains, so a sky pixel contributes no coverage. Built-in shapes already
behaved this way (their bounded distance field puts a far-plane point outside the shape); the gate is
now explicit for custom leaves, whose SDF may be intentionally unbounded. A plugin that relied on
painting the sky from a `surface` leaf will no longer do so.
- **A built-in world GLSL mask plugin, `vfxweaver:blobs_glsl`.** The only shipped GLSL plugin was
`vfxweaver:ringed_glsl`, a screen-space 2D figure, so `space: "world"` plugin leaves had no volume
to march and the dynamic-data demo could not show the data driving anything. `vfxweaver:blobs_glsl`
is a genuine world-space SDF — a union of up to eight spheres, negative inside — whose sphere
centres/radii come from the leaf's dynamic data (`mask.p<N>.d0..d31`, four floats per sphere) and
whose `p0..p7` are global controls (offset, radius scale/bias, count). It also declares the optional
`vfx_shape_custom_bounds()` broad phase over the same data+params extent, so `volume: "aura"` with
it is cheap. It works for both `surface` and `aura`; the three mask demos use it. The plugin leaf
shape is inert on the `1.21.11` node (no shader-source hook). Additive: `vfxweaver:ringed_glsl`
stays the screen reference, no existing pass's UBO/wire layout changes, `PROTOCOL_VERSION` unchanged.
See [masks](guide/datapack/masks.md).
### Fixed
- **`film_grain` noise no longer repeats in a fixed pattern.** The cells were hashed with a
`fract(sin(dot(...)))` hash, which is only well distributed for large inputs: on the small integers a
pixel cell produces it repeats in a visible lattice, so the frame read as one small tile stamped over
and over. Cells are now hashed with an integer bit-mix, and the per-frame offset is an integer tick
mix rather than a `fract()`-scaled float.
- **`film_grain` and `pixelate` cell grids grow from the centre, not the corner.** Both derived their
grid straight from the UV, so an animated cell size expanded the pattern out of the bottom-left
corner; the grid is now measured from the screen centre. Effects whose pattern is per-scanline
(`scanlines`, `vhs`, `digital_glitch`) are deliberately unchanged — those bands run from the top edge
and have no centre to grow from.
- **`sky_pattern` `anchor: "stars"` really tracks the star field.** The anchor previously drove
`dome_rotation`, a world-**Y** spin, while vanilla rotates the star sphere about a different axis
(its pose is `Ry(-90°) · Rx(starAngle)`), so a `stars` pattern was only an approximation and turned
about the wrong axis. The sky shader now takes the sampled direction into the star sphere's own
frame with the exact inverse, so the pattern is locked to the star material. `dome_rotation` is not
hijacked — it stays the author's world-Y spin, applied first. Two internal Config fields are
appended after `sky_mode`; no datapack field was renamed and `PROTOCOL_VERSION` is unchanged. See
[sky_pattern](guide/effects/screen/sky-pattern.md).
- **`sky_pattern` `patch` decals have no built-in clip, and the ring demo no longer circles the
player.** A `patch` is a flat sign on the tangent plane: it is evaluated out to the tangent-plane
horizon, where a pixel behind the decal (`dot(dir, anchor) <= 0`) is discarded - no wrapping, no
pole convergence and no built-in clip. A large patch can therefore show that horizon as a straight
cut; restrict the effect with a mask if you need a clean edge. (An earlier unit-disc bound was
removed: it visibly cut the figure.) The demos use each projection for what it is for:
`show_sky_pattern_texture` (a whole-sky texture) and `sky_cracks` are `fill`, while
`show_sky_pattern` (the ring) and `nine_red_pixels` are small `patch` decals. The ring demo no
longer animates `dome_rotation` - in `patch` mode that moves the decal along its latitude, around
the sky, so it now animates only `tile_scale` and `opacity` and stays at its spot. The effect is
not in any release yet; no datapack field was renamed and `PROTOCOL_VERSION` is unchanged.
release
VFXWeaver API 2.1.0 (26.2)
2.1.025 сент. 2026 г.30Minecraft: 26.2fabric
Что нового
## 2.1.0 — 2026-09-25
### Added
- **`film_grain` can be coloured (`chroma`).** A new `chroma` param (`0`, monochrome, default; `1`,
fully coloured) mixes an independent per-channel grain into the shared monochrome grain and stops
halfway, so at `1` the grain keeps its luminance structure instead of dissolving into plain colour
noise. Animated like any other param (`keyframes`, `expr`, `setParam`, `fade_ticks`). Additive: absent
= exactly the previous monochrome look, no datapack field renamed, no wire change. See
[film_grain](guide/effects/screen/film-grain.md).
- **`fog_modifier` — change the vanilla fog.** A new value-modifier effect (like `fov_modifier`, not
a post pass): `fog_start_scale`/`fog_end_scale` multiply the vanilla fog start/end distance
(neutral `1.0`; `>1` sees further, `<1` pulls the fog closer) and optional `fog_r`/`fog_g`/`fog_b`
(neutral "unset") override the fog colour. `fog_color_amount` (`0..1`, neutral `1`/unset) scales how
far the colour moves from vanilla — animating it ramps smoothly from the dimension's own fog colour
to the authored one, and `0` is a true no-op that never writes the colour. The mod rewrites the fog
at the **return of vanilla `FogRenderer.setupFog`**, mutating the instance the game produced in
place before anything consumes it: the same values then reach the fog uniforms (`FogEnvironmental`/
`FogRenderDistance` start+end, `FogSkyEnd`/`FogCloudsEnd` and `FogColor`), the level renderer and
the sky/cloud shaders, so vanilla terrain, entities, the **horizon band** and the entity-effect
geometry all see the change; absent params leave the fog bit-for-bit untouched. Scales combine
additively around neutral (`1 + Σ((s−1)·weight)`) and the colour is a weighted average blended from
vanilla (order-independent, clamped; each effect's colour weight is its `weight·fog_color_amount`),
so overlapping effects stack predictably. All six params are ordinary animatable params, so
`keyframes`, `expr`, graphs, `setParam` and `fade_ticks` work. The start/end scales apply to every
distance-based fog on all three nodes — overworld, **nether** (`fog_start`/`fog_end` distances) and
End, plus water/lava/snow/blindness/darkness, and `fog_end_scale` also scales the sky/cloud fade
ends; these versions have **no** density-fog field or attribute, so nothing is left out. **Honest
limits:** under an **Iris shaderpack** this effect is a **no-op** (a pack computes its own fog and
ignores the vanilla values); and the `block_tint`/`block_outline`/`glow`/spark overlays use a
fog-free shader, so only the entity-effect geometry inherits the change. No shader change, no UBO
change, no new dependency; `PROTOCOL_VERSION` unchanged. The mixin hooks the return of vanilla
`FogRenderer.setupFog` at `priority 900` (before Sodium's fog mixin) and mutates the returned
instance in place, so the fog uniforms, the level renderer and the sky/cloud shaders all see it.
**Not verified in game:** the visual result; the static contract — the per-node
`FogRenderer.setupFog` hook point (return of `setupFog`, mutated in place), the colour-amount
combination maths and the type/neutral/isPostProcessing wiring — is asserted by
`scripts/check-fog-modifier.ps1`.
- **`sky_pattern` — follow the sun, the moon or the stars (`anchor`).** The overnight sky body is a
new optional top-level `anchor` string: omitted (or `"dome"`) is the world-fixed
`anchor_yaw`/`anchor_pitch` authoring; `"sun"` and `"moon"` make the pattern track the vanilla
body; `"stars"` locks the pattern to the rotating star sphere (the shader inverts vanilla's star
pose exactly — see Fixed below). The body angles are read from the client's own sky render state
**on the CPU** and fed to the existing `anchor_yaw`/`anchor_pitch` uniforms (plus two internal
star fields), so there is **no mixin** — which is what keeps it working under an Iris shaderpack
(the world time, hence the angles, is still computed by the game). When the sky state is unavailable (no overworld sky, or the render
state is not ready) the effect **contributes nothing** instead of painting at a guessed spot, and
warns once. Additive: a new top-level field only, no existing effect changed, no datapack field
renamed, `PROTOCOL_VERSION` unchanged, and no UBO/Config change. Honest limits: the effect is an
**overlay**, so the vanilla sun/moon are still drawn underneath (size the pattern to cover them:
sun ≈33° apparent width, moon ≈24°), and because the vanilla star colour is not reached without a
`SkyRenderer` mixin, `"stars"` positions a pattern at the star sphere rather than recolouring the
vanilla stars. The rendering is not verified in game by the author.
- **`sky_pattern` — a datapack figure or texture drawn on the sky dome.** A new screen post effect
and the sky sibling of `surface_pattern`: the same structural `pattern` block, shared shape
library and `pattern.texture` addressing, but the pixel's view ray is projected onto the dome
instead of a depth-reconstructed world surface (the projection is chosen by `sky_mode` — see the
`sky_mode` entry below). The dome anchor is authored as
`anchor_yaw`/`anchor_pitch` (degrees) and the image can be spun about world Y with `dome_rotation`;
`repeat: [3, 3]` draws nine dots with no texture, and a `pattern.texture` with a `sheet` plus the
animatable `frame` param draws an animated sky. The pass is gated on the depth sky test, so it
never touches terrain, the hand or the GUI. Additive: a new effect type only, no existing effect
changed, no datapack field renamed, `PROTOCOL_VERSION` unchanged, and no existing shader's UBO
layout touched. The rendering is not verified in game by the author;
like every post effect it composes over an Iris shaderpack's sky.
- **`light_beam` can end on a chosen point.** Three optional params, `end_at_x`/`end_at_y`/`end_at_z`,
name a world point the **far end** of the beam lands on: the anchor is derived from it
(`end − axis × height`), so the geometry finishes exactly there — with the default axis the beam
hangs straight down from `height` above the point, which is how a beam comes out of the sky. They
are ordinary animatable params, so the landing point can be keyframed, driven by an expression or
bound, and a beam from A to B is `end_at = B` with the axis and length computed between the two
points. Unset, the beam behaves exactly as before. No shader, pipeline, UBO or wire change.
- **`light_beam` can point in any direction.** Three new params, `dir_x`/`dir_y`/`dir_z` (defaults
`0`/`1`/`0`), replace the fixed vertical axis, so a beam can be aimed at an angle — e.g. from a
point on the sky. They are ordinary animatable params, so they can be keyframed, driven by an
expression or bound, including to the camera look (`{"bind": "look_x"}` etc.). With the defaults the
beam renders exactly as before. No shader, pipeline, UBO or wire change.
- **Sky masks.** Two new mask leaves address the sky instead of the world: `sky` (the whole visible
sky, no parameters) and a 2D shape (`circle`/`ellipse`/`rect`/`polygon`) in the new
`space: "dome"`, which places it on an equirectangular map of the sky with the centre authored as
`[yaw, pitch]` degrees. A dome leaf is sky-occluded by construction — a hill or a wall occupying a
dome direction is never tinted — and both fail closed (zero coverage) when no trustworthy depth is
available. This is what lets any existing screen effect be restricted to the sky, e.g. a green or
glitching sky. Additive: no datapack field was renamed, the wire format and `PROTOCOL_VERSION` are
unchanged and the coverage UBO layout did not change. The far-depth test is verified in game on all
three lines (26.2, 26.1.2, 1.21.11). The sun, the moon and the clouds are drawn without depth, so a
`sky`/`dome` leaf covers them too (a green sky turns the sun green); the horizon fog is baked into
the terrain, so it is not recoloured and a strong tint can leave a seam there.
- **`volume: "aura"` on a world GLSL-plugin mask leaf.** A custom mask leaf registered with
`VFXAPI.registerMaskShapeGlsl` can now be a real volume instead of a surface-only shape: the pixel's
view ray is sphere-traced through the plugin's own distance field and the effect fills the volume —
air and sky included — wherever the scene does not occlude it, with the same silhouette, edge fade
and occlusion maths as a built-in `sphere`/`box` aura. A composed custom leaf (no raw SDF to march)
and a screen-space plugin leaf are per-file parse errors naming the reason rather than silent
fallbacks. An optional `vec4 vfx_shape_custom_bounds()` in the plugin source (world centre and
radius) acts as an analytic broad phase: a ray that misses it is rejected without evaluating the SDF,
which makes the average cost proportional to the volume's screen area instead of the whole frame. A
plugin that declares neither function compiles and behaves exactly as before; the wire format,
`PROTOCOL_VERSION` and the coverage UBO layout are unchanged.
### Changed
- **`sky_pattern` addresses the sky with `patch` or `fill` — the legacy equirectangular `dome` mode
was removed before release.** A single global chart of a sphere is topologically unable to tile it
cleanly: `u` is undefined at the poles (a whole-sky pattern winds into a funnel at the zenith) and
`u` must wrap at ±180° yaw (a mandatory visible seam / mirror axis) — properties of the chart, not
formula bugs, so no formula fixes them. The effect therefore offers only local-chart modes:
**`patch`** (default, `0`) is one gnomonic decal at the anchor (no pole, no seam, clean discard
past the decal horizon) and **`fill`** (`1`) covers the whole sphere with three orthographic charts
blended by a sharpened partition of unity (no pole convergence, no seam anywhere; the charts'
coverage and colour are blended, never their UVs). `"dome"` and any other unknown value are now a
per-file parse error naming the accepted values. `sky_mode` is appended last to the Config UBO, so
no existing shader's layout changes; no datapack field was renamed and `PROTOCOL_VERSION` is
unchanged. The effect is not in any release yet, so removing `dome` breaks nothing. A full skybox
**cube** remains a separate future feature.
- **A custom mask leaf in `surface` mode no longer tints the sky.** The surface path classifies only
what the depth buffer contains, so a sky pixel contributes no coverage. Built-in shapes already
behaved this way (their bounded distance field puts a far-plane point outside the shape); the gate is
now explicit for custom leaves, whose SDF may be intentionally unbounded. A plugin that relied on
painting the sky from a `surface` leaf will no longer do so.
- **A built-in world GLSL mask plugin, `vfxweaver:blobs_glsl`.** The only shipped GLSL plugin was
`vfxweaver:ringed_glsl`, a screen-space 2D figure, so `space: "world"` plugin leaves had no volume
to march and the dynamic-data demo could not show the data driving anything. `vfxweaver:blobs_glsl`
is a genuine world-space SDF — a union of up to eight spheres, negative inside — whose sphere
centres/radii come from the leaf's dynamic data (`mask.p<N>.d0..d31`, four floats per sphere) and
whose `p0..p7` are global controls (offset, radius scale/bias, count). It also declares the optional
`vfx_shape_custom_bounds()` broad phase over the same data+params extent, so `volume: "aura"` with
it is cheap. It works for both `surface` and `aura`; the three mask demos use it. The plugin leaf
shape is inert on the `1.21.11` node (no shader-source hook). Additive: `vfxweaver:ringed_glsl`
stays the screen reference, no existing pass's UBO/wire layout changes, `PROTOCOL_VERSION` unchanged.
See [masks](guide/datapack/masks.md).
### Fixed
- **`film_grain` noise no longer repeats in a fixed pattern.** The cells were hashed with a
`fract(sin(dot(...)))` hash, which is only well distributed for large inputs: on the small integers a
pixel cell produces it repeats in a visible lattice, so the frame read as one small tile stamped over
and over. Cells are now hashed with an integer bit-mix, and the per-frame offset is an integer tick
mix rather than a `fract()`-scaled float.
- **`film_grain` and `pixelate` cell grids grow from the centre, not the corner.** Both derived their
grid straight from the UV, so an animated cell size expanded the pattern out of the bottom-left
corner; the grid is now measured from the screen centre. Effects whose pattern is per-scanline
(`scanlines`, `vhs`, `digital_glitch`) are deliberately unchanged — those bands run from the top edge
and have no centre to grow from.
- **`sky_pattern` `anchor: "stars"` really tracks the star field.** The anchor previously drove
`dome_rotation`, a world-**Y** spin, while vanilla rotates the star sphere about a different axis
(its pose is `Ry(-90°) · Rx(starAngle)`), so a `stars` pattern was only an approximation and turned
about the wrong axis. The sky shader now takes the sampled direction into the star sphere's own
frame with the exact inverse, so the pattern is locked to the star material. `dome_rotation` is not
hijacked — it stays the author's world-Y spin, applied first. Two internal Config fields are
appended after `sky_mode`; no datapack field was renamed and `PROTOCOL_VERSION` is unchanged. See
[sky_pattern](guide/effects/screen/sky-pattern.md).
- **`sky_pattern` `patch` decals have no built-in clip, and the ring demo no longer circles the
player.** A `patch` is a flat sign on the tangent plane: it is evaluated out to the tangent-plane
horizon, where a pixel behind the decal (`dot(dir, anchor) <= 0`) is discarded - no wrapping, no
pole convergence and no built-in clip. A large patch can therefore show that horizon as a straight
cut; restrict the effect with a mask if you need a clean edge. (An earlier unit-disc bound was
removed: it visibly cut the figure.) The demos use each projection for what it is for:
`show_sky_pattern_texture` (a whole-sky texture) and `sky_cracks` are `fill`, while
`show_sky_pattern` (the ring) and `nine_red_pixels` are small `patch` decals. The ring demo no
longer animates `dome_rotation` - in `patch` mode that moves the decal along its latitude, around
the sky, so it now animates only `tile_scale` and `opacity` and stays at its spot. The effect is
not in any release yet; no datapack field was renamed and `PROTOCOL_VERSION` is unchanged.
release
VFXWeaver API 2.0.2 (1.21.11, NeoForge)
2.0.223 сент. 2026 г.16Minecraft: 1.21.11neoforge
Что нового
## v2.0.2
A GLSL mask plugin shape can now read live per-leaf float data, so its geometry can move every tick without recompiling the shader variant. No datapack format or wire format changed (`PROTOCOL_VERSION` stays 6), and an existing plugin that ignores the new helper compiles and behaves exactly as before.
### Added
- **GLSL mask plugin shapes can read live per-leaf float data.** A custom mask leaf now carries 32 reserved dynamic floats, `mask.p<N>.d0 ... d31`, authored as a `"data": [ ... ]` array on the leaf or set every tick with the new `VFXAPI.maskData` / `VFXAPI.sendMaskData` (or `setParam` / `sendSetParam` on a single slot). A plugin reads its own leaf's values through the wrapper helper `vfx_mask_data(vfx_shape_data_base + j)`, so a set of moving primitives far larger than the eight animatable params can be driven without recompiling the shader variant.
### Changed
- **The mask coverage UBO gains a `shape_data` array** (a vec4-packed, per-leaf slice; 18 fields, 2336 bytes, appended after every existing field).
- **The network parameter cap is raised 32 to 256** so a large mask's resolved parameter map fits. The wire format is unchanged and `PROTOCOL_VERSION` stays 6; the cap is read-side, so a 2.0.1 client still caps the map at 32 and a many-slot mask needs a 2.0.2 client.
### Notes
- A mask's custom leaves may reference only one distinct GLSL plugin id; two different plugin ids in one mask fail to compile and fall back to neutral coverage.
- On 1.21.11 there is no shader-source hook, so a GLSL-plugin shape is inert there (the composed-SDF kind works on every node).
release
VFXWeaver API 2.0.2 (1.21.11)
2.0.223 сент. 2026 г.35Minecraft: 1.21.11fabric
Что нового
## v2.0.2
A GLSL mask plugin shape can now read live per-leaf float data, so its geometry can move every tick without recompiling the shader variant. No datapack format or wire format changed (`PROTOCOL_VERSION` stays 6), and an existing plugin that ignores the new helper compiles and behaves exactly as before.
### Added
- **GLSL mask plugin shapes can read live per-leaf float data.** A custom mask leaf now carries 32 reserved dynamic floats, `mask.p<N>.d0 ... d31`, authored as a `"data": [ ... ]` array on the leaf or set every tick with the new `VFXAPI.maskData` / `VFXAPI.sendMaskData` (or `setParam` / `sendSetParam` on a single slot). A plugin reads its own leaf's values through the wrapper helper `vfx_mask_data(vfx_shape_data_base + j)`, so a set of moving primitives far larger than the eight animatable params can be driven without recompiling the shader variant.
### Changed
- **The mask coverage UBO gains a `shape_data` array** (a vec4-packed, per-leaf slice; 18 fields, 2336 bytes, appended after every existing field).
- **The network parameter cap is raised 32 to 256** so a large mask's resolved parameter map fits. The wire format is unchanged and `PROTOCOL_VERSION` stays 6; the cap is read-side, so a 2.0.1 client still caps the map at 32 and a many-slot mask needs a 2.0.2 client.
### Notes
- A mask's custom leaves may reference only one distinct GLSL plugin id; two different plugin ids in one mask fail to compile and fall back to neutral coverage.
- On 1.21.11 there is no shader-source hook, so a GLSL-plugin shape is inert there (the composed-SDF kind works on every node).
release
VFXWeaver API 2.0.2 (26.1.x, NeoForge)
2.0.223 сент. 2026 г.10Minecraft: 26.1, 26.1.1, 26.1.2neoforge
Что нового
## v2.0.2
A GLSL mask plugin shape can now read live per-leaf float data, so its geometry can move every tick without recompiling the shader variant. No datapack format or wire format changed (`PROTOCOL_VERSION` stays 6), and an existing plugin that ignores the new helper compiles and behaves exactly as before.
### Added
- **GLSL mask plugin shapes can read live per-leaf float data.** A custom mask leaf now carries 32 reserved dynamic floats, `mask.p<N>.d0 ... d31`, authored as a `"data": [ ... ]` array on the leaf or set every tick with the new `VFXAPI.maskData` / `VFXAPI.sendMaskData` (or `setParam` / `sendSetParam` on a single slot). A plugin reads its own leaf's values through the wrapper helper `vfx_mask_data(vfx_shape_data_base + j)`, so a set of moving primitives far larger than the eight animatable params can be driven without recompiling the shader variant.
### Changed
- **The mask coverage UBO gains a `shape_data` array** (a vec4-packed, per-leaf slice; 18 fields, 2336 bytes, appended after every existing field).
- **The network parameter cap is raised 32 to 256** so a large mask's resolved parameter map fits. The wire format is unchanged and `PROTOCOL_VERSION` stays 6; the cap is read-side, so a 2.0.1 client still caps the map at 32 and a many-slot mask needs a 2.0.2 client.
### Notes
- A mask's custom leaves may reference only one distinct GLSL plugin id; two different plugin ids in one mask fail to compile and fall back to neutral coverage.
- On 1.21.11 there is no shader-source hook, so a GLSL-plugin shape is inert there (the composed-SDF kind works on every node).
release
VFXWeaver API 2.0.2 (26.1.x)
2.0.223 сент. 2026 г.36Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
## v2.0.2
A GLSL mask plugin shape can now read live per-leaf float data, so its geometry can move every tick without recompiling the shader variant. No datapack format or wire format changed (`PROTOCOL_VERSION` stays 6), and an existing plugin that ignores the new helper compiles and behaves exactly as before.
### Added
- **GLSL mask plugin shapes can read live per-leaf float data.** A custom mask leaf now carries 32 reserved dynamic floats, `mask.p<N>.d0 ... d31`, authored as a `"data": [ ... ]` array on the leaf or set every tick with the new `VFXAPI.maskData` / `VFXAPI.sendMaskData` (or `setParam` / `sendSetParam` on a single slot). A plugin reads its own leaf's values through the wrapper helper `vfx_mask_data(vfx_shape_data_base + j)`, so a set of moving primitives far larger than the eight animatable params can be driven without recompiling the shader variant.
### Changed
- **The mask coverage UBO gains a `shape_data` array** (a vec4-packed, per-leaf slice; 18 fields, 2336 bytes, appended after every existing field).
- **The network parameter cap is raised 32 to 256** so a large mask's resolved parameter map fits. The wire format is unchanged and `PROTOCOL_VERSION` stays 6; the cap is read-side, so a 2.0.1 client still caps the map at 32 and a many-slot mask needs a 2.0.2 client.
### Notes
- A mask's custom leaves may reference only one distinct GLSL plugin id; two different plugin ids in one mask fail to compile and fall back to neutral coverage.
- On 1.21.11 there is no shader-source hook, so a GLSL-plugin shape is inert there (the composed-SDF kind works on every node).
release
VFXWeaver API 2.0.2 (26.2, NeoForge)
2.0.223 сент. 2026 г.14Minecraft: 26.2neoforge
Что нового
## v2.0.2
A GLSL mask plugin shape can now read live per-leaf float data, so its geometry can move every tick without recompiling the shader variant. No datapack format or wire format changed (`PROTOCOL_VERSION` stays 6), and an existing plugin that ignores the new helper compiles and behaves exactly as before.
### Added
- **GLSL mask plugin shapes can read live per-leaf float data.** A custom mask leaf now carries 32 reserved dynamic floats, `mask.p<N>.d0 ... d31`, authored as a `"data": [ ... ]` array on the leaf or set every tick with the new `VFXAPI.maskData` / `VFXAPI.sendMaskData` (or `setParam` / `sendSetParam` on a single slot). A plugin reads its own leaf's values through the wrapper helper `vfx_mask_data(vfx_shape_data_base + j)`, so a set of moving primitives far larger than the eight animatable params can be driven without recompiling the shader variant.
### Changed
- **The mask coverage UBO gains a `shape_data` array** (a vec4-packed, per-leaf slice; 18 fields, 2336 bytes, appended after every existing field).
- **The network parameter cap is raised 32 to 256** so a large mask's resolved parameter map fits. The wire format is unchanged and `PROTOCOL_VERSION` stays 6; the cap is read-side, so a 2.0.1 client still caps the map at 32 and a many-slot mask needs a 2.0.2 client.
### Notes
- A mask's custom leaves may reference only one distinct GLSL plugin id; two different plugin ids in one mask fail to compile and fall back to neutral coverage.
- On 1.21.11 there is no shader-source hook, so a GLSL-plugin shape is inert there (the composed-SDF kind works on every node).
release
VFXWeaver API 2.0.2 (26.2)
2.0.223 сент. 2026 г.16Minecraft: 26.2fabric
Что нового
## v2.0.2
A GLSL mask plugin shape can now read live per-leaf float data, so its geometry can move every tick without recompiling the shader variant. No datapack format or wire format changed (`PROTOCOL_VERSION` stays 6), and an existing plugin that ignores the new helper compiles and behaves exactly as before.
### Added
- **GLSL mask plugin shapes can read live per-leaf float data.** A custom mask leaf now carries 32 reserved dynamic floats, `mask.p<N>.d0 ... d31`, authored as a `"data": [ ... ]` array on the leaf or set every tick with the new `VFXAPI.maskData` / `VFXAPI.sendMaskData` (or `setParam` / `sendSetParam` on a single slot). A plugin reads its own leaf's values through the wrapper helper `vfx_mask_data(vfx_shape_data_base + j)`, so a set of moving primitives far larger than the eight animatable params can be driven without recompiling the shader variant.
### Changed
- **The mask coverage UBO gains a `shape_data` array** (a vec4-packed, per-leaf slice; 18 fields, 2336 bytes, appended after every existing field).
- **The network parameter cap is raised 32 to 256** so a large mask's resolved parameter map fits. The wire format is unchanged and `PROTOCOL_VERSION` stays 6; the cap is read-side, so a 2.0.1 client still caps the map at 32 and a many-slot mask needs a 2.0.2 client.
### Notes
- A mask's custom leaves may reference only one distinct GLSL plugin id; two different plugin ids in one mask fail to compile and fall back to neutral coverage.
- On 1.21.11 there is no shader-source hook, so a GLSL-plugin shape is inert there (the composed-SDF kind works on every node).
release
VFXWeaver API 2.0.1 (1.21.11, NeoForge)
2.0.122 сент. 2026 г.4Minecraft: 1.21.11neoforge
Что нового
## v2.0.1
A correctness release - every change is a bug fix. No datapack format, network protocol or Java API change, so existing worlds and packs keep working.
### Added
- **Entity selectors support `tag=`, `name=`, `distance=`, `limit=` and `sort=`** (each optionally negated or quoted), alongside `@s`/`@p`/`@a`/`@r`/`@e`/bare name and `type=`. A selector outside the supported subset now fails closed and warns once, naming the argument, instead of silently binding the wrong entity.
### Changed
- **The `blur` effect uses a fixed-tap separable Gaussian**, so an animated radius no longer pops: both passes always take 12 taps per side with fixed weights spanning ±3σ, and only the sample spacing scales with the radius.
### Fixed
- **VFX effects in Flashback replays follow the replay timeline instead of the wall clock.** Pausing holds the effects, a scrub places or removes them (including effects from other mods and effects already running when the recording started), and a recorded play or live edit only takes effect when the replay time reaches its tick.
- **Looping and persistent effects are recorded into replays** - they used to be skipped.
- **A recorded entity tint keeps its resolved entity target and animates from the effect's replay age**, instead of losing its entity or freezing its parameters.
- **A paused replay can still be inspected with stop-motion running**, and scrubbing back past an effect's trigger removes it.
- **The replay-timeline integration works on 1.21.11 Fabric again** (Flashback 0.39.9 declares the playback field private); the playback symbols are resolved individually and tolerantly, so a Flashback change degrades only the feature that needs it.
- **A player's effects survive a re-login and keep counting while offline** - the elapsed time away is included in the effect's age, and looping/persistent effects resume at the phase they would be in.
release
VFXWeaver API 2.0.1 (26.1.x, NeoForge)
2.0.122 сент. 2026 г.8Minecraft: 26.1, 26.1.1, 26.1.2neoforge
Что нового
## v2.0.1
A correctness release - every change is a bug fix. No datapack format, network protocol or Java API change, so existing worlds and packs keep working.
### Added
- **Entity selectors support `tag=`, `name=`, `distance=`, `limit=` and `sort=`** (each optionally negated or quoted), alongside `@s`/`@p`/`@a`/`@r`/`@e`/bare name and `type=`. A selector outside the supported subset now fails closed and warns once, naming the argument, instead of silently binding the wrong entity.
### Changed
- **The `blur` effect uses a fixed-tap separable Gaussian**, so an animated radius no longer pops: both passes always take 12 taps per side with fixed weights spanning ±3σ, and only the sample spacing scales with the radius.
### Fixed
- **VFX effects in Flashback replays follow the replay timeline instead of the wall clock.** Pausing holds the effects, a scrub places or removes them (including effects from other mods and effects already running when the recording started), and a recorded play or live edit only takes effect when the replay time reaches its tick.
- **Looping and persistent effects are recorded into replays** - they used to be skipped.
- **A recorded entity tint keeps its resolved entity target and animates from the effect's replay age**, instead of losing its entity or freezing its parameters.
- **A paused replay can still be inspected with stop-motion running**, and scrubbing back past an effect's trigger removes it.
- **The replay-timeline integration works on 1.21.11 Fabric again** (Flashback 0.39.9 declares the playback field private); the playback symbols are resolved individually and tolerantly, so a Flashback change degrades only the feature that needs it.
- **A player's effects survive a re-login and keep counting while offline** - the elapsed time away is included in the effect's age, and looping/persistent effects resume at the phase they would be in.
release
VFXWeaver API 2.0.1 (26.2, NeoForge)
2.0.122 сент. 2026 г.12Minecraft: 26.2neoforge
Что нового
## v2.0.1
A correctness release - every change is a bug fix. No datapack format, network protocol or Java API change, so existing worlds and packs keep working.
### Added
- **Entity selectors support `tag=`, `name=`, `distance=`, `limit=` and `sort=`** (each optionally negated or quoted), alongside `@s`/`@p`/`@a`/`@r`/`@e`/bare name and `type=`. A selector outside the supported subset now fails closed and warns once, naming the argument, instead of silently binding the wrong entity.
### Changed
- **The `blur` effect uses a fixed-tap separable Gaussian**, so an animated radius no longer pops: both passes always take 12 taps per side with fixed weights spanning ±3σ, and only the sample spacing scales with the radius.
### Fixed
- **VFX effects in Flashback replays follow the replay timeline instead of the wall clock.** Pausing holds the effects, a scrub places or removes them (including effects from other mods and effects already running when the recording started), and a recorded play or live edit only takes effect when the replay time reaches its tick.
- **Looping and persistent effects are recorded into replays** - they used to be skipped.
- **A recorded entity tint keeps its resolved entity target and animates from the effect's replay age**, instead of losing its entity or freezing its parameters.
- **A paused replay can still be inspected with stop-motion running**, and scrubbing back past an effect's trigger removes it.
- **The replay-timeline integration works on 1.21.11 Fabric again** (Flashback 0.39.9 declares the playback field private); the playback symbols are resolved individually and tolerantly, so a Flashback change degrades only the feature that needs it.
- **A player's effects survive a re-login and keep counting while offline** - the elapsed time away is included in the effect's age, and looping/persistent effects resume at the phase they would be in.
release
VFXWeaver API 2.0.1 (1.21.11)
2.0.122 сент. 2026 г.15Minecraft: 1.21.11fabric
Что нового
## v2.0.1
A correctness release - every change is a bug fix. No datapack format, network protocol or Java API change, so existing worlds and packs keep working.
### Added
- **Entity selectors support `tag=`, `name=`, `distance=`, `limit=` and `sort=`** (each optionally negated or quoted), alongside `@s`/`@p`/`@a`/`@r`/`@e`/bare name and `type=`. A selector outside the supported subset now fails closed and warns once, naming the argument, instead of silently binding the wrong entity.
### Changed
- **The `blur` effect uses a fixed-tap separable Gaussian**, so an animated radius no longer pops: both passes always take 12 taps per side with fixed weights spanning ±3σ, and only the sample spacing scales with the radius.
### Fixed
- **VFX effects in Flashback replays follow the replay timeline instead of the wall clock.** Pausing holds the effects, a scrub places or removes them (including effects from other mods and effects already running when the recording started), and a recorded play or live edit only takes effect when the replay time reaches its tick.
- **Looping and persistent effects are recorded into replays** - they used to be skipped.
- **A recorded entity tint keeps its resolved entity target and animates from the effect's replay age**, instead of losing its entity or freezing its parameters.
- **A paused replay can still be inspected with stop-motion running**, and scrubbing back past an effect's trigger removes it.
- **The replay-timeline integration works on 1.21.11 Fabric again** (Flashback 0.39.9 declares the playback field private); the playback symbols are resolved individually and tolerantly, so a Flashback change degrades only the feature that needs it.
- **A player's effects survive a re-login and keep counting while offline** - the elapsed time away is included in the effect's age, and looping/persistent effects resume at the phase they would be in.
release
VFXWeaver API 2.0.1 (26.1.x)
2.0.122 сент. 2026 г.9Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
## v2.0.1
A correctness release - every change is a bug fix. No datapack format, network protocol or Java API change, so existing worlds and packs keep working.
### Added
- **Entity selectors support `tag=`, `name=`, `distance=`, `limit=` and `sort=`** (each optionally negated or quoted), alongside `@s`/`@p`/`@a`/`@r`/`@e`/bare name and `type=`. A selector outside the supported subset now fails closed and warns once, naming the argument, instead of silently binding the wrong entity.
### Changed
- **The `blur` effect uses a fixed-tap separable Gaussian**, so an animated radius no longer pops: both passes always take 12 taps per side with fixed weights spanning ±3σ, and only the sample spacing scales with the radius.
### Fixed
- **VFX effects in Flashback replays follow the replay timeline instead of the wall clock.** Pausing holds the effects, a scrub places or removes them (including effects from other mods and effects already running when the recording started), and a recorded play or live edit only takes effect when the replay time reaches its tick.
- **Looping and persistent effects are recorded into replays** - they used to be skipped.
- **A recorded entity tint keeps its resolved entity target and animates from the effect's replay age**, instead of losing its entity or freezing its parameters.
- **A paused replay can still be inspected with stop-motion running**, and scrubbing back past an effect's trigger removes it.
- **The replay-timeline integration works on 1.21.11 Fabric again** (Flashback 0.39.9 declares the playback field private); the playback symbols are resolved individually and tolerantly, so a Flashback change degrades only the feature that needs it.
- **A player's effects survive a re-login and keep counting while offline** - the elapsed time away is included in the effect's age, and looping/persistent effects resume at the phase they would be in.
release
VFXWeaver API 2.0.1 (26.2)
2.0.122 сент. 2026 г.14Minecraft: 26.2fabric
Что нового
## v2.0.1
A correctness release - every change is a bug fix. No datapack format, network protocol or Java API change, so existing worlds and packs keep working.
### Added
- **Entity selectors support `tag=`, `name=`, `distance=`, `limit=` and `sort=`** (each optionally negated or quoted), alongside `@s`/`@p`/`@a`/`@r`/`@e`/bare name and `type=`. A selector outside the supported subset now fails closed and warns once, naming the argument, instead of silently binding the wrong entity.
### Changed
- **The `blur` effect uses a fixed-tap separable Gaussian**, so an animated radius no longer pops: both passes always take 12 taps per side with fixed weights spanning ±3σ, and only the sample spacing scales with the radius.
### Fixed
- **VFX effects in Flashback replays follow the replay timeline instead of the wall clock.** Pausing holds the effects, a scrub places or removes them (including effects from other mods and effects already running when the recording started), and a recorded play or live edit only takes effect when the replay time reaches its tick.
- **Looping and persistent effects are recorded into replays** - they used to be skipped.
- **A recorded entity tint keeps its resolved entity target and animates from the effect's replay age**, instead of losing its entity or freezing its parameters.
- **A paused replay can still be inspected with stop-motion running**, and scrubbing back past an effect's trigger removes it.
- **The replay-timeline integration works on 1.21.11 Fabric again** (Flashback 0.39.9 declares the playback field private); the playback symbols are resolved individually and tolerantly, so a Flashback change degrades only the feature that needs it.
- **A player's effects survive a re-login and keep counting while offline** - the elapsed time away is included in the effect's age, and looping/persistent effects resume at the phase they would be in.
release
VFXWeaver API 2.0.0 (1.21.11, NeoForge)
2.0.020 сент. 2026 г.6Minecraft: 1.21.11neoforge
Что нового
## v2.0.0
### Added
- **Value graphs** - an effect can drive any numeric input from a `graph` + `inputs` block: constant, time, random, noise, curve, math, mix, clamp, remap, bind and expr nodes, compare/boolean/if/switch logic (short-circuit) and reusable subgraphs. Additive - a definition without them behaves exactly as before.
- **Per-pixel fields** - an input may carry a shader-side field (noise, shape, gradient, curve, texture, depth, depth_gradient, normal_facing, screen_uv, world_pos and bounded compositions). First consumers: `dent.intensity` and `color_grade.tint_r`.
- **Masks** - restrict where an effect applies with a composed tree (union/intersection/difference/invert) of screen shapes, world volumes, block-geometry leaves and code-registered GLSL shapes. Bindings fail closed per leaf. New `VFXAPI.sendMaskMove` / `VFXAPI.maskMove`.
- **surface_pattern** - a world-anchored figure (circle/ellipse/rect/polygon, tiled) projected onto the terrain behind each pixel, with face selection, a min/max band, optional planar stitch and an optional texture (atlas/source/channel/sheet/aspect). Additive and off by default.
- **Spark particles** - the `particles` effect can emit glowing additive sprites via `"particle": "spark"` or a `kind: "spark"` preset (count, speed, spread, life, gravity, bounce, size, trail, glow, size_curve/color_curve). Built-ins `vfxweaver:ember` and `vfxweaver:sparks`; `VFXAPI.registerSpark`.
- **`/vfx stop [<player>]`** - stops every active effect of a player; new `VFXAPI.sendStopAll(ServerPlayer)`.
### Changed
- **Scene depth now works on every supported line, not only 26.2.** The depth/world reconstruction (surface_pattern, depth fields, depth-needing mask families) is injected as a per-node `VFX_DEPTH_REVERSED` shader define: 26.2 is reversed depth, 26.1.2 and 1.21.11 are standard.
- The effect clock and camera/player snapshots advance before screen layer 0, so layer-0 effects use the current frame.
- Mask coverage is screen-space, computed at layer 0 against the intact scene depth.
- Test/demo effects are iterated as a datapack, not baked into the mod.
### Fixed
- Network `sendSetParamExpr` and `sendMove` now do what they say (they were unreachable and replayed as a play, restarting the effect).
- A `persistent: true` effect without loop now animates and emits again; per-instance particle state is pruned when an effect stops.
- Textured `surface_pattern` projections resolve on every node; textures sample with crisp NEAREST filtering; the std140 layout guard no longer aborts the post layer.
- Numerous mask, field and surface_pattern parse/projection/depth-gate fixes across all three Minecraft lines, plus less per-frame work in the post chain.
release
VFXWeaver API 2.0.0 (26.1.x, NeoForge)
2.0.020 сент. 2026 г.26Minecraft: 26.1, 26.1.1, 26.1.2neoforge
Что нового
## v2.0.0
### Added
- **Value graphs** - an effect can drive any numeric input from a `graph` + `inputs` block: constant, time, random, noise, curve, math, mix, clamp, remap, bind and expr nodes, compare/boolean/if/switch logic (short-circuit) and reusable subgraphs. Additive - a definition without them behaves exactly as before.
- **Per-pixel fields** - an input may carry a shader-side field (noise, shape, gradient, curve, texture, depth, depth_gradient, normal_facing, screen_uv, world_pos and bounded compositions). First consumers: `dent.intensity` and `color_grade.tint_r`.
- **Masks** - restrict where an effect applies with a composed tree (union/intersection/difference/invert) of screen shapes, world volumes, block-geometry leaves and code-registered GLSL shapes. Bindings fail closed per leaf. New `VFXAPI.sendMaskMove` / `VFXAPI.maskMove`.
- **surface_pattern** - a world-anchored figure (circle/ellipse/rect/polygon, tiled) projected onto the terrain behind each pixel, with face selection, a min/max band, optional planar stitch and an optional texture (atlas/source/channel/sheet/aspect). Additive and off by default.
- **Spark particles** - the `particles` effect can emit glowing additive sprites via `"particle": "spark"` or a `kind: "spark"` preset (count, speed, spread, life, gravity, bounce, size, trail, glow, size_curve/color_curve). Built-ins `vfxweaver:ember` and `vfxweaver:sparks`; `VFXAPI.registerSpark`.
- **`/vfx stop [<player>]`** - stops every active effect of a player; new `VFXAPI.sendStopAll(ServerPlayer)`.
### Changed
- **Scene depth now works on every supported line, not only 26.2.** The depth/world reconstruction (surface_pattern, depth fields, depth-needing mask families) is injected as a per-node `VFX_DEPTH_REVERSED` shader define: 26.2 is reversed depth, 26.1.2 and 1.21.11 are standard.
- The effect clock and camera/player snapshots advance before screen layer 0, so layer-0 effects use the current frame.
- Mask coverage is screen-space, computed at layer 0 against the intact scene depth.
- Test/demo effects are iterated as a datapack, not baked into the mod.
### Fixed
- Network `sendSetParamExpr` and `sendMove` now do what they say (they were unreachable and replayed as a play, restarting the effect).
- A `persistent: true` effect without loop now animates and emits again; per-instance particle state is pruned when an effect stops.
- Textured `surface_pattern` projections resolve on every node; textures sample with crisp NEAREST filtering; the std140 layout guard no longer aborts the post layer.
- Numerous mask, field and surface_pattern parse/projection/depth-gate fixes across all three Minecraft lines, plus less per-frame work in the post chain.
release
VFXWeaver API 2.0.0 (26.2, NeoForge)
2.0.020 сент. 2026 г.14Minecraft: 26.2neoforge
Что нового
## v2.0.0
### Added
- **Value graphs** - an effect can drive any numeric input from a `graph` + `inputs` block: constant, time, random, noise, curve, math, mix, clamp, remap, bind and expr nodes, compare/boolean/if/switch logic (short-circuit) and reusable subgraphs. Additive - a definition without them behaves exactly as before.
- **Per-pixel fields** - an input may carry a shader-side field (noise, shape, gradient, curve, texture, depth, depth_gradient, normal_facing, screen_uv, world_pos and bounded compositions). First consumers: `dent.intensity` and `color_grade.tint_r`.
- **Masks** - restrict where an effect applies with a composed tree (union/intersection/difference/invert) of screen shapes, world volumes, block-geometry leaves and code-registered GLSL shapes. Bindings fail closed per leaf. New `VFXAPI.sendMaskMove` / `VFXAPI.maskMove`.
- **surface_pattern** - a world-anchored figure (circle/ellipse/rect/polygon, tiled) projected onto the terrain behind each pixel, with face selection, a min/max band, optional planar stitch and an optional texture (atlas/source/channel/sheet/aspect). Additive and off by default.
- **Spark particles** - the `particles` effect can emit glowing additive sprites via `"particle": "spark"` or a `kind: "spark"` preset (count, speed, spread, life, gravity, bounce, size, trail, glow, size_curve/color_curve). Built-ins `vfxweaver:ember` and `vfxweaver:sparks`; `VFXAPI.registerSpark`.
- **`/vfx stop [<player>]`** - stops every active effect of a player; new `VFXAPI.sendStopAll(ServerPlayer)`.
### Changed
- **Scene depth now works on every supported line, not only 26.2.** The depth/world reconstruction (surface_pattern, depth fields, depth-needing mask families) is injected as a per-node `VFX_DEPTH_REVERSED` shader define: 26.2 is reversed depth, 26.1.2 and 1.21.11 are standard.
- The effect clock and camera/player snapshots advance before screen layer 0, so layer-0 effects use the current frame.
- Mask coverage is screen-space, computed at layer 0 against the intact scene depth.
- Test/demo effects are iterated as a datapack, not baked into the mod.
### Fixed
- Network `sendSetParamExpr` and `sendMove` now do what they say (they were unreachable and replayed as a play, restarting the effect).
- A `persistent: true` effect without loop now animates and emits again; per-instance particle state is pruned when an effect stops.
- Textured `surface_pattern` projections resolve on every node; textures sample with crisp NEAREST filtering; the std140 layout guard no longer aborts the post layer.
- Numerous mask, field and surface_pattern parse/projection/depth-gate fixes across all three Minecraft lines, plus less per-frame work in the post chain.
release
VFXWeaver API 2.0.0 (1.21.11)
2.0.020 сент. 2026 г.12Minecraft: 1.21.11fabric
Что нового
## v2.0.0
### Added
- **Value graphs** - an effect can drive any numeric input from a `graph` + `inputs` block: constant, time, random, noise, curve, math, mix, clamp, remap, bind and expr nodes, compare/boolean/if/switch logic (short-circuit) and reusable subgraphs. Additive - a definition without them behaves exactly as before.
- **Per-pixel fields** - an input may carry a shader-side field (noise, shape, gradient, curve, texture, depth, depth_gradient, normal_facing, screen_uv, world_pos and bounded compositions). First consumers: `dent.intensity` and `color_grade.tint_r`.
- **Masks** - restrict where an effect applies with a composed tree (union/intersection/difference/invert) of screen shapes, world volumes, block-geometry leaves and code-registered GLSL shapes. Bindings fail closed per leaf. New `VFXAPI.sendMaskMove` / `VFXAPI.maskMove`.
- **surface_pattern** - a world-anchored figure (circle/ellipse/rect/polygon, tiled) projected onto the terrain behind each pixel, with face selection, a min/max band, optional planar stitch and an optional texture (atlas/source/channel/sheet/aspect). Additive and off by default.
- **Spark particles** - the `particles` effect can emit glowing additive sprites via `"particle": "spark"` or a `kind: "spark"` preset (count, speed, spread, life, gravity, bounce, size, trail, glow, size_curve/color_curve). Built-ins `vfxweaver:ember` and `vfxweaver:sparks`; `VFXAPI.registerSpark`.
- **`/vfx stop [<player>]`** - stops every active effect of a player; new `VFXAPI.sendStopAll(ServerPlayer)`.
### Changed
- **Scene depth now works on every supported line, not only 26.2.** The depth/world reconstruction (surface_pattern, depth fields, depth-needing mask families) is injected as a per-node `VFX_DEPTH_REVERSED` shader define: 26.2 is reversed depth, 26.1.2 and 1.21.11 are standard.
- The effect clock and camera/player snapshots advance before screen layer 0, so layer-0 effects use the current frame.
- Mask coverage is screen-space, computed at layer 0 against the intact scene depth.
- Test/demo effects are iterated as a datapack, not baked into the mod.
### Fixed
- Network `sendSetParamExpr` and `sendMove` now do what they say (they were unreachable and replayed as a play, restarting the effect).
- A `persistent: true` effect without loop now animates and emits again; per-instance particle state is pruned when an effect stops.
- Textured `surface_pattern` projections resolve on every node; textures sample with crisp NEAREST filtering; the std140 layout guard no longer aborts the post layer.
- Numerous mask, field and surface_pattern parse/projection/depth-gate fixes across all three Minecraft lines, plus less per-frame work in the post chain.
release
VFXWeaver API 2.0.0 (26.1.x)
2.0.020 сент. 2026 г.26Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
## v2.0.0
### Added
- **Value graphs** - an effect can drive any numeric input from a `graph` + `inputs` block: constant, time, random, noise, curve, math, mix, clamp, remap, bind and expr nodes, compare/boolean/if/switch logic (short-circuit) and reusable subgraphs. Additive - a definition without them behaves exactly as before.
- **Per-pixel fields** - an input may carry a shader-side field (noise, shape, gradient, curve, texture, depth, depth_gradient, normal_facing, screen_uv, world_pos and bounded compositions). First consumers: `dent.intensity` and `color_grade.tint_r`.
- **Masks** - restrict where an effect applies with a composed tree (union/intersection/difference/invert) of screen shapes, world volumes, block-geometry leaves and code-registered GLSL shapes. Bindings fail closed per leaf. New `VFXAPI.sendMaskMove` / `VFXAPI.maskMove`.
- **surface_pattern** - a world-anchored figure (circle/ellipse/rect/polygon, tiled) projected onto the terrain behind each pixel, with face selection, a min/max band, optional planar stitch and an optional texture (atlas/source/channel/sheet/aspect). Additive and off by default.
- **Spark particles** - the `particles` effect can emit glowing additive sprites via `"particle": "spark"` or a `kind: "spark"` preset (count, speed, spread, life, gravity, bounce, size, trail, glow, size_curve/color_curve). Built-ins `vfxweaver:ember` and `vfxweaver:sparks`; `VFXAPI.registerSpark`.
- **`/vfx stop [<player>]`** - stops every active effect of a player; new `VFXAPI.sendStopAll(ServerPlayer)`.
### Changed
- **Scene depth now works on every supported line, not only 26.2.** The depth/world reconstruction (surface_pattern, depth fields, depth-needing mask families) is injected as a per-node `VFX_DEPTH_REVERSED` shader define: 26.2 is reversed depth, 26.1.2 and 1.21.11 are standard.
- The effect clock and camera/player snapshots advance before screen layer 0, so layer-0 effects use the current frame.
- Mask coverage is screen-space, computed at layer 0 against the intact scene depth.
- Test/demo effects are iterated as a datapack, not baked into the mod.
### Fixed
- Network `sendSetParamExpr` and `sendMove` now do what they say (they were unreachable and replayed as a play, restarting the effect).
- A `persistent: true` effect without loop now animates and emits again; per-instance particle state is pruned when an effect stops.
- Textured `surface_pattern` projections resolve on every node; textures sample with crisp NEAREST filtering; the std140 layout guard no longer aborts the post layer.
- Numerous mask, field and surface_pattern parse/projection/depth-gate fixes across all three Minecraft lines, plus less per-frame work in the post chain.
release
VFXWeaver API 2.0.0 (26.2)
2.0.020 сент. 2026 г.17Minecraft: 26.2fabric
Что нового
## v2.0.0
### Added
- **Value graphs** - an effect can drive any numeric input from a `graph` + `inputs` block: constant, time, random, noise, curve, math, mix, clamp, remap, bind and expr nodes, compare/boolean/if/switch logic (short-circuit) and reusable subgraphs. Additive - a definition without them behaves exactly as before.
- **Per-pixel fields** - an input may carry a shader-side field (noise, shape, gradient, curve, texture, depth, depth_gradient, normal_facing, screen_uv, world_pos and bounded compositions). First consumers: `dent.intensity` and `color_grade.tint_r`.
- **Masks** - restrict where an effect applies with a composed tree (union/intersection/difference/invert) of screen shapes, world volumes, block-geometry leaves and code-registered GLSL shapes. Bindings fail closed per leaf. New `VFXAPI.sendMaskMove` / `VFXAPI.maskMove`.
- **surface_pattern** - a world-anchored figure (circle/ellipse/rect/polygon, tiled) projected onto the terrain behind each pixel, with face selection, a min/max band, optional planar stitch and an optional texture (atlas/source/channel/sheet/aspect). Additive and off by default.
- **Spark particles** - the `particles` effect can emit glowing additive sprites via `"particle": "spark"` or a `kind: "spark"` preset (count, speed, spread, life, gravity, bounce, size, trail, glow, size_curve/color_curve). Built-ins `vfxweaver:ember` and `vfxweaver:sparks`; `VFXAPI.registerSpark`.
- **`/vfx stop [<player>]`** - stops every active effect of a player; new `VFXAPI.sendStopAll(ServerPlayer)`.
### Changed
- **Scene depth now works on every supported line, not only 26.2.** The depth/world reconstruction (surface_pattern, depth fields, depth-needing mask families) is injected as a per-node `VFX_DEPTH_REVERSED` shader define: 26.2 is reversed depth, 26.1.2 and 1.21.11 are standard.
- The effect clock and camera/player snapshots advance before screen layer 0, so layer-0 effects use the current frame.
- Mask coverage is screen-space, computed at layer 0 against the intact scene depth.
- Test/demo effects are iterated as a datapack, not baked into the mod.
### Fixed
- Network `sendSetParamExpr` and `sendMove` now do what they say (they were unreachable and replayed as a play, restarting the effect).
- A `persistent: true` effect without loop now animates and emits again; per-instance particle state is pruned when an effect stops.
- Textured `surface_pattern` projections resolve on every node; textures sample with crisp NEAREST filtering; the std140 layout guard no longer aborts the post layer.
- Numerous mask, field and surface_pattern parse/projection/depth-gate fixes across all three Minecraft lines, plus less per-frame work in the post chain.
release
VFXWeaver API 1.2.0 (1.21.11, NeoForge)
1.2.018 сент. 2026 г.4Minecraft: 1.21.11neoforge
Что нового
## v1.2.0 / Guide v32
### Added
- **Block-model particles (`particles` block mode + `vfx_particles` presets)** — the `particles` effect can now emit real block models instead of vanilla particles, chosen inline (`"particle": "block"` with a `block` state) or by preset id (`data/<namespace>/vfx_particles/<name>.json`, or `VFXAPI.registerBlockParticle`). Each particle has block-display brightness (`-1` = world light, `[blockLight, skyLight]`), gravity, air friction, optional world collision with surface friction and bounce, size, lifetime and spin; the `particles` params override the preset's defaults. `VFXAPI.spawnBlockParticle(spec, position, velocity)` spawns a single particle immediately on the client. Presets are **client-local and never synced**, live in a two-layer registry (datapack wins over code), and render as client-side display entities so vanilla interpolates their motion.
- **Item-model particles (`particles` item mode)** — the same particle engine can now draw real **item** models: `"particle": "item"` with an `item` id (e.g. `"minecraft:skeleton_skull"`), or a `vfx_particles` preset declaring `"item"` instead of `"block"` (exactly one of the two is required; a missing or unknown entry is a per-file parse error). Items render as `ItemDisplay` entities, with the same brightness/physics params as block particles. This is what makes **skull/head particles** possible — the block form has no baked model (a block-entity renderer draws it), the item form does. `VFXBlockParticleSpec.item(ItemStack)` / `builder(ItemStack)` build the spec from code; the API methods are unchanged.
- **Configurable block/item particle rotation** — `vfx_particles` presets (and the `VFXBlockParticleSpec` builder) gained `spin_mode` (`tumble` = the physical full-3D spin, `yaw` = a uniform spin about one axis, `none` = no rotation), `spin_axis` (`random`/`x`/`y`/`z`/`[x, y, z]`), `spin_random` (`0..1`: how random the initial orientation and tumble axis are; `0` = strictly upright and identical for every particle), `spin_friction` (`0..1`: how much the contacted block's friction damps the spin; `0` = never decays) and `spin_roll` (`0..1`: how much tangential impact speed feeds the tumble; `0` = no roll transfer). The `spin`/`spin_random`/`spin_friction`/`spin_roll` effect params override a named preset; the defaults reproduce the previous tumbling behaviour exactly, so existing presets are unchanged.
- **NeoForge support for all three Minecraft lines** — a NeoForge node is built beside each Fabric node from the same source tree (`26.2-neoforge`, `26.1.2-neoforge`, `1.21.11-neoforge`), so the project produces six jars, one per (Minecraft line, loader). The build scripts are split per loader (`build.fabric.gradle` / `build.neoforge.gradle`) and a platform layer (`dev.vfxweaver.platform` / `client.platform`) hides every loader API so the render, datapack, API and command code stays loader-agnostic. The only per-line loader API split is `<26.1`: NeoForge `21.11` has no submit-geometry event and reads the collector from `LevelRenderer.submitNodeStorage`, while NeoForge `26.1.2` matches `26.2`; dependency ranges stay per line (`deps.neo_compat` for NeoForge, `deps.mc_compat` for Minecraft). **Flashback recording is Fabric-only** — Flashback has no NeoForge build, so the compatibility layer no-ops there.
- **`vfxweaver:block_chain` built-in demo** — the `block_chain` effect type now ships a built-in definition, so it appears in `/vfx` tab-completion and `/vfx play vfxweaver:block_chain` works without writing a datapack. It hangs a 6-link `minecraft:iron_chain` from the local player (`pos_x/y/z` bound to `player_x/y/z`, `physics: 1`, `length: 6`, `sway: 0.4`), the same self-anchoring pattern as the `vfxweaver:particles` demo; built-in definitions now number 44.
### Changed
- **Block/item particles tumble like real falling cubes** — `spin` (degrees/tick) is now the magnitude of a full 3D angular velocity about a random axis, with a random full-3D initial orientation, instead of a yaw around world Y. The orientation is integrated each physics tick and slerped between ticks for rendering. On contact the tumble is damped by the contacted block's own friction (the same `Block#getFriction` rule `block_chain` uses), so a cube lands and stops spinning; a little of the tangential slip becomes roll, and a particle with no slip gains none. `spin = 0` now yields a static but randomly oriented model (previously upright).
### Fixed
- **Block/item particles move smoothly and spin on a sane axis** — the model particles were submitted as geometry with their own pose interpolation, which read as jerky stepping and rotated around an odd pivot. They are now driven through client-side `BlockDisplay`/`ItemDisplay` entities, so vanilla interpolates position between ticks, `spin` drives the model's orientation, and brightness/scale map onto the display's own brightness override and transformation. One display entity per live particle, removed on death/effect stop/world unload; the physics is unchanged.
- **Physics `block_chain` ropes now settle on surfaces instead of sliding forever** — the depenetration is a pure displacement (pos and prev shift by the same correction, the player-push idiom) so the push itself injects no velocity, and on contact the inward velocity is dropped while the remaining tangential velocity is damped every contacting tick by the contacted block's own friction (`Block#getFriction`: stone/air 0.6, ice 0.98). A rope therefore grips stone, glides on ice, and a joint lifted out of the ground gains no residual horizontal velocity.
- **Physics `block_chain` ropes no longer sink through the ground** — a rope joint that moved more than half a block into terrain in one tick was being ejected through the block's far face (nearest-face resolution), so the chain tunnelled downwards; the resolver now sweeps the path from the joint's previous position, stops at the first contact and pushes the joint back out through the face it entered, dropping only the inward normal component of its velocity and keeping the tangential part. Links rest on surfaces without sinking, a joint that spawns inside a block still comes free, and a moving anchor drags the chain across the ground (instead of the joint being reverted to its previous position and freezing there). Multi-box collision shapes are approximated by their union box.
- **NeoForge world overlays render on the `26.1.2` line** — overlays now capture the camera, the render buffers and the level renderer's submit storage from the level-render stage event; the earlier `SubmitCustomGeometryEvent` path left those unset on `26.1.2`, so every buffer-based overlay threw and `block_chain` geometry was submitted in a phase that was never drawn.
release
VFXWeaver API 1.2.0 (26.1.x, NeoForge)
1.2.018 сент. 2026 г.8Minecraft: 26.1, 26.1.1, 26.1.2neoforge
Что нового
## v1.2.0 / Guide v32
### Added
- **Block-model particles (`particles` block mode + `vfx_particles` presets)** — the `particles` effect can now emit real block models instead of vanilla particles, chosen inline (`"particle": "block"` with a `block` state) or by preset id (`data/<namespace>/vfx_particles/<name>.json`, or `VFXAPI.registerBlockParticle`). Each particle has block-display brightness (`-1` = world light, `[blockLight, skyLight]`), gravity, air friction, optional world collision with surface friction and bounce, size, lifetime and spin; the `particles` params override the preset's defaults. `VFXAPI.spawnBlockParticle(spec, position, velocity)` spawns a single particle immediately on the client. Presets are **client-local and never synced**, live in a two-layer registry (datapack wins over code), and render as client-side display entities so vanilla interpolates their motion.
- **Item-model particles (`particles` item mode)** — the same particle engine can now draw real **item** models: `"particle": "item"` with an `item` id (e.g. `"minecraft:skeleton_skull"`), or a `vfx_particles` preset declaring `"item"` instead of `"block"` (exactly one of the two is required; a missing or unknown entry is a per-file parse error). Items render as `ItemDisplay` entities, with the same brightness/physics params as block particles. This is what makes **skull/head particles** possible — the block form has no baked model (a block-entity renderer draws it), the item form does. `VFXBlockParticleSpec.item(ItemStack)` / `builder(ItemStack)` build the spec from code; the API methods are unchanged.
- **Configurable block/item particle rotation** — `vfx_particles` presets (and the `VFXBlockParticleSpec` builder) gained `spin_mode` (`tumble` = the physical full-3D spin, `yaw` = a uniform spin about one axis, `none` = no rotation), `spin_axis` (`random`/`x`/`y`/`z`/`[x, y, z]`), `spin_random` (`0..1`: how random the initial orientation and tumble axis are; `0` = strictly upright and identical for every particle), `spin_friction` (`0..1`: how much the contacted block's friction damps the spin; `0` = never decays) and `spin_roll` (`0..1`: how much tangential impact speed feeds the tumble; `0` = no roll transfer). The `spin`/`spin_random`/`spin_friction`/`spin_roll` effect params override a named preset; the defaults reproduce the previous tumbling behaviour exactly, so existing presets are unchanged.
- **NeoForge support for all three Minecraft lines** — a NeoForge node is built beside each Fabric node from the same source tree (`26.2-neoforge`, `26.1.2-neoforge`, `1.21.11-neoforge`), so the project produces six jars, one per (Minecraft line, loader). The build scripts are split per loader (`build.fabric.gradle` / `build.neoforge.gradle`) and a platform layer (`dev.vfxweaver.platform` / `client.platform`) hides every loader API so the render, datapack, API and command code stays loader-agnostic. The only per-line loader API split is `<26.1`: NeoForge `21.11` has no submit-geometry event and reads the collector from `LevelRenderer.submitNodeStorage`, while NeoForge `26.1.2` matches `26.2`; dependency ranges stay per line (`deps.neo_compat` for NeoForge, `deps.mc_compat` for Minecraft). **Flashback recording is Fabric-only** — Flashback has no NeoForge build, so the compatibility layer no-ops there.
- **`vfxweaver:block_chain` built-in demo** — the `block_chain` effect type now ships a built-in definition, so it appears in `/vfx` tab-completion and `/vfx play vfxweaver:block_chain` works without writing a datapack. It hangs a 6-link `minecraft:iron_chain` from the local player (`pos_x/y/z` bound to `player_x/y/z`, `physics: 1`, `length: 6`, `sway: 0.4`), the same self-anchoring pattern as the `vfxweaver:particles` demo; built-in definitions now number 44.
### Changed
- **Block/item particles tumble like real falling cubes** — `spin` (degrees/tick) is now the magnitude of a full 3D angular velocity about a random axis, with a random full-3D initial orientation, instead of a yaw around world Y. The orientation is integrated each physics tick and slerped between ticks for rendering. On contact the tumble is damped by the contacted block's own friction (the same `Block#getFriction` rule `block_chain` uses), so a cube lands and stops spinning; a little of the tangential slip becomes roll, and a particle with no slip gains none. `spin = 0` now yields a static but randomly oriented model (previously upright).
### Fixed
- **Block/item particles move smoothly and spin on a sane axis** — the model particles were submitted as geometry with their own pose interpolation, which read as jerky stepping and rotated around an odd pivot. They are now driven through client-side `BlockDisplay`/`ItemDisplay` entities, so vanilla interpolates position between ticks, `spin` drives the model's orientation, and brightness/scale map onto the display's own brightness override and transformation. One display entity per live particle, removed on death/effect stop/world unload; the physics is unchanged.
- **Physics `block_chain` ropes now settle on surfaces instead of sliding forever** — the depenetration is a pure displacement (pos and prev shift by the same correction, the player-push idiom) so the push itself injects no velocity, and on contact the inward velocity is dropped while the remaining tangential velocity is damped every contacting tick by the contacted block's own friction (`Block#getFriction`: stone/air 0.6, ice 0.98). A rope therefore grips stone, glides on ice, and a joint lifted out of the ground gains no residual horizontal velocity.
- **Physics `block_chain` ropes no longer sink through the ground** — a rope joint that moved more than half a block into terrain in one tick was being ejected through the block's far face (nearest-face resolution), so the chain tunnelled downwards; the resolver now sweeps the path from the joint's previous position, stops at the first contact and pushes the joint back out through the face it entered, dropping only the inward normal component of its velocity and keeping the tangential part. Links rest on surfaces without sinking, a joint that spawns inside a block still comes free, and a moving anchor drags the chain across the ground (instead of the joint being reverted to its previous position and freezing there). Multi-box collision shapes are approximated by their union box.
- **NeoForge world overlays render on the `26.1.2` line** — overlays now capture the camera, the render buffers and the level renderer's submit storage from the level-render stage event; the earlier `SubmitCustomGeometryEvent` path left those unset on `26.1.2`, so every buffer-based overlay threw and `block_chain` geometry was submitted in a phase that was never drawn.
release
VFXWeaver API 1.2.0 (26.2, NeoForge)
1.2.018 сент. 2026 г.14Minecraft: 26.2neoforge
Что нового
## v1.2.0 / Guide v32
### Added
- **Block-model particles (`particles` block mode + `vfx_particles` presets)** — the `particles` effect can now emit real block models instead of vanilla particles, chosen inline (`"particle": "block"` with a `block` state) or by preset id (`data/<namespace>/vfx_particles/<name>.json`, or `VFXAPI.registerBlockParticle`). Each particle has block-display brightness (`-1` = world light, `[blockLight, skyLight]`), gravity, air friction, optional world collision with surface friction and bounce, size, lifetime and spin; the `particles` params override the preset's defaults. `VFXAPI.spawnBlockParticle(spec, position, velocity)` spawns a single particle immediately on the client. Presets are **client-local and never synced**, live in a two-layer registry (datapack wins over code), and render as client-side display entities so vanilla interpolates their motion.
- **Item-model particles (`particles` item mode)** — the same particle engine can now draw real **item** models: `"particle": "item"` with an `item` id (e.g. `"minecraft:skeleton_skull"`), or a `vfx_particles` preset declaring `"item"` instead of `"block"` (exactly one of the two is required; a missing or unknown entry is a per-file parse error). Items render as `ItemDisplay` entities, with the same brightness/physics params as block particles. This is what makes **skull/head particles** possible — the block form has no baked model (a block-entity renderer draws it), the item form does. `VFXBlockParticleSpec.item(ItemStack)` / `builder(ItemStack)` build the spec from code; the API methods are unchanged.
- **Configurable block/item particle rotation** — `vfx_particles` presets (and the `VFXBlockParticleSpec` builder) gained `spin_mode` (`tumble` = the physical full-3D spin, `yaw` = a uniform spin about one axis, `none` = no rotation), `spin_axis` (`random`/`x`/`y`/`z`/`[x, y, z]`), `spin_random` (`0..1`: how random the initial orientation and tumble axis are; `0` = strictly upright and identical for every particle), `spin_friction` (`0..1`: how much the contacted block's friction damps the spin; `0` = never decays) and `spin_roll` (`0..1`: how much tangential impact speed feeds the tumble; `0` = no roll transfer). The `spin`/`spin_random`/`spin_friction`/`spin_roll` effect params override a named preset; the defaults reproduce the previous tumbling behaviour exactly, so existing presets are unchanged.
- **NeoForge support for all three Minecraft lines** — a NeoForge node is built beside each Fabric node from the same source tree (`26.2-neoforge`, `26.1.2-neoforge`, `1.21.11-neoforge`), so the project produces six jars, one per (Minecraft line, loader). The build scripts are split per loader (`build.fabric.gradle` / `build.neoforge.gradle`) and a platform layer (`dev.vfxweaver.platform` / `client.platform`) hides every loader API so the render, datapack, API and command code stays loader-agnostic. The only per-line loader API split is `<26.1`: NeoForge `21.11` has no submit-geometry event and reads the collector from `LevelRenderer.submitNodeStorage`, while NeoForge `26.1.2` matches `26.2`; dependency ranges stay per line (`deps.neo_compat` for NeoForge, `deps.mc_compat` for Minecraft). **Flashback recording is Fabric-only** — Flashback has no NeoForge build, so the compatibility layer no-ops there.
- **`vfxweaver:block_chain` built-in demo** — the `block_chain` effect type now ships a built-in definition, so it appears in `/vfx` tab-completion and `/vfx play vfxweaver:block_chain` works without writing a datapack. It hangs a 6-link `minecraft:iron_chain` from the local player (`pos_x/y/z` bound to `player_x/y/z`, `physics: 1`, `length: 6`, `sway: 0.4`), the same self-anchoring pattern as the `vfxweaver:particles` demo; built-in definitions now number 44.
### Changed
- **Block/item particles tumble like real falling cubes** — `spin` (degrees/tick) is now the magnitude of a full 3D angular velocity about a random axis, with a random full-3D initial orientation, instead of a yaw around world Y. The orientation is integrated each physics tick and slerped between ticks for rendering. On contact the tumble is damped by the contacted block's own friction (the same `Block#getFriction` rule `block_chain` uses), so a cube lands and stops spinning; a little of the tangential slip becomes roll, and a particle with no slip gains none. `spin = 0` now yields a static but randomly oriented model (previously upright).
### Fixed
- **Block/item particles move smoothly and spin on a sane axis** — the model particles were submitted as geometry with their own pose interpolation, which read as jerky stepping and rotated around an odd pivot. They are now driven through client-side `BlockDisplay`/`ItemDisplay` entities, so vanilla interpolates position between ticks, `spin` drives the model's orientation, and brightness/scale map onto the display's own brightness override and transformation. One display entity per live particle, removed on death/effect stop/world unload; the physics is unchanged.
- **Physics `block_chain` ropes now settle on surfaces instead of sliding forever** — the depenetration is a pure displacement (pos and prev shift by the same correction, the player-push idiom) so the push itself injects no velocity, and on contact the inward velocity is dropped while the remaining tangential velocity is damped every contacting tick by the contacted block's own friction (`Block#getFriction`: stone/air 0.6, ice 0.98). A rope therefore grips stone, glides on ice, and a joint lifted out of the ground gains no residual horizontal velocity.
- **Physics `block_chain` ropes no longer sink through the ground** — a rope joint that moved more than half a block into terrain in one tick was being ejected through the block's far face (nearest-face resolution), so the chain tunnelled downwards; the resolver now sweeps the path from the joint's previous position, stops at the first contact and pushes the joint back out through the face it entered, dropping only the inward normal component of its velocity and keeping the tangential part. Links rest on surfaces without sinking, a joint that spawns inside a block still comes free, and a moving anchor drags the chain across the ground (instead of the joint being reverted to its previous position and freezing there). Multi-box collision shapes are approximated by their union box.
- **NeoForge world overlays render on the `26.1.2` line** — overlays now capture the camera, the render buffers and the level renderer's submit storage from the level-render stage event; the earlier `SubmitCustomGeometryEvent` path left those unset on `26.1.2`, so every buffer-based overlay threw and `block_chain` geometry was submitted in a phase that was never drawn.
release
VFXWeaver API 1.2.0 (1.21.11)
1.2.018 сент. 2026 г.11Minecraft: 1.21.11fabric
Что нового
## v1.2.0 / Guide v32
### Added
- **Block-model particles (`particles` block mode + `vfx_particles` presets)** — the `particles` effect can now emit real block models instead of vanilla particles, chosen inline (`"particle": "block"` with a `block` state) or by preset id (`data/<namespace>/vfx_particles/<name>.json`, or `VFXAPI.registerBlockParticle`). Each particle has block-display brightness (`-1` = world light, `[blockLight, skyLight]`), gravity, air friction, optional world collision with surface friction and bounce, size, lifetime and spin; the `particles` params override the preset's defaults. `VFXAPI.spawnBlockParticle(spec, position, velocity)` spawns a single particle immediately on the client. Presets are **client-local and never synced**, live in a two-layer registry (datapack wins over code), and render as client-side display entities so vanilla interpolates their motion.
- **Item-model particles (`particles` item mode)** — the same particle engine can now draw real **item** models: `"particle": "item"` with an `item` id (e.g. `"minecraft:skeleton_skull"`), or a `vfx_particles` preset declaring `"item"` instead of `"block"` (exactly one of the two is required; a missing or unknown entry is a per-file parse error). Items render as `ItemDisplay` entities, with the same brightness/physics params as block particles. This is what makes **skull/head particles** possible — the block form has no baked model (a block-entity renderer draws it), the item form does. `VFXBlockParticleSpec.item(ItemStack)` / `builder(ItemStack)` build the spec from code; the API methods are unchanged.
- **Configurable block/item particle rotation** — `vfx_particles` presets (and the `VFXBlockParticleSpec` builder) gained `spin_mode` (`tumble` = the physical full-3D spin, `yaw` = a uniform spin about one axis, `none` = no rotation), `spin_axis` (`random`/`x`/`y`/`z`/`[x, y, z]`), `spin_random` (`0..1`: how random the initial orientation and tumble axis are; `0` = strictly upright and identical for every particle), `spin_friction` (`0..1`: how much the contacted block's friction damps the spin; `0` = never decays) and `spin_roll` (`0..1`: how much tangential impact speed feeds the tumble; `0` = no roll transfer). The `spin`/`spin_random`/`spin_friction`/`spin_roll` effect params override a named preset; the defaults reproduce the previous tumbling behaviour exactly, so existing presets are unchanged.
- **NeoForge support for all three Minecraft lines** — a NeoForge node is built beside each Fabric node from the same source tree (`26.2-neoforge`, `26.1.2-neoforge`, `1.21.11-neoforge`), so the project produces six jars, one per (Minecraft line, loader). The build scripts are split per loader (`build.fabric.gradle` / `build.neoforge.gradle`) and a platform layer (`dev.vfxweaver.platform` / `client.platform`) hides every loader API so the render, datapack, API and command code stays loader-agnostic. The only per-line loader API split is `<26.1`: NeoForge `21.11` has no submit-geometry event and reads the collector from `LevelRenderer.submitNodeStorage`, while NeoForge `26.1.2` matches `26.2`; dependency ranges stay per line (`deps.neo_compat` for NeoForge, `deps.mc_compat` for Minecraft). **Flashback recording is Fabric-only** — Flashback has no NeoForge build, so the compatibility layer no-ops there.
- **`vfxweaver:block_chain` built-in demo** — the `block_chain` effect type now ships a built-in definition, so it appears in `/vfx` tab-completion and `/vfx play vfxweaver:block_chain` works without writing a datapack. It hangs a 6-link `minecraft:iron_chain` from the local player (`pos_x/y/z` bound to `player_x/y/z`, `physics: 1`, `length: 6`, `sway: 0.4`), the same self-anchoring pattern as the `vfxweaver:particles` demo; built-in definitions now number 44.
### Changed
- **Block/item particles tumble like real falling cubes** — `spin` (degrees/tick) is now the magnitude of a full 3D angular velocity about a random axis, with a random full-3D initial orientation, instead of a yaw around world Y. The orientation is integrated each physics tick and slerped between ticks for rendering. On contact the tumble is damped by the contacted block's own friction (the same `Block#getFriction` rule `block_chain` uses), so a cube lands and stops spinning; a little of the tangential slip becomes roll, and a particle with no slip gains none. `spin = 0` now yields a static but randomly oriented model (previously upright).
### Fixed
- **Block/item particles move smoothly and spin on a sane axis** — the model particles were submitted as geometry with their own pose interpolation, which read as jerky stepping and rotated around an odd pivot. They are now driven through client-side `BlockDisplay`/`ItemDisplay` entities, so vanilla interpolates position between ticks, `spin` drives the model's orientation, and brightness/scale map onto the display's own brightness override and transformation. One display entity per live particle, removed on death/effect stop/world unload; the physics is unchanged.
- **Physics `block_chain` ropes now settle on surfaces instead of sliding forever** — the depenetration is a pure displacement (pos and prev shift by the same correction, the player-push idiom) so the push itself injects no velocity, and on contact the inward velocity is dropped while the remaining tangential velocity is damped every contacting tick by the contacted block's own friction (`Block#getFriction`: stone/air 0.6, ice 0.98). A rope therefore grips stone, glides on ice, and a joint lifted out of the ground gains no residual horizontal velocity.
- **Physics `block_chain` ropes no longer sink through the ground** — a rope joint that moved more than half a block into terrain in one tick was being ejected through the block's far face (nearest-face resolution), so the chain tunnelled downwards; the resolver now sweeps the path from the joint's previous position, stops at the first contact and pushes the joint back out through the face it entered, dropping only the inward normal component of its velocity and keeping the tangential part. Links rest on surfaces without sinking, a joint that spawns inside a block still comes free, and a moving anchor drags the chain across the ground (instead of the joint being reverted to its previous position and freezing there). Multi-box collision shapes are approximated by their union box.
- **NeoForge world overlays render on the `26.1.2` line** — overlays now capture the camera, the render buffers and the level renderer's submit storage from the level-render stage event; the earlier `SubmitCustomGeometryEvent` path left those unset on `26.1.2`, so every buffer-based overlay threw and `block_chain` geometry was submitted in a phase that was never drawn.
release
VFXWeaver API 1.2.0 (26.1.x)
1.2.018 сент. 2026 г.12Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
## v1.2.0 / Guide v32
### Added
- **Block-model particles (`particles` block mode + `vfx_particles` presets)** — the `particles` effect can now emit real block models instead of vanilla particles, chosen inline (`"particle": "block"` with a `block` state) or by preset id (`data/<namespace>/vfx_particles/<name>.json`, or `VFXAPI.registerBlockParticle`). Each particle has block-display brightness (`-1` = world light, `[blockLight, skyLight]`), gravity, air friction, optional world collision with surface friction and bounce, size, lifetime and spin; the `particles` params override the preset's defaults. `VFXAPI.spawnBlockParticle(spec, position, velocity)` spawns a single particle immediately on the client. Presets are **client-local and never synced**, live in a two-layer registry (datapack wins over code), and render as client-side display entities so vanilla interpolates their motion.
- **Item-model particles (`particles` item mode)** — the same particle engine can now draw real **item** models: `"particle": "item"` with an `item` id (e.g. `"minecraft:skeleton_skull"`), or a `vfx_particles` preset declaring `"item"` instead of `"block"` (exactly one of the two is required; a missing or unknown entry is a per-file parse error). Items render as `ItemDisplay` entities, with the same brightness/physics params as block particles. This is what makes **skull/head particles** possible — the block form has no baked model (a block-entity renderer draws it), the item form does. `VFXBlockParticleSpec.item(ItemStack)` / `builder(ItemStack)` build the spec from code; the API methods are unchanged.
- **Configurable block/item particle rotation** — `vfx_particles` presets (and the `VFXBlockParticleSpec` builder) gained `spin_mode` (`tumble` = the physical full-3D spin, `yaw` = a uniform spin about one axis, `none` = no rotation), `spin_axis` (`random`/`x`/`y`/`z`/`[x, y, z]`), `spin_random` (`0..1`: how random the initial orientation and tumble axis are; `0` = strictly upright and identical for every particle), `spin_friction` (`0..1`: how much the contacted block's friction damps the spin; `0` = never decays) and `spin_roll` (`0..1`: how much tangential impact speed feeds the tumble; `0` = no roll transfer). The `spin`/`spin_random`/`spin_friction`/`spin_roll` effect params override a named preset; the defaults reproduce the previous tumbling behaviour exactly, so existing presets are unchanged.
- **NeoForge support for all three Minecraft lines** — a NeoForge node is built beside each Fabric node from the same source tree (`26.2-neoforge`, `26.1.2-neoforge`, `1.21.11-neoforge`), so the project produces six jars, one per (Minecraft line, loader). The build scripts are split per loader (`build.fabric.gradle` / `build.neoforge.gradle`) and a platform layer (`dev.vfxweaver.platform` / `client.platform`) hides every loader API so the render, datapack, API and command code stays loader-agnostic. The only per-line loader API split is `<26.1`: NeoForge `21.11` has no submit-geometry event and reads the collector from `LevelRenderer.submitNodeStorage`, while NeoForge `26.1.2` matches `26.2`; dependency ranges stay per line (`deps.neo_compat` for NeoForge, `deps.mc_compat` for Minecraft). **Flashback recording is Fabric-only** — Flashback has no NeoForge build, so the compatibility layer no-ops there.
- **`vfxweaver:block_chain` built-in demo** — the `block_chain` effect type now ships a built-in definition, so it appears in `/vfx` tab-completion and `/vfx play vfxweaver:block_chain` works without writing a datapack. It hangs a 6-link `minecraft:iron_chain` from the local player (`pos_x/y/z` bound to `player_x/y/z`, `physics: 1`, `length: 6`, `sway: 0.4`), the same self-anchoring pattern as the `vfxweaver:particles` demo; built-in definitions now number 44.
### Changed
- **Block/item particles tumble like real falling cubes** — `spin` (degrees/tick) is now the magnitude of a full 3D angular velocity about a random axis, with a random full-3D initial orientation, instead of a yaw around world Y. The orientation is integrated each physics tick and slerped between ticks for rendering. On contact the tumble is damped by the contacted block's own friction (the same `Block#getFriction` rule `block_chain` uses), so a cube lands and stops spinning; a little of the tangential slip becomes roll, and a particle with no slip gains none. `spin = 0` now yields a static but randomly oriented model (previously upright).
### Fixed
- **Block/item particles move smoothly and spin on a sane axis** — the model particles were submitted as geometry with their own pose interpolation, which read as jerky stepping and rotated around an odd pivot. They are now driven through client-side `BlockDisplay`/`ItemDisplay` entities, so vanilla interpolates position between ticks, `spin` drives the model's orientation, and brightness/scale map onto the display's own brightness override and transformation. One display entity per live particle, removed on death/effect stop/world unload; the physics is unchanged.
- **Physics `block_chain` ropes now settle on surfaces instead of sliding forever** — the depenetration is a pure displacement (pos and prev shift by the same correction, the player-push idiom) so the push itself injects no velocity, and on contact the inward velocity is dropped while the remaining tangential velocity is damped every contacting tick by the contacted block's own friction (`Block#getFriction`: stone/air 0.6, ice 0.98). A rope therefore grips stone, glides on ice, and a joint lifted out of the ground gains no residual horizontal velocity.
- **Physics `block_chain` ropes no longer sink through the ground** — a rope joint that moved more than half a block into terrain in one tick was being ejected through the block's far face (nearest-face resolution), so the chain tunnelled downwards; the resolver now sweeps the path from the joint's previous position, stops at the first contact and pushes the joint back out through the face it entered, dropping only the inward normal component of its velocity and keeping the tangential part. Links rest on surfaces without sinking, a joint that spawns inside a block still comes free, and a moving anchor drags the chain across the ground (instead of the joint being reverted to its previous position and freezing there). Multi-box collision shapes are approximated by their union box.
- **NeoForge world overlays render on the `26.1.2` line** — overlays now capture the camera, the render buffers and the level renderer's submit storage from the level-render stage event; the earlier `SubmitCustomGeometryEvent` path left those unset on `26.1.2`, so every buffer-based overlay threw and `block_chain` geometry was submitted in a phase that was never drawn.
release
VFXWeaver API 1.2.0 (26.2)
1.2.018 сент. 2026 г.14Minecraft: 26.2fabric
Что нового
## v1.2.0 / Guide v32
### Added
- **Block-model particles (`particles` block mode + `vfx_particles` presets)** — the `particles` effect can now emit real block models instead of vanilla particles, chosen inline (`"particle": "block"` with a `block` state) or by preset id (`data/<namespace>/vfx_particles/<name>.json`, or `VFXAPI.registerBlockParticle`). Each particle has block-display brightness (`-1` = world light, `[blockLight, skyLight]`), gravity, air friction, optional world collision with surface friction and bounce, size, lifetime and spin; the `particles` params override the preset's defaults. `VFXAPI.spawnBlockParticle(spec, position, velocity)` spawns a single particle immediately on the client. Presets are **client-local and never synced**, live in a two-layer registry (datapack wins over code), and render as client-side display entities so vanilla interpolates their motion.
- **Item-model particles (`particles` item mode)** — the same particle engine can now draw real **item** models: `"particle": "item"` with an `item` id (e.g. `"minecraft:skeleton_skull"`), or a `vfx_particles` preset declaring `"item"` instead of `"block"` (exactly one of the two is required; a missing or unknown entry is a per-file parse error). Items render as `ItemDisplay` entities, with the same brightness/physics params as block particles. This is what makes **skull/head particles** possible — the block form has no baked model (a block-entity renderer draws it), the item form does. `VFXBlockParticleSpec.item(ItemStack)` / `builder(ItemStack)` build the spec from code; the API methods are unchanged.
- **Configurable block/item particle rotation** — `vfx_particles` presets (and the `VFXBlockParticleSpec` builder) gained `spin_mode` (`tumble` = the physical full-3D spin, `yaw` = a uniform spin about one axis, `none` = no rotation), `spin_axis` (`random`/`x`/`y`/`z`/`[x, y, z]`), `spin_random` (`0..1`: how random the initial orientation and tumble axis are; `0` = strictly upright and identical for every particle), `spin_friction` (`0..1`: how much the contacted block's friction damps the spin; `0` = never decays) and `spin_roll` (`0..1`: how much tangential impact speed feeds the tumble; `0` = no roll transfer). The `spin`/`spin_random`/`spin_friction`/`spin_roll` effect params override a named preset; the defaults reproduce the previous tumbling behaviour exactly, so existing presets are unchanged.
- **NeoForge support for all three Minecraft lines** — a NeoForge node is built beside each Fabric node from the same source tree (`26.2-neoforge`, `26.1.2-neoforge`, `1.21.11-neoforge`), so the project produces six jars, one per (Minecraft line, loader). The build scripts are split per loader (`build.fabric.gradle` / `build.neoforge.gradle`) and a platform layer (`dev.vfxweaver.platform` / `client.platform`) hides every loader API so the render, datapack, API and command code stays loader-agnostic. The only per-line loader API split is `<26.1`: NeoForge `21.11` has no submit-geometry event and reads the collector from `LevelRenderer.submitNodeStorage`, while NeoForge `26.1.2` matches `26.2`; dependency ranges stay per line (`deps.neo_compat` for NeoForge, `deps.mc_compat` for Minecraft). **Flashback recording is Fabric-only** — Flashback has no NeoForge build, so the compatibility layer no-ops there.
- **`vfxweaver:block_chain` built-in demo** — the `block_chain` effect type now ships a built-in definition, so it appears in `/vfx` tab-completion and `/vfx play vfxweaver:block_chain` works without writing a datapack. It hangs a 6-link `minecraft:iron_chain` from the local player (`pos_x/y/z` bound to `player_x/y/z`, `physics: 1`, `length: 6`, `sway: 0.4`), the same self-anchoring pattern as the `vfxweaver:particles` demo; built-in definitions now number 44.
### Changed
- **Block/item particles tumble like real falling cubes** — `spin` (degrees/tick) is now the magnitude of a full 3D angular velocity about a random axis, with a random full-3D initial orientation, instead of a yaw around world Y. The orientation is integrated each physics tick and slerped between ticks for rendering. On contact the tumble is damped by the contacted block's own friction (the same `Block#getFriction` rule `block_chain` uses), so a cube lands and stops spinning; a little of the tangential slip becomes roll, and a particle with no slip gains none. `spin = 0` now yields a static but randomly oriented model (previously upright).
### Fixed
- **Block/item particles move smoothly and spin on a sane axis** — the model particles were submitted as geometry with their own pose interpolation, which read as jerky stepping and rotated around an odd pivot. They are now driven through client-side `BlockDisplay`/`ItemDisplay` entities, so vanilla interpolates position between ticks, `spin` drives the model's orientation, and brightness/scale map onto the display's own brightness override and transformation. One display entity per live particle, removed on death/effect stop/world unload; the physics is unchanged.
- **Physics `block_chain` ropes now settle on surfaces instead of sliding forever** — the depenetration is a pure displacement (pos and prev shift by the same correction, the player-push idiom) so the push itself injects no velocity, and on contact the inward velocity is dropped while the remaining tangential velocity is damped every contacting tick by the contacted block's own friction (`Block#getFriction`: stone/air 0.6, ice 0.98). A rope therefore grips stone, glides on ice, and a joint lifted out of the ground gains no residual horizontal velocity.
- **Physics `block_chain` ropes no longer sink through the ground** — a rope joint that moved more than half a block into terrain in one tick was being ejected through the block's far face (nearest-face resolution), so the chain tunnelled downwards; the resolver now sweeps the path from the joint's previous position, stops at the first contact and pushes the joint back out through the face it entered, dropping only the inward normal component of its velocity and keeping the tangential part. Links rest on surfaces without sinking, a joint that spawns inside a block still comes free, and a moving anchor drags the chain across the ground (instead of the joint being reverted to its previous position and freezing there). Multi-box collision shapes are approximated by their union box.
- **NeoForge world overlays render on the `26.1.2` line** — overlays now capture the camera, the render buffers and the level renderer's submit storage from the level-render stage event; the earlier `SubmitCustomGeometryEvent` path left those unset on `26.1.2`, so every buffer-based overlay threw and `block_chain` geometry was submitted in a phase that was never drawn.
release
VFXWeaver API 1.1.4 (1.21.11)
1.1.417 сент. 2026 г.46Minecraft: 1.21.11fabric
Что нового
# VFX Weaver API 1.1.4
## Changed
- `speed_lines`: the lines are no longer evenly spaced. A new `pos_rand` param (default 1.0) jitters each line's angular position inside its own slice, so the layout is uneven instead of one line per equal sector; `pos_rand: 0` restores the old even layout. `seed` now drives a line's position as well as its length, so animating it churns the whole layout, and a line pushed against a slice boundary is measured to the nearest centre, so it is not cut off.
## Fixed
- `pulse_ring`: `thickness: 0` now really means zero - the ring is not drawn at all (it used to be clamped to a 0.05 minimum and rendered as a hairline).
- The `speed_lines` shader declared its line-length uniform as `line_length` while the parameter is `length`, which logged an unknown-uniform warning on every startup; the names now match.
release
VFXWeaver API 1.1.4 (26.1.x)
1.1.417 сент. 2026 г.11Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
# VFX Weaver API 1.1.4
## Changed
- `speed_lines`: the lines are no longer evenly spaced. A new `pos_rand` param (default 1.0) jitters each line's angular position inside its own slice, so the layout is uneven instead of one line per equal sector; `pos_rand: 0` restores the old even layout. `seed` now drives a line's position as well as its length, so animating it churns the whole layout, and a line pushed against a slice boundary is measured to the nearest centre, so it is not cut off.
## Fixed
- `pulse_ring`: `thickness: 0` now really means zero - the ring is not drawn at all (it used to be clamped to a 0.05 minimum and rendered as a hairline).
- The `speed_lines` shader declared its line-length uniform as `line_length` while the parameter is `length`, which logged an unknown-uniform warning on every startup; the names now match.
release
VFXWeaver API 1.1.4 (26.2)
1.1.417 сент. 2026 г.14Minecraft: 26.2fabric
Что нового
# VFX Weaver API 1.1.4
## Changed
- `speed_lines`: the lines are no longer evenly spaced. A new `pos_rand` param (default 1.0) jitters each line's angular position inside its own slice, so the layout is uneven instead of one line per equal sector; `pos_rand: 0` restores the old even layout. `seed` now drives a line's position as well as its length, so animating it churns the whole layout, and a line pushed against a slice boundary is measured to the nearest centre, so it is not cut off.
## Fixed
- `pulse_ring`: `thickness: 0` now really means zero - the ring is not drawn at all (it used to be clamped to a 0.05 minimum and rendered as a hairline).
- The `speed_lines` shader declared its line-length uniform as `line_length` while the parameter is `length`, which logged an unknown-uniform warning on every startup; the names now match.
release
VFXWeaver API 1.1.3 (1.21.11)
1.1.316 сент. 2026 г.4Minecraft: 1.21.11fabric
Что нового
# VFX Weaver API 1.1.3
## Added
- Code-registered effect definitions for client-only mods: `VFXAPI.registerDefinitions(Map<Identifier, String>)` and `VFXAPI.unregisterDefinition(Identifier)` add definitions at runtime with exactly the datapack validation, in a separate local layer that survives `/reload` and a server sync. Before this, a client-only companion mod could not own its effect ids on a server: a mod-provided `data/<ns>/vfx/*.json` only loads on a multiplayer client in single player (Fabric never reloads `SERVER_DATA` packs there), and a server running this mod replaced the whole definition set on join, so `playEffect("mymod:thing", ...)` could not resolve. Local definitions stay private to this client (they are never synced to other players), the datapack/server layer wins for the same id, and a broken entry is logged, skipped and reported by `/vfx validate` / `/vfx list`.
release
VFXWeaver API 1.1.3 (26.1.x)
1.1.316 сент. 2026 г.9Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
# VFX Weaver API 1.1.3
## Added
- Code-registered effect definitions for client-only mods: `VFXAPI.registerDefinitions(Map<Identifier, String>)` and `VFXAPI.unregisterDefinition(Identifier)` add definitions at runtime with exactly the datapack validation, in a separate local layer that survives `/reload` and a server sync. Before this, a client-only companion mod could not own its effect ids on a server: a mod-provided `data/<ns>/vfx/*.json` only loads on a multiplayer client in single player (Fabric never reloads `SERVER_DATA` packs there), and a server running this mod replaced the whole definition set on join, so `playEffect("mymod:thing", ...)` could not resolve. Local definitions stay private to this client (they are never synced to other players), the datapack/server layer wins for the same id, and a broken entry is logged, skipped and reported by `/vfx validate` / `/vfx list`.
release
VFXWeaver API 1.1.3 (26.2)
1.1.316 сент. 2026 г.11Minecraft: 26.2fabric
Что нового
# VFX Weaver API 1.1.3
## Added
- Code-registered effect definitions for client-only mods: `VFXAPI.registerDefinitions(Map<Identifier, String>)` and `VFXAPI.unregisterDefinition(Identifier)` add definitions at runtime with exactly the datapack validation, in a separate local layer that survives `/reload` and a server sync. Before this, a client-only companion mod could not own its effect ids on a server: a mod-provided `data/<ns>/vfx/*.json` only loads on a multiplayer client in single player (Fabric never reloads `SERVER_DATA` packs there), and a server running this mod replaced the whole definition set on join, so `playEffect("mymod:thing", ...)` could not resolve. Local definitions stay private to this client (they are never synced to other players), the datapack/server layer wins for the same id, and a broken entry is logged, skipped and reported by `/vfx validate` / `/vfx list`.
release
VFXWeaver API 1.1.2 (1.21.11)
1.1.216 сент. 2026 г.6Minecraft: 1.21.11fabric
Что нового
# VFX Weaver API 1.1.2
## Added
- Client-local anchored playback: `VFXAPI.playEffect`/`playEffectId` accept an optional world position and entity-UUID list for local (no packet) plays, and `VFXAPI.moveEffect(effectId, instanceId, pos)` re-anchors a running instance. Spatial bindings (`screen_x`, `screen_y`, `proximity`, ...) re-anchor to the point exactly like `/vfx playat`, so `dent` / `shockwave` / `vortex` land at the event and can follow it.
- Client-side live control: `VFXAPI.setParam`, `VFXAPI.setParamExpr` and `VFXAPI.setKeyframe` mirror the network actions locally, so a pure client-side mod can drive effects without a server round-trip.
- Chained animation segments: a live keyframe with a **negative time** pins the value the parameter has right now and runs the segment to the new value over `|time|` ticks - ramp a blur up and hold it, then send time `-20` / value `0` to fade it back out from where it stands. One instance, one continuous curve.
- More `expr` math functions: `floor`, `ceil`, `round`, `fract`, `sign`, `clamp`, `lerp`/`mix`, `step`, `smoothstep`, `mod`, `tan`, `atan(y)`/`atan(y, x)`, `exp`, `log`. Argument counts are validated when the expression is compiled.
- Live edits are recorded into Flashback replays: `setParam`/`setParamExpr`/`setKeyframe` replay along with the original play (existing recordings still decode).
release
VFXWeaver API 1.1.2 (26.1.x)
1.1.216 сент. 2026 г.8Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
# VFX Weaver API 1.1.2
## Added
- Client-local anchored playback: `VFXAPI.playEffect`/`playEffectId` accept an optional world position and entity-UUID list for local (no packet) plays, and `VFXAPI.moveEffect(effectId, instanceId, pos)` re-anchors a running instance. Spatial bindings (`screen_x`, `screen_y`, `proximity`, ...) re-anchor to the point exactly like `/vfx playat`, so `dent` / `shockwave` / `vortex` land at the event and can follow it.
- Client-side live control: `VFXAPI.setParam`, `VFXAPI.setParamExpr` and `VFXAPI.setKeyframe` mirror the network actions locally, so a pure client-side mod can drive effects without a server round-trip.
- Chained animation segments: a live keyframe with a **negative time** pins the value the parameter has right now and runs the segment to the new value over `|time|` ticks - ramp a blur up and hold it, then send time `-20` / value `0` to fade it back out from where it stands. One instance, one continuous curve.
- More `expr` math functions: `floor`, `ceil`, `round`, `fract`, `sign`, `clamp`, `lerp`/`mix`, `step`, `smoothstep`, `mod`, `tan`, `atan(y)`/`atan(y, x)`, `exp`, `log`. Argument counts are validated when the expression is compiled.
- Live edits are recorded into Flashback replays: `setParam`/`setParamExpr`/`setKeyframe` replay along with the original play (existing recordings still decode).
release
VFXWeaver API 1.1.2 (26.2)
1.1.216 сент. 2026 г.4Minecraft: 26.2fabric
Что нового
# VFX Weaver API 1.1.2
## Added
- Client-local anchored playback: `VFXAPI.playEffect`/`playEffectId` accept an optional world position and entity-UUID list for local (no packet) plays, and `VFXAPI.moveEffect(effectId, instanceId, pos)` re-anchors a running instance. Spatial bindings (`screen_x`, `screen_y`, `proximity`, ...) re-anchor to the point exactly like `/vfx playat`, so `dent` / `shockwave` / `vortex` land at the event and can follow it.
- Client-side live control: `VFXAPI.setParam`, `VFXAPI.setParamExpr` and `VFXAPI.setKeyframe` mirror the network actions locally, so a pure client-side mod can drive effects without a server round-trip.
- Chained animation segments: a live keyframe with a **negative time** pins the value the parameter has right now and runs the segment to the new value over `|time|` ticks - ramp a blur up and hold it, then send time `-20` / value `0` to fade it back out from where it stands. One instance, one continuous curve.
- More `expr` math functions: `floor`, `ceil`, `round`, `fract`, `sign`, `clamp`, `lerp`/`mix`, `step`, `smoothstep`, `mod`, `tan`, `atan(y)`/`atan(y, x)`, `exp`, `log`. Argument counts are validated when the expression is compiled.
- Live edits are recorded into Flashback replays: `setParam`/`setParamExpr`/`setKeyframe` replay along with the original play (existing recordings still decode).
release
VFXWeaver API 1.1.1
1.1.114 сент. 2026 г.18Minecraft: 26.2fabric
Что нового
# VFXWeaver API 1.1.1
## Added
- Server-synchronized `scoreboard` bindings: the server derives the tracked (objective, holder) pairs from the effect definitions it sends and pushes value diffs once per tick; the client caches them. Fixes scoreboard binds on objectives that are not displayed in a slot. The network protocol version is bumped - update client and server together.
- Standard cubic-Bezier easing curves: inline { "cubicBezier": [x1, y1, x2, y2] } and named data/<ns>/vfx_curves/<name>.json files with { "cubicBezier": [...] } (e.g. ease-out-back = [0.34, 1.56, 0.64, 1]). Smooth motion instead of a piecewise-linear polyline.
- Multi-version build: one source ships for Minecraft 1.21.11, 26.1.x and 26.2.
## Fixed
- Custom easing curves no longer silently degrade to LINEAR (named curves resolve lazily, inline curves use the client's own definition copy, and curves are applied before definitions on sync).
- Animated start/end parameters now pass the end value through the easing.
- /vfx stop <collection> now cancels the whole pending subtree, including nested collections.
- Player-bound world overlays (pos_x/y/z bound to player_x/y/z) no longer jitter at 20 Hz and are no longer offset by +0.5 block on X/Z.
release
VFXWeaver API 1.1.1 (26.1.x)
1.1.114 сент. 2026 г.55Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
# VFXWeaver API 1.1.1
## Added
- Server-synchronized `scoreboard` bindings: the server derives the tracked (objective, holder) pairs from the effect definitions it sends and pushes value diffs once per tick; the client caches them. Fixes scoreboard binds on objectives that are not displayed in a slot. The network protocol version is bumped - update client and server together.
- Standard cubic-Bezier easing curves: inline { "cubicBezier": [x1, y1, x2, y2] } and named data/<ns>/vfx_curves/<name>.json files with { "cubicBezier": [...] } (e.g. ease-out-back = [0.34, 1.56, 0.64, 1]). Smooth motion instead of a piecewise-linear polyline.
- Multi-version build: one source ships for Minecraft 1.21.11, 26.1.x and 26.2.
## Fixed
- Custom easing curves no longer silently degrade to LINEAR (named curves resolve lazily, inline curves use the client's own definition copy, and curves are applied before definitions on sync).
- Animated start/end parameters now pass the end value through the easing.
- /vfx stop <collection> now cancels the whole pending subtree, including nested collections.
- Player-bound world overlays (pos_x/y/z bound to player_x/y/z) no longer jitter at 20 Hz and are no longer offset by +0.5 block on X/Z.
release
VFXWeaver API 1.1.1 (1.21.11)
1.1.114 сент. 2026 г.11Minecraft: 1.21.11fabric
Что нового
# VFXWeaver API 1.1.1
## Added
- Server-synchronized `scoreboard` bindings: the server derives the tracked (objective, holder) pairs from the effect definitions it sends and pushes value diffs once per tick; the client caches them. Fixes scoreboard binds on objectives that are not displayed in a slot. The network protocol version is bumped - update client and server together.
- Standard cubic-Bezier easing curves: inline { "cubicBezier": [x1, y1, x2, y2] } and named data/<ns>/vfx_curves/<name>.json files with { "cubicBezier": [...] } (e.g. ease-out-back = [0.34, 1.56, 0.64, 1]). Smooth motion instead of a piecewise-linear polyline.
- Multi-version build: one source ships for Minecraft 1.21.11, 26.1.x and 26.2.
## Fixed
- Custom easing curves no longer silently degrade to LINEAR (named curves resolve lazily, inline curves use the client's own definition copy, and curves are applied before definitions on sync).
- Animated start/end parameters now pass the end value through the easing.
- /vfx stop <collection> now cancels the whole pending subtree, including nested collections.
- Player-bound world overlays (pos_x/y/z bound to player_x/y/z) no longer jitter at 20 Hz and are no longer offset by +0.5 block on X/Z.
release
VFXWeaver API 1.1.0
1.1.011 сент. 2026 г.55Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
# VFXWeaver API 1.1.0
Big feature release: a large batch of new screen effects, world overlays, an entity effect, plus datapack, binding and API features.
## New screen effects
- `slice_shift` — a line slices the frame; the halves slide along it with wrap/mirror fill (`angle`, `offset`, `shift`, `mirror`).
- `noise_warp` — animated value-noise field warps the picture in fluid patches (`scale`, `amplitude`, `contrast`, `coherence`, `speed`, `drift_x/y`).
- `solarize` — bright pixels invert, dark stay (`threshold`, `softness`, `intensity`).
- `double_vision` — two ghost copies with slow drift (`offset`, `ghost_opacity`, `drift`, `intensity`).
- `eyelids` — two curved dark lids sliding in from top/bottom (`openness`, `softness`, `curve`).
- `iris_wipe` — old-film iris transition (`radius`, `softness`, `center_x/y`, `zoom`).
- `digital_glitch` — horizontal band tearing + RGB split in slot-gated bursts (`block`, `displacement`, `rate`, `chroma`, `chance`, `intensity`).
- `vhs` — worn tape: crawling tracking band, wobble, colour bleed, washed contrast (`tracking`, `band_height`, `band_speed`, `bleed`, `wobble`).
- `shockwave` — one refraction ring rippling outward (`center_x/y`, `radius`, `width`, `amplitude`, `sharpness`).
- `afterimage` — decaying history echo on a double-buffered feedback buffer (`decay`, `blend`, `drift`, `desat`, `intensity`).
- `stop_motion` — CPU hold-gated frame freezing at N updates/second (`fps`).
## New world overlays
- `light_beam` — a soft glowing column with layered shells (`radius`, `height`, `top_scale`, `softness`, `top_fade`, `bottom_fade`).
- `pulse_ring` — a glowing ring; camera-facing billboard mode or a fixed plane (`radius`, `thickness`, `billboard`, `rot_x/y/z`).
- `guide_line` — a dashed parabolic line crawling between two anchors (`width`, `dash_length`, `gap`, `speed`, `arc`).
- `particles` — vanilla particles in animated shapes (`sphere`/`ring`/`helix`/`line`/`cube`/`point`) with no custom textures; `aim`/`spread`/`accel`/`lifetime` give aimed accelerating streams.
- `block_chain` — a line of real textured block-model links between two anchors; `physics: 1` runs a client-side verlet rope (gravity sag, collision, player push, `sway`, `length`).
## New entity effect
- `entity_displace` — flat per-vertex displaced echo over the intact model (`amplitude`, `scale`, `seed`, colour, `through_blocks`).
## Camera / misc
- `camera_roll` — dutch-angle camera tilt with optional sinusoidal wobble (`angle`, `wobble`, `wobble_speed`).
- `camera_shake`: new `hand` param — first-person hand multiplier (default `0.5`); the minimum across active shakes wins. `camera_shake`/`camera_roll` now also move the held-item hand.
## Datapack, bindings & API
- Entity-anchored `positions`: `{"entity": "@s", "offset": [x,y,z], "point": "feet|center|eyes", "dir": "look", "distance": N}`; followed every frame.
- `scoreboard` binding — a param can follow a scoreboard value (`objective`, optional `holder`), as a value or a `multiply` multiplier.
- Collections accept full parameter specs on children (`start`/`end`, `keyframes`, `bind`, `expr`, `multiply`).
- Custom easing curves — named `data/<ns>/vfx_curves/<name>.json` and inline `{ "curve": [[t,v], ...] }`.
- `multiply` modifier — final value = base × bound value.
- Java API: `sendSetParamExpr`, `sendMove`, `EffectRequest` builder, instance ids, serverbound `vfxweaver:vfx_request`.
- `/vfx validate [namespace]` — dry-run datapack health report.
- Built-in effects moved to datapack JSON (`data/vfxweaver/vfx/*.json`) — override-able by packs.
- Sub-block position precision for world-overlay geometry.
## Changed
- Network protocol bumped to 6 — client and server mods must match.
## Removed (cut before release)
- `block_displace`, `god_rays`, `scan_sweep`, `hud_fade`.
## Fixed
- Effects no longer replay fresh on every world join (reconnect memory now uses wall-clock time); invisible/expired effects are dropped correctly.
- Item-frame overlay drawn once and aligned to the frame plane.
- `light_beam` no longer shows triangle artifacts under flat-vertex-colour shaderpacks.
- `particles` `cube` shape samples all faces uniformly; `block_chain` renders camera-relative and integrates at a fixed tick rate.
release
VFXWeaver API 1.0.5
1.0.524 авг. 2026 г.9Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
### Added
- **First-person hand effects.** `entity_tint`/`entity_outline` active on the local player now render on the first-person arm as well (the arm bypasses the normal entity submit path, so it needed its own hook). `through_blocks` is ignored there - the hand always draws on top of the world.
- **`camera_shake` `frequency` parameter** - noise oscillations per second, default 7 (previous fixed value).
- **`screen_layer` parameter for all screen effects** - where the effect applies: `0` = below the first-person hand and the GUI, `1` = above the hand below the GUI (default, previous behaviour), `2` = above everything including the GUI.
### Fixed
- **Persistent effects were dropped from reconnect memory instead of re-applied.** `applyTo` treated the `-1` duration of persistent effects as "expired" and deleted them when their viewer rejoined - a permanent entity tint disappeared forever after one relog. Persistent plays are now re-sent as-is on every join.
- **Parameter overrides for names not declared in the definition were silently dropped.** Setting `through_blocks` via `/vfx playentity` on the built-in entity/block effects did nothing because `createTimeline` only applied overrides for declared params. Undeclared overrides now land as constants; `through_blocks` is also declared on the four built-ins (tab-completion).
- **Outlines now always render under their target.** Entity outline with `through_blocks:1` used to cover the entity with the shell colour; it is now drawn as an opaque shell before the body pass at a lower submit order, so only the rim around the silhouette survives while staying visible through walls. Block outlines ignore `through_blocks` entirely (always occluded) for the same reason.
release
VFXWeaver API 1.0.4
1.0.421 авг. 2026 г.3Minecraft: 26.1, 26.1.1, 26.1.2fabric
Что нового
### Added
- **`[players]` argument in `/vfx playentity`.** The command now accepts an optional player list at the end — who sees the effect. Previously an entity effect was always sent only to the player who ran the command, so there was no way to show it to someone else (e.g. to all players in cutscene maps).
```
/vfx playentity <effect> [{params}] <targets> [players]
```
Examples:
- `/vfx playentity vfxweaver:entity_outline @e[type=!player,distance=..10] @a` — everyone sees the outlines;
- `/vfx playentity vfxweaver:entity_tint @e[tag=boss] Alice Bob` — only Alice and Bob see the tint.
Without `[players]` behaviour is unchanged (the executing player sees it). Each viewer gets their own copy of the effect, so it also survives their reconnects independently.
### Changed
- **`duration` now ends every non-looping instance.** The one path that ignored it was `/vfx set` on a *not-running* effect: it used to start an immortal persistent instance whose animation was stretched over `Integer.MAX_VALUE` ticks — visually frozen on its first frame, never removed on its own, and replayed from scratch after every reconnect. Such instances now start with the definition's own `duration` and end on schedule like a normal play. Definitions explicitly marked `"persistent": true` keep their until-stopped semantics.
- **Protocol version 4 → 5** (`vfxweaver:vfx_trigger`). The play packet now carries a resume offset used when re-applying effects after a reconnect (see Fixed). A 1.0.4 client ignores packets from older servers and vice versa — update both sides together.
### Removed
- **The `/vfx key` command.** Nobody used it; runtime keyframing stays available to mods via `VFXAPI.sendKeyframe` and the network `KEYFRAME` action, which are unchanged.
### Fixed
- **Effects no longer restart from the first keyframe after a reconnect.** The server keeps a per-player memory of running effects; on rejoin it re-sends each still-running one with an elapsed-time offset plus any runtime keyframes, so the animation continues exactly where it left off instead of starting over. Keyframes past the nominal duration also extend the effect's lifetime correctly — previously such effects were either dropped early or replayed from the beginning.
- **Invisible effects no longer linger until the cap.** A non-looping instance whose runtime edits have all animated down to zero (or been set to `0`) is invisible but used to keep occupying one of the 64 active-effect slots until the oldest-effect eviction kicked in. It is now removed as soon as every edited parameter rests at zero. Definition-driven animations and looping effects are unaffected.
- Expression parser now accepts `_` in identifiers — documented variables (`player_x/y/z`, `light_level`, `time_of_day`) were declared in the `expr` switch but could never be parsed.
release
VFXWeaver API 1.0.2
1.0.221 авг. 2026 г.9Minecraft: 26.1, 26.1.1, 26.1.2fabric