Skip to content

Guide changelog

This page is the authoritative record of guide revisions (vN): each entry describes how the documentation changed. A guide revision is not a mod version — the two numbers are independent, and released mod versions are recorded on the mod Changelog instead. Where a guide revision shipped with a release, the entry says so (for example v47 — released as 2.0.0).

Current guide revision: v85. Mod version it documents: 2.5.0. Which jar to download is in the download table.

v85 — documents 2.5.0 (released as 2.5.0)

  • The showcase datapack now covers every 2.5.0 effect. The eight new demos (show_datamosh, show_torn_edge, show_spectral, show_hex_grid, show_heartbeat, show_hum, show_noise, show_volume_duck) are listed on the page and in the pack's own cheat sheet, and the page now says where the pack's source lives.

v84 — documents 2.5.0 (released as 2.5.0)

  • New screen effects: torn_edge, spectral and hex_grid, and a rewritten datamosh. torn_edge cuts the frame along a line whose angle is re-rolled every beat and draws the two sides at opposite brightness under a glowing tear (see torn_edge); spectral paints the frame through a seven-stop prism ramp in three variants - the prism duotone, the chromatic smear and the spectral ring (see spectral); hex_grid draws a triangular lattice whose lines take their colour from the screen position (see hex_grid). datamosh was rebuilt as a real datamosh: the frame is decoded against a reference that never arrives, the movement is searched once per 8x8 cell, and the pixels that moved are carried over as they are (see datamosh).
  • New audio effects: heartbeat, hum, noise and volume_duck, and the audio thread behind them. The voices are synthesised on a dedicated audio thread in fixed 5 ms blocks, so the frame rate does not reach the sound (see heartbeat, hum, noise and volume_duck).
  • The effect index and the download table list the new effects and the 2.5.0 jars.

v83 — documents 2.4.1 (released as 2.4.1)

  • 2.4.1 release. No page changed: v81–v82 already documented the uncapped params, and now ship as mod 2.4.1. See the mod Changelog.

v82 — documents 2.4.1 (released as 2.4.1)

  • More params are unbounded above: particles shape params and 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. The renderer keeps only the lower guards (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), and the editor schema mirrors the same floor-only bounds. See particles, block_chain, light_beam, entity_displace and spark presets. No datapack field, UBO, API or wire change.

v81 — documents 2.4.1 (released as 2.4.1)

  • light_beam radius/height and pulse_ring radius are unbounded above. The renderer keeps the lower guards (light_beam: radius >= 0.1, height >= 1; pulse_ring: radius >= 0) but no longer caps the top end (previously 16/256 and 64), and the editor schema no longer bounds them either. See light_beam and pulse_ring. No datapack field, UBO, API or wire change.

v80 — documents 2.4.0 (released as 2.4.0)

  • The visual node editor's v1.1 "quick wins". The editor page now documents: an empty tab is valid (delete the effect node and the canvas shows a + Add effect placeholder to retype the tab); snapshot undo/redo (one drag or one action is one step, capped at 100) in place of the old command stack; a keyboard-shortcut table (Del, Ctrl+C/V/D/A, Ctrl+Z, Space, R, K, F/Home, Ctrl+S, Tab/double-click, Ctrl+G, [/], ?); a timeline tick ruler with adaptive major/minor ticks, keyframes clamped to [0, duration] on drag and a red out-of-schema-range highlight, and a draggable duration bar; inline select-on-focus editing with no floating value popup, and the driven→read-only rule for value-node slots and inspector params (read-only value + source badge + "go to node"); and the bind value node's binding dropdown with its output labelled by the binding's unit. Editor-only; no datapack field, UBO, API or wire change.

v79 — documents 2.4.0 (released as 2.4.0)

  • A new page Visual node editor documents the local web editor. Start it with /vfxeditor (it toggles the loopback bridge, prints a token-bearing URL and opens it); the page covers the tab / canvas / inspector / timeline layout, live play and incremental edits, Ctrl/Cmd+Z undo that recompiles the whole effect (never an incremental game undo), the SSE reconnect with 1 s/2 s/4 s backoff and revision reconcile, the three export targets (world datapacks folder / loose folder / zip), import and sidecar layout restore, the golden path from empty editor to /vfx play, and the browser baseline (current Chrome/Firefox + previous, fetch/EventSource/canvas, with an "Unsupported browser" banner when the canvas cannot load). New page: Visual node editor.

v78 — documents 2.4.0 (released as 2.4.0)

  • A new page for screen_image: draw a picture over the game frame. The effect composites a resource-pack PNG (or an image a mod registered with VFXAPI.registerImage) onto the game's own framebuffer inside an animatable rectangle, at the screen layer you pick. The page states what the fields do not: the canvas is the Minecraft window, not the monitor, so the picture scales with the window and nothing off the frame is touched; size_w : size_h are the picture's own proportions in one common unit (the smaller window side), so equal values are a square on any window and a deliberate difference stretches it; pos_* maps over the space size_* leaves, so pos_x: 1 is flush with the edge and pos_x: 0.5 centres; screen_layer 0/1 stay under the GUI (default 1, above the first-person hand) and 2 draws over chat and hotbar; a resource-pack .png.mcmeta animation wins over frames/frame_time (and is the only way to get non-uniform frame times) while interpolate: true is not supported; and an id that does not resolve draws nothing and warns once rather than blanking the frame. Additive: one new effect type with one required top-level texture string, no existing field renamed, no UBO layout changed, PROTOCOL_VERSION unchanged, no new dependency. New page: screen_image. Asserted by scripts/check-screen-image.ps1; the GLSL cannot compile headless here. Not verified in game.

v77 — documents 2.4.0 (released as 2.4.0)

  • A window's texture may now be an image a mod supplies from code, not only a .png in a pack. Added VFXAPI.registerImage(id, width, height, argb) / VFXAPI.unregisterImage(id), documented in the API: a mod hands over ARGB pixels under a resource id and that id then works wherever a texture id does — a window_create picture, and the surface_pattern/sky_pattern texture fields. Nothing a user already authored changes: an id that is not registered resolves from the resource pack exactly as before. Additive, no datapack field, no wire or UBO change.

v76 — documents 2.4.0 (released as 2.4.0)

  • The window page gained a known interaction: animating the Minecraft window's size (id "0") interrupts chat input. While size_w/size_h change every frame the OS window is resized every frame, which drops the chat's command history keys (Up/Down) and tab-completion until it settles; the page says so and recommends not animating the game-window size while typing.
  • A new guide page for window_create / window_control, the two effects that open a real picture window on the desktop. Documents what a user cannot guess from the fields: the window is undecorated, non-resizable, always on top and click-through at all times, never takes focus, is created hidden and sized to the work area of the monitor the game is on, and is never moved or resized afterwards — pos_x/pos_y/size_w/size_h animate the picture's rectangle inside that canvas, so an animation costs no OS call per frame. Also: titles + a clamped animated title_index pick the title, opacity animates, a frame sheet steps on frame_time, up to 8 windows may be live and a duplicate name is first-one-wins, and Wayland is best-effort. New page: window_create and window_control. Not verified in game.

v75 — released 2.2.1

  • Two optional box bounds for a GLSL mask shape: a containment box and a skip box. A world plugin leaf on "volume": "aura" may declare an axis-aligned box as its broad phase, in two further optional functions (vfx_shape_custom_bounds_box, vfx_shape_custom_skip_bounds), dispatched per leaf after the existing sphere's. 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. Swapping the two 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, 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 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. Documented in the API and in the mod Changelog. Asserted by scripts/check-aura-bounds.ps1 (the two contracts checked against a sampled field, the padded cull, the skip advance with its tail clamp, and the CPU ray classes); the GLSL cannot compile headless here. Not verified in game.
  • aura_steps: an optional per-leaf cap on a custom aura leaf's 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, spending 1 to 20 of the loop's 40 + 16 iterations on the check's ray classes — so the new field is a cap, not a spend: a last resort for a leaf that really does burn the budget, with a declared broad phase as the real lever. Accepted only on a custom leaf with "volume": "aura" (a parse error naming the shape elsewhere, since only a plugin leaf has a march to bound), a negative value is a parse error, a value above the compiled budget is clamped to it, and absent or 0 means no cap — the leaf is then identical to today, ray for ray. 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), which is a softer rim and never a fabricated bright one. Additive: one new optional leaf field on an existing reserved slot, no UBO or Config field added, no field renamed, PROTOCOL_VERSION unchanged. Documented on masks and in the mod Changelog. Asserted by scripts/check-aura-bounds.ps1 (the gate, the clamp, cap 0 against the uncapped march ray for ray, and the best-effort truncation); the GLSL cannot compile headless here. Not verified in game.

