Skip to content

Patch catalog

ProjectM TV Engine is projectM at commit 6f6480746 (unreleased 4.2 master) plus 37 ordered patches in tools/projectm-patches/. They are applied when the native library is built; the upstream source is never edited in place.

Most patches make the engine behave more like MilkDrop 2. projectM is a clean-room reimplementation of MilkDrop on OpenGL, and over the years small differences crept in: in how equation code is accepted, how HLSL becomes GLSL, where Direct3D 9 and OpenGL put pixel centres, and which per-frame variables are actually read. Each entry below names what projectM did differently, what MilkDrop 2 does (citing its released source where we checked it), what the patch changes, and the preset that shows it.

Two patches deliberately go beyond MilkDrop 2. 0010 adds correct behaviour for something MilkDrop never supported: presets whose textures live in different folders. 0005 repairs a MilkDrop 2 typo instead of reproducing it.

How the images were made

The images for 0001 to 0014 come from the current-patch proof. A preset is rendered by real libprojectM on a GPU-accelerated Android TV emulator: GLES 3.0, frozen audio, seed 12345, a frame/30 clock, frames 0 to 119, each role captured twice with byte-identical results. Without means the full series minus only that patch (a single-patch ablation); with is the full series at that recorded 14-patch checkpoint. Images are unbrightened and rendered at 512×288 (some at 256×144) with quad lines and Native trails off. They establish cause and effect for that preset on that GPU. They are not Windows reference renders, and they do not certify every preset or every TV.

Earlier upstream-master comparisons for the audit repairs

The earlier whole-engine pairs in 0017 to 0033 compare unmodified upstream master e98fca85 with ProjectM TV 8a15996e with all 34 patches. They are new native Apple M4 Pro GPU captures: OpenGL 4.1 over Metal, matching preset bytes, fixed float32 mono audio, seed 12345, 48×32 mesh and 30 Hz clock. Every RGB frame repeats byte for byte within each role, with no reported GL errors. Originals run 480 frames; small supplementary diagnostics and 4K pairs run 240. Each caption gives the actual dimensions and the selected frame.

Both engines use actual output dimensions, classic lines, antialiasing off and TV feedback detail off. These full-engine pairs include all earlier fixes, so they are neither single-patch ablations nor production Native Standard-trails screenshots. The linked issue controls establish the cause of each repair. The pairs cover 18 retained repair IDs; visible effects and measured performance benefits have separate evidence. Synthetic diagnostics are marked as such, and affected-preset counts are unconfirmed. The gamma repair has no demonstrated visible improvement in its isolated original witness, but its separate native-M4 benchmark demonstrates lower rendering cost. Click any image for the lossless full-resolution frame. Matching zooms crop the original browser image without editing its brightness or pixels. Capture records and reproduction include exact identities and limits. Desktop images do not certify Windows/D3D appearance or TV performance.

Close-ups that isolate each correction

Sections 0021, 0023, 0028, 0030, 0032 and 0033 lead with close-ups of real presets at native 3840×2160. The left panel disables only the named repair and the right panel enables it. All other patches, preset bytes, frozen audio, seed and clock match. These Apple M4 Pro GPU captures use the Native Standard profile: 1280×720 line/shader reference, antialiasing enabled and feedback detail 0. Each selected RGB frame repeats exactly in two runs, with no reported GL errors. Each caption names the feature to look at; click a close-up for its unchanged full frame.

The earlier comparisons of upstream master with the complete TV engine are still inside each section. They include several fixes and use a different rendering profile, so their overall brightness cannot identify the effect of an individual repair. Isolated capture records and reproduction distinguish those comparisons. Desktop images establish these witnesses, not physical-TV performance or Windows pixel identity.

At a glance

Patch What it fixes Witness
0001 MilkDrop's tolerant equation loading; sampler, random-texture and blur-target fixes; resolution independence; TV rendering 161.milk
0002 HLSL that Microsoft's compiler accepted Flexi - madness portal.milk
0003 NS-EEL's lone . number; undisturbed rand() Stahlregen - funky Blur (lotus mix) …
0004 Wrap/bilinear sampling for textured shapes widest swing.milk
0005 A usable blur range when min ≈ max flexi - a julia fractal for hexcollie embossed (Jelly).milk
0006 Mirroring by negative zoom Hexcollie - This is where we begin stripped.milk
0007 Per-frame wave_mode, dots, thickness, additive Hexcollie - now entering the wormhole2 …
0008 Per-frame gamma, echo, brighten, darken, solarize, invert synthetic control
0009 Released texture bytes after projectM's image-loader change suksma - chemosynthetic nosferatu … rand tritex - inv play.milk
0010 Beyond MilkDrop 2: per-pack texture folders that blend without swapping images Aurora SOL / LUNA packs
0011 CPU sine/cosine for rot EoS_Phat_PeterP_Sentinel_Aware_6 …
0012 Direct3D 9 pixel centres for custom shapes amandio c - the green machine 2 … btbam covers sepultura.milk
0013 Exact texel reads in custom composites DemonLD_-_Toxic_water_diffusion …
0014 Authored fShader tint amount; mode-1 spiral opacity and open shape BrainStain- boiling-mix2(redi jedi full carb mix).milk
0015 CPU-defined negative nested powers beyond authored exponent one synthetic nested-unit, square and cube controls
0016 Retain the static mesh until size/aspect/producer changes cache controls
0017 Original mode alpha and volume amplification Happening.milk
0018 Centered and separated valid audio windows Mig_304 - geiss remix 2.milk
0019 Separate original gamma-only epsilon suksma - type o negative - world coming down.milk
0020 Original double decimal inputs audit pi precision.milk · synthetic
0021 Inverse aspect and fresh wave-point inputs Crush ice; Carnival
0022 Signed nonzero horizontal flip audit negative echo.milk · synthetic
0023 Original physical Y in built-in warp deformation Geiss - Mega Swirl 2 (2).milk
0024 Original physical triangle diagonal 07.milk
0025 Original physical row evaluation order audit legacy physical traversal.milk · synthetic
0026 Original signed-zero negative-X seam Liquido.milk
0027 Clamp local RGB before nonzero brightening Geiss - Blur Mix 3.milk
0028 Original border strip-fan coverage Dbleja - Escape (Red Mix).milk
0029 Reference-width cap on original line budgets $$$ Royal - Mashup (103).milk
0030 Authored dots without extra interpolated points phat + EoS - Bass_responce_Red_Movements_Disorienting nebula3.milk
0031 Original angular spacing and closure before smoothing $$$ Royal - Mashup (11).milk
0032 Evaluated style captured per instance and replay martin - city lights v2(1).milk
0033 Refresh actual warp UV while vectors are hidden Seizure Salad
0034 Compatible continuous UV on GLES without float render targets capability, integer storage and decoder controls
0035 Retained diagnostic API for the shared program binary cache program-cache-switch control
0036 Protect PowerVR framebuffer retirement during changes/resizing real-GL binding/lifetime control
0037 Share line geometry, shaders and instanced Mesh drawing in Renderer Mesh and matched-render controls

