Commit Graph
244 Commits
Author SHA1 Message Date
Æmotion StudioandClaude Opus 5 f16390cff7 docs: correct the ceilings for decorrelation, which inverts the ordering
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>
2026-09-12 08:32:10 -07:00
Æmotion StudioandClaude Opus 5 c938137645 fix: never paint a metadata stream, even with shade_non_spatial on
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>
2026-09-12 08:25:05 -07:00
Æmotion StudioandClaude Opus 5 7661d7cf17 fix: raise DECORRELATION_BASIS from 8 to 64, which was set by guess
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>
2026-09-12 08:24:13 -07:00
Æmotion StudioandClaude Opus 5 2780a4a611 docs: record all five directions as worked, with what each left behind
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>
2026-09-12 02:40:25 -07:00
Æmotion StudioandClaude Opus 5 08b7d3bc56 feat: vary the shader across the run with stage_progression
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>
2026-09-12 02:35:50 -07:00
Æmotion StudioandClaude Opus 5 36e9e8c3ab feat: optionally paint the non-spatial streams too
`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>
2026-09-12 02:32:57 -07:00
Æmotion StudioandClaude Opus 5 86aac9e27d feat: add Shader Noise Walk, a node that ramps one parameter across a batch
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>
2026-09-12 02:25:25 -07:00
Æmotion StudioandClaude Opus 5 653fc97760 docs: update HANDOFF and CHANGELOG for items 1 and 2
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>
2026-09-12 02:13:23 -07:00
Æmotion StudioandClaude Opus 5 0da8bb6e04 fix: give each latent channel its own shader draw, and stop temporal_coherent ignoring the seed
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>
2026-09-12 02:07:17 -07:00
Æmotion StudioandClaude Opus 5 d391f08320 feat: normalise shader_strength across blend modes
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>
2026-09-12 01:53:17 -07:00
Æmotion StudioandClaude Opus 5 f8ecb22c6c docs: add HANDOFF.md with five directions for the node
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>
2026-09-12 01:34:41 -07:00
Æmotion StudioandClaude Opus 5 7b40c556d4 feat: expose temporal_coherent and correct the parameter tooltips
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>
2026-09-12 01:32:41 -07:00
Æmotion StudioandClaude Opus 5 363ac1e657 fix: sample MiniMax H3's paired audio+video latent correctly
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>
2026-09-12 01:32:10 -07:00
Æmotion StudioandClaude Opus 5 1b21bcba83 docs: mark the legacy pipeline as frozen
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>
2026-09-11 15:55:47 -07:00
Æmotion StudioandClaude Opus 5 2b13d8c10d docs: release 2.0.0 (sampling correctness)
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>
2026-09-11 15:54:40 -07:00
Æmotion StudioandClaude Opus 5 1bd794c353 feat(web): show the shader preview on the Direct node
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>
2026-09-11 15:52:03 -07:00
Æmotion StudioandClaude Opus 5 d97a946588 refactor: deprecate the display node and key its cache on the params file
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>
2026-09-11 15:37:47 -07:00
Æmotion StudioandClaude Opus 5 3af34338ab feat: add sampling_mode and route the node to the corrected pipeline
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>
2026-09-11 15:35:14 -07:00
Æmotion StudioandClaude Opus 5 cc0e1a52b9 feat: add the corrected sampling pipeline (not yet wired to the nodes)
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>
2026-09-11 15:30:07 -07:00
Æmotion StudioandClaude Opus 5 4f3ddc6a14 refactor: register each generator once and drop __js_files__
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>
2026-09-11 15:18:41 -07:00
Æmotion StudioandClaude Opus 5 711981a734 refactor: delete the dead mapper, duplicate node module and stale artifacts
- 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>
2026-09-11 15:17:21 -07:00
Æmotion StudioandClaude Opus 5 d5325f25bc refactor: drop dead code paths and harden the save_params route
- 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>
2026-09-11 15:14:54 -07:00
Æmotion StudioandClaude Opus 5 141225b357 test: add pytest scaffold, generator smoke tests and legacy goldens
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>
2026-09-11 15:03:28 -07:00
Æmotion StudioandClaude Opus 5 ff49a204a7 fix(shaders): tensor_field crashed for almost every configuration
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>
2026-09-11 15:03:28 -07:00
Æmotion StudioandClaude Opus 5 2296799a09 fix: support NestedTensor (multi-stream) latents
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>
2026-09-11 14:47:33 -07:00
Æmotion Studio c8173d9305 chore: bump project versions to 1.3.5 and 1.0.4 2026-06-16 20:47:12 -07:00
Æmotion StudioandClaude Opus 4.8 353e8868ac fix(security): Bump vitest to ^4.1.0 to patch GHSA-5xrq-8626-4rwp
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>
2026-06-02 14:39:33 -07:00
Æmotion Studio 24a977cfa1 docs: Document fixes for shader display draw order in versions 1.3.2 and 1.3.3 of the changelog. 2026-03-24 16:29:16 -07:00
Æmotion Studio 3bfb894b96 fix: Adjust shader canvas drawing order to render on top of the node's title gradient. 2026-03-24 16:23:57 -07:00
Æmotion Studio 0d31532aa3 Remove Jules-related documentation files and update .gitignore to ignore the .jules directory. 2026-03-24 16:13:50 -07:00
Æmotion Studio ddf0fc0f83 feat: Add GLSL shader definitions and adjust gradient title drawing order to render as a background layer. 2026-03-24 16:10:10 -07:00
Æmotion Studio 59128d1549 fix: Restore GLSL shader functionality, improve WebGL compatibility, and address various shader-related bugs in version 1.3.1. 2026-02-14 11:07:57 -08:00
Æmotion Studio 954ffb0827 Merge pull request #45 from AEmotionStudio/jules/fix-api-validation-5635055536066903961
🛡️ Sentinel: [CRITICAL] Fix API Input Validation
2026-02-14 11:00:41 -08:00
Æmotion Studio 34acb6e7d0 fix: validate both camelCase and snake_case param keys 2026-02-14 10:55:50 -08:00
Æmotion Studio 130db7613a Merge pull request #47 from AEmotionStudio/fix/restore-v260-glsl-shaders
fix: restore complete v260 GLSL shaders with Chromium compatibility
2026-02-14 10:51:51 -08:00
Æmotion Studio 8527a73958 fix: resolve 6 PR review issues - resource leaks, debug logs, normalize safety 2026-02-14 10:47:42 -08:00
Æmotion Studio cf372430fb fix: resolve PR review issues - smoothstep ordering and HSV hue wrapping 2026-02-14 10:35:07 -08:00
Æmotion Studio 1b2aae8260 fix: restore complete v260 GLSL shaders with Chromium compatibility 2026-02-14 10:26:29 -08:00
Æmotion Studio 4b62134ee1 Merge pull request #46 from AEmotionStudio/bolt-optimize-temporal-noise-7114727873385316346
⚡ Bolt: Optimize Temporal Coherent Noise Generation
2026-02-09 20:13:11 -08:00
Æmotion Studio d28645a4d2 Merge pull request #44 from AEmotionStudio/palette-ux-improvement-12595650097569698846
🎨 Palette: Enhance modal accessibility and copy feedback
2026-02-09 19:58:17 -08:00
google-labs-jules[bot]andAEmotionStudio dfd2fe0611 feat(ux): improve accessibility for matrix modal and copy button
- 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>
2026-02-09 23:32:27 +00:00
google-labs-jules[bot]andAEmotionStudio 89ce935c27 feat: optimize temporal coherent noise generation
- 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>
2026-02-09 16:22:16 +00:00
google-labs-jules[bot]andAEmotionStudio 73b31156f4 fix: validate and sanitize input in save_shader_params API endpoint
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>
2026-02-09 16:11:26 +00:00
google-labs-jules[bot]andAEmotionStudio 5fdd35ff05 feat(ux): improve accessibility for matrix modal and copy button
- 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>
2026-02-09 16:07:22 +00:00
AEmotionStudio b78538578b feat: Add server-side parameter saving API and changelog
- 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
2026-01-29 23:53:29 -08:00
Æmotion Studio 9bee5a33d4 Merge pull request #43 from AEmotionStudio/fix/shader-imports-and-video-comparer
Fix shader imports and refactor video comparer
2026-01-29 18:04:03 -08:00
AEmotionStudio d62aa6a8be fix(video_comparer): resolve PR review issues
- 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
2026-01-29 17:55:42 -08:00
AEmotionStudio 7c4c806687 Fix shader imports and refactor video comparer
- 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
2026-01-29 17:44:22 -08:00
Æmotion Studio 2491688c0b Merge pull request #41 from AEmotionStudio/feature/typescript-migration
feat: Complete TypeScript migration with 85 unit tests
2026-01-28 16:49:15 -08:00
AEmotionStudio 50b37bea2c fix: Make TITLE_GRADIENT_COLORS module-private
Remove export keyword from TITLE_GRADIENT_COLORS since it's only used
internally by createTitleGradient() and never imported elsewhere.

All 85 tests pass.
2026-01-28 12:38:25 -08:00