v74 — released as 2.2.0

  • /vfxconfig: the in-game settings screen. A vanilla screen (no Cloth Config / Mod Menu / 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) - and writes them to config/vfxweaver.json immediately, so a change applies without a restart. 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 field, UBO, API or wire change. Documented on Commands and in the mod Changelog. Not verified in game.
  • Post-chain fusion: how the screen chain is drawn now. A run of adjacent pointwise screen effects plus each masked effect's coverage consumer is compiled into one generated fragment program, so the frame is read and written once for the run instead of once per pass (blur-class effects, the feedback pair afterimage/stop_motion and anything unannotated cut the chain). The output is unchanged: every fused stage boundary is re-quantised to the RGBA8 the unfused chain wrote. The effects index states it and the two kill switches: -Dvfxweaver.fusion=false turns fusion off entirely and -Dvfxweaver.fusion.remap=false disables the one-tap remaps (distortion, vortex, pixelate, noise_warp, slice_shift), which are on by default. No datapack field, UBO, API or wire change. Asserted by scripts/check-post-fusion.ps1 (the annotation list and the golden generated-source shape); the GLSL cannot compile headless here. Not verified in game.
  • Half-resolution effect runs. A run of the heavy multi-tap screen effects (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). Everything else about the chain - pointwise and fused stages, mask consumers and their coverage, the pre-effect image, afterimage/stop_motion and every unlisted effect - renders exactly as before. On by default: -Dvfxweaver.chainres=0.5 (the default) scales the chain and -Dvfxweaver.chainres=1.0 leaves it unchanged, and only those two values are accepted (anything else warns once and is treated as 0.5). The output inside a scaled pass is slightly softer, which is the point. No datapack field, UBO, API or wire change. Asserted by scripts/check-chainres-registration.ps1 (the scalable set and each shader-backed tap count), scripts/check-chainres-conversions.ps1 and the planner fixtures in scripts/check-fusion-budget.ps1; the GLSL cannot compile headless here. Not verified in game.