0001 — TV rendering and preset compatibility

The largest patch consolidates 27 historical patches from the projectM 4.1.7 era, ported onto 4.2 master's new Mesh and shader-cache ownership.

Upstream rejects 161.milk; ProjectM TV renders it

Equation code is loaded like MilkDrop

In projectM, a compile error in any equation block aborts the whole preset. Upstream 4.2 refuses to load 161.milk ([PerFrameContext] Could not compile per-frame code: syntax error).

MilkDrop 2's CState::StripLinefeedCharsAndComments and CState::RecompileExpressions (state.cpp) join numbered lines, strip comments and leave out a block that still fails to compile.

ProjectM TV leaves code that the evaluator accepts untouched. It retries rejected code once in MilkDrop's form: numbered records joined, // and \\ comments removed, and a ; inside parentheses with no following operand read as a space (as NS-EEL does). A block that still fails is left out:

  • failed preset init leaves q variables at 0;
  • failed wave/shape init leaves t variables at 0;
  • failed per-frame, per-pixel or per-point code does not run.

Only a parse error in the file fails the load. Each omission is logged as Preset code left out (<preset>): <reason> (line N, column M). In the source inventory, 62 bundled presets had raw equation code that projectM rejected.

Shader samplers and random textures

  • sampler_state blocks no longer break the shader. projectM stripped them only from a search copy, so shader_body was replaced at the wrong offset and the stage fell back to a default shader (51 bundled presets, for example ORB - Arctic Chill). The block fields are still ignored, as in projectM: the sampler name prefix chooses filtering and wrap.
  • Warp texture unit 0 is reserved for the unqualified main sampler, so named point/clamp/wrap aliases keep their requested modes.
  • Each random-texture alias from rand00 to rand15 gets its requested sampler mode and filename filter (pc_rand00_red), and the emitted short alias binds the same texture unit. ProjectM TV also keeps one image per slot for the whole preset load, shared by the warp and composite shaders and kept across shader reloads. MilkDrop 2 does not share them: it chooses separately for each shader (plugin.cpp:2927). See Textures.

Rendering state

  • Allocating blur textures unbinds the read and draw targets. projectM saved the framebuffer bindings after allocation, so when a warp shader sampled blur, the waves and borders drawn next went to framebuffer 0. Witness: midgitstraights of majillaen - featy sweet.milk. On Mali the error is silent, because framebuffer 0 is a valid target.
  • Fresh feedback starts as transparent black instead of undefined memory.
  • A resize keeps history: old colour contents are scaled into the new textures, so a resolution change does not restart every feedback preset.

Resolution independence and Native trails

0001 also adds quad lines, reference-scaled blur and wave counts, virtual texsize and Native trails. These address the resolution-scaling part of projectM issue #682; the round joins and caps it suggests are not implemented. Rendering MilkDrop at 4K explains them.

TV performance work

The patch also carries program-binary and translated-GLSL caches for background prewarming, batched custom-shape uploads, a texture pool, an outgoing-preset frame divisor for blends, and tile-GPU framebuffer invalidation; see How a frame reaches your TV. The historical speedups were measured on the 4.1.7 engine and are not claimed for 4.2.

GLES 3.0

Unmodified upstream at the pin requires GLES 3.2. 0001 admits GLES 3.0, which most TV boxes provide. Upstream master, as observed at e98fca85, has since made the same change.

0002 — HLSL compatibility and exact float literals

MilkDrop 2 compiled preset shaders with Microsoft's HLSL compiler. projectM translates them to GLSL, and a translation failure silently replaces the authored shader with a default one, so the preset looks completely different.

Flexi - madness portal: red disc without 0002, authored yellow/blue composite with it

Flexi - madness portal.milk becomes a plain red disc when its composite cannot translate, because it declares a local variable named sample. With 0002 the authored yellow and blue composite renders. Removing only 0002 changes 120 of 120 frames.

What MilkDrop's compiler accepted projectM before ProjectM TV
Writing to a uniform inside a shader (q18 = 1;, time *= 0.4;) Uninitialized local copy per function: other components and other functions read garbage (426 translated shaders) One global copy per assigned uniform, initialized from the uniform at the start of main()
Flat array initializers const float4 samples[5] = {…20 values…} Emitted as one vec4[] of 20 scalars, rejected by GLSL (56 bundled presets) Grouped into element constructors; ambiguous layouts fail loudly instead of being guessed
A local named sample; #define macros; (float2(x,y)).x Parse failure or changed precedence (16 bundled witnesses) Contextual identifiers, token-replacement macros with authored spacing, full postfix parsing
Plain uninitialized globals (float mus;) Undefined GLSL globals External inputs that read 0 (the D3D9 compiler treated them as constants)
Float literals such as 4194304.0 Printed with 6 significant digits (4.1943e+06 = 4194300) Exact float32 round trip; nonfinite literals fail the stage instead of becoming an identifier named inf

In an earlier corpus pass over 15,576 authored shaders, historical patches 0030 to 0032 (now split between 0001 and 0002) made 102 more shaders compile and lost none.

0003 — Evaluator: lone dot and per-thread random

Synthetic lone-dot fixture: upstream rejects it, without 0003 the block is dropped, with 0003 it runs

NS-EEL accepts a lone . as a number (0). projectm-eval requires a digit, so zoom=zoom+.10*sin(rad+.+15.15) is a syntax error. Upstream rejects the Stahlregen funky Blur presets entirely. With 0001 alone their per-pixel block is dropped; removing only 0003 changes 120 of 120 frames of the unchanged preset. The image uses a synthetic fixture in which the dropped block changes the wave colour, which is easier to see. With 0003 the block runs as on MilkDrop. Seven bundled presets use the construction.

