The sliders were lying
Datamosh's Mosh Amount spent most of its travel doing nothing and the rest doing everything. Measuring each control instead of trusting it changed almost every number in the plugin — and then a bug turned up that no test could ever have caught.
If you moved Datamosh's Mosh Amount slider from 0 to 0.5, you got 0.3% of the effect. At 0.9 you got 14%. Half of the usable range sat above 0.95.
Nobody designed that. It is what you get when a control is wired straight to a per-frame retention fraction and shipped without measuring the curve. The slider was not broken in any way a test would catch — it moved, the number changed, the picture changed at the top. It simply spent four fifths of its travel lying about what it would do next.
Releaseffgl-datamoshPrev0.2.0Measuring rather than trusting
Once you measure one control you have to measure them all, and most of them had the same shape:
- Decay put 0.5 at a four-second half-life — longer than any burst — so nothing happened for four fifths of the slider and then it collapsed.
- Motion Threshold was a linear 0–8 px range, but per-frame motion on ordinary footage is mostly 0.5–3 px. The whole decision lived in the bottom quarter and the rest was a wall. Worse, at Quality Low the estimator's pyramid only reaches 8 px, so Quality Low with the threshold at 1.0 was a total bypass — indistinguishable from a plugin that had failed to load.
- Motion Smoothing and Freeze were the same operation applied twice. Two successive mixes toward the same target compose to one, symmetrically. A sweep measured their response curves identical to every digit. One axis, two names.
Mosh Amount was also frame-rate dependent, three lines below a comment in the same shader condemning exactly that: a composition authored at 60 fps moshed visibly harder at 30. It is now a hold time on a log scale, so 0.5 is a 0.45 second hold at any frame rate, and the burst release — nominally a 250 ms ramp, perceptually a 25 ms snap — became the ramp it claimed to be.
FFGL has no migration hook. A saved composition stores slider positions, and there is no callback that lets a plugin say "this 0.5 used to mean something else". So every remap had to land in one release rather than breaking compositions five times across five of them, and the endpoints were preserved throughout: 0 still means what it meant, 1 still means what it meant. It is the middle that moved, because the middle was where nothing happened.
The default that looked like a crash
A fresh instance of the plugin used to be a pixel-exact passthrough. Drop it on a layer and nothing happens — which is exactly what a plugin that failed to load looks like. This project's first documented trap, shipped as the default.
Mosh Amount now defaults to 0.3. That is only survivable because the slider is a hold time now; under the old taper, any non-zero default was either invisible or catastrophic.
Presets also stopped writing Mosh Amount at all. Every style used to push it to 1.0, which saturated the gate and silently disabled Trigger, Hold, Auto Mode, Cut Sensitivity, Burst Length, Beat Divisor and Audio Amount in the same instant. Picking a look was the gesture that disarmed everything you were about to play. A style describes character; the gate stays the operator's.
Asking the plugin what it sees
Releaseffgl-datamoshPrev0.3.00.3.0 added a View control — Result, Motion or Gate — so the plugin can draw what it is thinking rather than only what it is doing. That turns most of "why is this knob doing nothing" from a checklist into one glance.
Motion draws the estimated field: hue is direction, brightness is speed on the same ruler as Motion Threshold, so a region's brightness is the threshold setting that would shut it. Gate draws the decision, and after the mask and the creep that decision has four answers, so it has four colours — black for no motion, red for motion the threshold rejected, green for open, cyan where the creep is doing the work.
Both replay what the mosh pass was actually handed rather than re-reading the live parameters, because the composite pass is not guaranteed to run in the same call as the advance: the frame gate skips advancing when host time has not moved, and composites anyway. A diagnostic that can draw a field the warp never applied is worse than none.
Pull requestffgl-datamoshmergedAdd Spread: damage that creeps into blocks which never movedSpread arrived in the same release, and it is the one pass with a spatial memory. Everything else the plugin does is affine — a per-pixel decision from this frame's numbers alone — so a block with no motion of its own could never mosh. Spread lets corruption start where the motion is strongest and creep outward, and when the level falls the frontier goes out the way it came, so it reads as a gesture rather than a setting.
Both its rates are per second, and the second one is the one that would have been missed: the kernel radius is speed × dt, because a fixed per-frame radius is a per-frame rate wearing a spatial disguise, moving the front twice as fast at 120 fps as at 60. It is a dilation rather than a blur, too — a mean blur is diffusion, whose front advances as the square root of elapsed time with no coefficient that recovers frame-rate independence, while a max dilation composes radii by addition exactly. Measured over the same half-second, the front reached 0.500 of the frame at 30 fps against 0.531 at 60.
The bug that was invisible from the code
Then there is this one, which is my favourite kind.
The plugin's parameter panel showed two Damage sections and two Output
sections. Resolume merges parameters into one collapsible section only when
they are consecutive — and both Spread and View had been appended at the
end of the parameter list, as they must be, because a parameter's index is its
identity in a saved composition. Filed under group names already used above
them, they rendered as second sections of each:
Mosh · Motion · Damage · Sync · Output · Mask · Damage · Output
It was found by looking at the panel in Resolume. It is invisible from the
code, where every AddGrouped call reads perfectly well on its own, and it was
invisible to every test, because nothing looked at the sequence of group
names. There is now a test that does.
Appending was never the problem and did not change. The rule is that whatever goes on the end needs a group name not already used above it.
Pull requestffgl-datamoshmergedFix the duplicated panel sections, and record that Resolume surfaces the descriptionAlso: the version is now visible
Through every release before 0.3.0, both plugins reported version 0.1 to the host. The numbers were hardcoded in the plugin registration while the project moved on without them. A user with the plugin installed had no way to tell which build they had — macOS carried it in the bundle, Windows carried nothing at all.
Both now take it from the project version, Windows DLLs carry a proper
VERSIONINFO resource, and the release workflow refuses to publish when the
tag and the build file disagree. That last one had already caused real damage:
a tag cut against an unbumped version published a package named for the new
release that silently upgraded nothing, because pkgbuild compares the
bundle's version when deciding whether an install is an upgrade.