v73 — released as 2.1.1

  • order: the compositing order of screen effects inside a layer. Screen effects now take an order param alongside 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: under the hand, above it, above the GUI) and order only sequences the effects that layer already selected; being fractions, 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. Asserted by scripts/check-post-layer-order.ps1 (the read with its finite fallback, the stable sort sitting between the layer filter and the chain, the layer semantics untouched). Not verified in game.
  • The aura march grades a ray by its deepest penetration, and the occlusion ramp has a screen-space floor. Two fixes to the plugin-aura path, both written up on masks. Entry and exit merge into one pooled loop that no longer stops at the first sign crossing, so a solid mass behind a soft bump is sampled instead of the bump standing in for the whole volume (the reported dip/arch across a bump: the profile went 1.00 ×9, 0.58 0.70 0.80 0.85 0.80 0.70 0.58, 1.00 ×9 → a flat 1.00), with an additive anti-crawl step and a deliberate refusal to saturate on budget exhaustion (a long shallow corridor would light up). Separately, the occlusion ramp is kept at least 48 pixels wide on screen where the reconstructed depth moves fast per pixel, capped at 4× the authored width, so a grazing view of blocky terrain no longer turns the silhouette into a comb: at 2.8 blocks per pixel the step per terrain block edge falls from 0.0223 to 0.0074. Costs moved the right way (the fixture's flat wings 22.0 → 8.9 SDF calls, a receding ray 5 instead of 40). No uniform, no UBO/wire change, no new user-facing knob; scripts/check-mask-aura-softness.ps1 reproduces the Void whiten mask from its own source and pins both.

v72 — released as 2.1.1

  • occlusion_softness: the occlusion width is its own knob. The ramp that hides an aura behind a surface was denominated in the leaf's own softness, so a soft silhouette also decided how softly the fill was occluded - which is what turned a blocky hillside (a 1-block staircase) into a stack of ~45 bands of ~0.022 coverage along the silhouette, and which also dimmed a volume sitting one block in front of a surface while half-showing one a block behind it. The new aura-only 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 animation included, so no existing mask changes, and a sub-block value collapses the terracing into a single silhouette edge. The terracing itself cannot be removed - the occluder really is quantised in blocks - so the page says so and shows the numbers instead of pretending otherwise. Verified by scripts/check-mask-aura-softness.ps1, which pins both directions of the knob (1.0 in front, 0.0 behind at a one-block offset, where the default wide ramp gives 0.66 and 0.41). Additive: no datapack field renamed, no UBO/Config change (shape_volume.w), PROTOCOL_VERSION unchanged. Not verified in game.

v71 — released as 2.1.1

  • The depth axis of a mask, written down: the depth leaf, occlusion: false, and what a depth mask can never see. The page gains a depth leaf (coverage 0.5 at distance, falling over the leaf's own softness, with the sky on the uncovered side and no driver needed to make it camera-relative) and the "occlusion": false opt-out for an aura leaf, which is the only way to fill through translucent geometry — and is explicitly all-or-nothing. An Honest limits block states the shared root of two long-standing reports: a depth mask knows only what wrote depth, so vanilla weather and particles are drawn over (weather renders with depth writes off, particles per their own render type), while translucent surfaces legitimately occlude and a surface leaf's answer over glass is correct. The occlusion ramp being denominated in the leaf's softness is documented with the measured consequence — a 1-block terrain staircase reads as ~45 bands of ~0.022 coverage at 44.8 blocks of softness — and narrowing it is recorded as a separate open change, with the measurement that only a sharp width actually hides the steps. Additive: no datapack field renamed, no UBO/Config change, PROTOCOL_VERSION unchanged. Not verified in game; the static contract and the maths are asserted by scripts/check-mask-aura-softness.ps1 (the opt-out independence, the depth sweep, a composed band, the ramp terracing and the two shader wirings).

v70 — released as 2.1.1

  • volume: "aura" on a GLSL-plugin leaf: the soft edge is smooth now, and the field contract is written down. The page now describes what the march actually does: a Lipschitz cone-envelope of the samples (continuous in screen space, exact for creased max/min fields, never thinner than the truth), false-position for the entry distance, and a near-miss that fades over the leaf's softness instead of a hard zero. That is the 2.1.0 fix for the banded ramp and the ~0.5 coverage cliff a wide soft volume showed (reported against 2.1.0 by the Void developer; the numbers live in scripts/check-mask-aura-softness.ps1, which ports their fixture). The page also states the one hard obligation on a plugin field — it must be a conservative distance, |grad d| <= 1, and C1 smoothness is explicitly not required — and the new VFXAPI.registerMaskShapeGlsl(id, plugin, lipschitz) overload for a field that is perturbed. API gained the same note. No datapack field renamed, no UBO/Config change, PROTOCOL_VERSION unchanged. Not verified in game; the GLSL cannot run headless here, so the static contract is asserted by the runnable check and the owner checks visually.

v69

  • A mask applies to post effects only, and the page now says so plainly. The mask coverage is a screen-space texture consumed by the post chain, so the world overlays — pulse_ring, light_beam, guide_line, particles, block_chain, block_tint, block_outline and the entity effects — never read it: a mask on one of them parses and then does nothing, with no error and no effect on the geometry (for the five geometry-only overlays the coverage prepass even runs and is never read). masks states this up front and points to the overlays' own spatial params (radius, thickness, intensity, region, pos_x/y/z) as the way to confine one, and the format block table mirrors it. Documentation only — no datapack field, UBO or wire change. Also corrected: the guide revision shown on the docs home and in this guide's index was still v67 after the v68 bump.

v68 — released as 2.1.0

  • The showcase media set is complete. The clips recorded for the release are embedded on the pages that previously described their demos in text only: the sky/dome masks and the three plugin-mask demos on masks, the texture variant and the three sky anchors in Variants on sky_pattern, and a new Variants section for the two aim forms on light_beam. The showcase datapack zip was also rebuilt: the published one stored its entries with backslash separators, which a Java consumer cannot resolve, so the downloaded pack resolved to a single oddly named entry and every effect in it was silently missing.
  • film_grain: chroma, and the noise fixes. The page documents the new chroma parameter (0 monochrome, default; 1 coloured) and the re-recorded clip shows the centred cell grid, the non-repeating hash and the chroma ramp. That clip is the one noise asset in the set, so it is encoded at 30 fps / crf 30 (7 MB) instead of the pipeline default, which produced 19.9 MB.
  • pixelate and film_grain now measure their cell grid from the screen centre, so an animated cell size grows the pattern outwards from the middle instead of out of the bottom-left corner. Behaviour only — no field changed; per-scanline effects were left alone.

v67

  • sky_pattern anchor: "stars" is now a real lock, not an approximation. The v62 stars anchor drove dome_rotation, a world-Y spin, while vanilla draws the star sphere with the pose Ry(-90°) · Rx(starAngle) (verified with javap in SkyRenderer.renderSunMoonAndStars on 26.2, 26.1.2 and 1.21.11), so the pattern turned about a different axis than the stars. The sky shader now takes the sampled direction into the star sphere's own frame with the exact inverse (include/dome.glsl: vfx_star_local_dir), gated on a new internal anchor_stars field, so a stars pattern is rigidly attached to the star material as it rotates. dome_rotation is not hijacked: it stays the author's world-Y spin, applied first, and sun/moon/dome are untouched. Two internal Config floats (anchor_stars, star_angle) are appended after sky_mode (no earlier std140 offset shifts); no datapack field renamed, PROTOCOL_VERSION unchanged. See sky_pattern. Not verified in game; scripts/check-sky-pattern.ps1 now asserts the shader's star-local transform is the exact inverse of the verified vanilla pose (a runnable round-trip over several angles and directions) and that the CPU passes the angle.
  • A built-in world GLSL mask plugin (vfxweaver:blobs_glsl), and the three plugin demos now use it. The only shipped GLSL plugin was vfxweaver:ringed_glsl, a screen-space 2D figure, so the space: "world" aura/surface demos rendered nonsense and the dynamic-data demo could not show the data driving anything (ringed_glsl ignores vfx_mask_data). The new built-in vfxweaver:blobs_glsl is a genuine world-space SDF (a union of up to eight spheres, negative inside): sphere k takes its centre/radius from consecutive vfx_mask_data entries (d[4k..4k+3]) and the leaf's p0..p7 are global controls (offset, radius scale/bias, count), with an optional vfx_shape_custom_bounds() broad phase over the same data+params extent so the aura march is cheap. The coverage shader now publishes the calling leaf's p0/p1 as vfx_shape_params0/vfx_shape_params1 (declared outside the injection markers, so an older plugin is unchanged) so the bounds can stay conservative. vfxweaver:ringed_glsl stays the screen reference. The three demos were rebuilt on it — show_mask_custom_glsl_aura_demo (a real filled volume, camera outside), show_mask_custom_glsl_surface_demo (geometry only; the sky is never painted) and show_mask_custom_data_demo (animated mask.p0.d<J> so the spheres visibly move). The plugin leaf shape is inert on the 1.21.11 node (no shader-source hook). Additive: no existing pass's UBO/wire layout changes, PROTOCOL_VERSION unchanged. See masks. Not verified in game; scripts/check-mask-world-plugin.ps1 asserts the registration is a WORLD plugin, the SDF reads the data, the bounds is present and reads the data, and aura/surface masks and all three demos parse.

v66

  • fog_modifier: the hook moved to vanilla FogRenderer.setupFog's return (fix + doc). The effect now mutates the fog instance the game produces in place at setupFog's return, before any consumer reads it, so the distance scales and the colour reach the fog uniforms, the level renderer and the sky/cloud shaders together. The previous hook on updateBuffer(FogData) substituted a new instance for the parameter only, while the caller kept reading the original instance — the distance change never reached the horizon band and looked invisible. The param surface, the maths and the datapack format are unchanged (no field renamed, no UBO/shader change, PROTOCOL_VERSION unchanged); the 1.21.11 path keeps the private @ModifyArgs on the six distance slots and adds a return hook for the caller-visible colour. The mixin runs at priority = 900 so it injects before Sodium's fog mixin. Documented: the new hook point, and that the zenith keeps the dimension's own sky colour (the sky shader fogs ColorModulator toward the fog colour and only the far horizon reaches it). See fog_modifier. Not verified in game: the visual result; the hook point was proven with javap against each node's real jar, and scripts/check-fog-modifier.ps1 asserts the per-node hooks (26.x @Inject at setupFog's return mutating the returned FogData in place; 1.21.11 @ModifyArgs slots plus the return colour hook), priority = 900 and the combination maths runnably.

v65

  • fog_modifier: a smooth transition from the vanilla fog colour (fog_color_amount), and the sky/cloud ends follow fog_end_scale. New optional fog_color_amount (0..1, neutral 1/unset, unauthored) scales the instance's colour weight: 0 leaves the colour completely untouched (the colour is not written at all and hasColor stays false), 1 is the full authored colour (today's behaviour) and in between is a partial blend. Because it is just a weight on the one order-independent weighted average (weight·fog_color_amount per effect) there is no second blend stage and the distance maths is unchanged; the final colour is vanilla + (target − vanilla)·min(1, Σ(w·amount)), clamped, and NaN never leaks. Author a constant fog_r/fog_g/fog_b and keyframe fog_color_amount from 0 to 1 to ramp from the dimension's own fog colour to the authored one and back — the smoothness the all-or-nothing colour could not give. Also documented (the code was already corrected before this revision): the sky/cloud fade ends (FogSkyEnd/FogCloudsEnd) scale with fog_end_scale, so fog_end_scale < 1 pulls the wall in and narrows the horizon band and brings the clouds closer; they have no start value, so fog_start_scale never touches them. Additive and backward-compatible: a new param only, absent = the full authored colour, no datapack field renamed, no shader/UBO change, PROTOCOL_VERSION unchanged, no new dependency. See fog_modifier. Not verified in game: the visual ramp and the distance look; scripts/check-fog-modifier.ps1 asserts the colour-amount maths runnably (amount 0/0.5/1, unauthored = 1.0, order-independent, no colour write at 0) and the per-node hooks statically.

v64

  • fog_modifier: change the vanilla fog at its source. A new effect, the fog sibling of fov_modifier and deliberately 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 it closer) and optional fog_r/fog_g/fog_b override the fog colour (neutral = unset). The mod rewrites the values vanilla serialises into the fog uniforms, so vanilla terrain/entities and the entity-effect geometry all see it; absent params leave the fog untouched. Scales combine additively around neutral and colour is a weight-averaged blend from vanilla (order-independent, clamped). Coverage: every distance-based fog on 26.2/26.1.2/1.21.11 — overworld, the nether (fog_start_distance 10 / fog_end_distance 96 in its dimension definition) and the End, plus water/lava/snow/blindness/darkness. The design warned the nether/end might be density-based; it was checked and is not on these nodes — FogData has no density field and EnvironmentAttributes has no density attribute on any of the three, and the dimensions express fog as start/end distances, so the scales are the complete control. Honest limits (stated in the page): under an Iris shaderpack the effect is a no-op (a pack computes its own fog and ignores the vanilla values); the sky/cloud fade distances are not touched; and the block_tint/block_outline/glow/spark overlays use the fog-free core/position_color shader, so only the entity-effect pipelines inherit the change. No shader change, no UBO change, PROTOCOL_VERSION unchanged, no new dependency. See fog_modifier. Not verified in game: the visual result; the write point was proven with javap against each node's real jar, and scripts/check-fog-modifier.ps1 asserts the type/neutral/isPostProcessing wiring, the manager accumulation, the per-node target and the combination maths runnably.