The evaluator's Mersenne Twister state for rand() was process-wide. ProjectM TV compiles upcoming presets on a background thread, so that work could advance the live preset's random stream. Each thread now has its own fixed-seed stream; the algorithm is unchanged.

0004 — Textured shapes sample with wrap and bilinear filtering

widest swing.milk with and without 0004

In projectM, a custom shape with textured=1 bound the main texture but inherited whatever sampler object was left on unit 0 (clamp, or nearest after the first instance).

MilkDrop 2 restores wrap/linear sampler state after the warp shader (RestoreShaderParams). On frames where it computes blur levels, the blur passes then set unit 0 back to clamp (milkdropfs.cpp:1611), and nothing resets it before DrawCustomShapes(). MilkDrop's textured shapes therefore wrap on frames without blur and clamp on frames with blur.

ProjectM TV gives every main-textured fill its own repeat/linear sampler, including geometry replayed for Native trails. That removes projectM's unpredictable nearest/clamp mix, but for presets that use blur it wraps where MilkDrop 2 clamps. Removing only 0004 changes 119 of 120 frames of widest swing.milk.

0005 — Blur ranges that cannot collapse

Julia fractal Jelly with a collapsed and a valid blur range

blur1_min…blur3_max define the colour range stored in each blur texture. MilkDrop 2's CPlugin::GetSafeBlurMinMax (milkdropfs.cpp 1551-1583) tries to push a too-narrow interval (max − min < 0.1) apart, but a typo sets both bounds to avg − 0.05, collapsing it. projectM reproduces the collapse, and the decode divides by zero.

ProjectM TV expands the upper bound upward, keeps the clamp-then-expand order, and falls back to a coherent 0 to 1 range for nonfinite or unrepresentable inputs. Where MilkDrop produced a degenerate blur, this deliberately differs from MilkDrop 2. flexi - a julia fractal for hexcollie embossed (Jelly) sets blur3 to 0.49-0.52 after blur2 0.78-0.91. Without 0005 it is darker, with altered embossing (frame 59 MAE 38.5). For other witnesses, such as Cope - The Cloud, the repair changes nothing visible.

0006 — Negative zoom mirrors

Hexcollie - This is where we begin stripped with and without 0006

MilkDrop 2 computes the zoom on the CPU: powf(fZoom, powf(fZoomExp, rad*2-1)) (milkdropfs.cpp 1877). With zoomexp = 1, a negative zoom stays negative and reflects the image through the warp centre. projectM evaluates pow in GLSL, which is undefined for a negative base; the witness gets 3,234 NaN UV components per frame. Patch 0006 uses the signed value when the authored exponent is exactly 1. 0015 extends the CPU calculation to other negative nested powers; fractional-domain appearance remains unresolved.

0007 — Per-frame waveform controls

Hexcollie wormhole2 with and without live wave_mode

projectM read wave_mode, wave_usedots, wave_thick and wave_additive from the preset file only and ignored per-frame changes. MilkDrop 2 reads the evaluated value every frame: (int)(*var_pf_wave_mode) % NUM_WAVES (milkdropfs.cpp 2852). ProjectM TV consumes the evaluated values and rebuilds the wave when the mode changes. The witness sets wave_mode=q8%7 per frame; without 0007, 135,296 of 147,456 pixels differ at frame 29.

projectM has 16 waveform modes, MilkDrop 2 has 8. The value wraps modulo 16 here and modulo 8 on MilkDrop 2, so wave_mode=9 is mode 1 on Windows and projectM's mode 9 here. Negative remainders draw nothing.

0008 — Per-frame display filters

Synthetic equation-only invert control

gamma, echo_alpha, echo_zoom, echo_orient, brighten, darken, solarize and invert are per-frame variables in MilkDrop 2 (milkdropfs.cpp 527-540). projectM used only the preset file's values and allocated the filters only when the file's default was on, so a preset could never switch one on from code. ProjectM TV reads them every frame, with MilkDrop's order (brighten, darken, solarize, invert), clamps (gamma 0 to 8, echo zoom 0.001 to 1000) and echo orientation modulo 4.

MilkDrop 2 uses the evaluated values for presets without a composite shader (composite version 0). For shader-version presets without comp_ code it bakes the file values into a generated shader; ProjectM TV uses the evaluated values for every built-in composite. Neither applies these effects with a custom composite shader. The tested original witness (idiot - Forty Six and 2) did not activate the difference under the proof audio, so the image shows a synthetic equation-only control.

0009 — Premultiplied user textures

rand tritex preset with and without premultiplication

ProjectM TV's releases based on projectM 4.1.7 loaded images with SOIL2 and premultiplied colour by alpha. 4.2 switched to stb_image without premultiplying, so presets that sample transparent textures changed. In feedback presets, even a 1-level byte difference grows: frame 29 of the witness shows a mean 3.66 and a maximum 201 channel difference. ProjectM TV restores the released bytes, (rgb·alpha + 128) >> 8 per channel. Whether MilkDrop 2's D3DX loader premultiplied is not verified.

0010 — Each preset keeps its own textures

Aurora SOL/LUNA packs: upstream swaps the outgoing emblem; patched keeps it

Here ProjectM TV Engine deliberately goes beyond MilkDrop 2. MilkDrop read every texture from one folder, so the question of which folder a preset's images come from never arose. Custom packs raise that question: a pack's presets prefer the pack's own images, with the bundled textures as fallback. Upstream projectM has one global texture search path, and changing it reloads every texture. During a blend from pack A to pack B, a same-named image in the outgoing preset would therefore switch to B's file.

With 0010, each preset keeps the texture paths it was loaded with until it retires. This per-preset lookup contract is an addition for a situation MilkDrop did not support. In the test, two packs created with the predictor tools each use the same image name. Upstream swaps SOL's orange emblem for LUNA's blue at frame 20; ProjectM TV keeps SOL's.

0011 — Huge rotation values

Rotation witness: upstream fails to load; ProjectM TV renders

MilkDrop 2 converts rot to float and computes sinf/cosf on the CPU (milkdropfs.cpp 1813, 1905-1906). projectM computes them in the vertex shader. On the observed GLES driver, sin/cos of ±10,000,000 both returned 0, collapsing the feedback to the rotation centre. ProjectM TV computes them on the CPU after float conversion, valid up to FLT_MAX. The witness uses per-pixel rot=10000000; upstream additionally fails to load it, because of a shape equation that 0001 handles. Large-argument trig inside your own shader code is still driver-dependent.

