Changelog¶
Format follows Keep a Changelog.
This page is the authoritative record of released mod versions. The newest entry is the current
mod version (2.0.0, read from mod_version in the build). Each ## heading names a release; where
a release shipped with a guide revision the heading shows both numbers (v1.2.0 / Guide v32), and a
heading titled only Guide vN is a guide change that shipped without a mod version bump. The two
numbers are independent: the mod version is what you download, the guide revision is how many
times this documentation has been revised. The authoritative record of guide revisions is the
Guide changelog. Which jar to download for your Minecraft line and loader is in
the download table. Add new entries at the top, in the same PR as the behaviour
change.
Unreleased¶
2.0.0 — 2026-09-20¶
2.0.0 gathers everything that landed after the previous release. Nearly all of it is additive — existing datapacks keep working unchanged — and it is the first release to carry the six Fabric + NeoForge jars for the 26.2 / 26.1.x / 1.21.11 lines.
Added¶
- Value graphs — an effect definition can drive any numeric input from a small node graph
(
graph+inputs) instead of one constant. Nodes:constant,time,random,noise,curve,math,mix,clamp,remap,bind,exprand the logic nodescompare,boolean,if,switch; reusablesubgraphsact as macros. Wiring is optional and lives outsideparams, so an older build ignores it and you can remove it without touching the rest. Reference built-ins:vfxweaver:graph_demoandvfxweaver:graph_logic_demo. - Per-pixel fields — a field-capable input can vary per pixel instead of once per frame, using
the built-in
noise,shape,gradient,curve,texture,depth,normal_facing,screen_uvandworld_posfunctions and simple compositions. First consumers:dent.intensityandcolor_grade.tint_r(built-insvfxweaver:dent_field_demo,vfxweaver:tint_field_demo). Depth/world fields needscreen_layer: 0; elsewhere they return the neutral value. - Masks — restrict where a post-processing effect applies, from a tree of screen shapes, world
volumes (
sphere/boxwithvolume: "surface"or"aura"), block-geometry leaves and code-registered GLSL shapes. A leaf whose source entity cannot be resolved drops only that leaf, never the whole mask, and never expands coverage.VFXAPI.sendMaskMove/maskMovemoves one leaf from the server or every tick. surface_pattern— projects a figure (or a texture) onto the terrain behind each pixel, anchored to a world position, with a structuralsurfaceblock choosing which faces receive it (faces, an inclusivemin/maxband,band_softness) and an optionalpattern.texture. Additive; needsscreen_layer: 0.- Spark particles — the
particleseffect can emit glowing additive sprites ("particle": "spark", or avfx_particlespreset with"kind": "spark") withcount,speed,life,gravity,bounce,size,trail,glowand size/colour curves. Built-in presets and playable references:vfxweaver:ember,vfxweaver:sparks. /vfx stop [<player>]— stops every active effect of a player (default: the executor). The old/vfx stop <effect> [players]form is unchanged; a bare token is read as an effect first, so target a player with a selector (@p/@a).
Changed¶
- Scene depth works on every supported Minecraft line, not only 26.2.
surface_pattern, the depth/world field functions and the depth-based mask shapes used to render on 26.2 alone. They now render on 26.1.2 and 1.21.11 as well; 26.2 behaviour is unchanged, and where no trustworthy depth is available the effect still fails closed rather than sampling the wrong convention. surface_patternno longer trails the player by one frame atscreen_layer: 0— the effect clock and camera/player snapshots now advance before layer 0.- Two plays of one masked definition share a single coverage (the first play's animated centre/radius/softness wins). Put the two masks in distinct definitions if they must differ.
- Mask coverage is screen-space, computed at layer 0 against the intact scene depth. A masked
effect running at a later layer can therefore tint the first-person hand where a masked block lies
behind it; run the masked effect at
screen_layer: 0, or depth-occlude the consumer, to avoid it. - The bundled mask demos were reworked for a clearer A/B:
vfxweaver:mask_entity_demoandvfxweaver:mask_world_demouse a fixed 4-block radius, andvfxweaver:mask_pulse_demoshows the growing-sphere, distance-derived look.
Fixed¶
VFXAPI.sendSetParamExprandsendMoveover the network now do what their names say. Both were misread by the client as a play and silently restarted the effect instead of editing or moving it; each action now has its own handler, so live edits work and replay correctly.- A
persistent: trueeffect withoutloopanimates and emits again. Itsstart/endparams used to freeze at their start value and particle/spark emission budgets could round down to nothing. - Long sessions no longer leak per-effect particle state — particle budgets, aimed-particle lists, rope simulations and spark budgets are cleaned up when an effect stops.
- Textured
surface_patternfigures resolve on every node, a missing sprite now shows the vanilla missing texture instead of drawing nothing, and the projections are crisp (NEAREST sampling, no mip bleed). - A cached texture view no longer goes stale after
/reloador a resource-pack change. surface_patternband edges andnormal_mask: 1.0no longer flicker.- Mask correctness batch — animated
softnessnow applies, right-nested compositions and over-cap leaves are per-file errors instead of mis-evaluating, and a block leaf's centre is animatable again. - A purely screen-space mask renders on every node and every layer, including where scene depth is unavailable.
- A malformed field
textureid fails only that one effect, not every post effect for the frame. surface_patternparse and projection fixes — an emptyfaceslist,pattern.center_x/y/zand entity-anchoredpositionsare now parse errors that name the problem.- A repeated explicit instance id no longer stacks duplicates — replaying a play with a non-zero
id restarts that instance in place, so
sendStop/sendMovestill address it. - A disconnecting player's server-side effect memory is released, so a long-lived server no longer accumulates per-player state for players who left.
- An unknown
particlesshapestops that frame cleanly instead of discarding the frame's emission budget. - Less per-frame work in the post chain (fewer lookups, cached block selection, parsed texture ids, no per-frame allocation in the sprite probe), and a mid-frame uniform-arena growth no longer risks using freed memory on the following frame.
Technical notes (for developers)¶
The user-visible list above is the changelog proper; these are the implementation details behind it.
- Per-node depth convention, proven from the real client jars. 26.2 is reversed
(
glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE)plus a near/far-swapped projection: near = 1, far = 0,CompareOp.GREATER_THAN_OR_EQUAL); 26.1.2 and 1.21.11 are standard (near = 0, far = 1). The flag is a per-node compile-time constant (VFXShaderPrograms.DEPTH_REVERSED) injected as theVFX_DEPTH_REVERSEDshader define, and the single shared reconstruction ininclude/camera.glslconverts the raw depth, picks the sky test and the block-occlusion comparison.depthRecipeVerified()gates every depth-needing pass. - The effect clock moved from the
FogRenderer.endFramehook (which runs afterrenderLevel) to before layer 0; layers ½ reuse the same snapshot and nothing advances twice. - One shared depth/world reconstruction in
include/camera.glsl, imported byfield.glslandsurface_pattern.fsh(was duplicated);normal_facingnow returns an outward, camera-facing normal. - Network receiver dispatch gives
STOP,SET_EXPRandMOVEeach one reachable handler (they were nested inside theSTOPbranch); the wire format is unchanged. - Persistence is a lifecycle flag, not an
Integer.MAX_VALUE-tick timeline, so animated params and emission budgets behave normally until stopped. - Per-instance state (vanilla-particle budgets, aimed-particle lists,
block_chainrope simulations, spark budgets) is keyed by instance id and pruned against the live set every frame. - Texture resolution has a real per-node implementation: 26.2/26.1.2 via the sprite
AtlasManager(SpriteIdkeyed byTextureAtlas.location()), 1.21.11 via the remapped modelAtlasManagerandTextureAtlas.getSprite; every source form funnels through oneresolved(...)factory that sets theRESOLVEDflag bit, and a standalone…/textures/…id gets.png. - The std140 layout guard 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 driver that reports only bare names cannot drop the post layer.
- The cached texture view is re-derived from its descriptor on every use, because a reload closes and recreates the loaders' GPU view while reusing the descriptor.
normal_mask: 1.0now tests the snapped normal (abs(n.y) >= 0.5) instead of the raw normal, andnormal_maskis clamped to0..1to avoid a collapsedsmoothstepedge.- Animated mask
softnessis written intoshape_op[i].zwith the parse-time default as fallback, and also drives a composed custom leaf's falloff (previously hard-fixed at0.25). - Mask composition folds left-associatively, so a right-nested
op, a third custom leaf and a second block leaf are per-file parse errors rather than silent mis-evaluation. - The coverage prepass binds the main target's depth view (else its colour view) as the
DepthSamplerplaceholder when depth is untrusted, avoiding a feedback loop with the coverage target's own texture. - Parse fixes: an empty
surface.facesis an error,pattern.center_x/y/zis rejected in favour ofpattern.center, an entity-anchoredpositionsentry on asurface_patternis rejected, and an invalidpattern.texture.id/atlasis a per-file error. - Crisp pattern textures: NEAREST filtering, no mipmaps, each repeat spans
tile_scaleblocks, sprite-sheet cells are inset by half a texel,preserveuses the cell's real pixel aspect, andframewraps into0..cols*rows-1. - Internal cleanup: removed the write-only
ProgramInfo.coverage/VFXPass.coverageflag (the coverage prepass is identified by its pipeline/PassRole).
v1.2.0 / Guide v32¶
Added¶
- Block-model particles (
particlesblock mode +vfx_particlespresets) — theparticleseffect can now emit real block models instead of vanilla particles, chosen inline ("particle": "block"with ablockstate) or by preset id (data/<namespace>/vfx_particles/<name>.json, orVFXAPI.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; theparticlesparams override the preset's defaults.VFXAPI.spawnBlockParticle(spec, position, velocity)spawns a single particle immediately on the client. Presets are client-local and never synced, live in a two-layer registry (datapack wins over code), and render as client-side display entities so vanilla interpolates their motion. - Item-model particles (
particlesitem mode) — the same particle engine can now draw real item models:"particle": "item"with anitemid (e.g."minecraft:skeleton_skull"), or avfx_particlespreset declaring"item"instead of"block"(exactly one of the two is required; a missing or unknown entry is a per-file parse error). Items render asItemDisplayentities, with the same brightness/physics params as block particles. This is what makes skull/head particles possible — the block form has no baked model (a block-entity renderer draws it), the item form does.VFXBlockParticleSpec.item(ItemStack)/builder(ItemStack)build the spec from code; the API methods are unchanged. - Configurable block/item particle rotation —
vfx_particlespresets (and theVFXBlockParticleSpecbuilder) gainedspin_mode(tumble= the physical full-3D spin,yaw= a uniform spin about one axis,none= no rotation),spin_axis(random/x/y/z/[x, y, z]),spin_random(0..1: how random the initial orientation and tumble axis are;0= strictly upright and identical for every particle),spin_friction(0..1: how much the contacted block's friction damps the spin;0= never decays) andspin_roll(0..1: how much tangential impact speed feeds the tumble;0= no roll transfer). Thespin/spin_random/spin_friction/spin_rolleffect params override a named preset; the defaults reproduce the previous tumbling behaviour exactly, so existing presets are unchanged. - NeoForge support for all three Minecraft lines — a NeoForge node is built beside each Fabric node from the same source tree (
26.2-neoforge,26.1.2-neoforge,1.21.11-neoforge), so the project produces six jars, one per (Minecraft line, loader). The build scripts are split per loader (build.fabric.gradle/build.neoforge.gradle) and a platform layer (dev.vfxweaver.platform/client.platform) hides every loader API so the render, datapack, API and command code stays loader-agnostic. The only per-line loader API split is<26.1: NeoForge21.11has no submit-geometry event and reads the collector fromLevelRenderer.submitNodeStorage, while NeoForge26.1.2matches26.2; dependency ranges stay per line (deps.neo_compatfor NeoForge,deps.mc_compatfor Minecraft). Flashback recording is Fabric-only — Flashback has no NeoForge build, so the compatibility layer no-ops there. vfxweaver:block_chainbuilt-in demo — theblock_chaineffect type now ships a built-in definition, so it appears in/vfxtab-completion and/vfx play vfxweaver:block_chainworks without writing a datapack. It hangs a 6-linkminecraft:iron_chainfrom the local player (pos_x/y/zbound toplayer_x/y/z,physics: 1,length: 6,sway: 0.4), the same self-anchoring pattern as thevfxweaver:particlesdemo; built-in definitions now number 44.
Changed¶
- Block/item particles tumble like real falling cubes —
spin(degrees/tick) is now the magnitude of a full 3D angular velocity about a random axis, with a random full-3D initial orientation, instead of a yaw around world Y. The orientation is integrated each physics tick and slerped between ticks for rendering. On contact the tumble is damped by the contacted block's own friction (the sameBlock#getFrictionruleblock_chainuses), so a cube lands and stops spinning; a little of the tangential slip becomes roll, and a particle with no slip gains none.spin = 0now yields a static but randomly oriented model (previously upright).
Fixed¶
- Block/item particles move smoothly and spin on a sane axis — the model particles were submitted as geometry with their own pose interpolation, which read as jerky stepping and rotated around an odd pivot. They are now driven through client-side
BlockDisplay/ItemDisplayentities, so vanilla interpolates position between ticks,spindrives the model's orientation, and brightness/scale map onto the display's own brightness override and transformation. One display entity per live particle, removed on death/effect stop/world unload; the physics is unchanged. - Physics
block_chainropes now settle on surfaces instead of sliding forever — the depenetration is a pure displacement (pos and prev shift by the same correction, the player-push idiom) so the push itself injects no velocity, and on contact the inward velocity is dropped while the remaining tangential velocity is damped every contacting tick by the contacted block's own friction (Block#getFriction: stone/air 0.6, ice 0.98). A rope therefore grips stone, glides on ice, and a joint lifted out of the ground gains no residual horizontal velocity. - Physics
block_chainropes no longer sink through the ground — a rope joint that moved more than half a block into terrain in one tick was being ejected through the block's far face (nearest-face resolution), so the chain tunnelled downwards; the resolver now sweeps the path from the joint's previous position, stops at the first contact and pushes the joint back out through the face it entered, dropping only the inward normal component of its velocity and keeping the tangential part. Links rest on surfaces without sinking, a joint that spawns inside a block still comes free, and a moving anchor drags the chain across the ground (instead of the joint being reverted to its previous position and freezing there). Multi-box collision shapes are approximated by their union box. - NeoForge world overlays render on the
26.1.2line — overlays now capture the camera, the render buffers and the level renderer's submit storage from the level-render stage event; the earlierSubmitCustomGeometryEventpath left those unset on26.1.2, so every buffer-based overlay threw andblock_chaingeometry was submitted in a phase that was never drawn.
v1.1.4 / Guide v31¶
Changed¶
speed_lineslines are no longer evenly spaced - a newpos_randparam (default 1.0) jitters each line's angular position inside its own slice, so the layout is uneven instead of one line per equal sector.seednow drives a line's position as well as its length (animating it churns the whole layout),pos_rand: 0restores the old even spacing, and a line pushed against a slice boundary is measured to the nearest centre so it is not cut off. Also fixed the shader's uniform name for the line length (line_length->length, matching the param), which used to log "Found unknown but potentially supported uniform line_length" at every startup.
Fixed¶
- Replay recording was silently disabled when Flashback was installed - the mod registered two custom Flashback actions, and Flashback keys its action registry by the action class; both reflection proxies share one generated class, so the second registration threw
Action already registered, the whole compatibility init was aborted and plays, stops and live edits never reached the recording. Everything now travels through a single action (the datapack definitions snapshot is marked by a reserved id in its payload), and recordings made by earlier builds still decode. pulse_ringwiththickness: 0drew a hairline instead of nothing - the band width was clamped to a 0.05 minimum, so asking for a zero-width ring still produced a visible thin ring.0is now accepted and the ring is skipped entirely (the natural reading of "no band"), while every other value behaves as before.
v1.1.3 / Guide v30¶
Added¶
- Code-registered effect definitions (for client-only mods) -
VFXAPI.registerDefinitions(Map<Identifier, String>)andVFXAPI.unregisterDefinition(Identifier)add definitions at runtime with exactly the datapack validation, in a separate local layer that survives/reloadand a server sync. Before this a client-only companion mod could not own effect ids on a server at all: a mod-provideddata/<ns>/vfx/*.jsononly loads on a multiplayer client in single player (Fabric never reloadsSERVER_DATApacks there), andVFXDefinitionManager.applySyncedreplaced the whole definition set on join, soplayEffect("mymod:thing", ...)could not resolve. Local definitions stay private to the client (never synced to other players), the datapack/server layer wins for the same id, and broken entries are logged, skipped and surfaced by/vfx validate.
v1.1.2 / Guide v29¶
Added¶
- Client-local anchored playback -
VFXAPI.playEffect/playEffectIdnow accept an optional world position (Vec3) and an optional entity-UUID list when playing locally (no packet), so screen-space effects (dent,shockwave,vortex, ...) land where the event happened instead of at the definition's default spot; the position re-anchors the definition's spatial world bindings (screen_x,screen_y,proximity, ...), exactly like/vfx playat.VFXAPI.moveEffect(effectId, instanceId, worldPos)re-anchors a running instance locally - call it every tick to follow a moving point or entity. Positions win over the definition's entity anchors; with a null position the supplied UUIDs are zipped with the definition's entity anchors in declaration order (datapackentity_selectors stay server-side, so a client-side caller resolves the entities itself). Anchored local plays are recorded into Flashback replays with their anchor. - Client-side live control -
VFXAPI.setParam,VFXAPI.setParamExprandVFXAPI.setKeyframemirror the network actions locally (sendSetParam/sendSetParamExpr/sendKeyframe), so a pure client-side mod can drive effects without a server round-trip, next to the already localplayEffect/playEffectId/moveEffect/stopEffect. A null or blank easing means linear for a keyframe segment. - Chained animation segments ("from here") - a live keyframe with a negative time pins the value the parameter has right now at the current time and runs the segment to the new value over
|time|ticks, so animation variations chain without the caller knowing the current value: ramp a blur up and let it hold, thensendKeyframe(player, effect, "radius", -20, 0.0F, easing)fades it back out from where it stands. The effect stays a single running instance with one continuous curve (nothing is applied twice), and the wire format is unchanged - only the meaning of a negativetimeis new. - More
exprmath 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)(positive, like GLSL),tan,atan(y)/atan(y, x)(= atan2),expandlog(natural) join the existingsin,cos,abs,min,max,pow,sqrt,random()andnoise(x,y,z). Argument counts are now validated when the expression is compiled, with a message naming the function and the expected count - a wrong count used to fail per frame while evaluating instead. - Live edits are recorded into Flashback replays -
setParam,setParamExprandsetKeyframe(client-local and server-driven alike) now write a live-edit action into the active replay, so a replay reproduces a mod animating a running effect instead of only its initial play. The encoding reuses the trigger action with new negative sentinels (-3set-param,-4keyframe,-5set-expr), so recordings made by older builds still decode; only a newer recording replayed on an older build is unsupported.
v1.1.1 / Guide v27¶
Added¶
- Cubic-Bézier easing curves - standard CSS-style easing. Inline:
"easing": { "cubicBezier": [x1, y1, x2, y2] }; named:data/<namespace>/vfx_curves/<name>.jsonwith{ "cubicBezier": [...] }. Endpoints are fixed at (0,0)/(1,1); the y ordinates may leave 0..1 for anticipation/overshoot (e.g. ease-out-back =[0.34, 1.56, 0.64, 1]). Evaluated by solving the curve parameter for the given progress, so the motion is smooth instead of a piecewise-linear polyline. - Multi-version builds - the project now ships from one source for Minecraft
26.1.2and1.21.11(Stonecutter; per-node dependencies and Loom). Effect behaviour, the datapack format and the network protocol are identical across both. - Server-synchronized
scoreboardbindings - the server derives the tracked(objective, holder)pairs from the effect definitions it sends (anullholder resolves to the receiving player's name) and pushes value diffs once per tick; the client caches them (VFXScoreboardCache, the vanilla client-scoreboard mirror stays a fallback). This fixesscoreboardbinds on objectives that are not displayed in a slot - the vanilla client only mirrors displayed objectives, so a client-side read alone returned nothing. Pairs come only from definitions the server sent (no client subscription), all collections are bounded. The network protocol version is bumped - update client and server together.
Changed¶
- Verbose logging is now off by default. Per-request messages (a packet received, an effect started or scheduled, a replay applied) moved from
INFOtoDEBUG, so an integration that firessendSetParam/sendKeyframeevery tick no longer floods the game log; enable thevfxweaverlogger atDEBUGto see them. Warnings that a caller could repeat every tick (unknown effect, protocol mismatch, missing permission,playEffectwithout a client, an override for an undeclared parameter) are now emitted once per distinct key through a boundedVFXLog.warnOnceinstead of on every call. Startup and datapack-reload summaries (client initialized,Loaded N effect definitions,Loaded N VFX curves) stay atINFO. - Per-node Minecraft range and Fabric Loader floor. The
26.1.xjar now declaresminecraft >=26.1 <26.2(it used to emit~26.1.2, so Fabric Loader refused to load it on 26.1 and 26.1.1 even though the Modrinth page listed them), and the loader requirement is per node (>=0.18.4for the 26.x jars,>=0.17.3for1.21.11) instead of one>=0.19.5.
Fixed¶
- A
nulleasing no longer NPEs on the client-local play path.VFXClientAPIcalledEasingFunction.builtIn(null)(and the Flashback recorder calledeasing.name()), so a caller passing the documentednull("use the definition default") crashed before the effect manager's own null fallback could run; both now pass a null function through, and a recorded replay marks "no easing override" with a blank name. - Custom easing curves silently degraded to LINEAR. Named curves were resolved while a definition was parsed, which happens before the curve registry has loaded (reload listeners
prepare()beforeapply()), so the reference fell back to LINEAR; inline curves also lost their control points over the network (only a name travels). Named curves now resolve lazily (cached, with a warning on a genuine miss), inline curves send a blank name so the client uses its own definition copy, and the client applies synced curves before definitions. - Animated
start/endparameters now pass the end value through the easing. For a curve whose value at t=1 is not 1 (a triangle/wave), the final tick snapped to the rawendvalue instead of the eased one. /vfx stop <collection>now cancels the whole pending subtree, including nested collections, instead of matching only a child's own definition id.- Player-bound world overlays (
pos_x/y/zwithbind: player_x/y/z) no longer jitter at the 20 Hz tick rate and are no longer offset by +0.5 block on X/Z: dynamic (bound/expression) positions use exact sub-block coordinates and the player position is interpolated per frame.
Guide v26¶
Added¶
camera_shakehandparam - first-person hand multiplier (0..1, default 0.5): scales how strongly the camera-shake offset is re-applied to the held-item pose; at 0 the hand stays still while the world shakes, at 1 it moves together. The minimum across active shakes wins.camera_rolltilt is unaffected.particlesworld-overlay effect - emits vanilla particles in animated shapes with zero custom textures: definition fieldsparticle(any simple vanilla id;dusttakes animatablecolor_r/g/b+size) andshape(sphere/ring/helix/line/cube/point;linespans the first twopositionsslots). Animatable params:rate(per second, × fade weight),radius,height/turns/spin(helix),speed(radial launch velocity),vel_y. Emission is framerate-independent (budgeted per instance, clamped 1024/s and 256/frame), entity anchors andsendMovework as for other world overlays, rendering uses the vanilla particle path so shaderpacks stay compatible. New builtin demo:vfxweaver:particles(golden dust helix).particles: aimed accelerating streams -aim: 1launches every particle towards the secondpositionsslot (any shape/particle; drag-free ballistics via a Fabric access widener on Particle velocity fields, conespread, per-tickaccel,lifetimeoverride) — accelerating energy lines that fly into a target block or track entity-anchored slots.block_chainworld-overlay effect - a line of real textured block-model links between two anchors (theblockdefinition field picks the block;spacing/arc/scale/alignparams), rendered through the vanilla submit pipeline (COLLECT_SUBMITS+submitMovingBlock, shaderpack-safe). Links tile the span end-to-end and stretch when pulled taut.block_chain: verlet rope physics -physics: 1turns the chain into a client-side rope simulation at a fixed tick rate: gravity sag, world collision (links catch on blocks), the local player pushes links away,swaywind wobble. One anchor = hanging chain (lengthblocks); two anchors = rope pinned at both ends,lengthsets the total chain length (more than the span = deeper sag, less = taut). Joint-count changes resample the rope shape, so animatedlengthnever snaps. Links capped at 512; no server-side collision.- Entity anchors:
point-{"entity": "@s", "point": "center"}selects the reference point on the entity:feet(default),center(bounding-box centre) oreyes. - Entity anchors:
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).
Fixed¶
particlescubeshape - two face axes reused one random value, collapsing points onto face diagonals; all six faces now sample uniformly.block_chainrendering - submit poses are camera-relative (links previously rendered off-screen); the physics rope integrates at a fixed tick rate with a time accumulator (rope froze at >20 FPS); joint-based rendering keeps links connected at bends, and joint-count changes resample the rope shape instead of resetting it.
Guide v25¶
Added¶
/vfx validate [namespace]command - dry-run definition health report: loaded count plus every broken datapack file with its parse error, optionally filtered by namespace (tab-completed). Operator-only.scoreboardworld binding - params can follow scoreboard values:{"bind": "scoreboard", "objective": "my_obj", "holder": "optional_name"}; default holder is the local player's own score, normalized onrange(default 16),invert/scaleas usual; usable as a value or amultiplymultiplier; missing objective/score evaluates to 0.- Java API: live expression override -
sendSetParamExpr(player, effectId, param, exprSource)swaps a running effect's parameter for a compiled math expression (same syntax as JSONexpr, per-instance seed) without restarting the timeline. Protocol actionSET_EXPR. - Java API: instance move -
sendMove(player, effectId, instanceId, Vec3)moves a running world-overlay instance to an exact point; per-tick calls produce smooth scripted motion. Protocol actionMOVE. - Serverbound effect requests - client mods can ask the server to play an effect via the new
vfxweaver:vfx_requestpacket: withoutbroadcastit plays only for the requester;broadcast: trueplays for every connected player and is gated behind operator (gamemaster) permission on the server.
Changed¶
- Built-in effects are datapack JSON now - all 42 built-in definitions moved from code to
data/vfxweaver/vfx/*.jsonresources inside the mod jar: they load through the regular datapack pipeline (so a broken built-in surfaces in/vfx list), sync to clients like any datapack file, and can be overridden/copied by packs (jar data is the lowest-priority layer). - Sub-block position precision - world-overlay geometry (
light_beam,pulse_ring,guide_line,block_tint,block_outline) consumes exactVec3coordinates: entity-anchored slots and API moves track at sub-block precision instead of snapping to the containing block; staticpositionsentries keep the historical block-centre behaviour. Network protocol version bumped 5 -> 6.
Guide v24¶
Added¶
- Entity-anchored
positionsfor world overlays - apositionsentry may be{"entity": "<selector>", "offset": [x,y,z]}(offset relative to the entity's feet, optional): the server resolves the selector once per play (plain/vfx play; fails when it matches nothing), the client follows the entity every frame. Slot order is preserved, soguide_lineendpoints can mix static and anchored entries; works forblock_tint,block_outline,light_beam,pulse_ring,guide_line./vfx playator a network position override wins over anchors. Java API: pass anchor UUIDs viaEffectRequest.target()in anchor order. No protocol change (reuses theentityUuidsfield). - Collections: full parameter specs on children - child
paramsvalues may bestart/end,keyframes,bind,expr,multiply(merged into a derived child definition); plain numbers keep working as constants.
Guide v23¶
Added¶
light_beambottom_fadeparam - fades the column alpha toward the bottom (0..1, default 0), mirroringtop_fade.
Fixed¶
light_beamunder shaderpacks - packs that declare vertex colourflat(e.g. Complementary) take each triangle's colour from one vertex, so the height fade rendered as clearly visible triangles ("broken" cylinders). Faded shells are now split into 32 narrow vertical slices, each quad carrying one uniform colour: flat-colour programs show a clean stepped fade with no triangle artifacts, vanilla gets an imperceptible stepped gradient.
v1.1.0 / Guide v22¶
Added¶
slice_shiftscreen effect - a straight line slices the frame; the halves slide along it with wrap/mirror fill (angle,offset,shift,mirror).noise_warpscreen effect - animated value-noise field warps the picture in fluid patches (scale,amplitude,contrast,coherence,speed,drift_x/y;time-driven morph).solarizescreen effect - bright pixels invert, dark stay (threshold,softness,intensity).double_visionscreen effect - two ghost copies with slow drift, energy-preserving blend (offset,ghost_opacity,drift,intensity).eyelidsscreen effect - two curved dark lids with correct open-state geometry, composited over the frame (openness,softness,curve).iris_wipescreen effect - old-film iris transition (radius,softness,center_x/y,zoom).digital_glitchscreen effect - band tearing + RGB split in slot-gated bursts with achanceparameter (not permanent tearing).vhsscreen effect - worn tape: a real crawling tracking band, wobble, bleed, washed contrast.shockwavescreen effect - refraction ring with full-weight composite (center_x/y,radius,width,amplitude,sharpness).afterimagescreen effect (feedback buffer) - decaying history echo with desaturation and optional drift zoom; history double-buffered, cleared on resize.stop_motionscreen effect - CPU hold-gated frame freezing at N updates/second (fps;<=1= full speed).entity_displaceentity effect - flat per-vertex displaced echo over the intact model (amplitude,scale,seed,alpha, color,through_blocks).light_beam/pulse_ring/guide_lineworld effects - additive world quad effects (columns, rings, dashed parabola).camera_rollmisc effect - dutch-angle camera tilt with optional sinusoidal wobble.
Changed¶
light_beam- layered cylindrical shells with cubic softness falloff (opaque core fading to a soft edge), optionaltop_scalefor tapering beams.pulse_ring- camera-facing billboard mode (flat ring always perpendicular to the camera) and arotparam for orientation in billboard mode.afterimage- history echo now uses an island blend (the ghost does not overwrite the live frame's transparency).camera_shake/camera_roll- now also shake/tilt the first-person hand, not just the world camera.entity_displace- vertex displacement over the intact model with a quantised (snap-glitch) field.
Removed¶
block_displace- opaque per-vertex block tearing effect. Cut before release (the look was not useful enough to keep the world-space hash + block-model pipeline).god_rays- additive body-beam entity effect. Cut before release (superseded bylight_beam).scan_sweep- sweep-sheet world effect. Cut before release (too close tolight_beam).hud_fade- HUD hide effect (binary F1-style hide +hide_hand). Cut before release (reverted; to be redesigned later).
Notes¶
- Screen effects that animate procedurally accept the auto-filled
timeparameter (effect age in ticks); film-grain-style shaders already used it. - The
exprparameter syntax (e.g. steppedseed) works in datapack definitions; command param-maps ({[...]}) accept floats only. - A
gradlew build-verified implementation batch; visual effects should be verified withgradlew runClient.
Fixed¶
- Effects replayed fresh on every world join and never expired. The reconnect memory used the server tick counter as its clock, which resets when the server instance is recreated (every singleplayer world reload): elapsed time collapsed to zero, so all previously played effects were re-applied at full duration on each join. The reconnect memory now uses wall-clock time (1 tick = 50 ms), stable across world reloads and restarts.
- Item frame overlay drawn twice / offset. The overlay hook fired on every PoseStack.popPose in the vanilla submit and the frame-local pose was rebuilt from identity - the quads landed at world origin or offset by the item transforms. It is now drawn once, anchored to the frame model pose with model-space coordinates matching the panel plane (z ~ 0.97).
v1.0.6 / Guide v21¶
Docs¶
- Documentation rewritten. Every effect now has a per-parameter reference (type, default, what it actually does) and copy-pasteable command/JSON examples.
Added¶
- Item frames as entity effect targets.
entity_tint/entity_outlinenow also apply to item frames (non-living entities): the UUID is captured from the frame renderer and a flat tint quad / rectangular outline is drawn on the frame plane, aligned with the frame model. vertex_displaceeffect (entities + blocks) — all vertices of the target model are randomly displaced; amount, field detail and the random seed are parameters, and the seed itself can be animated/bound to drive the glitch motion (stepped snaps or smooth morphing).look_atworld binding. Likelook, but the target direction is derived from a worldpos: [x,y,z]anchor instead of explicit yaw/pitch (rangedefault 90, supportsinvert/scale).
v1.0.5 / Guide v20¶
Added¶
- First-person hand effects.
entity_tint/entity_outlineactive on the local player now render on the first-person arm as well (the arm bypasses the normal entity submit path, so it needed its own hook).through_blocksis ignored there - the hand always draws on top of the world. camera_shakefrequencyparameter - noise oscillations per second, default 7 (previous fixed value).screen_layerparameter for all screen effects - where the effect applies:0= below the first-person hand and the GUI,1= above the hand below the GUI (default, previous behaviour),2= above everything including the GUI.- Datapack
region: [x0,y0,z0,x1,y1,z1]syntax for block effect positions. VFXAPI.EffectRequestfluent builder for play/send.
Fixed¶
- Persistent effects were dropped from reconnect memory instead of re-applied.
applyTotreated the-1duration of persistent effects as "expired" and deleted them when their viewer rejoined - a permanent entity tint disappeared forever after one relog. Persistent plays are now re-sent as-is on every join. - Parameter overrides for names not declared in the definition were silently dropped. Setting
through_blocksvia/vfx playentityon the built-in entity/block effects did nothing becausecreateTimelineonly applied overrides for declared params. Undeclared overrides now land as constants;through_blocksis also declared on the four built-ins (tab-completion). - Outlines now always render under their target. Entity outline with
through_blocks:1used to cover the entity with the shell colour; it is now drawn as an opaque shell before the body pass at a lower submit order, so only the rim around the silhouette survives while staying visible through walls. Block outlines ignorethrough_blocksentirely (always occluded) for the same reason.
v1.0.4¶
Added¶
[players]argument in/vfx playentity. The command now accepts an optional player list at the end — who sees the effect. Previously an entity effect was always sent only to the player who ran the command, so there was no way to show it to someone else (e.g. to all players in cutscene maps).
Examples:
- /vfx playentity vfxweaver:entity_outline @e[type=!player,distance=..10] @a — everyone sees the outlines;
- /vfx playentity vfxweaver:entity_tint @e[tag=boss] Alice Bob — only Alice and Bob see the tint.
Without [players] behaviour is unchanged (the executing player sees it). Each viewer gets their own copy of the effect, so it also survives their reconnects independently.
Changed¶
durationnow ends every non-looping instance. The one path that ignored it was/vfx seton a not-running effect: it used to start an immortal persistent instance whose animation was stretched overInteger.MAX_VALUEticks — visually frozen on its first frame, never removed on its own, and replayed from scratch after every reconnect. Such instances now start with the definition's owndurationand end on schedule like a normal play. Definitions explicitly marked"persistent": truekeep their until-stopped semantics.- Protocol version 4 → 5 (
vfxweaver:vfx_trigger). The play packet now carries a resume offset used when re-applying effects after a reconnect (see Fixed). A 1.0.4 client ignores packets from older servers and vice versa — update both sides together.
Removed¶
- The
/vfx keycommand. Nobody used it; runtime keyframing stays available to mods viaVFXAPI.sendKeyframeand the networkKEYFRAMEaction, which are unchanged.
Fixed¶
- Effects no longer restart from the first keyframe after a reconnect. The server keeps a per-player memory of running effects; on rejoin it re-sends each still-running one with an elapsed-time offset plus any runtime keyframes, so the animation continues exactly where it left off instead of starting over. Keyframes past the nominal duration also extend the effect's lifetime correctly — previously such effects were either dropped early or replayed from the beginning.
- Invisible effects no longer linger until the cap. A non-looping instance whose runtime edits have all animated down to zero (or been set to
0) is invisible but used to keep occupying one of the 64 active-effect slots until the oldest-effect eviction kicked in. It is now removed as soon as every edited parameter rests at zero. Definition-driven animations and looping effects are unaffected.
v1.0.3¶
Fixed¶
- Expression parser now accepts
_in identifiers — documented variables (player_x/y/z,light_level,time_of_day) were declared in theexprswitch but could never be parsed.
v1.0.2 / Guide v17¶
Added¶
- Flashback compatibility — client-local VFX effects (started via
VFXAPI.playEffector other mods on the client) are written into Flashback replays as custom actions and re-triggered during playback; effects already running when a recording starts are snapshotted into the replay. Flashback is a soft dependency (suggests, reflection-based, no compile-time coupling). Server-triggered effects already travel asvfxweaver:vfx_triggerpackets which Flashback replays on its own. - Server-side effect memory (
VFXServerEffects) — effects sent viaVFXAPI.sendEffectare remembered per player and re-applied on reconnect/join with their remaining duration (persistent-1effects always; finite ones while not expired). Pruned when expired; disabled during Flashback replay playback so replays are not doubled.
Changed¶
- Minecraft support widened to 26.1 – 26.1.2 (built against 26.1.2; the mod metadata's Minecraft range covers the whole 26.1 line, verified to compile on 26.1.2 without changes).
speed_linesreworked — lines now emanate from the screen borders as wedges (full width at the edge, clipped by it, tapering to a point towards the centre) with sharp step edges, instead of a radial band around the centre. Newlength_randparam (0..1) controls how much the per-line length varies with the seed.
v1.0.0 / Guide v16¶
Added¶
- Entity effects
entity_tint/entity_outline(by UUID),/vfx playentity. through_blocks(0/1) on both entity effect types.
Added¶
- Parameter overrides in
/vfx play,playat,playentity. These commands now accept an optional param-map{[name:value],...}(like/vfx set) that overrides the definition's default params at trigger time — including world coordinates (pos_x/y/z). This gives the command/datapack the same capability as the Java API (sendEffect(...overrides)).
Added¶
- New bindings and player variables. Bindings:
distance(raw distance from the camera toposin blocks),look_x/look_y/look_z(components of the camera's look vector),player_x/player_y/player_z(the local player's position). Math expressions (expr) now expose player variables:health,hunger,speed,light_level,time_of_day,player_x/y/z. Datapack examples:test_expr_health(screen_flash,expr: 1.0 - health),test_distance(vignette viabind: distance).
Added¶
- Feedback for broken datapacks.
/vfx listnow prints the list of datapack files that failed to parse on the last/reload, with the error text — previously they were silently skipped (log only).VFXDefinitionManagerstoresparseErrors(id → message).
Added¶
entity_selectorin the effect definition. Theentity_selectorfield (a selector string, e.g."@e[type=minecraft:zombie,distance=..10]") lets an entity effect (entity_tint/entity_outline) find its own targets: the server resolves the selector into UUIDs on every play, so/vfx play <effect>works withoutplayentity. Datapack example —test_zombie_outline.
Added¶
gradient_map: gradient build mode and colour coordinate. New paramsmode(0 = linear, 1 = constant/stepped) andpos(0..1, colour coordinate). In linear modeposshifts the transition centre (0.5 — no shift); in stepped mode it is a hard threshold: brighter thanpos→ colourto, darker →from(useful for masks/stylized shadows). The built-invfxweaver:gradient_mapgot defaultsmode: 0,pos: 0.5. True grayscale — linear withfrom=black,to=white,pos=0.5; a hard black/white mask — constant withpos=0.5 (0 — black, 0.5+ — white). Datapack examples:test_grayscale,test_grayscale_constant,test_gradient_constant.posterize: clean colour reduction. Removed the per-pixel dithering that produced large random colour steps at high strength ("pixelation"). The shader now does clean quantization (255 → 2 levels) without grain.
Added¶
- Entity effects respect the entity texture.
entity_tint/entity_outlinenow bind the entity texture (Sampler0) and use it as an alpha mask (like vanillarendertype_outline): transparent pixels are discarded, so the effect follows the texture silhouette rather than a flat box around the model.entity_tintgained atextureparam (0/1):1— recolour the texture (texture × colour, pattern visible),0— flat colour with the texture only as a mask. Render types memoized per entity texture.
Added¶
- Entity tint/outline (
entity_tint,entity_outline) — new effect types targeting entities by UUID. New subcommand/vfx playentity <effect> <targets>collects target UUIDs (up to 16) and sends them invfxweaver:vfx_trigger; the client stores the UUID on the render state of living entities (mixinLivingEntityRenderState) and, in a second pass, redraws the entity model with a custom render type:entity_tint— a solid translucent fill of the effect colour,entity_outline— an inverted hull (inflated silhouette with front faces discarded), thickness viawidth. Both supportthrough_blocks(0 — hidden behind walls, 1 — visible through them). Pipelines registered on the client, shaders —assets/vfxweaver/shaders/core/entity_fx.{vsh,fsh}.
Fixed¶
- Camera shake works again: the per-instance seed (
instanceSeed) shifted the noise domain byseed * 0.0001— for a random 64-bit seed that's ~10¹⁴,SimplexNoise.fastFlooroverflows the int cast, and all noise samples became exactly 0 → the shake silently didn't play. The seed is now masked to 32 bits (phase range ~4.3e5, safe for the int lattice).
Added¶
- Math expressions in params — a new way to set a param via
"expr": "sin(t * 0.1) + noise(x, y, z) * 0.2". Variables:t(ticks since start),x/y/z(camera coordinates), constantspi/e; functionssin,cos,abs,min,max,pow,sqrt,random()(0..1),noise(x,y,z)(simplex 3D, -1..1). The string compiles to an AST once (Recursive Descent Parser) when the instance is created and is evaluated every frame viaeval(t,x,y,z)— no per-frame parsing.random()/noise()are deterministic per-instance seed, so each instance gets its own noise. - Unique camera shake —
VFXActiveEffectcarries a randominstanceSeed;CameraShakeManagershifts the noise domain by this seed, so every/vfx play vfxweaver:camera_shakegives a non-repeating shake. SimplexNoisemoved to the shared (common) source (com.tom.vfx.noise) — now available both to math expressions and to camera shake.- Sound params
volume/pitch— the effect sound (thesoundfield) can now set volume and pitch via the reservedvolume/pitchparams, supporting all modes (constant, animation, world/camera bind, expression). Values are read once at start time (one-shot sound). Example:"volume": { "bind": "proximity", "pos": [8,80,8], "range": 32 }— louder near the point. - Positional sound
sound_pos— thesound_pos: [x,y,z]field plays the sound in the world at coordinates via the vanilla mechanism (like/playsound ... x y z, with distance falloff); without it the sound plays directly to the player. The position is overridable via the API (sound_pos_x/y/zinsendEffectoverrides).
Security¶
- Server packet sizes bounded: the param map in
vfxweaver:vfx_trigger— max 32 entries; the definition/curve maps invfxweaver:vfx_sync— 1024/256 entries (strings already capped byByteBufCodecs.STRING_UTF8). Protects the client from OOM on a hostile/broken server. VFXSyncPayloadgotprotocolVersion(checked on the client before applying).VFXEffectManager.playcaps server-supplied duration (MAX_DURATION_TICKS= 1 hour); persistent/loop semantics from the definition are preserved, but an arbitrary negative/hugedurationTicksfrom the server no longer creates an infinite effect.VFXEffectManager.stop(effectId, instanceId)checks that the instance belongs to the given effect — a server-supplied instance id cannot stop another instance.- The mutating
/vfxsubcommands (play,playat,stop,set,key) require operator rights (gamemaster level 2);/vfx liststays open. - The client log no longer prints the packet position (less noise on spam).
Fixed¶
- Datapack VFX effects and curves now sync with dedicated-server clients. Previously
VFXDefinitionManager/VFXCurveManagerloaded definitions only fromPackType.SERVER_DATA, so a dedicated-server client held only the built-invfxweaver:*and ignored custom datapack effects (Ignoring unknown VFX effect). Added a server→clientvfxweaver:vfx_syncpacket (raw definition/curve JSON) sent to each player on join (ServerLifecycleEvents.SYNC_DATA_PACK_CONTENTS) and to everyone after/reload(END_DATA_PACK_RELOAD); the client merges them over the built-ins.
Added¶
- Direct world-position passing in
VFXAPI.sendEffect(player, effectId, Vec3 worldPos, ...)— the client re-anchors spatial bindings (screen_x/y,proximity) to the point without thepos_x/y/zhack. positionandinstanceIdfields in thevfxweaver:vfx_triggerpacket — stopping a specific effect instance viasendStop(player, effectId, instanceId).- Custom easing curves: named files
data/<ns>/vfx_curves/<name>.json(an array of control pointspoints) and inline objects"easing": { "curve": [[t,v],...] }— anywhere an easing is expected (effect default, keyframe, collection child). - Multiplicative param modifier:
"strength": { "keyframes": [...], "multiply": { "bind": "proximity", ... } }— final value = base × multiplier (e.g. an animated dent fading with distance to a point). - Client
VFXAPI.playEffectId(...)returns the id of the created instance;VFXAPI.stopEffect(long instanceId)stops one specific instance.
Changed¶
- BREAKING:
PROTOCOL_VERSION2 → 4: the packet carries an optional position and instance id; the easing field is now a string name (built-in or custom curve id). /vfx playatmoved to the new packet position field (backward compat with the oldpos_x/y/ztrick kept).VFXTimelinesupports param multipliers alongside bindings (position rebinding reconfigures both).VFXDefinitionManager/VFXCurveManagerstore raw JSON sources for network sync; parsing moved intoreload().
Fixed¶
VFXDefinitionManager.prepare()no longer aborts loading all VFX definitions because of one malformed datapack file —catchwidened toIllegalArgumentException(previously an unknowntypeor brokenpositionswould hit it).
Changed¶
getModelQuads()(VFXWorldOverlayRenderer) now logs an error when collecting block geometry instead of silently swallowing it.
Docs¶
- The user guide was restructured, and the project gained separate Java API, architecture and changelog documents alongside the README and contribution guide.
v11¶
block_outlinehas two modes by the booleanshell(default0):0— extruded walls,1— a classic scaled shell with back faces, clipped by the block via the depth buffer.
v10¶
- The outline was reworked from a scaled shell to "walls" (each face is extruded outwards along its normal by
width/2) — the contour physically cannot cover the block in either mode. block_outlinethrough_blocksdefault is now0(occluded by other blocks),block_tint—1(see-through).
v9¶
through_blocks(0/1) onblock_tint/block_outline— visibility through blocks or with occlusion.- The outline no longer covers the block itself (the shell is drawn only with back faces and masked by the block's own depth).
/vfx playatcorrectly sets the position again (command positions take priority over the definition'spositions).soundoncollectionnow plays (locally, only to targeted players).
v8¶
- Added
vignette,screen_flash,motion_blur,fov_modifier. block_tintrestored.block_outlinerewritten as a scaled model shell with multi-block support (positions) and depth test disabled.- The network protocol gained an
actionfield (PLAY/STOP) and a version (protocolVersion). - Datapacks support
soundandpositions. - Adaptive blur;
distortionsupports negativeamountvalues. - The
/vfx playatcommand for quick block-effect testing by coordinates.
v7¶
block_tintremoved; the outline rewritten onto the custom shadervfxweaver:core/block_outline.
v6¶
- Tint fixed under Iris (custom pipeline instead of
debug_filled_box). - Overlay fault tolerance (try/catch per effect and on flush).
- The
lookbinding (yaw/pitch/range) to bind effects to the camera rotation.
v5¶
- Model geometry for tint/outline; outline with depth test (outside only);
loop; guide created.
v4¶
block_tint,block_outline,persistent/fade_ticks,collection. (historically: cube tint)
v3¶
- World bindings (
bind),dent.
v2¶
- Keyframes, two-pass blur, posterize, command hints.
v1¶
- Base shader effects,
camera_shake, datapacks, commands, network.