v63

  • sky_pattern drops the legacy equirectangular dome mode. The effect is unreleased, so the single global chart is removed rather than kept as a legacy path: patch is renumbered to 0 (default) and fill to 1, and "dome" - or any other unknown value - is now a per-file parse error naming the accepted values. The reason is stated plainly in the page: a single global chart of a sphere has a pole singularity and a mandatory seam - topological, not fixable - so there is no equirectangular mode; a true skybox cube (six authored faces) remains a separate future feature. vfx_dome_uv stays in include/dome.glsl for the space: "dome" mask path (the mask is untouched). The built-in sky_pattern and every sky demo use only patch/fill. No new shader field, no UBO/Config change, PROTOCOL_VERSION unchanged. See sky_pattern. Not verified in game: the GLSL cannot run headless here - the static contract is asserted by scripts/check-sky-pattern.ps1 and the owner checks visually.

v62

  • sky_pattern can follow the sun, the moon or the stars. A new optional top-level anchor field: omitted (or "dome") keeps today's world-fixed anchor_yaw/anchor_pitch; "sun" and "moon" make the pattern track the vanilla body; "stars" locks the pattern to the rotating star sphere by driving dome_rotation from the star angle. The body angles are read from the client's own sky render state on the CPU and written to the same three uniforms, so there is no shader change and no mixin — this is what makes the anchor work under Iris: the pack may draw the sky itself, but the world time (and so the angles) is still computed by the game. Fail-closed: if the sky state cannot be read (no overworld sky, or the render state is not ready this frame) the effect contributes nothing rather than painting at a guessed spot, and reports once through the logger. Honest limits, stated in the page: the effect is an overlay, so the vanilla sun/moon stay drawn underneath (size the pattern to cover them — sun ≈33° apparent width, moon ≈24°); and the vanilla star colour cannot be changed without a SkyRenderer mixin, so "stars" places a pattern at the star sphere rather than recolouring the vanilla stars. Two further design items are deliberately not implemented because they would be Iris no-ops: a SkyRenderer mixin to draw under the vanilla bodies (S4b) and a frame-graph sky-capture pass (S5). Additive: a new top-level field only, no datapack field renamed, PROTOCOL_VERSION and every UBO layout unchanged, and the effect is not in a release yet. See sky_pattern. Not verified in game: the anchor's exact tracking and its look under an Iris pack; the shipped scripts/check-sky-pattern.ps1 asserts the parse, the resolver and the angle→yaw/pitch maths runnably, and the per-node sky-state access was proven with javap on the real jars.

v61

  • A patch has no built-in clip, and the ring demo no longer circles the player. The v60 unit-disc bound was removed - the owner tested it and rejected it (the circular clip visibly cut the figure; the earlier unbounded behaviour was not a defect to them). A patch is a flat sign on the tangent plane with no built-in clip: it runs out to the tangent-plane horizon and a pixel behind the decal (dot(dir, anchor) <= 0) is discarded as a hard backstop. A large patch can show that horizon as a straight cut - the rule is the developer who needs a clean edge adds a mask. tile_scale is documented as the cell scale on that plane (the half-angle table is gone), and the demos keep the v60 sky_mode reassignment (show_sky_pattern_texture and sky_cracks are fill; show_sky_pattern and nine_red_pixels are small patch decals). The show_sky_pattern ring demo no longer animates dome_rotation: in patch mode that spins the sampled direction before projection and carries the decal along its latitude, so it circled the player; the demo now animates only tile_scale and opacity, which leave the anchor fixed, and the docs state the semantics (dome_rotation locks a decal to the rotating star sphere, the in-plane rotation spins the figure in place). No datapack field renamed, no UBO/Config change, PROTOCOL_VERSION unchanged. See sky_pattern. Not verified in game: the GLSL cannot run headless here - the static contract is asserted by scripts/check-sky-pattern.ps1 and the owner checks visually.

v60

  • patch decals are bounded to a clean disc, and the demos use each sky_mode for what it is for. The gnomonic patch decal divided by dot(dir, anchor), so its tiling frequency grew without bound as the ray approached the tangent plane's horizon and the old facing gate cut the decal off with a hard straight edge - a large patch read as a flat plane lying over the sky with a smear at its rim. patch now bounds the decal to its unit disc: tile_scale is the patch's angular half-size, tan(half angle) (0.27 ≈ 15°, 0.45 ≈ 24°, 0.577 = 30°, 1.0 = 45°), radius 1 is the patch edge, the coverage fades to zero at the rim (over softness, min 0.05 cell units) and nothing is evaluated past radius 1, so the singular rim is never visible. The honest limit is documented: the decal is a flat sign (one tangent plane), so it does not follow the dome's curvature - use fill for content that must wrap the whole sky. The demos were reassigned: 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 bounded patch decals (tile_scale 0.4-0.55 and 0.3). Additive and backward compatible: no datapack field renamed, PROTOCOL_VERSION and every UBO layout unchanged, and the effect is not in a release yet. See sky_pattern. Not verified in game: the GLSL cannot run headless here - the static contract is asserted by scripts/check-sky-pattern.ps1 and the owner checks visually.

v59

  • sky_pattern gains a sky_mode projection and stops funnelling at the zenith. The first release projected the whole dome through one global equirectangular chart, which has two topological defects: u is undefined at the poles (tiling there has infinite frequency, so the pattern winds into a funnel at the zenith - where players look) and u must wrap at ±180° yaw (a visible seam / mirror axis). sky_mode replaces the addressing with an atlas of local charts plus a smooth partition of unity: patch (default) is one gnomonic (tangent-plane) decal at the anchor - no pole, no seam, clean discard past the decal horizon, keep tile_scale <= 1.0; fill covers the whole sphere with three orthographic charts blended by a sharpened partition of unity - no pole convergence and no seam anywhere, with coverage and colour blended (never the UVs) and the colour renormalised so texels stay saturated in the ~±8° crossfade ribbons, density varying up to ~1.7x; and dome is the legacy equirect path, kept byte-for-byte, with its inherent pole funnel and north seam, not for new content. The anchor is a position in patch and a tiling phase shift in fill; dome_rotation spins the whole field. Additive and backward compatible: sky_mode is appended last to the Config UBO, no existing shader's layout changes, no field renamed, PROTOCOL_VERSION unchanged, and the effect is not in any release yet, so the new default (patch) breaks nothing. A full skybox cube remains a separate future feature. See sky_pattern. Not verified in game: the new projection maths; the static contract is asserted by scripts/check-sky-pattern.ps1.

v58

  • sky_pattern: a datapack figure or texture painted on the sky dome. A new screen post effect (stage S3 of the skybox design) and the sky sibling of surface_pattern: the same structural pattern block, shape library and pattern.texture addressing, but the projection target is the dome. The pixel's view ray is reconstructed, optionally spun about world Y by the new dome_rotation param, and mapped to an equirectangular dome coordinate, where the figure is placed by the new anchor_yaw/anchor_pitch params (degrees). The whole pass is gated on the depth sky test, so it only ever paints far-depth sky pixels - never terrain, the hand or the GUI. repeat: [3, 3] gives nine dots with no texture; a pattern.texture with a sheet and the animatable frame param gives an animated sky. Additive: a new effect type only, no existing effect changed, no field renamed, PROTOCOL_VERSION unchanged, and no existing shader's UBO layout touched (the new sky_pattern Config adds a vec4 cam_pos the other depth pass does not have). See sky_pattern. The dome's pole pinch and the u wrap seam are inherent to equirectangular; the far-depth gate uses the same in-game-verified test as the sky mask. Not verified in game: the rendering itself, and the effect under an Iris shaderpack (a post pass composes over the pack's sky, as with every other post effect).

v57

  • light_beam can end on a chosen point. Three optional params — end_at_x/end_at_y/end_at_z — name the world point the far end of the beam lands on. The anchor is then derived as end − axis × height, so the geometry finishes exactly at the point (with the default axis it hangs straight down from height above it — a beam falling out of the sky). They are ordinary animatable params (keyframes, expr, graph, bind), and a beam from A to B is end_at = B plus the axis and length computed between the two points. Unset, the beam is unchanged. No shader, pipeline, UBO or wire change. See light_beam.