0012 — Tiny shapes land on the right pixels

Green machine btbam: pink/brown wedges without 0012, green/yellow structure with it

Direct3D 9 samples integer pixel centres; OpenGL samples half-integers. MilkDrop 2's DrawCustomShapes (milkdropfs.cpp 2298) places shapes in D3D9 coordinates, so a shape with rad=.002 at x=y=.5 covers one pixel under the Direct3D 9 rasterization rules and zero pixels in projectM. Feedback presets that seed their image with many tiny shapes lose them entirely. ProjectM TV shifts shape fills and outlines by half a destination pixel per render target. Equations, radii, colours and UVs are unchanged. In amandio c - the green machine 2 skin lard bone beacon nz+ btbam covers sepultura.milk, 34,365 of 36,864 pixels differ by more than 16 at frame 59.

0013 — Composite reads the exact feedback texel

DemonLD Toxic water with and without the composite half-texel bias

In MilkDrop 2, plugin.cpp shifts the composite grid by −½ pixel (the D3D9 convention) and keeps the UVs. projectM's GL mesh is already unbiased, yet it also added +½ texel to the UVs, so every tex2D(sampler_main, uv) in a custom composite read between four texels. That is a hidden 2×2 blur: a single 255 feedback texel became four pixels of 64. ProjectM TV removes the redundant bias. In DemonLD_-_Toxic_water_diffusion … the bugged window was about 18% brighter at frame 59 (mean luma 57.0 against 48.4). The fix removes the blur, which does not always darken a preset.

0014 — Legacy colour shading and mode-1 spirals

The patch corrects three differences in the classic (non-shader) rendering path, found while investigating why a preset looked darker than expected (milkdropfs.cpp 2927-2946, 3359-3365, 4117-4144):

Stage MilkDrop 2 projectM ProjectM TV
Legacy hue shading No tint when fShader ≤ 0.001; otherwise the animated corner colours are mixed with white by fShader Always the full animated tint Respects the authored amount and threshold
Mode-1 waveform opacity Alpha × 1.25, then volume modulation and clamp Multiplier missing Multiplier restored before the clamp
Mode-1 waveform shape Open line strip Closed loop with an extra segment Open, as in MilkDrop

The witness, BrainStain- boiling-mix2(redi jedi full carb mix).milk, can still look sparse and dark after the fix: its video echo shows only a zoomed crop of the image and its darken filter squares the colours, as authored. The investigation also showed that the source predictor shared these mistaken assumptions, so agreement with the predictor is not proof of MilkDrop behaviour. Evidence.

Fixed upstream, dropped from the series

Rebasing onto 4.2 master retired nine historical patches because upstream had already made the same fix:

