The tooltips quoted ceilings measured before decorrelate_channels existed, so
they were understating the usable range by roughly three times for the default
generator. Re-derived on real H3 at the current basis of 64:
generator without with
domain_warp ~0.25 ~0.75 (degrades at 1.0)
temporal_coherent ~0.35 ~0.75
tensor_field ~0.5 ~0.5 unchanged, skipped
curl_noise ~0.2 ~0.2 unchanged, skipped
This reverses the earlier finding that tensor_field tolerated the most strength.
Rank was the only thing limiting domain_warp; once that is fixed it overtakes the
generators decorrelation skips, which are limited by their spatial character
instead and gain nothing.
The sweep ran with zero conditioning, so content comes from the model's prior
rather than a prompt. Coherence is still unambiguous, and the control in the same
batch reproduced the green quilt seen earlier under real prompts, so the
comparison holds.
Two handoff items close with this; a third opens. Rank still tops out near 60% of
channels because the basis draws are not independent of each other either, so
mixing genuinely orthogonal fields would close the remaining gap.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
shade_non_spatial painted every stream, which is right for MiniMax H3 and LTXAV
where the second stream is audio, and wrong for TripoSplat where it is a
[B, 1, 5] camera. Writing shader noise into that moves the viewpoint rather than
varying the subject -- the tooltip warned about it, which is not the same as
handling it.
Streams carrying fewer than 64 cells per batch item are now skipped. The gap is
wide: TripoSplat's camera is 5, and H3's audio, the smallest real content stream
in the roster, is 414. The first stream is never skipped, so the refusal for a
genuinely unpaintable primary stream still comes from require_spatial_latent
with its named error rather than from silence here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The basis shipped at 8 with the comment that it bounded cost "which matters at
LTXV's 128 channels". Both halves of that were wrong.
The quality claim, measured on real H3 at strength 0.75 where stock noise is a
solid quilted green texture:
stock destroyed
basis 8 still mostly destroyed
basis 16 coherent
basis 32 clean
So 8 was leaving most of the benefit on the table. Achieved rank runs at about
85-90% of the basis until it reaches the channel count, so a basis of 64 gives
full independence to every model up to 64 channels and takes LTXV's 128 from
rank 7.6 to 43.5.
The cost it was guarding against does not exist. Seconds per draw, worst case
across shapes:
model stock basis 8 basis 16 basis 32
SD 1.5 512 0.01 0.03 0.03 0.03
Flux 768 0.01 0.09 0.18 0.16
H3 608x352x56 0.13 0.45 0.79 1.12
LTXV 0.03 0.07 0.09 0.14
One draw happens per stage boundary, on runs that take thirty to fifty seconds.
At the new default the worst measured case is 1.22s.
Effective rank at the new default:
(1, 4, 64, 64) 1.00 -> 2.43 (capped by 4 channels)
(1, 24, 17, 22, 38) 2.03 -> 14.39 MiniMax H3
(1, 48, 5, 24, 24) 2.15 -> 28.55 Wan 2.2
(1, 128, 32, 32) 2.37 -> 43.48 LTXV
The no-regression guarantee still holds across all sixteen shape/generator
combinations: turning decorrelation on never narrows the noise.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The handoff now opens with a status table and, per item, separates what shipped
from what is still open. The open work is the point: every item left something.
- 1 covers blend modes only; shader types and shape masks resist calibration and
two metrics for them were measured and rejected.
- 2's ceilings table was measured before decorrelate_channels existed and needs
re-deriving; _expand_channels is still collapsed at source for every caller
outside the standard pipeline.
- 3 has no two-parameter grid and no value output for labelling sheets.
- 4 works mechanically but its aesthetic value rests on one prompt and one sample
per setting, nobody has listened to the output, and the sequence-latent path
has never seen a real audio checkpoint.
- 5 shows the progressions differ but not which is better, and per-stage shader
type was the half that did not get built.
Also keeps the two failed metrics and the disproved premise written down with
their numbers, since both are the obvious-sounding first idea.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stages have been segments of one trajectory since 2.0, but every stage drew the
same shader at the same zoom and differed only in strength. The trajectory is
not uniform -- early steps settle composition, late steps settle detail -- so
identical noise character at both ends wastes structure the pipeline already has.
`stage_progression`, default "uniform" so nothing changes unasked:
coarse_to_fine starts zoomed in on large features with fewer octaves and
ends zoomed out on small ones with more, lining the noise up
with what each part of the run is deciding
fine_to_coarse the mirror
The README already calls noise_scale the zoom control and octaves the detail
slider; this ties them to position in the schedule. The span is 0.5x to 2x
noise_scale and plus or minus one octave, centred on the widget values, so a
shaped run still sits on the settings the user chose rather than drifting off
them. Deliberately modest: this shapes the walk, it is not a second strength.
Position comes from the boundary's place in the schedule, not the stage index.
Sequential and injection stages interleave, and what matters is how far along
the trajectory the noise lands, not which list it came from.
Event tuples widen from (strength, seed) to (strength, seed, shaping), with
shaping empty under the default. _shaped() copies the params dict rather than
adjusting it, since one dict is shared across every stage -- there is a test for
that, because mutating it would have silently compounded stage after stage.
7 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`shade_non_spatial`, default off. Two things it enables, which turned out to need
very different amounts of work.
MiniMax H3 and LTXAV's audio stream needed no new layout code at all. It is
[B, 32, 2, T] -- already rank 4, which latent_layout reads as 32 channels on a
2 x T grid, stereo as height and time as width. The generators take that shape
as-is. Only the pipeline needed to stop painting noise_streams[0] exclusively.
Sequence latents needed a new path: [B, C, L] is painted as a one-row strip and
folded back. That unblocks the five families require_spatial_latent refused
outright -- Stable Audio 1/3, ACE-Step 1.5, MiniMax Music 3, Hunyuan3D and
TripoSplat. They still raise by default.
Each stream draws from its own seed offset, so two streams never carry the same
field.
Measured on real H3 across a 0.05 to 0.40 sweep against a control with audio
left alone, all with decorrelate_channels on:
run rms dB centroid Hz flatness
audio off -18.5 4244 0.1044
on 0.05 -20.4 4618 0.0975
on 0.10 -21.0 4702 0.0744
on 0.20 -20.1 3801 0.0943
on 0.40 -21.9 4730 0.0655
Spectral flatness is 1.0 for white noise and falls toward 0 for tonal content.
It drops in all four runs and roughly tracks strength, which reads as the model
being pushed toward tonal sound and away from broadband texture -- on a forge
scene, toward the hammer's clang and away from the fire's crackle. Level is
about 2 dB quieter throughout. This is one prompt and one sample per setting,
and centroid and level are not monotone, so it is a hint rather than a result.
The video stayed clean and photoreal at every setting including 0.40, which is
worth noting on its own: H3 denoises both streams in one packed sequence, so
perturbing audio was as likely to damage the picture as not.
Two cautions in the tooltip. Audio has no busy scene to hide structure in, so
the content-dependence that let rain and surf absorb 0.5 does not apply and the
sensible starting point is near 0.05. And TripoSplat's second stream is camera
parameters rather than audio, so enabling this there paints the camera.
4 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pack's premise is exploring around a seed rather than hopping between seeds,
and a strength ramp shows it plainly. Producing one meant a queue run per value
with a widget nudged between each. This does the ramp in a single run.
ShaderNoiseWalk subclasses the Direct sampler and derives INPUT_TYPES from it, so
every sampler input is present and a future one cannot silently go missing --
test_the_walk_node_offers_everything_the_direct_node_does asserts that. It adds
walk_parameter (strength, phase_shift, noise_scale, warp_strength, octaves,
shape_mask_strength, color_intensity or seed), walk_start, walk_end and
walk_steps, and returns a batched LATENT for the comparer nodes or one VAE
decode.
Each point is a full run through pipelines.standard.run with the model resident
throughout, which is the economy of it: five H3 runs at 608x352/56 frames took
125s total, against several minutes for the 21 GB load alone.
Multi-stream latents batch stream by stream through ComfyUI's cat_nested, so a
three-step H3 walk returns [(3,24,...), (3,32,...)]. batch_index is dropped --
it picks a noise slot for a single run and means nothing across several; other
latent keys carry through.
Walking `seed` is offered deliberately. It is ordinary seed-hopping, the thing
the README contrasts this pack against, so having it in the same node makes the
comparison one click.
Measured on H3, distance from the unshaded baseline across five points:
shader_strength 0.00 -> 0.30 0.0000 .. 0.1761, monotonic
phase_shift 0.00 -> 1.50 0.1126 .. 0.1695, spread 3.1x tighter
which is the first evidence for the documented claim that phase shift reveals
"different facets of the same core elements": it holds roughly constant distance
while rearranging detail, where strength sweeps distance from zero. It is not
perfectly flat -- distance drifts mildly downward as phase rises.
10 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Items 1 and 2 are worked; 3, 4 and 5 are untouched. The handoff now leads with a
status table and records, for each item, what was shipped and what is left.
Two things worth keeping that are not code:
- The metrics that did NOT work are written down, with their numbers, so nobody
spends the time again. Low-frequency spectral energy (this document's own
original proposal) and block mean-structure / non-stationarity both fail to
predict the observed ceilings; one has the wrong sign outright.
- Item 2's premise is recorded as disproved rather than quietly rewritten. The
plan was to scale strength by latent size and step count; SD 1.5 at a 64x64
latent with 20 steps breaks down harder than H3 at 38x22 with 8, so there was
nothing to scale. The real cause was the channel axis.
Also notes that every ceiling in the background section was measured with
decorrelate_channels off, and that re-deriving them with it on is outstanding.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two related defects, found while testing whether the strength ceiling scales with
latent size and step count. It does not -- SD 1.5 at a 64x64 latent and 20 steps
breaks down harder and earlier than MiniMax H3 at 38x22 and 8 steps, which
inverts the premise. The cause is the channel axis.
shaders/base.py::_expand_channels builds every extra channel as a pointwise
function (sin, abs) of a mixture of the FIRST TWO channels, so the draw spans
almost nothing however many channels are asked for. Effective rank, measured as
the participation ratio of the channel covariance spectrum:
4ch 16ch 24ch 128ch
gaussian 4.00 16.00 23.90 120.30
domain_warp 1.00 2.22 2.06 2.37
temporal_coherent 1.00 1.00 1.00 1.00
curl_noise 3.58 10.88 22.11 25.22
tensor_field 3.81 14.31 23.54 90.78
domain_warp -- the default -- is a two-dimensional signal copied across up to 128
channels, and temporal_coherent is literally one channel repeated. Samplers
expect i.i.d. noise; that collapse is why the shader's own pattern surfaces so
readily. It also explains the ordering already observed on H3, where
tensor_field (near full rank) tolerated the most strength.
`decorrelate_channels` fills the channel axis from DECORRELATION_BASIS
independent renders mixed through a seeded random matrix, so the rank becomes
min(channels, basis) while every basis element is still that shader. On SD 1.5
it moves the usable ceiling from below 0.25 -- where stock is already an
abstract green smear -- to a clean photoreal variation at 0.25 that stays partly
readable at 0.5.
Guarded twice, because neither guess held on its own: generators already wider
than the basis are skipped (curl_noise spans 25 of 128 channels despite looking
correlated, so a correlation threshold was the wrong test), and the remix is
kept only when it actually widens the draw (curl_noise at four channels remixes
narrower than it started). Turning it on can never narrow the noise.
Separately, shaders/temporal_coherent_noise.py read params["base_seed"]
unconditionally where domain_warp gates it on use_temporal_coherence. The node
always sets base_seed, so that generator ignored its seed argument entirely:
every stage and every decorrelation draw produced the identical field, and only
`time` still varied. It now matches domain_warp. Nothing could select
temporal_coherent from a workflow until the previous commit, so no saved
workflow changes; the 11 legacy goldens still pass.
Exposed as an optional `decorrelate_channels` input, default off. 23 new tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One strength value meant eight different things. Measured as the cosine between
the mixed noise and the shader -- mix_noise standardises both operands, so the
result lies on the unit sphere they span and that cosine is exactly the shader's
share -- the modes differ by a factor of twenty-three at strength 0.5: `normal`
hands over 0.71, `difference` 0.03.
That matches how they behave in a picture. On MiniMax H3 at strength 0.30,
`normal` and `hard_light` show green blocking and vertical striping while
`soft_light` is barely moved from the baseline.
BLEND_SHADER_FRACTION records the measured curve per mode, and
normalized_strength() re-expresses a strength on the reference mode's scale.
`multiply` is the reference, so a workflow that never changes blend_mode is
untouched; `soft_light` needs x1.77 its old number, `add` x0.64, `hard_light`
x0.54, `normal` x0.40. `difference` cannot reach the top of the reference scale
at all -- it saturates at 0.048 -- which is why it stayed subtle at every
setting tested.
Verified on real H3 weights, six blend modes at shader_strength 0.30:
audio spread across modes std dev
without 4.6 dB 1.76
with 2.1 dB 0.71
Audio level is an independent signal here -- the shader never touches the audio
stream, the level moves only through the DiT's joint attention -- so it agrees
with the cosine calibration from a different direction. Visually the six
normalised runs are clean and comparably varied; the un-normalised ones are not.
Exposed as an optional `normalize_strength` input, default off, so existing
workflows reproduce their seeds. Standard sampling only.
One static table serves every case: the curves vary by at most 0.07 across four
shader types, a shape mask, and 4D/5D latents at three channel counts. Six tests
cover it, including a drift guard that re-measures the table across five
configurations and fails if a blend formula changes.
Scope: this normalises blend modes only. Shader types and shape masks differ in
the character of the noise rather than how much is injected, and two candidate
metrics for that (low-frequency spectral energy; block mean-structure and
non-stationarity) were measured and rejected -- neither predicts the observed
ceilings. The tooltips carry that guidance instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Written after fixing MiniMax H3 support and running ~46 real generations
against H3 Max to characterise the node's behaviour on a video model.
The five, in the order they should be done:
1. Normalise strength across shader types, blend modes and shape masks, so the
slider means one thing. It currently means eight different things -- 0.25 is
safe with domain_warp/multiply, ruinous with a shape mask.
2. Auto-scale the default to the latent's spatial size and the step count. The
node already has both and uses neither.
3. A batch-walk node that emits N latents along a strength or phase ramp,
closing the loop with the existing comparer nodes.
4. Shader noise on the audio stream, gated and off by default. The streams are
strongly coupled -- perturbing video alone already moves audio by up to 5 dB
-- so driving audio directly is unexplored in both directions.
5. Per-stage shader parameters: coarse structure early, fine structure late.
Each section carries the measurements it rests on, the files to touch, and what
to avoid. Also records the H3 ceilings table, the reproduction recipe for
running the 21 GB UNet without the 32 GB text encoder, and two deliberate
exclusions (the frozen legacy pipeline, and the three remaining uncalled core
modules).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Direct node's shader_type list offered three of the four generators this
repository ships. temporal_coherent was registered, covered by the test suite
and reachable from both the standard and legacy dispatch paths -- but absent
from the combo, so no workflow could select it. Nothing tied the advertised
list to the registry, so nothing caught it.
It is the generator best suited to video: 4D simplex with time as a real axis
rather than a per-frame redraw. On real MiniMax H3 weights it degrades far more
gracefully than the others (faint background striping at strength 0.35, where
domain_warp already shows blocking and curl_noise has heavy colour casts), and
it is the only type whose audio level stays flat as strength rises: -24.0 /
-23.7 / -23.7 dB against a -22.6 dB baseline, where domain_warp climbs to -17.0.
Two tests now pin the dropdown and the registry to each other in both
directions. The second fails on the previous list, naming temporal_coherent.
The tooltips implied the whole 0.0-1.0 strength range was usable. Measured on
H3 at 608x352 and 8 steps it is not:
domain_warp + multiply photoreal to ~0.25, gone by 0.75
tensor_field tolerates ~0.5
curl_noise ~0.2, heavy colour casts by 0.5
soft_light / difference clean at 0.3, the gentlest blends
add ~0.15, the most aggressive
shape masks ~0.2; at 0.6 the mask is drawn into the picture
use_temporal_coherence ~0.2; 0.5 swamps the frame
use_temporal_coherence was the worst offender: it claimed to help "maintain
frame-to-frame consistency", when holding one seed across frames stops the
pattern varying between them and so reinforces it rather than averaging it out.
The tooltips now carry these ceilings, rank the blend modes by aggressiveness,
and note that the live preview only draws three of the four types.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
H3's latent is a NestedTensor of a video stream [B,24,T,H,W] and an audio
stream [B,32,2,T], and the model -- not its latent format -- carries audio
scaled onto the video sigma schedule (audio_scale = shift / audio_shift = 4.0).
_split_noise inverted a segment boundary through latent_format.process_in,
which for MiniMaxH3AV is an identity, so the audio residual handed to the next
segment was wrong by that factor of 4. It now inverts through the model's own
process_latent_in / process_latent_out, which is what CFGGuider.inner_sample
actually applies. Every other model is unaffected: BaseModel.process_latent_in
just calls the format.
Measured on real H3 weights, two stages at shader_strength 0, where a segmented
run must reproduce an uninterrupted one:
video max error audio max error
before 9.3e-01 (stream max 4.80) 1.3e+00 (stream max 1.35)
after 4.8e-07 2.4e-07
The audio stream was almost entirely wrong, and because H3 denoises both
streams in one packed sequence the error reached the video through the DiT's
joint attention -- so this degraded picture as well as sound. Verified
bit-identical output on SD 1.5 and Wan 2.1, confirming it is a no-op elsewhere.
Also:
- shader noise at a boundary reads its shape from the noise it is about to
paint, rather than a shape captured before the run started
- latents with no spatial grid ([B,C,L]: Stable Audio, ACE-Step 1.5, MiniMax
Music 3, Hunyuan3D, TripoSplat) raise UnsupportedLatentError naming the
shape, before sampling starts, instead of a bare ValueError from inside noise
generation. At shader_strength 0 they sample through as a plain KSampler.
- delete core/model_compat.py and its three stale tables. Nothing called it;
its tables stopped at LTXV, its model_type == "FLOW" branch was unreachable
(str(ModelType.FLOW).upper() is "MODELTYPE.FLOW"), and its 5-D layout guess
defaulted to [B,F,C,H,W], which ComfyUI never produces. The legacy mode's own
detector is untouched, so pre-2.0 workflows still reproduce their seeds.
Noise generation is now exercised at every channel count ComfyUI ships -- 3, 4,
8, 12, 16, 24, 32, 48, 64, 128 and 256 -- for both image and video latents.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This module is the pre-2.0 implementation, kept so older workflows reproduce
their seeds. Its behaviour is pinned byte for byte by the golden tests, so the
header says plainly that it takes crash fixes only and that new work belongs in
core/ and pipelines/standard.py -- including why the inert visualiser and
debugger calls throughout are deliberately left alone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CHANGELOG gains the 2.0.0 entry and the missing 1.3.5 one. README: corrected
the Known Issues (denoise and parameter queuing are fixed, legacy mode
explained), pointed users at the Direct node now that it carries the preview,
replaced the "twelve archetypes" claim with the three generators that ship,
and documented sampling_mode.
CODE_REVIEW.md records which of its findings this release addresses and which
were left alone on purpose.
186 Python tests, 91 web tests, typecheck clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The preview now attaches to ShaderNoiseKSamplerDirect as well. That node
already exposes every shader control as a real input, so instead of adding a
second, unconnected copy of each widget, the preview mirrors the node's own
inputs into the properties its WebGL uniforms read, and follows them as they
change.
Its four preview-only widgets (Show Shader, Show Tooltips, Animation Speed,
Pixel Resolution) are marked serialize: false there. Saved workflows map widget
values by position, so serializing them would shift every stored input when an
older workflow loads.
The deprecated display node keeps its own widgets and behaviour unchanged.
Also adds vitest coverage for the sampling-mode migration helpers.
Typecheck clean, 91 web tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ShaderNoiseKSampler is superseded by ShaderNoiseKSamplerDirect, which exposes
the same shader controls as real inputs. DEPRECATED = True hides it from node
search while keeping saved workflows loading; the subclass sets it back to
False, since it would otherwise inherit the flag and vanish as well.
Its IS_CHANGED now reports the mtime of data/shader_params.json. That file is
where the node's shader parameters actually come from, and it is invisible to
ComfyUI's cache key, so saving new parameters could leave a cached result in
place. The old implementation only restated widget values that are already
part of that key.
Dropped CONTEXT_MENUS and has_preview: neither is a ComfyUI node API.
186 Python tests pass, including the 11 legacy goldens.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Direct node now dispatches: "standard" (default) runs the new pipeline,
"legacy" runs the frozen pre-2.0 one. Measured through the node with a
recording sampler, 20 steps:
standard, 1 stage 1 call x 20 steps, from sigma 14.61
standard, 2 stages 2 calls x 10 steps, from 14.61 then 1.48
standard, denoise 0.6 1 call x 20 steps, from 2.23
standard, 3 injections 4 calls x 5 steps, 14.61 / 3.87 / 1.48 / 0.60
legacy, 2 stages 2 calls, each rebuilding a full schedule
The 1.48 is the fix: stages continue one trajectory instead of restarting at
maximum noise, where flow models discard the previous stage entirely. The 2.23
is denoise finally reaching the schedule.
Also on the node:
- sequential_distribution, injection_distribution and fast_high_channel_noise
become real optional inputs. As V1 `hidden` tuple inputs ComfyUI never
delivered them, so they were stuck at their defaults.
- the debug/visualisation hidden inputs are gone; they drove stub no-ops.
- IS_CHANGED is removed: it only restated widget values that are already part
of the cache key, and would have rejected the new input.
- new widgets are appended last, so saved workflows keep their widget order.
web/src/sampling_mode_migration.ts switches nodes loaded from pre-2.0
workflows to "legacy", recognising them by the absence of the snk_version
property, so existing seeds keep reproducing.
186 Python tests and 85 web tests pass; the 11 legacy goldens now exercise the
legacy branch through this dispatch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
core/noise_math.py — compositing modes run in uniform space (normal CDF in,
inverse CDF out) and everything is re-standardised, so blended noise reaches
the sampler as mean 0 / std 1. Measured on the old path: overlay had mean
+0.39 at strength 0.3, soft_light std 4.2 at strength 1.0, and the inverse
transform peaked near 4e4. "normal" now mixes with cos/sin, which preserves
variance where (1-k)*base + k*shader fell to std 0.71 at k=0.5.
core/schedule.py — one sigma schedule for the whole run, honouring denoise and
using custom sigmas as given. Boundaries enforce a 2-step minimum, so the
20-step/3-injection case can no longer end in a 1-step segment.
core/shader_noise.py — shape taken from the latent ([B,C,H,W], [B,C,T,H,W]),
which fixes frames being confused with channels when a video has as many
latent frames as channels (a 61-frame Wan clip has 16 of each). Generation
runs inside fork_rng, and fractional octaves interpolate between integer
renders instead of being truncated by ShaderParams.validate().
pipelines/standard.py — samples the schedule in segments. At a boundary the
latent is split into a denoised estimate and its noise in the model's internal
space, the noise is re-mixed, and the next segment resumes from that pair.
Verified lossless to 4.8e-07 for both EPS and flow models, so at
shader_strength 0 a segmented run reproduces an uninterrupted one.
179 tests pass, including the 11 legacy goldens.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The @shader_generator decorators in shaders/*.py already register
domain_warp, tensor_field, curl_noise and temporal_coherent, so __init__
re-registering them logged four "Overwriting existing shader generator"
warnings on every startup. Only the "curl" and "temporal_coherent_noise"
aliases are unique to this file, so only those are registered here.
Also removed the no-op add_*_to_tensor / integrate_temporal_coherent_noise
calls (those functions are `pass`) and __js_files__, which is not a ComfyUI
API: ComfyUI loads every web/**/*.js recursively, in no guaranteed order.
Verified: all six shader names still resolve to the same generator classes,
the startup warnings are gone, and the legacy goldens still match.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- direct_shader_ksampler: remove ParameterResponseMapper and its call site.
Its only trigger, target_attribute_changes, is a V1 `hidden` tuple input,
which ComfyUI never passes to a node, so the block was unreachable.
- advanced_comparer.py: an older duplicate of nodes/comparer.py that nothing
imported (the live class is exported through nodes/__init__.py).
- web/types/*.js(.map): compiled leftovers of the type-only .ts declarations.
ComfyUI loads web/**/*.js recursively, so these were served to the browser
as extensions for no reason. The .ts sources stay.
All 11 legacy goldens, 64 generator smoke tests and 85 web tests still pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- shader_noise_ksampler: import torch.nn.functional at module level. It was
only imported inside one video branch, so the image-side shape correction
raised NameError and silently fell back to all-zero shader noise.
- shader_noise_ksampler: delete the Parameter Response Mapper block. It
referenced an undefined name (the class lives in direct_shader_ksampler),
and its trigger input is a V1 `hidden` tuple input, which ComfyUI never
delivers, so the block could only ever have raised.
- api_routes: keep only known keys before persisting, since the file is read
back into shader params on every run; stop returning exception text to the
caller; log through the logging module.
- gitignore the runtime data/ directory and local console dumps.
Behaviour is unchanged: all 11 legacy golden recordings still match.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Runs inside ComfyUI's venv without a server, loading the pack (its folder
name has hyphens) as the importable package `snk`:
~/ComfyUI/venv/bin/python -m pytest custom_nodes/comfyui-shadernoiseksampler/tests
- helpers.py: package loader, a FakeModel carrying ComfyUI's real
model_sampling/latent_format objects, and a recording fake for
comfy.sample.sample.
- test_generators_smoke.py: every generator must return [B, C, H, W] and
finite values across octaves, channels, masks and colour schemes. This
is what caught the tensor_field crash.
- golden_cases.py / capture_golden.py / test_legacy_golden.py: 11 recorded
configurations pinning exactly what the pre-2.0 pipeline sends to the
sampler, so the upcoming refactor cannot change legacy behaviour.
pytest.ini lives in tests/ so this directory is the rootdir; otherwise
pytest imports the pack's __init__.py as a test package and fails on its
relative imports.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
compute_tensor_properties returns [B, H, W, 1] tensors, but the
eigenvalue-difference branch (viz_type == 1) unsqueezed again, so the
following permute(0, 3, 1, 2) got a 5D tensor and raised RuntimeError.
viz_type is (int(octaves) + channel_index) % 4, so almost any setting hit
the branch on some channel: 46 of 48 parameter combinations failed,
including the node's own defaults (octaves=1). Dropping the unsqueeze
leaves the existing 3D normalisation below to handle both shapes.
Only previously-crashing configurations change; the legacy golden
snapshots are unaffected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Audio+video models such as MiniMax H3 and LTX AV hand the sampler a
NestedTensor latent, which crashed on `.clone()`. Shader noise now drives
the primary (video) stream, companion streams get plain gaussian noise,
and channel detection prefers the model's latent_format.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Upgrade vitest and @vitest/coverage-v8 from ^1.2.2 (resolved 1.6.1) to
^4.1.0 (resolved 4.1.8) to address CVE-2026-47429, a critical (CVSS 9.8)
arbitrary file read/write/execute vulnerability in the Vitest UI server
affecting versions < 4.1.0. Dev-only dependency.
The regenerated lockfile resolves vite@8 (vitest 4 requires vite >= 6).
Verified the major version bump: all 85 tests pass, tsc --noEmit is clean,
and the v8 coverage report generates successfully.
Bump package versions: pyproject 1.3.3 -> 1.3.4, web 1.0.2 -> 1.0.3, and
document the fix in the changelog.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Restore focus to main canvas when closing the matrix modal to support keyboard navigation
- Update aria-label to 'Copied' on copy button success to support screen readers
- Conditionally restore original aria-label to prevent overwriting on error
Co-authored-by: AEmotionStudio <163354043+AEmotionStudio@users.noreply.github.com>
- Replaces runtime bitwise logic and branching in `grad3d` with `torch.nn.functional.embedding` and a precomputed gradient table (`SIMPLEX_GRADIENTS`).
- Removes dead code in gradient selection logic.
- Adds `verification/benchmark_simplex.py` to verify correctness and measure performance.
- Achieves ~2.1x speedup on CPU for Simplex noise generation.
Co-authored-by: AEmotionStudio <163354043+AEmotionStudio@users.noreply.github.com>
Add input validation using ShaderParamsReader.validate_and_sanitize_params
to prevent saving malformed or malicious data to shader_params.json.
Create .jules/sentinel.md with security learnings.
Co-authored-by: AEmotionStudio <163354043+AEmotionStudio@users.noreply.github.com>
- Restore focus to main canvas when closing the matrix modal to support keyboard navigation
- Update aria-label to 'Copied' on copy button success to support screen readers
- Restore original aria-label after timeout
Co-authored-by: AEmotionStudio <163354043+AEmotionStudio@users.noreply.github.com>
- Add api_routes.py with POST /shader_noise_ksampler/save_params endpoint
- Update frontend to use fetch() API instead of browser download
- Register API routes with PromptServer in __init__.py
- Add CHANGELOG.md with version history
- Update README.md with navigation links and version 1.3.0
- Video comparer optimizations for memory efficiency
- Remove duplicate VideoComparer class from root (only nodes/ version used)
- Fix memory threshold check order: check 16GB before 12GB so force cleanup can trigger
- Fix metadata key calculation using proper key mapping instead of broken string replace
- Fix absolute imports in shaders/ to use relative paths
- Correct indentation in shader files
- Refactor video_comparer.py and nodes/video_comparer.py
- Add new example workflows
- Update gitignore and package.json
Remove export keyword from TITLE_GRADIENT_COLORS since it's only used
internally by createTitleGradient() and never imported elsewhere.
All 85 tests pass.