v56

  • The sky mask confirmed in game, plus two things it does and does not cover. The far-depth assumption behind sky/space: "dome" is now verified in game on all three lines (26.2, 26.1.2 and 1.21.11), not just built from the convention. Two clarifications from that testing: the sun, the moon and the clouds are drawn without writing depth, so a sky/dome leaf covers them exactly like the empty sky — a recoloured sky recolours the sun and moon with it (to sit under the celestial bodies, the planned sky_layer selector is needed); and the horizon fog is baked into the terrain pixels, so a sky mask does not recolour it and a strong tint can leave a seam at the horizon. See Masks.

v55

  • light_beam points in any direction. Three new params — dir_x/dir_y/dir_z, defaults 0/1/0 — set the beam axis, replacing the fixed vertical one, so a beam can be aimed at an angle (e.g. from a point on the sky). They are ordinary animatable params (keyframes, expr, graph, bind), including to the camera look via {"bind": "look_x"}/look_y/look_z. With the defaults the emitted geometry is unchanged, and a zero direction falls back to straight up. No shader, pipeline, UBO or wire change. See light_beam.

v54

  • Sky masks: the sky leaf and space: "dome". A mask can now target the sky instead of the world. sky covers the whole visible sky (no parameters); a 2D shape (circle/ellipse/rect/polygon) with "space": "dome" is placed on an equirectangular map of the sky, with center authored as [yaw, pitch] degrees (yaw 0 = south, 90 = west, ±180 = north; pitch -90 = up, 0 = horizon, 90 = down) and radius in dome units where 0.5 spans half the dome. Both need depth (the sky test is the depth test) and both fail closed to zero coverage without trustworthy depth. A dome leaf is sky-occluded by construction, so geometry occupying a dome direction is never tinted — unlike a world leaf, which classifies geometry, and a screen leaf, which can cover anything. A dome center must be a literal [yaw, pitch]; a bound centre is a parse error for now, a sky leaf may not be screen/world, and dome space accepts only the 2D shapes and sky. The far-depth test is verified on 26.2; on 26.1.2/1.21.11 it uses the same proven per-node convention but is not yet confirmed in game. No wire, UBO or Java API change. See Masks.

v53

  • A GLSL mask plugin leaf can be a volume, and a surface custom leaf no longer paints the sky. A world custom leaf registered through VFXAPI.registerMaskShapeGlsl now accepts "volume": "aura": the pixel's view ray is sphere-traced through the plugin's own SDF and the volume is filled — air and sky included — wherever the scene does not occlude it, using 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 that name the reason, never a silent fallback. A plugin may declare an optional vec4 vfx_shape_custom_bounds() (world centre and radius) as an analytic broad phase, so a ray that misses the volume is rejected without evaluating the SDF and the cost follows the volume's screen area; the march covers the first chord only, and a 0.5-block minimum step caps its reach at roughly 30 blocks. In the other direction, the default surface mode now gates the sky for custom leaves: a sky pixel contributes no coverage, so an intentionally unbounded plugin SDF no longer paints it (built-in shapes already behaved that way). An existing plugin that declares neither function compiles and behaves exactly as before; no wire, UBO or Java API change. See Masks and the Java API.

v52 — released as 2.0.2

  • Mod version 2.0.2. The dynamic mask float-data feature documented in v51 ships as 2.0.2, together with the raised network parameter cap and the coverage UBO growth it required (see v51). No guide behaviour changed in this entry; only mod_version and the version wording moved. The full list is in the mod changelog.

v51

  • GLSL mask plugin shapes can read live per-leaf float data, so their geometry changes without a shader recompile. A custom mask leaf now carries 32 reserved dynamic floats (mask.p<N>.d0 … d31), authored as a "data": [ … ] array or set every tick with the new VFXAPI.maskData/sendMaskData (or setParam/sendSetParam on a single mask.p<N>.d<J>). A plugin reads its own leaf's slice through the wrapper helper vfx_mask_data(vfx_shape_data_base + j), so one plugin source drives a moving set of primitives far past the eight animatable params. The coverage UBO gains a vec4-packed shape_data array (18 fields, 2336 bytes; appended after every existing field). Backward compatible: an existing plugin that ignores the helper compiles and behaves exactly as before, and a data update is an ordinary param edit that never recompiles the variant (keyed only by the plugin-id set). The wire param cap (VFXTriggerPayload.MAX_PARAMS, VFXTimeline.MAX_OVERRIDES) and the Flashback snapshot cap are raised 32 → 256 to fit a large mask's params; PROTOCOL_VERSION is unchanged, but the cap is read-side, so a 2.0.1 client still caps at 32 and needs a 2.0.2 client for a many-slot mask. 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), and on 1.21.11 the missing shader-source hook leaves a GLSL-plugin shape inert. See Masks and the Java API.

v50 — released as 2.0.1

  • Mod version 2.0.1. A correctness release: the blur effect uses a fixed-tap separable Gaussian, so an animated radius no longer pops; client entity selectors support tag=, name=, distance=, limit= and sort=, and a selector outside the supported subset fails closed with a warning naming the argument instead of binding the wrong entity (see Masks); and the Flashback replay integration follows the replay clock — pause holds the effects, a scrub places or removes them (including effects from other mods and effects already running when the recording started), looping/persistent effects are recorded, and on 1.21.11 the private/public playback field is resolved tolerantly per symbol (the replay items documented in v48–v49). Effects also keep counting while a player is offline and resume on re-join from the elapsed time. No datapack format, network protocol or Java API changed; the full list is in the mod changelog.

v49

  • An effect another mod triggers through the API is removed when a replay is scrubbed back past its trigger, and a played effect no longer flashes for a single frame. A play that reached the effect loop without being recorded into the replay (a network trigger or a client-local API play while a replay was open) used to be owned by nobody, so a backward scrub left it running. A seek now stops every active instance and re-places only the effects whose recorded trigger is at or before the new time, so a scrub back before its trigger removes it - finite, looping or persistent alike - without dropping anything between seeks (the earlier per-frame removal made the replay's own effect disappear after one frame). See Flashback compatibility.

v48

  • Looping and persistent effects are recorded into Flashback replays. They used to be skipped when a recording started (an effect already running was not snapshotted) and a persistent play (-1) was dropped entirely; both now record like a finite effect, because the replay-timeline controller keeps a recorded play alive until a recorded stop (or the whole replay when it was never stopped). This is what makes a looping showcase such as vfxweaver:graph_demo or vfxweaver:graph_logic_demo reproduce in a replay. See Flashback compatibility.

v47 — released as 2.0.0

  • Mod version 2.0.0. Everything accumulated since v32 — guides v33–v46: value graphs and macros, per-pixel fields, masks with block geometry and GLSL-plugin shapes, surface_pattern with textures/faces/bands/stitch, spark particles and /vfx stop [<player>], plus per-line scene depth — ships together as 2.0.0. No guide behaviour changed in this entry; only mod_version and the version wording moved, so the actual changes are the v33–v46 entries below.

v46

  • Textured surface_pattern now resolves on 1.21.11 (and on 26.1.2/26.2 without the first-frame abort). The texture resolver was written inside a >=26.1 guard with a neutral fallback for the 1.21.11 node, so once the depth pass started running there the texture never resolved, tex_flags never reached the RESOLVED bit and every textured projection drew nothing (the built-in procedural figure still worked). The resolver now has a real per-node implementation — 26.2/26.1.2 through the sprite AtlasManager (SpriteId keyed by TextureAtlas.location()), 1.21.11 through the remapped model AtlasManager and TextureAtlas.getSprite — funnelling every source form (block/item/atlas/standalone) through one resolved(...) factory that sets the flag bit. A standalone texture is shared by both nodes. See surface_pattern.
  • The surface_pattern std140 layout guard no longer aborts the post layer. The registration-time guard queried glGetUniformIndices with block-qualified names (Config.tile_scale); a driver that reports only the bare member name returned -1, which was then passed to glGetActiveUniformsiv (GL_INVALID_VALUE) and threw out of execute, so the first frame of a textured pattern failed with "Failed to apply VFX post-processing". It queries the bare member name first with a qualified fallback, skips members the driver does not list, and logs at ERROR without throwing, so a false positive cannot drop a frame.

v45

  • Scene depth works on every supported line, not just 26.2. surface_pattern, the depth/world field functions and the depth-needing mask families (a world leaf, an aura volume, a block leaf's occlude) used to be gated to 26.2 because the world reconstruction assumed 26.2's reversed depth. The per-node depth convention is now proven from the client jars and injected into one shared shader source: 26.2 is reversed (glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE) — near = 1, far = 0) and 26.1.2 / 1.21.11 are standard (near = 0, far = 1). The shader converts the raw sampled depth, picks the correct sky test and the correct block-occlusion comparison per node, so a depth-needing effect now renders on 26.1.2 and 1.21.11 as well. Fail-closed is unchanged where no trustworthy depth exists. See surface_pattern and Masks.

v44

  • surface.stitch — opt-in planar (top-down) projection. A new boolean in the structural surface block (default false). With "stitch": true every face samples the same top-down coordinate p = world.xz, so a wall pixel (x, y, z) shows exactly what the floor pixel at the wall base (x, floor_y, z) shows — the image's row at the wall line extruded vertically (a ring reaching a wall becomes two vertical stripes from its crossing points). The seam is continuous for any anchor height (the anchor's Y no longer matters), and min/max become a height slab that bounds how far up the wall the image stretches. A ceiling is excluded (a top-down projection cannot light a down-facing surface). Deliberate trade-off: a wall shows a 1D slice (vertically constant colour columns), not a 2D unwrapped image. Additive and off by default — without it the hard floor/wall plane switch is unchanged, so every existing definition and the built-in render exactly as before. See surface_pattern.
  • normal_mask: 1.0 coverage is no longer noisy. The 1.0 fallback compared the raw depth-derived normal against exactly 1.0, which a normalized normal numerically almost never reaches (0.9999…), so coverage flickered at the threshold (worst at grazing angles near the floor). It now tests the snapped normal (abs(n.y) >= 0.5), which is exact for an axis-aligned block face.

