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,spectralandhex_grid, and a rewrittendatamosh.torn_edgecuts 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);spectralpaints the frame through a seven-stop prism ramp in three variants - the prism duotone, the chromatic smear and the spectral ring (see spectral);hex_griddraws a triangular lattice whose lines take their colour from the screen position (see hex_grid).datamoshwas 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,noiseandvolume_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:
particlesshape params anddustsize,block_chainspacing/scale, outline widths,light_beamtop_scale,pulse_ringthickness,guide_linewidth/dashes, spark speed/size andentity_displacescale. The renderer keeps only the lower guards (particles:radius >= 0.05,height >= 0.5,turns >= 0.25,speed/accel/lifetime >= 0,vel_yunbounded,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; sparkspeed >= 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_beamradius/height andpulse_ringradius 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 (previously16/256and64), 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 thebindvalue 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 editordocuments 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+Zundo 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 withVFXAPI.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_hare 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 spacesize_*leaves, sopos_x: 1is flush with the edge andpos_x: 0.5centres;screen_layer0/1stay under the GUI (default1, above the first-person hand) and2draws over chat and hotbar; a resource-pack.png.mcmetaanimation wins overframes/frame_time(and is the only way to get non-uniform frame times) whileinterpolate: trueis 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-leveltexturestring, no existing field renamed, no UBO layout changed,PROTOCOL_VERSIONunchanged, no new dependency. New page: screen_image. Asserted byscripts/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
texturemay now be an image a mod supplies from code, not only a.pngin a pack. AddedVFXAPI.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 — awindow_createpicture, and thesurface_pattern/sky_patterntexture 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. Whilesize_w/size_hchange 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_hanimate the picture's rectangle inside that canvas, so an animation costs no OS call per frame. Also:titles+ a clamped animatedtitle_indexpick the title,opacityanimates, a frame sheet steps onframe_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> 0and 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 bysoftness / 2and the skip keeps the lastsoftnessbefore the range limit sampled, so no cliff appears at a box face and the plugin never has to knowsoftness. Both are optional and a plugin that declares none of them renders exactly as before. Additive: two default methods onVFXMaskShapeGlsl, no newVFXAPIoverload, no datapack field, no wire or UBO layout change,PROTOCOL_VERSIONunchanged. Documented in the API and in the mod Changelog. Asserted byscripts/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 or0means 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 shippedtExit = tLimitrule, 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_VERSIONunchanged. Documented on masks and in the mod Changelog. Asserted byscripts/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 toconfig/vfxweaver.jsonimmediately, 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 pairafterimage/stop_motionand 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=falseturns fusion off entirely and-Dvfxweaver.fusion.remap=falsedisables 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 byscripts/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,vhsanddigital_glitch) can render at half resolution, wrapped in an exact downsample/upsample pair, once the run's summed tap count reaches 8 (vhsanddigital_glitchcarry 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_motionand every unlisted effect - renders exactly as before. On by default:-Dvfxweaver.chainres=0.5(the default) scales the chain and-Dvfxweaver.chainres=1.0leaves it unchanged, and only those two values are accepted (anything else warns once and is treated as0.5). The output inside a scaled pass is slightly softer, which is the point. No datapack field, UBO, API or wire change. Asserted byscripts/check-chainres-registration.ps1(the scalable set and each shader-backed tap count),scripts/check-chainres-conversions.ps1and the planner fixtures inscripts/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 anorderparam alongsidescreen_layer— a float, lower runs earlier, default0, and effects that leave it alone keep the order they were started in, so no existing pack changes.screen_layerstays the outer, semantic axis (which part of the frame the pass belongs to: under the hand, above it, above the GUI) andorderonly sequences the effects that layer already selected; being fractions,10.5slots an effect between two at10and11. It is an ordinary param, so the datapack, the command param-map,setParamand keyframes all reach it and the wire format, UBO andPROTOCOL_VERSIONare untouched. Asserted byscripts/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 flat1.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.ps1reproduces 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
depthleaf,occlusion: false, and what a depth mask can never see. The page gains adepthleaf (coverage 0.5 atdistance, falling over the leaf's ownsoftness, with the sky on the uncovered side and no driver needed to make it camera-relative) and the"occlusion": falseopt-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'ssoftnessis 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_VERSIONunchanged. Not verified in game; the static contract and the maths are asserted byscripts/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 creasedmax/minfields, never thinner than the truth), false-position for the entry distance, and a near-miss that fades over the leaf'ssoftnessinstead 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 inscripts/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 newVFXAPI.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_VERSIONunchanged. 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_outlineand the entity effects — never read it: amaskon 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 stillv67after 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 newchromaparameter (0monochrome, default;1coloured) 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.pixelateandfilm_grainnow 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_patternanchor: "stars"is now a real lock, not an approximation. The v62starsanchor drovedome_rotation, a world-Y spin, while vanilla draws the star sphere with the poseRy(-90°) · Rx(starAngle)(verified withjavapinSkyRenderer.renderSunMoonAndStarson 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 internalanchor_starsfield, so astarspattern is rigidly attached to the star material as it rotates.dome_rotationis not hijacked: it stays the author's world-Y spin, applied first, andsun/moon/domeare untouched. Two internal Config floats (anchor_stars,star_angle) are appended aftersky_mode(no earlier std140 offset shifts); no datapack field renamed,PROTOCOL_VERSIONunchanged. See sky_pattern. Not verified in game;scripts/check-sky-pattern.ps1now 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 wasvfxweaver:ringed_glsl, a screen-space 2D figure, so thespace: "world"aura/surface demos rendered nonsense and the dynamic-data demo could not show the data driving anything (ringed_glslignoresvfx_mask_data). The new built-invfxweaver:blobs_glslis a genuine world-space SDF (a union of up to eight spheres, negative inside): spherektakes its centre/radius from consecutivevfx_mask_dataentries (d[4k..4k+3]) and the leaf'sp0..p7are global controls (offset, radius scale/bias, count), with an optionalvfx_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'sp0/p1asvfx_shape_params0/vfx_shape_params1(declared outside the injection markers, so an older plugin is unchanged) so the bounds can stay conservative.vfxweaver:ringed_glslstays 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) andshow_mask_custom_data_demo(animatedmask.p0.d<J>so the spheres visibly move). The plugin leaf shape is inert on the1.21.11node (no shader-source hook). Additive: no existing pass's UBO/wire layout changes,PROTOCOL_VERSIONunchanged. See masks. Not verified in game;scripts/check-mask-world-plugin.ps1asserts 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 vanillaFogRenderer.setupFog's return (fix + doc). The effect now mutates the fog instance the game produces in place atsetupFog'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 onupdateBuffer(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_VERSIONunchanged); the 1.21.11 path keeps the private@ModifyArgson the six distance slots and adds a return hook for the caller-visible colour. The mixin runs atpriority = 900so 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 fogsColorModulatortoward 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 withjavapagainst each node's real jar, andscripts/check-fog-modifier.ps1asserts the per-node hooks (26.x@InjectatsetupFog's return mutating the returnedFogDatain place; 1.21.11@ModifyArgsslots plus the return colour hook),priority = 900and the combination maths runnably.
v65¶
fog_modifier: a smooth transition from the vanilla fog colour (fog_color_amount), and the sky/cloud ends followfog_end_scale. New optionalfog_color_amount(0..1, neutral1/unset, unauthored) scales the instance's colour weight:0leaves the colour completely untouched (the colour is not written at all andhasColorstays false),1is 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_amountper effect) there is no second blend stage and the distance maths is unchanged; the final colour isvanilla + (target − vanilla)·min(1, Σ(w·amount)), clamped, andNaNnever leaks. Author a constantfog_r/fog_g/fog_band keyframefog_color_amountfrom0to1to 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 withfog_end_scale, sofog_end_scale < 1pulls the wall in and narrows the horizon band and brings the clouds closer; they have no start value, sofog_start_scalenever touches them. Additive and backward-compatible: a new param only, absent = the full authored colour, no datapack field renamed, no shader/UBO change,PROTOCOL_VERSIONunchanged, no new dependency. See fog_modifier. Not verified in game: the visual ramp and the distance look;scripts/check-fog-modifier.ps1asserts the colour-amount maths runnably (amount0/0.5/1, unauthored =1.0, order-independent, no colour write at0) and the per-node hooks statically.
v64¶
fog_modifier: change the vanilla fog at its source. A new effect, the fog sibling offov_modifierand deliberately not a post pass:fog_start_scale/fog_end_scalemultiply the vanilla fog start/end distance (neutral1.0;>1sees further,<1pulls it closer) and optionalfog_r/fog_g/fog_boverride 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_distance10 /fog_end_distance96 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 —FogDatahas no density field andEnvironmentAttributeshas 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 theblock_tint/block_outline/glow/spark overlays use the fog-freecore/position_colorshader, so only the entity-effect pipelines inherit the change. No shader change, no UBO change,PROTOCOL_VERSIONunchanged, no new dependency. See fog_modifier. Not verified in game: the visual result; the write point was proven withjavapagainst each node's real jar, andscripts/check-fog-modifier.ps1asserts the type/neutral/isPostProcessingwiring, the manager accumulation, the per-node target and the combination maths runnably.
v63¶
sky_patterndrops the legacy equirectangulardomemode. The effect is unreleased, so the single global chart is removed rather than kept as a legacy path:patchis renumbered to0(default) andfillto1, 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_uvstays ininclude/dome.glslfor thespace: "dome"mask path (the mask is untouched). The built-insky_patternand every sky demo use onlypatch/fill. No new shader field, no UBO/Config change,PROTOCOL_VERSIONunchanged. See sky_pattern. Not verified in game: the GLSL cannot run headless here - the static contract is asserted byscripts/check-sky-pattern.ps1and the owner checks visually.
v62¶
sky_patterncan follow the sun, the moon or the stars. A new optional top-levelanchorfield: omitted (or"dome") keeps today's world-fixedanchor_yaw/anchor_pitch;"sun"and"moon"make the pattern track the vanilla body;"stars"locks the pattern to the rotating star sphere by drivingdome_rotationfrom 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 aSkyRenderermixin, 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: aSkyRenderermixin 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_VERSIONand 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 shippedscripts/check-sky-pattern.ps1asserts the parse, the resolver and the angle→yaw/pitch maths runnably, and the per-node sky-state access was proven withjavapon the real jars.
v61¶
- A
patchhas 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). Apatchis 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_scaleis documented as the cell scale on that plane (the half-angle table is gone), and the demos keep the v60sky_modereassignment (show_sky_pattern_textureandsky_cracksarefill;show_sky_patternandnine_red_pixelsare smallpatchdecals). Theshow_sky_patternring demo no longer animatesdome_rotation: inpatchmode that spins the sampled direction before projection and carries the decal along its latitude, so it circled the player; the demo now animates onlytile_scaleandopacity, which leave the anchor fixed, and the docs state the semantics (dome_rotationlocks a decal to the rotating star sphere, the in-planerotationspins the figure in place). No datapack field renamed, no UBO/Config change,PROTOCOL_VERSIONunchanged. See sky_pattern. Not verified in game: the GLSL cannot run headless here - the static contract is asserted byscripts/check-sky-pattern.ps1and the owner checks visually.
v60¶
patchdecals are bounded to a clean disc, and the demos use eachsky_modefor what it is for. The gnomonicpatchdecal divided bydot(dir, anchor), so its tiling frequency grew without bound as the ray approached the tangent plane's horizon and the oldfacinggate 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.patchnow bounds the decal to its unit disc:tile_scaleis 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 (oversoftness, 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 - usefillfor content that must wrap the whole sky. The demos were reassigned:show_sky_pattern_texture(a whole-sky texture) andsky_cracksarefill, whileshow_sky_pattern(the ring) andnine_red_pixelsare small boundedpatchdecals (tile_scale0.4-0.55 and 0.3). Additive and backward compatible: no datapack field renamed,PROTOCOL_VERSIONand 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 byscripts/check-sky-pattern.ps1and the owner checks visually.
v59¶
sky_patterngains asky_modeprojection and stops funnelling at the zenith. The first release projected the whole dome through one global equirectangular chart, which has two topological defects:uis undefined at the poles (tiling there has infinite frequency, so the pattern winds into a funnel at the zenith - where players look) andumust wrap at ±180° yaw (a visible seam / mirror axis).sky_modereplaces 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, keeptile_scale <= 1.0;fillcovers 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; anddomeis 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 inpatchand a tiling phase shift infill;dome_rotationspins the whole field. Additive and backward compatible:sky_modeis appended last to the Config UBO, no existing shader's layout changes, no field renamed,PROTOCOL_VERSIONunchanged, 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 byscripts/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 ofsurface_pattern: the same structuralpatternblock, shape library andpattern.textureaddressing, but the projection target is the dome. The pixel's view ray is reconstructed, optionally spun about world Y by the newdome_rotationparam, and mapped to an equirectangular dome coordinate, where the figure is placed by the newanchor_yaw/anchor_pitchparams (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; apattern.texturewith asheetand the animatableframeparam gives an animated sky. Additive: a new effect type only, no existing effect changed, no field renamed,PROTOCOL_VERSIONunchanged, and no existing shader's UBO layout touched (the newsky_patternConfig adds avec4 cam_posthe other depth pass does not have). See sky_pattern. The dome's pole pinch and theuwrap seam are inherent to equirectangular; the far-depth gate uses the same in-game-verified test as theskymask. 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_beamcan 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 asend − axis × height, so the geometry finishes exactly at the point (with the default axis it hangs straight down fromheightabove it — a beam falling out of the sky). They are ordinary animatable params (keyframes,expr, graph, bind), and a beam from A to B isend_at = Bplus 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 asky/domeleaf covers them exactly like the empty sky — a recoloured sky recolours the sun and moon with it (to sit under the celestial bodies, the plannedsky_layerselector 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_beampoints in any direction. Three new params —dir_x/dir_y/dir_z, defaults0/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
skyleaf andspace: "dome". A mask can now target the sky instead of the world.skycovers 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, withcenterauthored as[yaw, pitch]degrees (yaw 0 = south, 90 = west, ±180 = north; pitch -90 = up, 0 = horizon, 90 = down) andradiusin 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 aworldleaf, which classifies geometry, and ascreenleaf, which can cover anything. A domecentermust be a literal[yaw, pitch]; a bound centre is a parse error for now, askyleaf may not bescreen/world, and dome space accepts only the 2D shapes andsky. 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
worldcustom leaf registered throughVFXAPI.registerMaskShapeGlslnow 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-insphere/boxaura. 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 optionalvec4 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 defaultsurfacemode 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_versionand 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 newVFXAPI.maskData/sendMaskData(orsetParam/sendSetParamon a singlemask.p<N>.d<J>). A plugin reads its own leaf's slice through the wrapper helpervfx_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 avec4-packedshape_dataarray (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_VERSIONis 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 on1.21.11the 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
blureffect uses a fixed-tap separable Gaussian, so an animated radius no longer pops; client entity selectors supporttag=,name=,distance=,limit=andsort=, 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 asvfxweaver:graph_demoorvfxweaver:graph_logic_demoreproduce 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_patternwith 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; onlymod_versionand the version wording moved, so the actual changes are the v33–v46 entries below.
v46¶
- Textured
surface_patternnow 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.1guard with a neutral fallback for the1.21.11node, so once the depth pass started running there the texture never resolved,tex_flagsnever reached theRESOLVEDbit 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 thespriteAtlasManager (SpriteIdkeyed byTextureAtlas.location()), 1.21.11 through the remapped model AtlasManager andTextureAtlas.getSprite— funnelling every source form (block/item/atlas/standalone) through oneresolved(...)factory that sets the flag bit. A standalone texture is shared by both nodes. See surface_pattern. - The
surface_patternstd140 layout guard no longer aborts the post layer. The registration-time guard queriedglGetUniformIndiceswith block-qualified names (Config.tile_scale); a driver that reports only the bare member name returned-1, which was then passed toglGetActiveUniformsiv(GL_INVALID_VALUE) and threw out ofexecute, 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 (aworldleaf, anauravolume, a block leaf'socclude) 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 structuralsurfaceblock (defaultfalse). With"stitch": trueevery face samples the same top-down coordinatep = 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), andmin/maxbecome 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.0coverage is no longer noisy. The1.0fallback compared the raw depth-derived normal against exactly1.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. NewVFXAPI.sendStopAll(ServerPlayer).- Mask depth gate is consistent across nodes.
depthRecipeVerified()was left in the 26.2 Stonecutter form on disk, so the active26.1.2Fabric node compiledreturn truewhile every other node (including26.1.2-neoforgeand1.21.11) compiledfalse. The active-node form is now thefalsebranch, 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
DepthSamplerplaceholder 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 on26.2,26.1.2and1.21.11.
v42¶
surface_patternnow requires 26.2 (it no longer silently passes through on 26.1.2). The pass is registered on>=26.2only, 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.endFramehook, which runs afterrenderLevel, so asurface_pattern(defaultscreen_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.0is no longer undefined. The degeneratesmoothstep(normal_mask, min(normal_mask + 0.2, 1.0), …)edge collapse (undefined in GLSL) is guarded by clampingnormal_maskto0..1and falling back to the exact hard test at1.0.- A
band_softnesswider 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. distortwarps a flat surface instead of translating it. Its phase wasdot(world, normal), constant on any flat axis-aligned surface; it now follows the in-plane coordinate. Definitions with a non-zerodistorton 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_zare rejected (usepattern.center); an entity-anchoredpositionsentry on asurface_patternis rejected; apattern.texture.id/atlaswith invalid id syntax is a per-file parse error. - Per-frame work in the post chain is reduced — one definition lookup per
surface_patternpass 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-nestedopis now a parse error), the custom-leaf (2) and block-leaf (1) caps, an animatable block centre, a composed custom leaf'ssoftness, the depth gate (a depth-needing mask fails closed wheresurface_patterndoes), 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_patternfigures — the structuralpatternblock accepts an optionaltextureobject: 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.idis required;source(block/item/atlas/standalone) is inferred from the id when omitted;channel(alphadefault,luminance/r/g/b),sheet([cols, rows],1..16,cols*rows <= 256) andaspect(preserve/stretch) are optional. The texture is the figure and an authoredfigurebecomes its mask (a texture with no figure is not clipped). New animatable paramsrotation(overrides the structural rotation),frame(sprite-sheet cell) andtexture_tint(0..1recolour). Atlas sprites use their stitched sub-rect and animate with the atlas; standalone textures sample0..1. Additive: without atexturethe 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
texturefunction and the new pattern texture. surface_patternanchor is world-anchored, never the camera — with no explicitcenterthe 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 declaredposition(/vfx playat), else thepos_x/pos_y/pos_zbinds, else the local player for a player-anchored play. An explicitcenterstill wins; camera-only movement no longer moves the pattern. See surface_pattern.
v39¶
surface_patternsurface selection — an optional top-level structuralsurfaceblock selects which face orientations receive the pattern (faces:up/down/north/south/east/west, axis aliasesx/y/z, groupshorizontal/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 numericnormal_maskbehaviour 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_patternband edge is no longer hard — a surface lying exactly on a bandmin/maxbound flickered, because the depth-reconstructed axis coordinate jitters across the inclusive test from pixel to pixel. The optionalband_softness(blocks,0..4, default0) fades the band edge over a small world-space distance, keeping the interior fully on and the outside fully off;0is the exact hard edge, so existing definitions are unchanged. See surface_pattern.
v38¶
- New
surface_patterneffect — a world-anchored shape pattern (circle/ellipse/rect/polygon, tiled by a structuralrepeatmodifier) 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-levelpatternblock, never inparams. 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
invertmask 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 reservedmask.p<N>.center_x|center_y|center_zparams (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 atscreen_layer: 0or 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
rectis the whole screen (the reported "whole screen tint when the villager is not resolvable"). The unresolved source still reports once throughVFXLog.warnOnce. See Masks. auramasks 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_demoand itssurfaceA/B partnervfxweaver:mask_world_demonow use a fixedradiusof 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 newvfxweaver:mask_pulse_demo, which also documents the"derive": "distance"binding form (54 built-ins).
v35¶
- World-volume masks gained an
auraevaluation mode — asphere/boxmask 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 oversoftness. Sky and missing depth count as "nothing occludes". The demovfxweaver:mask_entity_demo(sphere + screen rect) now usesaura;vfxweaver:mask_world_demois the same entity-following sphere in the defaultsurfacemode for an A/B comparison (53 built-ins).
v34¶
- Per-pixel fields — a field-capable input (currently
dent.intensityandcolor_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 functionsconstant,noise,shape,gradient,curve,texture,depth,depth_gradient,normal_facing,screen_uvandworld_pos, the shared shape set (circle/ellipse/rect/polygonwithfill: solid|stroke,softnessand arepeattiling modifier, plus the 3Dsphere/boxhelpers), 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-insvfxweaver:dent_field_demoandvfxweaver:tint_field_demoare the reference examples (48 built-ins).
v33¶
- Value graphs — an effect can drive any numeric input from an optional
graph+inputsblock (see Value graphs):constant,time,random,noise,curve,math,mix,clamp,remap,bindandexprnodes plus the logic nodescompare,boolean,ifandswitch, and reusablesubgraphs(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 insideparams. A broken graph fails that file only, and the built-invfxweaver:graph_demois the reference example (45 built-ins).
v32¶
- Block-model particles — the
particleseffect can emit real block models instead of vanilla particles:"particle": "block"with ablockstate, or a reusable preset indata/<namespace>/vfx_particles/<name>.json(registerable from code withVFXAPI.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.spawnBlockParticlespawns one immediately on the client. - Item-model particles — the same engine can draw real item models:
"particle": "item"with anitemid (e.g."minecraft:skeleton_skull"), or avfx_particlespreset 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 asItemDisplayentities 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_frictionandspin_roll;spinis 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. Thespin/spin_random/spin_friction/spin_rolleffect params override a named preset; the defaults (tumble/random/1.0/1.0/0.5) reproduce the previous tumbling behaviour exactly. vfxweaver:block_chainbuilt-in demo — theblock_chaineffect now ships a built-in definition (44 built-ins), so it appears in/vfxtab-completion and/vfx play vfxweaver:block_chainworks 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_particlespresets 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: newpos_randparam (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 (0restores the even spacing).seeddrives the position as well as the length, so animating it churns the layout.pulse_ring:thickness: 0now 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/reloadand 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.setParamExprandVFXAPI.setKeyframemirror the network actions (sendSetParam,sendSetParamExpr,sendKeyframe) locally, next to the already-localplayEffect/playEffectId/moveEffect/stopEffect- a pure client-side mod can drive effects end to end. - Live edits are recorded into Flashback replays:
setParam,setParamExprandsetKeyframe(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/playEffectIdtake an optional world position and entity-UUID list, andVFXAPI.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.11build alongside26.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) oreyes(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/alignparams), rendered through the vanilla submit pipeline (shaderpack-safe). Optionalphysics: 1— a verlet rope with gravity sag, world collision, player push,swaywind and animatablelength(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 viadust. 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+ optionalholder, normalized onrange, works as a value or amultiplymultiplier). - 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 serverboundvfxweaver:vfx_requestpacket letting client mods play effects through the server (broadcast is operator-gated). - Built-in effects now ship as JSON resources (
data/vfxweaver/vfx/*.jsoninside 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
Vec3coordinates: entity anchors and API moves are no longer snapped to block centres (staticpositionsentries keep the historical block-centre behaviour). Protocol version 6.
v24¶
- World overlays:
positionsentries 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
paramsvalues are now full parameter specs —start/end,keyframes,bind,expr,multiply(plain numbers still work as constants).
v23¶
light_beam: newbottom_fadeparam (fades the column toward the bottom, mirrorstop_fade).- Fixed
light_beamrendering under shaderpacks: packs that declare vertex colourflat(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. afterimageuses an island blend;camera_shake/camera_rollalso 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_blockson the built-in effects) now apply as constant values instead of being silently dropped;through_blocksis 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:1draws an opaque shell before the body (rim around the silhouette, visible through walls); block outlines ignorethrough_blocksand never cover the block itself. camera_shakegained afrequencyparameter (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 seton a non-running effect starts a normal instance with the definition's owndurationinstead of an immortal one./vfx playentityaccepts an optional[players]list — who sees the effect (default: the executing player).- Removed the
/vfx keycommand (nobody used it;VFXAPI.sendKeyframeand the networkKEYFRAMEaction 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) andentity_outline(an "inverted hull" silhouette outline, thicknesswidth). 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"(variablest/x/y/z/pi/e, functionssin/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
/vfxcommands (play,playat,stop,set,key) now require operator rights;/vfx listis open to all. - Tighter client protection from a hostile/broken server: caps on network packet sizes (effect params, definition/curve sync),
vfx_syncpacket 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_syncpacket on join and after/reload) — custom effects now play for players on a dedicated server, like in single-player.
v12¶
sendEffectaccepts a direct world position — nopos_x/y/zhack (the client immediately re-anchors spatial bindings to the point).- Custom easing curves: files
data/<ns>/vfx_curves/<name>.jsonor an inline object{ "curve": [[t,v],...] }in anyeasingfield. - 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.