Historical Fix Upstream
0001 Plasma transition float overflow 0227b7a61 (issue #872)
0008 HLSL float remainder types and operator precedence PR #1031, contributed from this project
0017 projectm_pcm_get_max_samples() returns 576 PR #1032
0018 Parenthesized constructor expressions PR #948
0019 Quadratic number scanning in the HLSL parser PR #1030
0020 projectm-eval 1.0.7: NS-EEL's 0.00001 comparison/division tolerance evaluator 22fb0cfd
0021 Self-referencing shader macro infinite loop c1469f0e5
0022 Custom waveform out-of-bounds read in the pin
0027 Preset exception diagnostics in the pin

projectM v4.1.8 (tagged 2026-10-06) backports most of these parser and waveform fixes, but not the plasma transition fix. It also uses a newer evaluator commit (e8c311e, a float-scanner fix) that our pin does not include; its visible impact is unverified.

0015 — Negative warp powers use MilkDrop CPU maths

Defined negative nested-unit, square and cube controls before and after

MilkDrop 2 computes powf(zoom, powf(zoomexp, rad*2-1)) on the CPU after float conversion (milkdropfs.cpp:1877–1938). A nested integer exponent can have a finite signed or squared/cubed result even when the authored zoomexp is not one. On the observed GLES backend, the old GPU branch returns NaN for these defined controls because GLSL does not define any negative-base pow. ProjectM TV prepares the negative result on CPU and supplies it through an instance-owned float buffer at attribute 9. Positive zoom keeps its shader path; raw equation values and prepared-mesh replay are unchanged.

These separate proof images use full published v2.3.25 and candidate AARs at 256×144, 48×32 mesh, 30 frozen frames and the same mono PCM/clock/seed on API 34/Apple M4 Pro/GLES3. Named diagnostic copies activate nested exponents 1/2/3 at selected vertices; constant blue 64 verifies their custom warp/composite programs. They are not the 120-frame single-patch ablations described above. Direct UV controls fail before/pass after, and real-mesh controls check values, NaNs, resize and replay. The buffer adds four bytes per vertex and nonunit negative vertices need two CPU powers; performance is unmeasured.

The exact Great Tulip Majesty (txtr wrap) witness has 33 fractional-domain NaNs per frame in both the original CPU expression and the GPU path. Its 30-frame original/repeat AAR replay is byte-identical after this correction. NaN and Inf are retained, with no epsilon or absolute-value substitution. Observations of invalid interpolation and sampling are backend-bound, so the source predictor's unresolved guard is still appropriate. Source, IEEE bits and capture evidence.

0016 — Cached static warp uploads

Static warp coordinates, radius/angle and topology are uploaded only when their size, aspect or producer changes. Dynamic attributes and equations still update each frame. This preserves the released mesh-cache fix and prepared replay; see the cache evidence.

0017 — Built-in wave opacity

MilkDrop multiplies mode-adjusted alpha by the volume ramp before clamping. Mode 3 replaces its starting alpha with the canvas coefficient times 1.3 × treb²; mode 1 keeps its 1.25 multiplier. A final alpha below .004 skips the draw. Existing Native/reference styles are unchanged. I17 evidence.

Before · upstream masterHappening.milk — Before · upstream master
After · ProjectM TVHappening.milk — After · ProjectM TV
Happening.milk · 3840×2160, frame 239 at 30 Hz.
Before, many streaks wash toward white. After, green, pink and blue remain distinct. This full-engine comparison also includes the legacy warp repairs.

0018 — Custom wave input windows

Valid custom oscilloscope windows are centered in the 480-sample input, and the channels shift in opposite directions by sep/2. Invalid original windows keep the safe prefix fallback, and oversized requests are still resampled. Spectrum inputs and Native prepared replay are unchanged. I08 evidence.

Before · upstream masterMig_304 - geiss remix 2.milk — Before · upstream master
After · ProjectM TVMig_304 - geiss remix 2.milk — After · ProjectM TV
Before · same zoomMatched crop of Mig_304 - geiss remix 2.milk — Before · same zoom
After · same zoomMatched crop of Mig_304 - geiss remix 2.milk — After · same zoom
Mig_304 - geiss remix 2.milk · 3840×2160, frame 239 at 30 Hz.
The centered, separated audio windows change the small blue custom-wave trace. The matched zoom makes its changed geometry easier to inspect. Zoom rectangle: (1344, 648), 1152×864 source pixels; identical crop and nearest-neighbour display, with no brightness adjustment.

0019 — Gamma-only pass count

Gamma-only output uses MilkDrop's original .001 pass-count epsilon; echo redraws keep .0001. At the admitted boundary, I31 removes a fullscreen gamma draw and the checked image stays the same. Float diffuse precision, live tint and echo policies are unchanged.

The proven benefit is lower rendering cost with unchanged checked images. An isolated 3840×2160 benchmark on the Apple M4 Pro compares the full engine with its exact 0019 ablation. In the Standard profile:

Measurement Without I31 With I31 Reduction
Gamma draws per frame 3 2 One fullscreen draw
GPU elapsed time 2.307 ms 1.847 ms 19.9%
Completed-frame time 2.704 ms 2.241 ms 17.1%

All eight balanced blocks favor I31. The gamma-2.0 inactive control keeps two draws, and its GPU/completed-frame intervals include zero change. Separate frames 119, 239 and 479 are RGB-identical across roles and repeats in both tested profiles. These are rendering-cost measurements, not app FPS. Gains on a TV depend on its bottlenecks and are unmeasured. Full benefit proof and replay · Historical I31 evidence.

Before · upstream mastersuksma - type o negative - world coming down.milk — Before · upstream master
After · ProjectM TVsuksma - type o negative - world coming down.milk — After · ProjectM TV
suksma - type o negative - world coming down.milk · 1280×720, frame 119 at 30 Hz.
This full upstream-master/main pair illustrates near-unchanged appearance and includes other patches. The separate single-patch 4K benchmark above proves a rendering-cost benefit; it does not depend on a visible improvement in this picture.

0020 — Named EEL constants

Named pi/e/phi decimals enter the double evaluator without intermediate float rounding. The original abbreviated e/phi decimals, later geometry/shader casts and RNG are unchanged. I09 evidence.

Before · upstream masteraudit pi precision.milk (synthetic) — Before · upstream master
After · ProjectM TVaudit pi precision.milk (synthetic) — After · ProjectM TV
audit pi precision.milk (synthetic) · 1280×720, frame 239 at 30 Hz.
This diagnostic deliberately amplifies the float-rounding error in $pi. Upstream makes an oversized red border; the double input restores the narrow border requested by the expression.

0021 — Original equation inputs

Presets keep the spatial scale and custom-wave colour rhythm their equations describe. The repair restores aspect compensation in per-pixel equations and gives custom-wave points their own fresh host clock, even when main equations slow their own time. I05/I06 evidence.

Only 0021 disabledcrush: Only 0021 disabled, matched close-up
ProjectM TV · fix enabledcrush: ProjectM TV · fix enabled, matched close-up
Flexi - crush ice 38.milk
The central glow separates into readable loops on the right. The old input stretches the feedback differently; the fix restores the aspect compensation used by the preset.
Native 3840×2160, frame 239; matched 1100×1050 pixel crop at (1600, 600). Click either close-up for its original 4K frame. No exposure or colour adjustment.
See both full 4K frames
Only 0021 disabledcrush: Only 0021 disabled, full frame
ProjectM TV · fix enabledcrush: ProjectM TV · fix enabled, full frame
Only 0021 disabledcarnival: Only 0021 disabled, matched close-up
ProjectM TV · fix enabledcarnival: ProjectM TV · fix enabled, matched close-up
Shreyas - Carnival loavthephysyq.milk
Look at the fine coloured pattern across the centre. Custom-wave colours follow their own host clock rather than inheriting the slowed main-equation clock.
Native 3840×2160, frame 239; matched 1100×1000 pixel crop at (1250, 650). Click either close-up for its original 4K frame. No exposure or colour adjustment.
See both full 4K frames
Only 0021 disabledcarnival: Only 0021 disabled, full frame
ProjectM TV · fix enabledcarnival: ProjectM TV · fix enabled, full frame
Earlier upstream-master comparison and diagnostic controls
Before · upstream master163.milk — Before · upstream master
After · ProjectM TV163.milk — After · ProjectM TV
163.milk · 1280×720, frame 300 at 30 Hz.
Inverse aspect inputs change the feedback displacement in this original. The separate synthetic drift example below makes the equation-input effect easier to see.
Before · upstream masteraudit inverse aspect.milk (synthetic) — Before · upstream master
After · ProjectM TVaudit inverse aspect.milk (synthetic) — After · ProjectM TV
Before · same zoomMatched crop of audit inverse aspect.milk (synthetic) — Before · same zoom
After · same zoomMatched crop of audit inverse aspect.milk (synthetic) — After · same zoom
audit inverse aspect.milk (synthetic) · 1280×720, frame 239 at 30 Hz.
Synthetic inverse-aspect drift: the same red square is displaced using dx=aspecty/10. Zoom rectangle: (200, 225), 600×300 source pixels; identical crop and nearest-neighbour display, with no brightness adjustment.
Before · upstream masterShreyas - Carnival loavthephysyq.milk — Before · upstream master
After · ProjectM TVShreyas - Carnival loavthephysyq.milk — After · ProjectM TV
Shreyas - Carnival loavthephysyq.milk · 1280×720, frame 479 at 30 Hz.
Fresh host time changes the colour pattern in the custom waves. It does not mean every preset should become brighter or darker.
Before · upstream masteraudit fresh wave time.milk (synthetic) — Before · upstream master
After · ProjectM TVaudit fresh wave time.milk (synthetic) — After · ProjectM TV
Before · same zoomMatched crop of audit fresh wave time.milk (synthetic) — Before · same zoom
After · same zoomMatched crop of audit fresh wave time.milk (synthetic) — After · same zoom
audit fresh wave time.milk (synthetic) · 1280×720, frame 239 at 30 Hz.
Synthetic fresh-time input: main time is slowed to one tenth; wave-point time must still use the host clock. Zoom rectangle: (225, 200), 250×320 source pixels; identical crop and nearest-neighbour display, with no brightness adjustment.

0022 — Negative odd echo orientation

Finite negative odd echo orientations flip horizontally using the original signed nonzero-remainder test. Modulo 4, vertical flip, zoom/mix, tint and gamma are unchanged. I29 evidence.

Before · upstream masteraudit negative echo.milk (synthetic) — Before · upstream master
After · ProjectM TVaudit negative echo.milk (synthetic) — After · ProjectM TV
audit negative echo.milk (synthetic) · 1280×720, frame 239 at 30 Hz.
Negative odd echo orientation moves the orange square from the left to the right by applying the authored horizontal mirror.

0023 — Legacy oscillator Y

Legacy feedback curls in the direction described by MilkDrop's original coordinate system. The correction restores that direction in the built-in and fallback warp; custom warp shaders keep their existing coordinates. I10 evidence.

Only 0023 disabledswirl: Only 0023 disabled, matched close-up
ProjectM TV · fix enabledswirl: ProjectM TV · fix enabled, matched close-up
Geiss - Mega Swirl 2 (2).milk
The central spiral builds a denser curl with distinct strands on the right. Restoring the original oscillator direction changes how the existing strokes accumulate; their authored warp strength and draw count stay the same.
Native 3840×2160, frame 239; matched 1200×1300 pixel crop at (1200, 500). Click either close-up for its original 4K frame. No exposure or colour adjustment.
See both full 4K frames
Only 0023 disabledswirl: Only 0023 disabled, full frame
ProjectM TV · fix enabledswirl: ProjectM TV · fix enabled, full frame
Earlier upstream-master comparison and diagnostic controls
Before · upstream masterBrainStain-fish on.milk — Before · upstream master
After · ProjectM TVBrainStain-fish on.milk — After · ProjectM TV
BrainStain-fish on.milk · 1280×720, frame 150 at 30 Hz.
The legacy oscillator uses the original physical Y direction, changing the curved feedback geometry. Custom warp-shader coordinates keep their separate contract.

0024 — Legacy mesh diagonal

Legacy warp triangles use the original physical diagonal. Custom topology and same-size static upload caching are unchanged. I11 evidence.

Before · upstream master07.milk — Before · upstream master
After · ProjectM TV07.milk — After · ProjectM TV
07.milk · 1280×720, frame 150 at 30 Hz.
The physical triangle diagonal changes interpolation within the legacy warp mesh. The original radial feedback makes that difference accumulate into a different pattern.

0025 — Legacy per-pixel traversal

Legacy stateful per-pixel equations visit physical rows in the original order. Custom traversal and once-only prepared replay are unchanged. I12 evidence.

Before · upstream masteraudit legacy physical traversal.milk (synthetic) — Before · upstream master
After · ProjectM TVaudit legacy physical traversal.milk (synthetic) — After · ProjectM TV
audit legacy physical traversal.milk (synthetic) · 1280×720, frame 239 at 30 Hz.
The equation increments q1 at each mesh point. Restoring physical row order changes its coloured shear; this control makes stateful traversal visible.

0026 — Legacy angle seam

At the exact negative-X axis, legacy equation angles retain the original signed-zero atan2 seam. Off-axis and custom inputs are unchanged. Independent CPU trig and actual GPU controls verify the consequence. I13 evidence.

Before · upstream masterLiquido.milk — Before · upstream master
After · ProjectM TVLiquido.milk — After · ProjectM TV
Before · same zoomMatched crop of Liquido.milk — Before · same zoom
After · same zoomMatched crop of Liquido.milk — After · same zoom
Liquido.milk · 1280×720, frame 180 at 30 Hz.
This original branches on ang at the exact left axis. The original seam value selects the intended branch; feedback then propagates the changed deformation. Zoom rectangle: (0, 216), 640×288 source pixels; identical crop and nearest-neighbour display, with no brightness adjustment.

0027 — Wave colour clamp

Local built-in wave RGB is clamped before nonzero brightening. Raw equation values and float colour/alpha submission are unchanged. A separate negative-darken activation is outside this repair. I18 evidence.

Before · upstream masterGeiss - Blur Mix 3.milk — Before · upstream master
After · ProjectM TVGeiss - Blur Mix 3.milk — After · ProjectM TV
Before · same zoomMatched crop of Geiss - Blur Mix 3.milk — Before · same zoom
After · same zoomMatched crop of Geiss - Blur Mix 3.milk — After · same zoom
Geiss - Blur Mix 3.milk · 1280×720, frame 239 at 30 Hz.
The brightened built-in waveform uses clamped colour channels before normalization. Inspect the narrow central wave in the matched zoom; the full pair includes the other wave repairs too. Zoom rectangle: (128, 288), 1024×158 source pixels; identical crop and nearest-neighbour display, with no brightness adjustment.

0028 — Border fan topology

Eight triangle indices restore the original border strip-fan coverage. Current float colour/alpha and Native rendering policies are unchanged. M02 evidence.

Only 0028 disabledescape: Only 0028 disabled, matched close-up
ProjectM TV · fix enabledescape: ProjectM TV · fix enabled, matched close-up
Dbleja - Escape (Red Mix).milk
The crossed border bands now have the original strip-fan coverage. In the left image, one side pinches into a diagonal; the right retains the matching band around the centre. This is an unchanged bundled preset, not a synthetic shape.
Native 3840×2160, frame 0; matched 1100×750 pixel crop at (1400, 650). Click either close-up for its original 4K frame. No exposure or colour adjustment.
See both full 4K frames
Only 0028 disabledescape: Only 0028 disabled, full frame
ProjectM TV · fix enabledescape: ProjectM TV · fix enabled, full frame
Earlier upstream-master comparison and diagnostic control
Before · upstream masteraudit border inverted.milk (synthetic) — Before · upstream master
After · ProjectM TVaudit border inverted.milk (synthetic) — After · ProjectM TV
audit border inverted.milk (synthetic) · 1280×720, frame 239 at 30 Hz.
An intentionally oversized, translucent border exposes overlapping coverage. Restored strip-fan indices change that coverage. The neutral colour also reflects earlier patch 0014 honoring fShader=0; it is not caused by the border topology repair.

0029 — Original line sample caps

Line modes 4/6/7 use their original raw sample budgets, capped by one third of the reference-equivalent width with a two-point safety floor. Native/reference scaling and extended modes are unchanged. The cap can substantially change feedback brightness, as in Royal 103. Brightness relative to the previous build does not by itself establish authored Windows appearance.

Before · upstream master$$$ Royal - Mashup (103).milk — Before · upstream master
After · ProjectM TV$$$ Royal - Mashup (103).milk — After · ProjectM TV
$$$ Royal - Mashup (103).milk · 1280×720, frame 239 at 30 Hz.
At 1280×720, the original width-dependent cap changes submitted waveform geometry and this witness becomes brighter. That direction is not universal: the uncapped 4K pair below is nearly unchanged.
Before · upstream master$$$ Royal - Mashup (103).milk — Before · upstream master
After · ProjectM TV$$$ Royal - Mashup (103).milk — After · ProjectM TV
$$$ Royal - Mashup (103).milk · 3840×2160, frame 239 at 30 Hz.
The same original at 3840×2160 with actual-width sampling and TV feedback detail off. Both width budgets reach the full raw sample count, so the cap itself is inactive here.

0030 — Discrete custom dots

The starfield keeps the points the preset actually authored. Dot mode no longer invents extra stars between those points; ordinary continuous lines still use smoothing. The close-up below includes three authored dots, so the removed extras are easy to pick out.

Only 0030 disablednebula: Only 0030 disabled, matched close-up
ProjectM TV · fix enablednebula: ProjectM TV · fix enabled, matched close-up
phat + EoS - Bass_responce_Red_Movements_Disorienting nebula3.milk
Three purple/white authored dots remain in both panels. The extra green/grey dots on the left disappear: they were invented by line interpolation. Dot mode now preserves the preset's discrete star positions.
Native 3840×2160, frame 0; matched 128×128 pixel crop at (2560, 768). Click either close-up for its original 4K frame. No exposure or colour adjustment.
See both full 4K frames
Only 0030 disablednebula: Only 0030 disabled, full frame
ProjectM TV · fix enablednebula: ProjectM TV · fix enabled, full frame
Earlier upstream-master comparison and diagnostic controls
Before · upstream masterphat + EoS - Bass_responce_Red_Movements_Disorienting nebula3.milk — Before · upstream master
After · ProjectM TVphat + EoS - Bass_responce_Red_Movements_Disorienting nebula3.milk — After · ProjectM TV
phat + EoS - Bass_responce_Red_Movements_Disorienting nebula3.milk · 3840×2160, frame 239 at 30 Hz.
One of this preset’s four enabled custom waves describes 42 stars. Discrete dots retain authored samples instead of inserting line-interpolation dots. A synthetic zoom below isolates an extra dot that disappears.
Before · upstream masteraudit followup dot audit dot ring.milk (synthetic) — Before · upstream master
After · ProjectM TVaudit followup dot audit dot ring.milk (synthetic) — After · ProjectM TV
Before · same zoomMatched crop of audit followup dot audit dot ring.milk (synthetic) — Before · same zoom
After · same zoomMatched crop of audit followup dot audit dot ring.milk (synthetic) — After · same zoom
audit followup dot audit dot ring.milk (synthetic) · 1280×720, frame 239 at 30 Hz.
Synthetic 12-sample dot ring. This nearest-neighbour crop surrounds one interpolated dot: it is present upstream and absent after the repair. The original full frames remain available by clicking. Zoom rectangle: (170, 340), 40×40 source pixels; identical crop and nearest-neighbour display, with no brightness adjustment.

0031 — Circle spacing and closure

Circles use the original angular spacing and duplicate the first endpoint before shared smoothing. Native styles, equation execution and the TV transition composition are unchanged.

Before · upstream master$$$ Royal - Mashup (11).milk — Before · upstream master
After · ProjectM TV$$$ Royal - Mashup (11).milk — After · ProjectM TV
$$$ Royal - Mashup (11).milk · 1280×720, frame 119 at 30 Hz.
Original circle spacing and exact closure change the accumulating circular feedback. This is the full preset result, not a close-up proof that every changed pixel lies at the closing seam.

0032 — Live shape thickness

Shape outlines now respond to the thickness set by their per-frame equations. In City lights, four overlay instances gain their requested thick strokes while the other 45 keep their thin outlines.

Only 0032 disabledcity-alt: Only 0032 disabled, matched close-up
ProjectM TV · fix enabledcity-alt: ProjectM TV · fix enabled, matched close-up
martin - city lights v2(1).milk
The white corner has a fuller, continuous outline on the right. This preset asks four overlay instances to become thick; previously their live thickness was ignored.
Native 3840×2160, frame 0; matched 64×64 pixel crop at (768, 128). Click either close-up for its original 4K frame. No exposure or colour adjustment.
See both full 4K frames
Only 0032 disabledcity-alt: Only 0032 disabled, full frame
ProjectM TV · fix enabledcity-alt: ProjectM TV · fix enabled, full frame
Earlier upstream-master comparison and diagnostic controls
Before · upstream mastermartin - city lights v2 c.milk — Before · upstream master
After · ProjectM TVmartin - city lights v2 c.milk — After · ProjectM TV
martin - city lights v2 c.milk · 1280×720, frame 239 at 30 Hz.
The four overlay shape instances can use their evaluated thick outlines while the other 45 remain thin. This busy original also contains other repaired features; the disjoint-square diagnostic below isolates live thickness.
Before · upstream masteraudit followup audit shape live disjoint-thick.milk (synthetic) — Before · upstream master
After · ProjectM TVaudit followup audit shape live disjoint-thick.milk (synthetic) — After · ProjectM TV
Before · same zoomMatched crop of audit followup audit shape live disjoint-thick.milk (synthetic) — Before · same zoom
After · same zoomMatched crop of audit followup audit shape live disjoint-thick.milk (synthetic) — After · same zoom
audit followup audit shape live disjoint-thick.milk (synthetic) · 1280×720, frame 239 at 30 Hz.
Synthetic disjoint shape instances, with thin saved style and live thick=1. The matched corner crop exposes the evaluated border width. Zoom rectangle: (275, 130), 70×70 source pixels; identical crop and nearest-neighbour display, with no brightness adjustment.

0033 — Latest previous motion field

When motion-vector strokes return on a beat, they follow the latest completed distortion. Updating the motion field while the strokes are hidden prevents them from resuming with an older displacement; the correct injection then develops through feedback.

Keeping this history current adds motion-field writes on hidden frames. Focused source, lifecycle and rendering-cost evidence records the fidelity and cost tradeoff.

Only 0033 disabledsalad: Only 0033 disabled, matched close-up
ProjectM TV · fix enabledsalad: ProjectM TV · fix enabled, matched close-up
Hexcollie, Martin, Flexi n AdamFX - Seizure Salad.milk
A real beat-gated preset: the returned vectors inject strokes using the latest completed distortion. The coloured rings develop differently instead of continuing from an older hidden-frame motion field. This frame shows the accumulated effect, not a promise of extra sharpness in every preset.
Native 3840×2160, frame 209; matched 1200×1200 pixel crop at (1200, 400). Click either close-up for its original 4K frame. No exposure or colour adjustment.
See both full 4K frames
Only 0033 disabledsalad: Only 0033 disabled, full frame
ProjectM TV · fix enabledsalad: ProjectM TV · fix enabled, full frame
Earlier upstream-master comparison and diagnostic controls
Before · upstream masteraudit followup uv default-feedback.milk (synthetic) — Before · upstream master
After · ProjectM TVaudit followup uv default-feedback.milk (synthetic) — After · ProjectM TV
Before · same zoomMatched crop of audit followup uv default-feedback.milk (synthetic) — Before · same zoom
After · same zoomMatched crop of audit followup uv default-feedback.milk (synthetic) — After · same zoom
audit followup uv default-feedback.milk (synthetic) · 1280×720, frame 239 at 30 Hz.
The motion field changes while vectors are hidden for one frame. When the red vectors return, they use the latest completed field. The matched zoom shows the changed segment; this is a diagnostic, not a confirmed stock-preset census. Zoom rectangle: (240, 348), 320×28 source pixels; identical crop and nearest-neighbour display, with no brightness adjustment.

Known remaining differences from MilkDrop 2

  • sampler_state { … } fields are ignored; the sampler name prefix selects the mode (as in MilkDrop).
  • In warp shaders, an unqualified sampler_main follows the preset's wrap setting; MilkDrop 2 ignores wrap there and uses the name prefix, which defaults to wrapping.
  • randNN images are shared between the warp and composite shaders for a preset load; MilkDrop 2 picks separately per shader.
  • Uninitialized shader globals read 0, not whatever a D3D9 constant register held.
  • Fractional negative warp powers have nonfinite coordinates and unresolved appearance. Negative-base pow inside authored shaders remains undefined in GLSL.
  • Nonfinite rot is unsupported.
  • wave_mode wraps at 16 projectM modes, not 8.
  • Blur ranges narrower than 0.1 are repaired instead of collapsing.
  • Some reference-scale effects remain resolution-dependent at 4K unless Native trails or diffusion compensation is active (details).
  • HLSL translator edge cases: decimal→double→float double rounding and unchecked integer narrowing.
  • Textured custom shapes always wrap; MilkDrop 2 clamps them on frames where blur levels are computed.
  • Wave modes 2, 3 and 5 use projectM's size buckets instead of MilkDrop's exact-width fade table.
  • Thick custom waves and shape outlines are offset by half a pixel; MilkDrop 2 offsets them by one canvas pixel, and its custom-wave dot size also grows on canvases 1024 px and wider.
  • decay above 1 is clamped to 1; MilkDrop 2 wraps it to nearly black.
  • No Windows reference renders exist in this project's evidence, so identical Windows appearance is never claimed.

These differences were found by auditing the source predictor against MilkDrop 2's code; they are candidates for future engine patches.

The full assessment, including upstream contribution notes, is docs/UPSTREAM_PATCH_VALUE.md.

0034 — GLES motion-field storage

Before allocating a float motion map, the engine checks core/extension capability and the actual mixed framebuffer. Contexts without compatible float rendering use required RG16UI storage containing half-float words. Both consumers decode before bilinear interpolation. Primary color, dithering and actual fragment clipping are unchanged, and so are capable RG16F shader paths. The correction adds no per-frame capability query or new render exception.

0035 — Program cache switch

This patch adds a TV-only troubleshooting control and does not change rendering. projectm_opengl_set_program_cache_enabled(false) makes every shader program compile and link from source in the context that uses it: no binary-retrievable hint, no glGetProgramBinary export, no glProgramBinary load, and cached entries are dropped. Turning it off waits for binary calls already running on the background compile thread, so none runs afterwards. Enabled again, the cache starts empty. The default stays enabled. The former app setting was removed after it did not resolve the Fire TV crashes; the additive API remains for existing consumers and laboratory controls. The real-GL program-cache-switch control checks the hint and the cache counters on GLES; desktop OpenGL builds have no binary cache.

0036 — PowerVR framebuffer retirement

Some Imagination/PowerVR drivers can retain a reference to a previously bound complete framebuffer after it has been deleted. Before deleting an engine framebuffer, this patch binds a small, complete guard framebuffer and then framebuffer zero on both read/draw targets, so neither retained reference points at the retiring object. Surviving bindings are restored. One guard is kept until context destruction, and other GPU vendors keep ordinary deletion. Hosts reset its names before retiring a lost-context instance.

The design follows Chromium/Skia’s corrected workaround. Real-GL tests intercept actual engine changes, resizes and cleanup: the unsafe binding sequence is observed without protection and absent with it, while nonblank pixels match. This qualifies the call sequence on the tested backend, not a confirmed fix for the reported Fire TV crash.

0037 — Reusable line rendering

This is an internal architecture change adapted from our upstream PR #1047, commit ba122baa24cd1794c2a413ec8e308c8ab6e9e4fe. Colored points, padded line batches, the line renderer and its generic shaders belong to Renderer. MilkDrop-specific reference sizing, sample/fade rules, dot styles, canvas inputs and thick-line pass offsets stay in MilkdropPreset.

Both line strips and motion-vector quads use Renderer::Mesh to own and stream their interleaved instance data, set attribute layouts/divisors and byte offsets, and draw a separate vertex/instance count. The existing indexed mesh path is retained. The motion-vector vertex shader stays with preset code because it samples the preset's warp field; it shares Renderer's fragment shader.

The Android port retains shader compilation during preset prewarming, cached uniform locations, per-instance shape styles, prepared Native-trails replay and the packed/floating motion-UV paths. This refactor is intended to preserve rendering rather than change a preset's appearance. Validation and backend limits are recorded in the refactor evidence.