v43

  • /vfx stop [<player>] stops every active effect of a player. With no argument it stops all of the executing player's effects; with a player (selector) it stops that player's effects. It reuses the existing per-effect stop payload on the server and, for the executor's own client, also clears effects played locally (single player / a client-only mod). The old /vfx stop <effect> [players] form is unchanged: a bare token is parsed as an <effect> first, so use a selector (@p/@a) to target a player. New VFXAPI.sendStopAll(ServerPlayer).
  • Mask depth gate is consistent across nodes. depthRecipeVerified() was left in the 26.2 Stonecutter form on disk, so the active 26.1.2 Fabric node compiled return true while every other node (including 26.1.2-neoforge and 1.21.11) compiled false. The active-node form is now the false branch, so a depth-needing mask fails closed (zero coverage) on every non-26.2 node, as documented, instead of silently using the unverified reversed-depth recipe on Fabric.
  • A screen-only mask renders on nodes without the verified depth recipe. The coverage prepass bound the coverage target's own colour texture as the DepthSampler placeholder when depth was untrusted (a feedback loop, undefined on some drivers); it now binds the main target's depth view (else its colour view), so a mask that needs no depth renders identically on 26.2, 26.1.2 and 1.21.11.

v42

  • surface_pattern now requires 26.2 (it no longer silently passes through on 26.1.2). The pass is registered on >=26.2 only, matching the mask coverage gate: the reversed-depth reconstruction it reads was verified on 26.2, while 26.1.2/1.21.11 use the other depth convention. On a non-26.2 node the effect type and JSON still parse, but no pass is registered and the effect draws nothing (previously 26.1.2 rendered a passthrough with a one-time warning). See surface_pattern.
  • The effect clock and camera/player snapshots are refreshed before layer 0. They used to advance in the FogRenderer.endFrame hook, which runs after renderLevel, so a surface_pattern (default screen_layer: 0) trailed the player by a frame and time-driven animation was a frame late. Layer 0 now advances the clock and republishes the snapshots; layers ½ reuse them, and nothing advances twice. See surface_pattern.
  • normal_mask: 1.0 is no longer undefined. The degenerate smoothstep(normal_mask, min(normal_mask + 0.2, 1.0), …) edge collapse (undefined in GLSL) is guarded by clamping normal_mask to 0..1 and falling back to the exact hard test at 1.0.
  • A band_softness wider than the band is clamped to half the band width, so a narrow band (min:10, max:10.5) is solid at its centre instead of evaluating to ~0.28.
  • distort warps a flat surface instead of translating it. Its phase was dot(world, normal), constant on any flat axis-aligned surface; it now follows the in-plane coordinate. Definitions with a non-zero distort on a floor see a different (intended) result.
  • Parse fixes that stop a definition from silently doing nothing. An empty faces: [] is now a parse error; pattern.center_x/center_y/center_z are rejected (use pattern.center); an entity-anchored positions entry on a surface_pattern is rejected; a pattern.texture.id/atlas with invalid id syntax is a per-file parse error.
  • Per-frame work in the post chain is reduced — one definition lookup per surface_pattern pass instead of three, and the texture id is parsed once at definition-parse time instead of every frame.

v41

  • Mask correctness and performance batch. Animated softness, composition nesting (left-only, a right-nested op is now a parse error), the custom-leaf (2) and block-leaf (1) caps, an animatable block centre, a composed custom leaf's softness, the depth gate (a depth-needing mask fails closed where surface_pattern does), cached block selection, cleared new coverage targets, live-source shader variants and one-extra-frame uniform-arena retirement. Two plays of one masked definition still share a coverage target (documented). See Masks.

v40

  • Textured surface_pattern figures — the structural pattern block accepts an optional texture object: a real texture (a block-atlas sprite, an item-atlas sprite, any atlas sprite, or a standalone resource-pack texture) projected onto the same surface the figure uses. id is required; source (block/item/atlas/standalone) is inferred from the id when omitted; channel (alpha default, luminance/r/g/b), sheet ([cols, rows], 1..16, cols*rows <= 256) and aspect (preserve/stretch) are optional. The texture is the figure and an authored figure becomes its mask (a texture with no figure is not clipped). New animatable params rotation (overrides the structural rotation), frame (sprite-sheet cell) and texture_tint (0..1 recolour). Atlas sprites use their stitched sub-rect and animate with the atlas; standalone textures sample 0..1. Additive: without a texture the block renders exactly as before. See surface_pattern.
  • Texture reload fix — a texture view cached across a resource reload used to dangle (the loaders close and recreate it); the cache now stores the descriptor and re-derives the view, for both the field texture function and the new pattern texture.
  • surface_pattern anchor is world-anchored, never the camera — with no explicit center the anchor fell back to the camera, so third-person (F5) or any camera-only motion slid the projection along the ground. The anchor is now the effect instance's world position: a Java-API move, else its first declared position (/vfx playat), else the pos_x/pos_y/pos_z binds, else the local player for a player-anchored play. An explicit center still wins; camera-only movement no longer moves the pattern. See surface_pattern.

v39

  • surface_pattern surface selection — an optional top-level structural surface block selects which face orientations receive the pattern (faces: up/down/north/south/east/west, axis aliases x/y/z, groups horizontal/vertical/all) and an optional inclusive band (min/max) applied along the fragment's dominant axis (Y for up/down, X for east/west, Z for north/south). Without the block the legacy numeric normal_mask behaviour is unchanged, so the built-in and every existing definition keep rendering exactly as before. The projection now follows the fragment's dominant world normal, so vertical walls get an upright, un-mirrored figure instead of a sheared XZ one. See surface_pattern.
  • surface_pattern band edge is no longer hard — a surface lying exactly on a band min/max bound flickered, because the depth-reconstructed axis coordinate jitters across the inclusive test from pixel to pixel. The optional band_softness (blocks, 0..4, default 0) fades the band edge over a small world-space distance, keeping the interior fully on and the outside fully off; 0 is the exact hard edge, so existing definitions are unchanged. See surface_pattern.

v38

  • New surface_pattern effect — a world-anchored shape pattern (circle/ellipse/rect/polygon, tiled by a structural repeat modifier) projected onto the terrain behind each pixel, so it stays fixed to world blocks. Additive: it renders on 26.1.2+ (it reads scene depth), needs "screen_layer": 0, and no existing definition changes behaviour. The figure strings live in a top-level pattern block, never in params. See surface_pattern.

v37

  • Mask bindings now fail closed per leaf (was per mask) — an unresolved binding (the source entity is absent or off-screen, or outside the client's tracking range, or there is no camera/player state) zeroes only that leaf's coverage, so an entity leaving the view no longer makes the whole effect vanish while a still-resolved world leaf knows where it is. An unresolved leaf still never expands coverage, and an invert mask does not turn an all-unresolved (empty) result into full screen. See Masks.
  • New VFXAPI.sendMaskMove / VFXAPI.maskMove — move one mask leaf to a world position by setting its three reserved mask.p<N>.center_x|center_y|center_z params (ordinary animatable effects params). Call it every tick to follow a point, and use it to drive a mask from the server when a client-side entity binding is not enough (an entity outside the client's tracking range). See Java API and Masks.
  • Mask coverage at later layers is documented — the coverage prepass runs at screen layer 0 against the intact scene depth and yields a screen-space coverage, so an effect consuming the mask at a later layer (e.g. screen_layer: 1) tints the first-person hand wherever a masked block lies behind it. Run the masked effect at screen_layer: 0 or depth-occlude the consumer to avoid it. See Masks.

v36

  • Mask bindings now fail closed — a mask that uses a world binding which cannot be resolved (the source entity is absent or off-screen, or there is no camera/player state) contributes zero coverage, so the effect applies nowhere, instead of falling back to the leaf's literal default — which for a bound screen rect is the whole screen (the reported "whole screen tint when the villager is not resolvable"). The unresolved source still reports once through VFXLog.warnOnce. See Masks.
  • aura masks fill the volume at full strength — the aura coverage is now sampled at the volume depth nearest the viewer along the view ray (the sphere's closest approach) instead of at the entry point on the boundary, so the interior reaches full coverage and the silhouette edge fades from both sides. See Masks.
  • Mask demos reworked — vfxweaver:mask_entity_demo and its surface A/B partner vfxweaver:mask_world_demo now use a fixed radius of 4 blocks, so the aura bubble stays the same size as the viewer moves. The entity-following sphere whose radius grew with the viewer's distance moved to the new vfxweaver:mask_pulse_demo, which also documents the "derive": "distance" binding form (54 built-ins).

v35

  • World-volume masks gained an aura evaluation mode — a sphere/box mask leaf now takes "volume": "surface" | "aura" (see Masks). "surface" is the default and keeps the original look (the visible surface is classified, so only geometry inside the volume is tinted); "aura" casts the pixel's view ray at the volume and fills the whole volume, including air and sky, wherever the scene does not occlude it, with the edge still fading over softness. Sky and missing depth count as "nothing occludes". The demo vfxweaver:mask_entity_demo (sphere + screen rect) now uses aura; vfxweaver:mask_world_demo is the same entity-following sphere in the default surface mode for an A/B comparison (53 built-ins).

v34

  • Per-pixel fields — a field-capable input (currently dent.intensity and color_grade.tint_r) can carry a { "field": ... } object that is evaluated per pixel inside the shader instead of once per frame (see Per-pixel fields): the built-in functions constant, noise, shape, gradient, curve, texture, depth, depth_gradient, normal_facing, screen_uv and world_pos, the shared shape set (circle/ellipse/rect/polygon with fill: solid|stroke, softness and a repeat tiling modifier, plus the 3D sphere/box helpers), and bounded compositions (multiply/add/subtract/mix/min/max). The block is additive — a definition without fields is bit-for-bit unchanged, and a mod that does not know fields ignores them. Depth/world fields need screen layer 0 and otherwise fall back to the neutral value. The built-ins vfxweaver:dent_field_demo and vfxweaver:tint_field_demo are the reference examples (48 built-ins).

v33

  • Value graphs — an effect can drive any numeric input from an optional graph + inputs block (see Value graphs): constant, time, random, noise, curve, math, mix, clamp, remap, bind and expr nodes plus the logic nodes compare, boolean, if and switch, and reusable subgraphs (macros with $ parameters, local prefixed ids and named outputs), all evaluated once per frame. All blocks are additive — a definition without them behaves exactly as before, and a mod that does not know graphs ignores them, because graph wiring never lives inside params. A broken graph fails that file only, and the built-in vfxweaver:graph_demo is the reference example (45 built-ins).

v32

  • Block-model particles — the particles effect can emit real block models instead of vanilla particles: "particle": "block" with a block state, or a reusable preset in data/<namespace>/vfx_particles/<name>.json (registerable from code with 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; VFXAPI.spawnBlockParticle spawns one immediately on the client.
  • Item-model particles — the same engine can draw real item models: "particle": "item" with an item id (e.g. "minecraft:skeleton_skull"), or a vfx_particles preset that declares "item" instead of "block" (exactly one is required). This is how a skull/head particle works — the block form has no baked block model, the item form does; items render as ItemDisplay entities with the same brightness/physics params.
  • Block/item particles render as client-side display entities — each live particle drives a BlockDisplay/ItemDisplay, so vanilla interpolates its motion (no more jerky stepping) and brightness uses the display's brightness override exactly like a real block display. The physics, presets, datapack/API surface and params are unchanged.
  • Block/item particle rotation is fully configurable — presets and the Java spec 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, spin_friction and spin_roll; spin is the magnitude of a full 3D angular velocity about a random axis, integrated per tick and damped by the contacted block's friction so a cube lands and settles. The spin/spin_random/spin_friction/spin_roll effect params override a named preset; the defaults (tumble/random/1.0/1.0/0.5) reproduce the previous tumbling behaviour exactly.
  • vfxweaver:block_chain built-in demo — the block_chain effect now ships a built-in definition (44 built-ins), so it appears in /vfx tab-completion and /vfx play vfxweaver:block_chain works without writing a datapack.
  • NeoForge support for all three Minecraft lines — a NeoForge node is built beside each Fabric node from one source tree (26.2-neoforge, 26.1.2-neoforge, 1.21.11-neoforge), so the project produces six jars, one per (Minecraft line, loader). Flashback is Fabric-only (Flashback has no NeoForge build). All formats — effect datapacks, vfx_particles presets and the effect definition fields — are additive, so existing datapacks keep working unchanged.

v31

  • Replay recording works again with current Flashback versions: the compatibility layer registered two custom actions, which Flashback rejects (it keys actions by class, and both reflection proxies share one class) - that aborted the whole init, so nothing was recorded. Plays, stops, live edits and the definitions snapshot now share one action.
  • speed_lines: new pos_rand param (default 1.0) - the per-line angular position is jittered inside its own slice, so the spacing is uneven instead of one line per equal sector (0 restores the even spacing). seed drives the position as well as the length, so animating it churns the layout.
  • pulse_ring: thickness: 0 now really means zero - the ring is not drawn at all (previously it was clamped to a 0.05 minimum and showed as a hairline).

v30

  • New Java API for client-only mods: VFXAPI.registerDefinitions(Map) / VFXAPI.unregisterDefinition(id) register effect definitions from code. They live in a local layer that survives /reload and a server sync (a datapack shipped by a client-side mod only loads in single player, and a server sync used to replace the whole definition set), stay private to this client, and use the same validation as datapack files.

v29

  • Live keyframes accept a negative time ("from here"): the value the parameter has right now is pinned at the current time and the segment runs to the new value over |time| ticks, so animation variations chain without the caller knowing the current value - sendKeyframe(player, effect, "radius", -20, 0.0F, easing) fades a held blur back out from wherever it stands.
  • The live edits are now available client-side without a packet: VFXAPI.setParam, VFXAPI.setParamExpr and VFXAPI.setKeyframe mirror the network actions (sendSetParam, sendSetParamExpr, sendKeyframe) locally, next to the already-local playEffect/playEffectId/moveEffect/stopEffect - a pure client-side mod can drive effects end to end.
  • Live edits are recorded into Flashback replays: setParam, setParamExpr and setKeyframe (local and server-driven) replay along with the original play, so an effect animated while recording keeps its animation on playback.

v28

  • Math expressions gained more functions: floor, ceil, round, fract, sign, clamp(x, lo, hi), lerp/mix(a, b, t), step(edge, x), smoothstep(e0, e1, x), mod(a, b), tan, atan(y) / atan(y, x) (= atan2), exp, log (natural). Argument counts are now validated when the expression is compiled (a wrong count used to fail while evaluating).
  • The client-local Java API can anchor effects: VFXAPI.playEffect/playEffectId take an optional world position and entity-UUID list, and VFXAPI.moveEffect(effectId, instanceId, pos) re-anchors a running instance - no packet involved. The position re-anchors spatial bindings (screen_x, screen_y, proximity, ...) like /vfx playat, so point effects (dent, shockwave, vortex) can be placed at an event and follow it.

v27

  • Build restructured for multiple Minecraft versions (Stonecutter). Adds a 1.21.11 build alongside 26.1.2; effect behavior, datapack format and network protocol are unchanged.

v26

  • World overlays: entity anchors gained point — the reference point on the entity: feet (default), center (bounding-box centre) or eyes (e.g. {"entity": "@s", "point": "center"}), so effects can attach to the middle/head of an entity instead of its feet.
  • World overlays: entity anchors gained dir: "look" + distance — the anchor is pushed along the tracked entity's live look direction (eyes + look × 24 = a laser target where the entity is looking).
  • New world-overlay effect block_chain: a line of real textured block-model links between two anchors (datapack picks the block, spacing/arc/scale/align params), rendered through the vanilla submit pipeline (shaderpack-safe). Optional physics: 1 — a verlet rope with gravity sag, world collision, player push, sway wind and animatable length (single-anchor hang or two-anchor slack; links stretch when pulled taut).
  • New world-overlay effect particles: emits vanilla particles in animated shapes (sphere/ring/helix/line/cube/point) with any RGB via dust. No custom textures — everything from the datapack; entity anchors and fades work as usual. Builtin demo: vfxweaver:particles (golden dust helix).

v25

  • New command /vfx validate [namespace] — dry-run definition health report (loaded count + parse errors per file), operator-only.
  • New world binding scoreboard: a param can follow a scoreboard value (objective + optional holder, normalized on range, works as a value or a multiply multiplier).
  • New Java API: sendSetParamExpr (swap a running effect's param for a live math expression), sendMove (move a running world-overlay instance to an exact point — call per tick for scripted motion), and a serverbound vfxweaver:vfx_request packet letting client mods play effects through the server (broadcast is operator-gated).
  • Built-in effects now ship as JSON resources (data/vfxweaver/vfx/*.json inside the mod jar) instead of code — they load, sync and can be overridden like any datapack definitions; copy a file out of the jar to tweak it.
  • World-overlay geometry uses exact Vec3 coordinates: entity anchors and API moves are no longer snapped to block centres (static positions entries keep the historical block-centre behaviour). Protocol version 6.

v24

  • World overlays: positions entries may anchor to a live entity — {"entity": "<selector>", "offset": [x,y,z]}. The server resolves the selector once per play (plain /vfx play; fails if it matches nothing), the client follows the entity every frame; the Java API passes anchors as target UUIDs in anchor order.
  • Collections: child params values are now full parameter specs — start/end, keyframes, bind, expr, multiply (plain numbers still work as constants).

v23

  • light_beam: new bottom_fade param (fades the column toward the bottom, mirrors top_fade).
  • Fixed light_beam rendering under shaderpacks: packs that declare vertex colour flat (Complementary) take each triangle's colour from one vertex, so the height fade showed as visible triangles. Faded shells are now split into 32 narrow slices, each quad with one uniform colour - clean stepped fade under shaders, imperceptible difference in vanilla.

v22

  • New screen effects: slice_shift, noise_warp, solarize, double_vision, eyelids, iris_wipe, digital_glitch, vhs, shockwave, afterimage, stop_motion.
  • New world effects: light_beam (layered shells, cubic softness, top_scale), pulse_ring (camera-facing billboard + rot), guide_line.
  • New entity effect: entity_displace (flat per-vertex displaced echo over the intact model).
  • New misc effects: camera_roll.
  • afterimage uses an island blend; camera_shake/camera_roll also move the first-person hand.
  • Removed before release: block_displace, god_rays, scan_sweep, hud_fade (cut before release, will be redesigned later).

v20

  • Parameter overrides whose name the effect definition does not declare (e.g. through_blocks on the built-in effects) now apply as constant values instead of being silently dropped; through_blocks is declared on the built-in entity/block tint/outline.
  • Entity tint/outline are rendered on the first-person hand as well (the arm bypasses the normal entity submit path).
  • Outlines always render under their target now. Entity outline with through_blocks:1 draws an opaque shell before the body (rim around the silhouette, visible through walls); block outlines ignore through_blocks and never cover the block itself.
  • camera_shake gained a frequency parameter (oscillations per second, default 7).
  • All screen effects accept screen_layer: 0 = under the first-person hand and GUI, 1 = above the hand below the GUI (default), 2 = above everything including the GUI.

v18

  • Effects survive a reconnect correctly: the server remembers runtime keyframes (Java API sendKeyframe) and resumes playback from the position it was left at (protocol version 5).
  • Non-looping effects whose runtime edits animate to zero remove themselves instead of lingering invisibly.
  • /vfx set on a non-running effect starts a normal instance with the definition's own duration instead of an immortal one.
  • /vfx playentity accepts an optional [players] list — who sees the effect (default: the executing player).
  • Removed the /vfx key command (nobody used it; VFXAPI.sendKeyframe and the network KEYFRAME action remain for mods).

v17

  • Flashback compatibility: client-local effects are recorded into Flashback replays and re-triggered during playback (soft dependency, reflection-based, no compile-time coupling).

v16

  • New effect types for entities: entity_tint (a solid translucent fill of the effect colour) and entity_outline (an "inverted hull" silhouette outline, thickness width). Targets are set by UUID.
  • New command /vfx playentity <effect> <targets> — plays an effect on entities picked by a selector (up to 16 UUIDs).
  • Both types support through_blocks: 0 — the effect hides behind walls, 1 — visible through them.

v15

  • New way to set a param — the math expression "expr" (variables t/x/y/z/pi/e, functions sin/cos/abs/min/max/pow/sqrt/random/noise). Compiled once, evaluated every frame; random()/noise() are unique per instance.
  • Camera shake (camera_shake) is now unique per call (per-instance seed).

v14

  • The mutating /vfx commands (play, playat, stop, set, key) now require operator rights; /vfx list is open to all.
  • Tighter client protection from a hostile/broken server: caps on network packet sizes (effect params, definition/curve sync), vfx_sync packet version check, cap on effect duration from the server, instance-id validation on stop.

v13

  • Datapack VFX effects and curves sync with dedicated-server clients (the vfxweaver:vfx_sync packet on join and after /reload) — custom effects now play for players on a dedicated server, like in single-player.

v12

  • sendEffect accepts a direct world position — no pos_x/y/z hack (the client immediately re-anchors spatial bindings to the point).
  • Custom easing curves: files data/<ns>/vfx_curves/<name>.json or an inline object { "curve": [[t,v],...] } in any easing field.
  • Param multiplier: "param": { keyframes/start-end/constant/binding + "multiply": { "bind": "proximity", ... } } — final value = base × multiplier (e.g. an animated dent fading with distance from a point).
  • VFXAPI.playEffectId(...) returns the instance id, VFXAPI.stopEffect(long) stops one specific instance; sendStop(player, effectId, instanceId) — over the network.
  • Network protocol version 4: the packet carries an optional position and instance id.